Kurs Pascal - IX. Podstawy programowania w Windows

D.F.

Były Moderator
Dołączył
Listopad 4, 2009
Posty
493
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ę:

Kod:
 program WinHelloWorld;

 uses Windows;

 begin
   MessageBox(0, 'Hello World!', 'Informacja', MB_OK+MB_ICONINFORMATION);
 end.

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.

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:

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.

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.

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:

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.

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.

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:

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.

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.

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:

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

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:
 cd "C:\Program Files\FPC\bin\i386-win32\"
 GoRC okno.rc

Lub po prostu przeciągnij plik okno.rc nad program GoRC.exe i upuść.

Oto kod programu wyświetlającego okno dialogowe z zasobów:

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.

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.


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
 
Do góry Bottom