Pomimo iż Delphi w dużym stopniu uwalnia programistę od operowania komunikatami Windows — na rzecz bardziej wygodnego mechanizmu zdarzeń — to jednak warto poświęcić komunikatom nieco uwagi, zwłaszcza w kontekście ich związku ze zdarzeniami Delphi. Dla projektanta nowych komponentów gruntowna znajomość natury komunikatów i zasad zarządzania nimi jest wręcz niezbędna; dla projektantów posługujących się gotowymi komponentami może być przydatna, z tego względu, iż bezpośrednie operowanie komunikatami bywa niekiedy koniecznością, bo nie wszystko da się wykonać za pomocą standardowych mechanizmów Delphi. W niniejszym rozdziale opiszemy pokrótce system komunikatów Win32 i zasady ich obsługi w Object Pascalu, zajmiemy się także ich związkiem ze zdarzeniami Delphi.
Wskazówka
Komunikaty są mechanizmem specyficznym dla Windows i nie znajdują zastosowania w aplikacjach międzyplatformowych (CLX). Szczegółowe informacje na temat aplikacji międzyplatformowych zawarte są w rozdziale 13.
Komunikaty Windows stanowią odzwierciedlenie — w postaci odpowiednich struktur danych i procedur — pewnych szczególnych sytuacji: naciśnięcia klawisza, kliknięcia, przesunięcia myszy, upłynięcia odcinka czasu itp.
Strukturą danych ucieleśniającą komunikat jest rekord zawierający informację o rodzaju zdarzenia oraz dane niosące informację dodatkową; rzeczownik „zdarzenie” należy tu rozumieć w znaczeniu potocznym, nie w sensie zdarzeń Delphi czy zdarzeń Win32. W kategoriach Object Pascala rekord ten, w najbardziej podstawowej formie, ma następującą strukturę:
Type
TMsg = packed record
// uchwyt okna, do którego komunikat jest adresowany
hwnd: HWND;
// identyfikator komunikatu
message: UINT;
// dwa 32–bitowe pola zawierające dodatkową informację
wParam: WPARAM;
lParam: LPARAM;
// czas utworzenia komunikatu
time: DWORD;
// pozycja kursora myszy w momencie utworzenia komunikatu
pt: TPoint;
end;
Powyższa definicja znajduje się w module Windows.pas, a znaczenie poszczególnych jej pól jest następujące:
· hwnd — 32-bitowy uchwyt okna, do którego komunikat jest adresowany; z każdą okienkową kontrolką Delphi związane jest okno, którego uchwyt przechowywany jest pod właściwością Handle.
· message — stała symboliczna klasyfikująca komunikat; oprócz stałych predefiniowanych w module Windows.pas możliwe jest definiowanie własnych komunikatów.
· wParam — zawartością tego pola jest najczęściej stała stowarzyszona z komunikatem; może ono również zawierać uchwyt okna źródłowego lub numer identyfikacyjny kontrolki związanej z treścią komunikatu.
· lParam — pole to zawiera najczęściej dodatkowe dane o zdarzeniu, bądź indeks, czy wskaźnik do określonej struktury danych w pamięci. Jako że pola wParam i lParam są 32-bitowe, możliwe jest ich rzutowanie na dowolny typ wskaźnikowy.
· time — zawiera czas utworzenia rekordu związanego z komunikatem.
· pt — zawiera położenie kursora myszy (we współrzędnych ekranowych) w momencie, gdy utworzono rekord związany z komunikatem.
Po zapoznaniu się z ogólną strukturą komunikatu, zobaczmy teraz, jakie typy komunikatów można napotkać w Win32.
Dla każdego standardowego komunikatu Windows, Delphi (w module Messages.pas) definiuje charakterystyczną stałą symboliczną, określającą jedną z wartości pola message rekordu TMsg. Każda z tych stałychsymbolicznych rozpoczyna się od liter WM_ (Windows Message). Zestawienie najczęściej spotykanych komunikatów zawiera tabela 3.1.
Tabela 3.1. Najczęściej występujące komunikaty Windows
Identyfikator komunikatu
Wartość
Znaczenie
WM_ACTIVATE
$0006
Okno docelowe staje się aktywne lub przestaje być aktywne.
WM_CHAR
$0102
Naciśnięto i zwolniono klawisz; komunikat ten występuje łącznie z parą komunikatów WM_KEYDOWN—WM_KEYUP .
WM_CLOSE
$0010
Okno docelowe powinno zostać zamknięte.
WM_KEYDOWN
$0100
Naciśnięto klawisz.
WM_KEYUP
$0101
Zwolniono klawisz.
WM_LBUTTONDOWN
$0201
Naciśnięto lewy przycisk myszy.
WM_MOUSEMOVE
$0200
Przesunięto kursor myszy.
WM_PAINT
$000F
Okno docelowe powinno odświeżyć swój obszar klienta (client area).
WM_TIMER
$0113
Wystąpiło zdarzenie zegarowe.
WM_QUIT
$0012
Należy zakończyć aplikację.
Z obsługą komunikatów Windows związane są trzy kluczowe elementy:
· Kolejka komunikatów (message queue) — system Windows utrzymuje oddzielną kolejkę komunikatów dla każdej aplikacji; za pobieranie komunikatów z tej kolejki i ich obsługę odpowiedzialna jest aplikacja.
· Pętla obsługi komunikatów (message loop) — centralną częścią każdej aplikacji Windows jest pętla pobierająca cyklicznie komunikaty z kolejki aplikacji i kierująca je do właściwych okien.
· Procedura okienkowa (window procedure) — jest to procedura związana z konkretnym oknem, zajmująca się obsługą komunikatów kierowanych do tego okna; jest wywoływana w trybie odwołania zwrotnego (callback), a rezultatem jej działania jest najczęściej zapisanie informacji zwrotnej w otrzymanym rekordzie komunikatu.
Notatka
Odwołanie zwrotne polega na asynchronicznym wywołaniu (przez system operacyjny) procedury lub funkcji stanowiącej część aplikacji; dokładniej zajmiemy się tym zagadnieniem w rozdziale 6.
„Koleje życia” typowego komunikatu przedstawiają się więc następująco:
Powyższy scenariusz jest przedstawiony schematycznie na rysunku 3.1. Kroki 3. i 4. realizują to, co przed chwilą nazwaliśmy pętlą obsługi komunikatu. Pętla taka jest charakterystyczna dla każdego programu Windows, gdyż cała jego praca sprowadza się do właściwego reagowania na zdarzenia zewnętrzne, czyli w konsekwencji — na komunikaty Windows. Może się oczywiście zdarzyć tak, że kolejka komunikatów jest pusta i działanie programu zostaje zawieszone w punkcie 3., aż do otrzymania jakiegoś komunikatu przez aplikację.
Rysunek 3.1. Schemat funkcjonowania komunikatów Windows
Biblioteka VCL w znacznym stopniu odciąża programistę od obsługi komunikatów, realizując chociażby wspomnianą pętlę pobierającą komunikaty (jest ona zaimplementowana w module Forms.pas). Delphi definiuje ponadto (w module Messages.pas) własną strukturę reprezentującą informację zawartą w komunikacie:
TMessage = packed record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
Struktura ta zawiera nieco mniej informacji niż jej pierwowzór TMsg, z którego przejmuje jedynie identyfikator komunikatu oraz parametry lParam i wParam; pozostałe pola są wykorzystywane wewnętrznie przez Delphi.
Pole Result, nie mające odpowiednika w strukturze TMsg, przeznaczone jest do przekazania informacji zwrotnej — jak napisaliśmy przed chwilą, procedura okienkowa musi informować system (w ściśle określonych kategoriach) o wyniku obsługi każdego komunikatu. Po zakończeniu obsługi komunikatu przez aplikację Delphi przetworzy go do postaci zgodnej ze strukturą TMsg, pobierając informację zwrotną z tego właśnie pola. Powrócimy do tej kwestii w dalszym ciągu niniejszego rozdziału.
Udogodnienia oferowane przez Delphi nie kończą się na poziomie struktury TMessage. By uwolnić programistę od mozolnego „odcyfrowywania” informacji zawartej w polach lParam i wParam, Delphi oferuje rekordy o strukturze specyficznej dla określonych typów komunikatów. Oto rekord charakterystyczny dla większości komunikatów związanych z myszą:
TWMMouse = record
Keys: Longint;
XPos: Smallint;
YPos: Smallint);
Pos: TSmallPoint;
Ten rekord jest dostępny także pod innymi nazwami synonimicznymi, co podkreśla jego związek z poszczególnymi komunikatami:
TWMLButtonDblClk = TWMMouse;
TWMLButtonDown = TWMMouse;
TWMLButtonUp = TWMMouse;
TWMMButtonDblClk = TWMMouse;
TWMMButtonDown = TWMMouse;
TWMMButtonUp = TWMMouse;
Podobne struktury zdefiniowane są dla niemal każdego standardowego komunikatu, zgodnie z jednolitą konwencją nazewniczą: po przedrostku „T” następuje nazwa komunikatu pozbawiona podkreślenia i przekształcona na charakterystyczną dla Pascala notację „wielbłądzią” — na przykład komunikatowi WM_SETFONT odpowiada struktura o nazwie TWMSetFont.
Nic oczywiście nie stoi na przeszkodzie, by zrezygnować z tych udogodnień i posługiwać się uniwersalnym rekordem TMessage, przydatnym dla każdego komunikatu.
Jak przed chwilą stwierdziliśmy, przetworzenie komunikatu przez aplikację polega na skierowaniu go do właściwej procedury okienkowej; ta dokonuje jego mozolnej klasyfikacji, interpretacji zawartej w nim informacji, po czym następuje jego właściwa obsługa i zwrotne przekazanie wyniku tej obsługi. Również w tym przypadku Delphi stwarza niebagatelne udogodnienie, g...
hansgolastyk