I. Wstęp
Witaj w części IX kursu programowania w języku Pascal dla początkujących. Opiszę tutaj podstawy programowania w systemie Windows, co ma służyć jako wprowadzenie i przygotowanie do bardziej zaawansowanego programowania. Niestety, aby tworzyć profesjonalne oprogramowanie należało będzie przejść na bardziej zaawansowany język programowania. Posiadając wiedzę z tego kursu bez problemu możesz startować do nauki C, C++, C# czy Java, a jeżeli nie chcesz rozstawać się z Pascalem możesz zainteresować się środowiskiem Delphi. Wszystko zależy od tego jakie programy chcesz pisać. Delphi oparte na odmianie języka Pascal nazwaną Object Pascal daje możliwości szybkiego i wygodnego tworzenia aplikacji, jednak (zależy to jeszcze od użytych technik) programy tworzone w tym środowisku nie są najwyższej jakości, jednak jest ono wystarczające do pisania prostych programów użytkowych dla systemu Windows.
II. Pierwszy program
Tradycyjnie, najpierw program Hello World!. Oto kod programu, który zaraz opiszę:
Budowa programu jest taka jak poprzednich, jednak widzimy tutaj, że używany jest moduł o nazwie Windows. Zawiera on różne funkcje (nazywane funkcjami Windows API, w skrócie WinAPI), stałe i struktury systemu Windows. W kodzie głównym programu widać wywołanie funkcji MessageBox. Jeżeli skompilowałeś i uruchomiłeś powyższy kod zauważyłeś, że wyświetlone zostało okno informacyjne. To właśnie wykonuje ta funkcja. Wyświetla ono predefiniowane okno informacyjne o podanej przez nas treści, tytule, rodzaju przycisków i ikonie. Funkcja ta znajduje się w bibliotece systemowej (ta znajduje się dokładnie w bibliotece user32.dll) i jest gotowa do użycia. Taki gotowy do użycia zestaw różnych funkcji bardzo ułatwia programowanie w Windows.Kod:program WinHelloWorld; uses Windows; begin MessageBox(0, 'Hello World!', 'Informacja', MB_OK+MB_ICONINFORMATION); end.
Teraz jeszcze jedna rzecz. Funkcje jak pamiętasz, różnią się od procedur tym, że zwracają wartość po wykonaniu jakiejś czynności. Ta funkcja też zwraca wartość. Z dokumentacji można się dowiedzieć, że zwraca ona wartość wciśniętego przez użytkownika przycisku.
Spójrz na poniższy kod:
W komunikacie wyświetlanym przez powyższy program zmienił się zestaw przycisków. Jako ostatni argument podana została stała MB_YESNO, która mówi, że wyświetlony komunikat ma mieć przyciski Tak i Nie. Wartość zwracana przez funkcję przypisywana jest do zmiennej R. Następnie instrukcją porównania sprawdzane jest czy zwrócona wartość równa jest stałej o nazwie IDYES (stała ta jest zadeklarowana w module Windows). Jeżeli zwrócona wartość jest równa tej stałej, to znaczy, że użytkownik kliknął przycisk Tak. W przeciwnym wypadku kliknął Nie.Kod:program WinHelloWorld; uses Windows; var R : Integer; begin R := MessageBox(0, 'Wybierz: Tak lub Nie.', 'Informacja', MB_YESNO); if R = IDYES then MessageBox(0, 'Wybrano przycisk Tak.','Informacja',0) else MessageBox(0, 'Wybrano przycisk Nie.','Informacja',0); end.
Jak już pisałem, możliwe do ustawienia przyciski i ikony opisane są w dokumentacji.
III. Okna, klasy okien, komunikaty
Teraz coś trudniejszego. Wyświetlimy okno, ale nie predefiniowane, tylko zarejestrujemy własną klasę okna. Kod poniżej może wydawać się długi i zniechęcający, ale po przeanalizowaniu tego wszystkiego, układa się to w logicznie działający mechanizm. W wielu programach okienkowych widziałeś zapewne różne przyciski, pola tekstowe, suwaki, paski postępu itp. Tak naprawdę to też są okna, tylko nam się przyjęło nazywać je tak. Dokładnie mówiąc są to okna potomne, a okno główne programu jest oknem nadrzędnym/oknem rodzica. Cała zasada działania opiera się na tym, że do okien wysyłane są wiadomości, a my w naszym programie sprawdzamy instrukcją warunkową jaka wiadomość została wysłana do okna i możemy napisać kod, który wykona się po otrzymaniu tej wiadomości. Takie wiadomości są przesyłane, również do kontrolek i analogicznie się je obsługuje.
Sprójrz na poniższy kod wyświetlający proste okno:
Na początku programu widzimy dyrektywę dla kompilatora. Nie jest to kod programu, ta dyrektywa służy tylko kompilatorowi, aby poinformować go, że program będzie programem okienkowym ({$APPTYPE GUI}) i, żeby ustawił kompatybilność ze środowiskiem Delphi ({$MODE DELPHI}). Krótko opisując, powyższy program rejestruje nową klasę okna funkcją RegisterWindow, tworzy okno funkcją CreateWindow i wyświetla je funkcją ShowWindow, po której następuje odświeżenie obszaru roboczego okna ("wnętrza okna") funkcją UpdateWindow.Kod:{$APPTYPE GUI} {$MODE DELPHI} program AplikacjaOkienkowa; uses Strings, Windows; //zmienne globalne var AMessage: Msg; hWindow: HWnd; WindowClass: WndClass; //funkcja obsługująca wiadomości wysyłane do okna function WindowProc(Window: HWnd; AMessage: UINT; WParam : WPARAM; LParam: LPARAM): LRESULT; stdcall; export; begin WindowProc := 0; case AMessage of //gdy wysłano wiadomość WM_DESTROY WM_DESTROY: begin //zamknij okno PostQuitMessage(0); Exit; end; end; //wszystkie nieobsługiwane wyżej wiadomości, //prześlij do domyślnej funkcji obsługi okna WindowProc := DefWindowProc(Window, AMessage, WParam, LParam); end; //program główny begin //wypełnienie pól struktury WndClass WindowClass.Style := CS_HREDRAW or CS_VREDRAW; WindowClass.lpfnWndProc := WndProc(@WindowProc); WindowClass.cbCl***tra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon(0, IDI_APPLICATION); WindowClass.hCursor := LoadCursor(0, IDC_ARROW); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := 'WindowClass'; //zarejestrowanie klasy okna RegisterClass(WindowClass); //stworzenie okna i zapisanie zwróconego uchwytu //do zmiennej hWindow := CreateWindow('WindowClass', 'Proste okno', WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, system.MainInstance, nil); //jeżeli udało się otworzyć okno (uchwyt różny od zera) //to wyświetl okno na ekranie if hWindow <> 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end; //pętla przetwarzająca wiadomości wysyłane //do okna while GetMessage(@AMessage, 0, 0, 0) do begin TranslateMessage(AMessage); DispatchMessage(AMessage); end; Halt(AMessage.wParam); end.
W opisie powyższego kodu pojawiło się wiele nazw funkcji WinAPI. Opis wszystkich funkcji możesz znaleźć na stronie MSDN (Microsoft Software Developer's Network). Opis niektórych funkcji Windows API możesz znaleźć na tej stronie (coder.org.pl) w dziale WinAPI/Funkcje. Niestety nie ma możliwości opisania tam wszystkich funkcji, zatem musisz nauczyć się i przyzwyczaić do korzystania z dokumentacji w języku angielskim.
IV. Tworzenie kontrolek
Jak już pisałem wcześniej, że kontrolki to też okna i tworzy się je tak jak okna. Używamy do tego funkcji CreateWindowEx podając jako klasę okna, nazwę klasy kontrolki.
Oto kod programu wyświetlającego okno z przyciskiem, po naciśnięciu którego wyświetlany jest komunikat:
W powyższym programie obsłużone zostały dwie dodatkowe wiadomości: WM_CREATE i WM_COMMAND. Wiadomość WM_CREATE wysyłana jest podczas tworzenia okna, a wiadomość WM_COMMAND wysyłana jest między innymi, gdy użytkownik kliknie w jakąś kontrolkę. Gdy okno odbierze wiadomość WM_CREATE, wykonuje się wtedy kod tworzący kontrolkę przycisku. Natomiast w wiadomości WM_COMMAND sprawdzamy, czy w parametrze wParam jest numer ID przycisku, jeżeli tak to wyświetlamy komunikat.Kod:{$APPTYPE GUI} {$MODE DELPHI} program AplikacjaOkienkowa; uses Strings, Windows; //zmienne globalne var AMessage: Msg; hWindow: HWnd; WindowClass: WndClass; //funkcja obsługująca wiadomości wysyłane do okna function WindowProc(Window: HWnd; AMessage: UINT; WParam : WPARAM; LParam: LPARAM): LRESULT; stdcall; export; begin WindowProc := 0; case AMessage of //gdy wysłano wiadomość WM_DESTROY WM_CREATE: begin //tworzymy przycisk CreateWindowEx(0, 'button', 'Witaj!', WS_CHILD or WS_VISIBLE, 50,50,100,30,Window,1001,system.MainInstance,nil); end; WM_COMMAND: begin //w parametrze wParam jest numer kontrolki //naszemu przyciskowi daliśmy numer 1001 //więc porównujemy wParam z tą wartością if wParam = 1001 then MessageBox(0,'Hello!','Info',0); end; WM_DESTROY: begin //zamknij okno PostQuitMessage(0); Exit; end; end; //wszystkie nieobsługiwane wyżej wiadomości, //prześlij do domyślnej funkcji obsługi okna WindowProc := DefWindowProc(Window, AMessage, WParam, LParam); end; //program główny begin //wypełnienie pól struktury WndClass WindowClass.Style := CS_HREDRAW or CS_VREDRAW; WindowClass.lpfnWndProc := WndProc(@WindowProc); WindowClass.cbCl***tra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon(0, IDI_APPLICATION); WindowClass.hCursor := LoadCursor(0, IDC_ARROW); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := 'WindowClass'; //zarejestrowanie klasy okna RegisterClass(WindowClass); //stworzenie okna i zapisanie zwróconego uchwytu //do zmiennej hWindow := CreateWindow('WindowClass', 'Proste okno', WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, system.MainInstance, nil); //jeżeli udało się otworzyć okno (uchwyt różny od zera) //to wyświetl okno na ekranie if hWindow <> 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end; //pętla przetwarzająca wiadomości wysyłane //do okna while GetMessage(@AMessage, 0, 0, 0) do begin TranslateMessage(AMessage); DispatchMessage(AMessage); end; Halt(AMessage.wParam); end.
Inne kontrolki tworzymy i obsługujemy analogicznie. Najważniejsze tutaj jest poznać zasadę działania, wtedy potrafimy sobie poradzić mając do dyspozycji tylko dokumentację.
V. Okno dialogowe z zasobów
Jeżeli nie chcemy tworzyć okna i kontrolek dynamicznie (w czasie działania programu), możemy umieścić skrypt zasobów w którym zdefiniujemy wygląd okna i kontrolek. Skrypt taki można utworzyć dowolnym edytorem zasobów.
Mamy taki skrypt zasobów:
Zapisz go jako plik z rozszerzeniem *.rc. Musimy go teraz skompilować do pliku z rozszerzeniem *.res. W katalogu FreePascala znajdź plik GoRC.exe. Jest to kompilator zasobów. Wywołaj go z Wiersza Polecenia, aby skompilować skrypt zasobów:Kod:#define IDD_MAIN 1000 #define IDC_BUTTON1001 1001 IDD_MAIN DIALOGEX 10,10,237,89 CAPTION "Okno dialogowe z zasobów" FONT 9,"Verdana" STYLE 0x90c80804 EXSTYLE 0x00000000 BEGIN CONTROL "Hello!",IDC_BUTTON1001,"Button",0x10010000,87,34,52,15,0x00000000 END
Lub po prostu przeciągnij plik okno.rc nad program GoRC.exe i upuść.Kod:cd "C:\Program Files\FPC\bin\i386-win32\" GoRC okno.rc
Oto kod programu wyświetlającego okno dialogowe z zasobów:
Widzimy, że kod tego programu jest o wiele krótszy od poprzedniego. Nie rejestrujemy tutaj własnej klasy okna, tylko korzystamy z okna predefiniowanego (okna dialogowego). Na początku programu widać dyrektywę dla kompilatora informującą go, aby dołączył do programu podany skompilowany skrypt zasobów. Mimo, że program wyświetla okno z zasobów, nie rejestruje własnej klasy okna, zasada działania jest zachowana, jest pętla przetwarzająca komunikaty, funkcja obsługująca okno itp.Kod:{$APPTYPE GUI} {$MODE DELPHI} {$R okno.RES} //dołączamy plik zasobów z oknem dialogowym program OknoDialogowe; uses Windows; var AMessage : MSG; function WindowProc(Window: HWnd; AMessage: UINT; WParam : WPARAM; LParam: LPARAM): LRESULT; stdcall; export; begin WindowProc := 0; case AMessage of WM_COMMAND: begin case WParam of 1001 : MessageBox(0,'Hello!','Info',0); end; end; WM_CLOSE: begin ExitProcess(0); end; end; WindowProc := 0; end; begin //Wyświetlenie okna dialogowego z zasobów DialogBoxParam(GetModuleHandle(0),1000,0,@WindowProc,0); //kolejka komunikatów przesyłanych do okna while GetMessage(@AMessage, 0, 0, 0) do begin TranslateMessage(AMessage); DispatchMessage(AMessage); end; end.
VI. Zakończenie
Na tym kończy się ten kurs. Poznałeś podstawowe zasady pisania programów, poznałeś najważniejsze instrukcje, jak tworzy się procedury i funkcje, jak programować prostą grafikę, a w tej części poznałeś podstawy programowania w Windows API. Sądzę, że po przejściu tego kursu jesteś gotowy rozpocząć naukę programowania w jakimś "bardziej zaawansowanym" języku.
Pozdrawiam i życzę sukcesów w programowaniu, Dawid '0DFh' Farbaniec


Odpowiedź z Cytatem