r03 04 (11)


Rozdział 3.

Obsługa komunikatów Windows

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 za­rządzania nimi jest wręcz niezbędna; dla projektantów posługujących się go­towymi komponentami może być przydatna, z tego względu, iż bez­pośrednie operowanie komunikatami bywa niekiedy koniecznością, bo nie wszyst­ko 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.

Natura komunikatów

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:

Po zapoznaniu się z ogólną strukturą komunikatu, zobaczmy teraz, jakie typy komunikatów można napotkać w Win32.

Typy komunikatów

Dla każdego standardowego komunikatu Windows, Delphi (w module Messages.pas) defi­niuje 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_ (Win­dows 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_KEYDOWNWM_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ę.

Jak funkcjonuje system komunikatów Windows?

Z obsługą komunikatów Windows związane są trzy kluczowe elementy:

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:

  1. W systemie występuje określone zdarzenie.

  2. System dokonuje klasyfikacji zdarzenia, tworzy reprezentującą je strukturę danych i umieszcza ją w kolejce związanej z aplikacją, której zdarzenie dotyczy.

  3. Aplikacja odczytuje wspomnianą strukturę z kolejki i formuje na jej podstawie rekord reprezentujący komunikat.

  4. Aplikacja przekazuje rekord komunikatu do właściwej procedury okienkowej

  5. Procedura okienkowa wykonuje działania specyficzne dla otrzymanego komunikatu.

Powyższy scenariusz jest przedstawiony schematycznie na rysunku 3.1. Kroki 3. i 4. realizują to, co przed chwilą nazwaliśmy pętlą ob­sł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 konse­kwencji — na komunikaty Windows. Może się oczywiście zdarzyć tak, że kolejka komunika­tów jest pusta i działanie programu zostaje zawieszone w punkcie 3., aż do otrzymania jakiegoś komunikatu przez aplikację.

0x01 graphic

Rysunek 3.1. Schemat funkcjonowania komunikatów Windows

Obsługa komunikatów w kategoriach Delphi

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);

end;

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 prze­kazania informacji zwrotnej — jak napisaliśmy przed chwilą, procedura okien­kowa 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 prze­tworzy 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.

Struktury specyficzne dla różnych typów komunikatów

Udogodnienia oferowane przez Delphi nie kończą się na poziomie struktury TMessage. By uwolnić programistę od mozolnego „odcyfrowywania” informacji zawartej w po­lach 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

Msg: Cardinal;

Keys: Longint;

case Integer of

0: (

XPos: Smallint;

YPos: Smallint);

1: (

Pos: TSmallPoint;

Result: Longint);

end;

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.

Przetwarzanie komunikatów

Jak przed chwilą stwierdziliśmy, przetworzenie komunikatu przez aplikację polega na skie­rowaniu go do właściwej procedury okienkowej; ta dokonuje jego mozolnej kla­syfikacji, interpretacji zawartej w nim informacji, po czym następuje jego właściwa ob­sługa i zwrotne przekazanie wyniku tej obsługi. Również w tym przypadku Delphi stwarza niebagatelne udogodnienie, gdyż, zamiast jednej uniwersalnej procedury okienkowej, możliwe jest definiowanie odrębnych procedur przeznaczonych dla wybranych typów komunikatów. Każda z tych procedur musi być metodą obiektu, posiadającą jeden parametr przekazywany przez referencję i opatrzoną klauzulą message specyfikującą identyfikator obsługiwanego komunikatu. Oto przykład deklaracji metody obsługującej komunikat WM_PAINT:

procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

Z punktu widzenia Delphi nazwa metody obsługującej komunikat może być dowolna, dla przejrzystości zaleca się jednak stosowanie konwencji nazewniczej takiej samej, jak w przypadku struktur dla poszczególnych komunikatów.

W charakterze przykładu stwórzmy metodę obsługującą komunikat WM_PAINT; „obsługa” będzie się tu sprowadzać do wyemitowania krótkiego sygnału dźwiękowego. Deklaracja metody w sekcji private formularza będzie mieć następującą postać:

Procedure WMPaint ( var Msg : TWMPaint ); message WM_PAINT;

Jej implementacja jest prosta:

Procedure WMPaint ( var Msg : TWMPaint );

begin

MessageBeep(0);

inherited;

end;

Instrukcja inherited powoduje przekazanie komunikatu do procedury obsługi w klasie macierzystej — w tym przypadku TForm.

Notatka

Zauważ, że po słowie inherited nie występuje nazwa metody, ponieważ nie chodzi tu o konkretnie nazwaną metodę klasy macierzystej, lecz metodę obsługującą konkretny komunikat.

Na wydruku 3.1 znajduje się kompletny kod projektu ilustrującego opisywaną obsługę komunikatu; na załączonym krążku CD-ROM projekt ten ma nazwę GetMess.dpr.

Wydruk 3.1. Przykład obsługi komunikatu

unit GMMain;

interface

uses

SysUtils, Windows, Messages, Classes, Graphics, Controls,

Forms, Dialogs;

type

TForm1 = class(TForm)

private

procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WMPaint(var Msg: TWMPaint);

begin

MessageBeep(0);

inherited;

end;

end.

Gdy aplikacja otrzymuje od systemu komunikat WM_PAINT (stanowiący polecenie odświeżenia zawartości formularza) wywoływana jest metoda WMPaint(), generująca krótki sygnał dźwiękowy i przekazująca komunikat do standardowej obsługi.

<ramka>

Generowanie sygnałów dźwiękowych od zawsze stanowiło swoistą namiastkę debuggera, gdyż wyemitowanie określonego wzorca dźwiękowego było świadectwem tego, iż wykonany został określony blok instrukcji. Niezależnie od niebywałego rozwoju technik programistycznych, w ostatnich dwóch dziesięcioleciach ów prymitywny „debugger babuni” nadal jest bardzo atrakcyjny — wywoływanie metody MessageBeep() z różnymi predefiniowanymi parametrami powoduje generowanie rozmaitych dźwięków, poprzez wbudowany głośniczek lub głośniki przyłączone do karty dźwiękowej. Mimo całego swego prymitywizmu ma to wiele niezaprzeczalnych zalet: nie wymaga ustawiania punktów przerwań, nie konsumuje zasobów Windows itp.

Jeżeli (jak autorzy) nie lubisz wpisywać długich nazw funkcji, możesz posłużyć się funkcją Beep() zdefiniowaną w module SysUtils:

{$IFDEF MSWINDOWS}

procedure Beep;

begin

MessageBeep(0);

end;

{$ENDIF}

{$IFDEF LINUX}

procedure Beep;

var

ch: Char;

begin

ch := #7;

__write(STDOUT_FILENO, ch, 1);

end;

{$ENDIF}

<ramka>

Kontraktowy charakter obsługi komunikatów

W pierwszym rozdziale książki stwierdziliśmy, że operowanie zdarzeniami Delphi ma charakter niekontraktowy (contract-free), co oznacza, iż programista nie ma obowiązku zajmować się zdarzeniami, w związku z którymi aplikacja nie wykonuje żadnych specjalnych czynności.

Z komunikatami rzecz się ma zupełnie inaczej: Windows, wysyłając do aplikacji określony komunikat, oczekuje od niej wykonania związanych z tym komunikatem konkretnych czynności. Na szczęście komponenty biblioteki VCL wykonują to zadanie, a jeżeli użytkownik chce (w ramach definiowanej przez siebie klasy) obsłużyć któryś komunikat w sposób niestandardowy, jedyną jego powinnością wobec kontraktowego charakteru komunikatów jest przekazanie komunikatu do klasy macierzystej (za pomocą instrukcji inherited).

Notatka

Kontraktowość komunikatów to jednak coś więcej niż tylko konieczność zapewnienia im obsługi odziedziczonej z klasy macierzystej; obsługa niektórych komunikatów wiąże się mianowicie z dodatkowymi ograniczeniami — na przykład w ramach obsługi komunikatu WM_KILLFOCUS (w kontekście danej kontrolki) niedozwolone jest przeniesienie --> skupienia [Author:AG] na inną kontrolkę, pod groźbą zawieszenia systemu operacyjnego.

Konsekwencje kontraktowego charakteru obsługi komunikatów możemy poznać bez trudu, usuwając instrukcję inherited z metody WMPaint() formularza projektu GetMess.dpr:

procedure TForm1.WMPaint(var Msg: TWMPaint);

begin

MessageBeep(0);

// inherited;

end;

Takie posunięcie nie daje systemowi operacyjnemu żadnej szansy na przetworzenie komunikatu WM_PAINT, wskutek czego formularz w ogóle nie zostanie wyświetlony. Mało tego — nieustanne wysyłanie do formularza komunikatu WM_PAINT spowoduje serię pisków w głośniku komputera, trwającą aż do zamknięcia lub zminimalizowania formularza.

Zdarza się jednak, iż brak odwołania do odziedziczonej obsługi komunikatu jest efektem zamierzonym — w ten właśnie sposób można np. powstrzymać minimalizację lub maksymalizację okna w odpowiedzi na komunikat WM_SYSCOMMAND.

Zwrotne przekazywanie wyniku obsługi komunikatu

System Windows, kierując do aplikacji różnorodne komunikaty, oczekuje zazwyczaj informacji zwrotnej na temat przebiegu ich obsługi. W kategoriach Delphi informacja ta wpisywana jest w pole Result struktury TMessage i innych struktur związanych z komunikatami. Klasycznym przy­kładem komunikatu tego typu jest komunikat WM_CTLCOLOR: system Windows oczekuje jako informacji zwrotnej uchwytu „pędzla” (brush) określającego kolor wyświetla­nego obiektu. Przy założeniu, iż uchwyt ten przechowywany jest w zmiennej BrushHand, stosow­na procedura obsługi komunikatu mogłaby wyglądać następująco:

procedure TForm1.WMCtlColor(var Msg: TWMCtlColor);

begin

inherited;

Msg.Result := BrushHand;

end;

Powyższy przykład ma jednak znaczenie raczej teoretyczne, ponieważ każdy komponent posiada właściwość Color, co uwalnia programistę od bezpośredniej obsługi komunikatu WM_CTLCOLOR.

Zdarzenie OnMessage klasy TApplication

Zdarzenie Application.OnMessage stanowi wygodny środek przechwytywania ko­munikatów skierowanych do aplikacji. Przypisanie mu procedury obsługi sprawi, że każ­dy komunikat pobrany z kolejki zostanie przez tę procedurę przechwycony przed skie­rowaniem go do standardowej obsługi przez Windows. Umożliwia to niestandardową obsługę komu­nikatów — a wręcz całkowitą kontrolę nad nimi — lecz programista powinien być świa­dom tego, iż może się to odbić niekorzystnie na ogólnej efektywności aplikacji, bo wspomniane zdarzenie wywoływane jest w odpowiedzi na każdy komunikat umieszczony w kolejce aplikacji. Wymaga to szczególnej efektywności od procedury zdarzeniowej — więc np. ustawienie punktu przerwania w jej wnętrzu byłoby raczej nierozsądne.

Nagłówek procedury obsługującej zdarzenie OnMessage powinien mieć następującą strukturę (TMessageEvent w kategoriach Delphi):

procedure JakiśObiekt.JakaśNazwa ( var Msg : TMsg; var Handled : Boolean);

Wyjściowa wartość drugiego parametru zawiera informację o tym, czy komunikat został obsłużony w całości (True), czy też wymagana jest jeszcze jego standardowa obsługa ze strony Windows (False).

Zdarzeniu Application.OnMessage można przypisać procedurę obsługi w kodzie programu:

Application.OnMessage := Form1.AppMessageHandler;

możemy to również uczynić na etapie projektowania, umieszczając na formularzu komponent TApplicationEvents ze strony Additional palety komponentów i oprogramowując jego zdarzenie OnMessage, na przykład:

var

NumMessages: integer;

procedure TForm1.ApplicationEvents1Message(var Nsg: tagMSG; var Handled:Boolean);

begin

Inc(NumMessages);

Handled := False;

end;

W powyższym przykładzie zmienna NumMessages stanowi licznik komunikatów pobieranych z kolejki aplikacji.

Zdarzenie Application.OnMessage nie jest generowane dla komunikatów kierowanych do aplikacji w sposób bezpośredni, z pominięciem kolejki. Więcej informacji na ten temat znajdziesz na początku rozdziału 13. książki „Delphi 4. Vademecum profesjonalisty”.

Wysyłanie własnych komunikatów

Jak Windows przekazuje komunikaty swoim aplikacjom, tak jest możliwe opra­cowanie własnego systemu komunikatów przesyłanych między komponentami tej samej aplikacji. Delphi oferuje kilka procedur ułatwiających tę czynność: SendMessage(), PostMessage()i Perform()— pierwsze dwie działają na podstawie Win32 API, trze­cia niezależnie od niego.

Metoda Perform()

Jest to metoda klasy TControl; służy do bezpośredniego przekaza­nia określonego komunikatu do wskazanej kontrolki. Metoda ta posiada trzy parametry: identyfikator komunikatu i dwie wartości stanowiące jego treść:

Function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

Metoda Perform() tworzy dla komunikatu odpowiedni blok TMessage oraz wywołuje metodę Dispatch(), omijając w ten sposób interfejs Win32 (metoda Dispatch() jest opisana w dalszej części niniejszego rozdziału). Metoda Perform() funkcjonuje w sposób synchroniczny — kończy swe działanie dopiero po całkowitym obsłużeniu komunikatu.

Funkcje SendMessage() i PostMessage()

Metoda Perform() okazuje się jednak całkowicie bezużyteczna, jeżeli chcemy przekazać komunikat do nieznanego obiektu, bądź do okna reprezentowanego jedynie przez uchwyt, należącego do aplikacji nie stworzonej w Delphi. Z pomocą przychodzą wówczas dwie procedury Win32 API: SendMessage() i PostMessage(), zadowalające się jedynie uchwytem (handle) okna docelowego. Obydwie te funkcje są niemal identyczne, jedyna różnica pomiędzy nimi sprowadza się do sposobu przekaza­nia komunikatu: SendMessage(), podobnie jak Perform(), wysyła komunikat bezpo­średnio do okna docelowego i oczekuje na zakończenie jego obsługi. PostMessage() — wprost przeciwnie — przekazuje natomiast komunikat do kolejki Windows i natychmiast zwraca ste­rowanie. Formalna deklaracja opisywanych funkcji wygląda tak:

Function SendMessage(hWnd: HWND; Msg: UINT;

wParam : WPARAM; lParam : LPARAM): LRESULT;stdcall;

Function PostMessage(hWnd: HWND; Msg: UINT;

wParam : WPARAM; lParam : LPARAM): BOOL;stdcall;

Znaczenie ich parametrów jest następujące:

Wspomniane funkcje różnią się także pod względem typu i znaczenia zwracanego wy­niku: SendMessage() zwraca wartość równą zwrotnej wartości pola Result z rekordu TMsg, natomiast PostMessage() informuje jedynie o umieszczeniu (True), bądź nie­umieszczeniu (False) komunikatu w kolejce okna docelowego.

Komunikaty niestandardowe

Oprócz „regularnych” komunikatów Windows, o identyfikatorach rozpoczynających się od WM_, istnieją dwie ważne ich grupy, wymagające odrębnego omówienia: są to komunikaty powiadamiające (notification messages) i komunikaty użytkownika (user-defined messages).

Komunikaty powiadamiające

Komunikaty powiadamiające przesyłane są od okien potomnych (child windows) do okna macie­rzystego (parent window) w celu poinformowania go o pewnych zdarzeniach. Mają miejsce niemal wyłącznie podczas współpracy ze standardowymi kontrolka­mi Windows — przyciskami, listami wyboru, listami rozwijanymi, okienkami edy­cyjnymi itp. „Naciśnięcie” przycisku, przesunięcie suwaka na pasku przewijania, wybra­nie określonego tekstu — wszystko to staje się źródłem komunikatów powiadamiających.

W środowisku Delphi tego rodzaju komunikaty najczęściej kierowane są ze strony kom­ponentów do formularza, a więc to on powinien zawierać procedury ich obsługi. Tabela 3.2 zawiera zestawienie komunikatów powiadamiających związanych ze standardowymi kontrolka­mi Win32.

Tabela 5.2. Komunikaty powiadamiające związane ze standardowymi kontrolka­mi Win32

Symbol

Znaczenie

Przycisk

BN_CLICKED

Kliknięto przycisk

BN_DISABLE

Przycisk został zablokowany

BN_DOUBLECLICKED

Kliknięto dwukrotnie przycisk

BN_HILITE

Przycisk stał się przyciskiem domyślnym

BN_PAINT

Należy odświeżyć obraz przycisku

BN_UNHILITE

Przycisk przestał być przyciskiem domyślnym

Lista rozwijana (combo)

CBN_CLOSEUP

Lista została zamknięta

CBN_DBLCLK

Kliknięto dwukrotnie pozycję listy

CBN_DROPDOWN

Lista została rozwinięta

CBN_EDITCHANGE

Została zmieniona zawartość w oknie edycyjnym

CBN_EDITUPDATE

Należy odświeżyć zmieniony tekst w oknie edycyjnym

CBN_ERRSPACE

Brak pamięci do wykonania operacji

CBN_KILLFOCUS

Lista przestaje być aktywna

CBN_SELCHANGE

Wybrano nową listę

CBN_SELENDCANCEL

Należy anulować wybór

CBN_SELENDOK

Należy zatwierdzić wybór

CBN_SETFOCUS

Lista stała się aktywna

Kontrolka edycyjna

EN_CHANGE

Zmieniono edytowany tekst

EN_ERRSPACE

Brak pamięci do wykonania operacji

EN_HSCROLL

Kliknięto poziomy pasek przewijania

EN_KILLFOCUS

Kontrolka edycyjna przestała być aktywna

EN_MAXTEXT

Po wstawieniu znaku tekst wykracza poza kontrolkę edycyjną

EN_SETFOCUS

Kontrolka edycyjna staje się aktywna

EN_UPDATE

Należy odświeżyć wyświetlany tekst w kontrolce edycyjnej

EN_VSCROLL

Kliknięto pionowy pasek przewijania

Lista wyboru

LBN_DBLCLK

Dwukrotne kliknięcie pozycji listy

LBN_ERRSPACE

Brak pamięci do wykonania operacji

LBN_KILLFOCUS

Lista przestaje być aktywna

LBN_SELCANCEL

Anulowano wybór

LBN_SELCHANGE

Przesunięto wybór na inną pozycję

LBN_SETFOCUS

Lista wyboru staje się aktywna

Wewnętrzne komunikaty VCL

Komponenty biblioteki VCL posiadają rozbudowaną kolekcję komunikatów wewnętrz­nych; komunikaty te nie są raczej przydatne w procesie budowania aplikacji, jednak ich zna­jomość jest nieodzowna dla projektanta nowych komponentów. Identyfikatory tych komunikatów roz­poczynają się od przedrostka CM_ (Component Message) lub CN_ (Component Notification), a komunikaty te wykorzystywane są do wewnętrznego ustawiania aktywności, kolo­rów, widzialności, odtwarzania okien, przeciągania komponentów itp. Ich kompletna lista znajduje się w systemie pomocy, w części poświęco­nej tworzeniu komponentów.

Jednym z przykładów zastosowania komunikatów na wewnętrzne potrzeby komponentów VCL jest wykrywanie sytuacji, gdy kursor myszy wchodzi w obszar danej kontrolki lub obszar ten opuszcza — generowane są wówczas komunikaty (odpowiednio) CM_MOUSEENTER i CM_MOUSELEAVE. Rozpatrzmy w charakterze przykładu następujący fragment definicji komponentu:

TSpecialPanel = class(TPanel)

protected

procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;

procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;

end;

procedure TSpecialPanel.CMMouseEnter(var Msg: TMessage);

begin

inherited;

Color := clWhite;

end;

procedure TSpecialPanel.CMMouseLeave(var Msg: TMessage);

begin

inherited;

Color := clBtnFace;

end;

Komponent TSpecialPanel reaguje na wniknięcie kursora w jego obszar przyjmując kolor biały; po przesunięciu kursora poza jego obszar wraca do standardowego koloru clBtnFace. Możesz to zaobserwować, uruchamiając przykładowy projekt CustMessage.dpr znajdujący się na załączonym krążku CD-ROM.

Komunikaty definiowane przez użytkownika

Często zdarza się, iż aplikacja chce poinformować inne aplikacje o pewnym zdarzeniu, bądź też chcą się ze sobą skomunikować obiekty tej samej aplikacji — chodzi tu o sytuacje charakterystyczne z punktu widzenia danej aplikacji. Wielce pomocne mogą się wówczas okazać komunikaty.

W przypadku komunikujących się obiektów tej samej aplikacji można by postawić pytanie „po co w ogóle posługiwać się komunikatami, czyż nie wystarczy wprost wywo­łać odpowiednią metodę obiektu docelowego?”

Dobre pytanie — i kilka równie dobrych odpowiedzi. Otóż, po pierwsze, przekazanie komunikatu do obiektu jest znacznie mniej krępującym sposobem komunikacji niż wywołanie jego me­tody — po wysłaniu komunikatu można kontynuować pracę i nie przejmować się tym, co stanie się z wysłanym komunikatem. Drugim ważnym powodem jest swoisty „polimorficzny” charakter komunikatów — wysyłając określony komunikat pod adresem danego obiektu, nie musimy znać szczegółów jego definicji w kategoriach syntaktycznych; przy bez­pośrednim wywoływaniu metody znajomość taka byłaby nieodzowna. Trzecia przyczyna związana jest natomiast z komunikatami rozgłaszającymi (o nich za chwilę) — pojedynczy komunikat tego typu zauważalny jest dla wielu adresatów, nie znanych nawet dokładnie obiektowi-nadawcy; bezpośrednie wywoływanie metod obiektów-adre­satów, jeżeli nawet dałoby się zrealizować, wymagałoby skomplikowanych iteracji. Wreszcie — obsługa komunikatu nie musi być obowiązkowa: jeżeli obiektu-adresata nie wiążą z danym komunikatem żadne szczególne zadania, nie musi on nawet definiować obsługującej go metody.

Przekonawszy się więc o celowości operowania komunikatami, przyjrzyjmy się dokład­niej ich poszczególnym kategoriom.

Wewnętrzne komunikaty aplikacji

Jest to dość nieskomplikowana kategoria komunikatów. Do ich przesyłania służą pozna­ne już funkcje SendMessage(), PostMessage() i Perform(), a jako identyfikatory można wykorzystywać wartości od WM_USER+100 do $7FFF (ten właśnie zakres system Windows rezerwuje dla komunikatów użytkownika). Oto prosty przykład:

const

sx_MyMessage = wm_User+100;

begin

SomeForm.Perform (sx_MyMessage, 0, 0);

{ lub }

SendMessage (SomeForm.Handle, sx_MyMessage, 0, 0);

{ lub }

PostMessage (SomeForm.Handle, sx_MyMessage, 0, 0);

....

end;

Formularz SomeForm należy oczywiście wyposażyć w stosowną procedurę obsługi komunikatu sx_Message:

TForm1 = class(TForm)

...

private

procedure SXMyMessage(var Msg: TMessage);
message sx_MyMessage;

end;

....

....

Procedure TForm1.SXMyMessage(var Msg: TMessage);

begin

MessageDlg('Ona zamieniła mnie w żabę!', mtInformation, [mbOK], 0);

end;

Obsługa komunikatów użytkownika nie różni się więc zbytnio od obsługi standardowych komunikatów Windows. Jest rzeczą zrozumiałą, iż komunikujące się aplikacje (lub poszczególne komponenty danej aplikacji) muszą stosować jednolitą konwencję numeracji komunikatów, zaś dla przejrzystości i czytelności kodu należy na oznaczenie poszczególnych komunikatów wybierać nazwy powiązane ze spełnianymi przez nie funkcjami.

Ostrzeżenie

Nie wysyłaj nigdy komunikatu z zakresu WM_USER ÷ WM_USER+$7F, chyba że masz gwarancję, iż obiekt docelowy obsłuży go zgodnie z Twoimi oczekiwaniami. Ponieważ każde okno może posługiwać się komunikatami z tego zakresu niezależnie od pozostałych okien, nietrudno o kolizję identyfikatorów.

Komunikaty międzyaplikacyjne

Narzucenie jednolitej konwencji numerowania komunikatów w przypadku współpracy różnych aplikacji, stworzonych często przez różnych autorów, jest zadaniem trud­nym, a czasem wręcz niewykonalnym. Ponadto, na etapie projektowania aplikacji niemożliwe jest wybranie numerów komunikatów nie kolidujących z innymi aplikacja­mi, leżącymi poza sferą naszego zainteresowania. Stąd pomysł, aby porozumiewające się aplikacje używały do identyfikacji komunikatów nie numerów, lecz napisów (łańcu­chów znaków); potrzebny byłby jeszcze tylko globalny mechanizm odwzorowujący jednakowe napisy w jednakowe identyfikatory numeryczne, nie kolidujące z innymi, już wykorzystywanymi w innych aplikacjach. Mechanizmu takiego dostarcza funkcja RegisterWindowMessage(). Posiada jeden parametr, będący łańcuchem z zero­wym ogranicznikiem zawierającym nazwę komunikatu, a jej wynikiem jest (nadany przez Windows) identyfikator tegoż komunikatu, z zakresu $C000 ÷ $FFFF. Funkcja ta gwarantuje, że w ramach tej samej sesji Windows identyczne napisy będą odwzorowywane w identyczne identyfi­katory.

Z komunikatami, których identyfikatory uzyskiwane są za pomocą funkcji Register­WindowMessage() wiąże się jednak pewna subtelna trudność. Otóż, jak sobie zapewne przypominasz, w definicji metody obiektu obsługującej komunikat, po słowie message występuje konkretny identyfikator komunikatu, ergo — ten sposób obsługi daje się za­stosować wyłącznie do komunikatów, których identyfikatory znane są już w momencie kompilacji. Komunikaty o dynamicznie uzyskiwanych identyfikatorach muszą być obsługiwane w inny sposób — mianowicie poprzez przedefiniowanie procedury okien­kowej (subclassing) lub w ramach domyślnej procedury obsługi, jaką jest metoda DefaultHandler()obiektu. Zagadnienie to zostało szczegółowo opisane w 13. rozdziale „Delphi 4. Vademecum profesjonalisty”.

Notatka

Identyfikator zwracany przez funkcję RegisterWindowMessage() ma znacze­nie lokalne dla bieżącej sesji Windows — w następnej sesji funkcja ta może zwrócić dla tego samego parametru inny identyfikator. Nie ma więc żadnego sensu używanie tego identyfikatora na etapie projektowania.

Komunikaty rozgłaszające

Komunikaty rozgłaszające (broadcasts) skierowane są do wszystkich kontrolek potomnych (child) danej kontrolki. Do ich rozsyłania służy metoda Broadcast() klasy TWinControl. Oto przykład wysłania komunikatu UM_FOO do wszystkich kontrolek zawartych w panelu Panel1:

var

M : TMessage;

begin

with M do

begin

Message := um_Foo;

wParam := 0;

lParam := 0;

Result := 0;

end;

Panel1.Broadcast (M);

end;

Anatomia systemu komunikatów VCL

Dla programisty korzystającego z komponentów VCL metoda obiektu, zadeklarowana z użyciem klauzuli message jest jedynym widocznym miejscem, w którym odbywa się obsługa komunikatu. Metoda ta jest jednakże tylko jednym z etapów, przez które przejść musi komunikat na swej „drodze życiowej” — pozostałe etapy skrywają się wewnątrz pro­cedur implementacyjnych biblioteki VCL. Mimo ich „skrytego” charakteru, dobrze jest mieć świadomość ich istnienia, zaś dla zaawansowanych programistów szczegóły obsłu­gi komunikatów trafiających do metod obiektów z pewnością okażą się interesujące.

A więc: dla komunikatów wysyłanych za pomocą PostMessage() — nazwijmy je ko­munikatami kolejkowanymi — pierwszym „przystankiem” jest metoda Application. ProcessMessage(), cyklicznie wywoływana w ramach metody Application.Pro­cessMessages(), aż do wyczerpania kolejki wejściowej:

procedure TApplication.ProcessMessages;

var

Msg: TMsg;

begin

while ProcessMessage(Msg) do {loop};

end;

Istotą metody ProcessMessage() jest wygenerowanie zdarzenia OnMessage:

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;

var

Handled: Boolean;

begin

Result := False;

if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then

begin

Result := True;

if Msg.Message <> WM_QUIT then

begin

Handled := False;

if Assigned(FOnMessage)

then

FOnMessage(Msg, Handled);

if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and

not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then

begin

TranslateMessage(Msg);

DispatchMessage(Msg);

end;

end

else

FTerminate := True;

end;

end;

Jak łatwo zauważyć, zdarzenie to nie jest generowane dla komunikatu WM_QUIT. Jeżeli komunikat nie został całkowicie obsłużony w ramach zdarzenia OnMessage (Handled = False), jest on (pod pewnymi warunkami) kierowany do funkcji DispatchMessage() wywołującej funkcję StdWndProc(); ta ostatnia kieruje komunikat do obiektu docelowego:

function StdWndProc(

Window: HWND;

Message, WParam: Longint;

LParam: Longint): Longint; stdcall; assembler;

asm

XOR EAX,EAX

PUSH EAX

PUSH LParam

PUSH WParam

PUSH Message

MOV EDX,ESP

MOV EAX,[ECX].Longint[4]

CALL [ECX].Pointer

ADD ESP,12

POP EAX

end;

Komunikaty wysyłane za pośrednictwem SendMessage() — nazwijmy je komunikatami bez­pośrednimi — nie trafiają do kolejki wejściowej. Funkcja SendMessage() przekazuje je bezpośrednio do funkcji StdWndProc() — jest więc jasne, że „omijają” one metodę ProcessMessage() i wobec tego nie powodują wystąpienia zdarzenia OnMessage().

Począwszy od funkcji StdWndProc(), dalszy los komunikatu jest już niezależny od spo­sobu jego wygenerowania (PostMessage() czy SendMessage()). Miejscem, do któ­rego przekazała go procedura StdWndProc(), jest metoda MainWndProc() obiektu docelowego, zdefiniowana w klasie TWinControl:

procedure TWinControl.MainWndProc(var Message: TMessage);

begin

try

try

WindowProc(Message);

finally

FreeDeviceContexts;

FreeMemoryContexts;

end;

except

Application.HandleException(Self);

end;

end;

Właściwość WindowProc reprezentuje procedurę, której adres znajduje się w polu FWindowProc kontrolki:

property WindowProc: TWndMethod read FWindowProc write FWindowProc;

Domyślnie pole to zawiera wskazanie na metodę WndProc() dokonującą standardowej dla VCL obsługi komunikatu; wskazanie to jest mu przypisywane w konstruktorze kontrolki:

constructor TControl.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FWindowProc := WndProc;

end;

Jeżeli więc programista będzie chciał zmienić standardowy sposób obsługi komunikatu przez daną kontrolkę, może to uczynić zmieniając właściwości WindowProc.

Metoda WndProc(), po wstępnym przetworzeniu komunikatu, kieruje go do metody Dispatch():

procedure TControl.WndProc(var Message: TMessage);

var

Form: TCustomForm;

KeyState: TKeyboardState;

WheelMsg: TCMMouseWheel;

begin

if (csDesigning in ComponentState) then

begin

Form := GetParentForm(Self);

if (Form <> nil) and (Form.Designer <> nil) and

Form.Designer.IsDesignMsg(Self, Message) then Exit

end;

if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then

begin

Form := GetParentForm(Self);

if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;

end

else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then

begin

if not (csDoubleClicks in ControlStyle) then

case Message.Msg of

WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:

Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);

end;

case Message.Msg of

WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);

WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:

begin

if FDragMode = dmAutomatic then

begin

BeginAutoDrag;

Exit;

end;

Include(FControlState, csLButtonDown);

end;

WM_LBUTTONUP:

Exclude(FControlState, csLButtonDown);

else

with Mouse do

if WheelPresent and (RegWheelMessage <> 0) and

(Message.Msg = RegWheelMessage) then

begin

GetKeyboardState(KeyState);

with WheelMsg do

begin

Msg := Message.Msg;

ShiftState := KeyboardStateToShiftState(KeyState);

WheelDelta := Message.WParam;

Pos := TSmallPoint(Message.LParam);

end;

MouseWheelHandler(TMessage(WheelMsg));

Exit;

end;

end;

end

else if Message.Msg = CM_VISIBLECHANGED then

with Message do

SendDockNotification(Msg, WParam, LParam);

Dispatch(Message);

end;

Ostatecznie, metoda Dispatch() przekazuje komunikat do przeznaczonej dla niego metody obiektu:

procedure TObject.Dispatch(var Message);

asm

PUSH ESI

MOV SI,[EDX]

OR SI,SI

JE @@default

CMP SI,0C000H

JAE @@default

PUSH EAX

MOV EAX,[EAX]

CALL GetDynaMethod

POP EAX

JE @@default

MOV ECX,ESI

POP ESI

JMP ECX

@@default:

POP ESI

MOV ECX,[EAX]

JMP dword ptr [ECX].vmtDefaultHandler

end;

Pierwsze dwa bajty struktury Message zawierają identyfikator komunikatu. Metoda Dispatch() sprawdza wpierw, czy jest to identyfikator zerowy — jeżeli tak, to komunikat kierowany jest do obsługi domyślnej przez metodę DefaultHandler(). Kolejny test polega na porównaniu identyfikatora komunikatu z wartością $C000 jako dolną granicą identyfikatorów generowanych przez funkcję RegisterWindowMessage(); jeżeli identyfikator należy do tej właśnie kategorii, od razu kierowany jest do obsługi domyślnej — jak przed chwilą zaznaczyliśmy, identyfikatory z tego zakresu nie mają sensu na etapie projektowania, nie da się im więc przypisać metody z klauzulą message.

Z punktu widzenia wewnętrznych mechanizmów Object Pascala, metoda z klauzulą message jest metodą dynamiczną, a identyfikator komunikatu jest jej indeksem. Odnalezienie żądanej metody dynamicznej obiektu realizowane jest przez funkcję systemową GetDynaMethod(). Otrzymuje ona w rejestrze EAX wartość identyfikującą klasę obiektu (czyli wskaźnik do tablicy VMT), zaś w rejestrze SI — indeks metody; adres żądanej metody zwracany jest w rejestrze ESI (o ile wyzerowana jest flaga ZF). Po powrocie z metody obsługującej komunikat przekazywany jest do obsługi domyślnej. Jeżeli metoda dynamiczna o żądanym indeksie nie istnieje (flaga ZF jest ustawiona), komunikat kierowany jest do obsługi domyślnej od razu.

W ten oto sposób komunikat, z chwilą przekazania go do dedykowanej mu metody obiektu, wymyka się spod kontroli systemu operacyjnego; jeżeli więc ma on być przekazany do obsługi odziedziczonej (inherited), przekazania tego musi dokonać wspomniana metoda.

Ponadto, jak już wcześniej powiedzieliśmy, komunikaty o identyfikatorach wygenerowanych przez metodę RegisterWindowMessage() trafiają od razu do metody DefaultHandler(); jest ona więc najbardziej odpowiednim miejscem do obsługi tego typu komunikatów.

Opisaną wędrówkę komunikatu w ramach biblioteki VCL przedstawia schematycznie rysunek 3.2.

0x01 graphic

Rysunek 3.2. Obsługa komunikatu w ramach biblioteki VCL

W celu ilustracji obsługi komunikatu na poszczególnych etapach skonstruowaliśmy przykładowy projekt o nazwie CatchIt; jego formularz główny jest przedstawiony na rysunku 3.3.

0x01 graphic

Rysunek 3.3. Formularz główny projektu CatchIt.dpr

Projekt umożliwia przesłanie komunikatu do formularza za pomocą metody PostMessage() albo SendMessage() w wyniku kliknięcia jednego z przycisków. Sposób przesłania komunikatu identyfikowany jest w jego polu wParam1 dla PostMessage() i 0 dla SendMessage():

procedure TMainForm.PostMessButtonClick(Sender: TObject);

{ zakolejkuj komunikat dla formularza }

begin

PostMessage(Handle, SX_MYMESSAGE, 1, 0);

end;

procedure TMainForm.SendMessButtonClick(Sender: TObject);

{ wyślij komunikat bezpośrednio do formularza }

begin

SendMessage(Handle, SX_MYMESSAGE, 0, 0);

end;

Projekt ten stwarza okazję do zakończenia obsługi komunikatu („zjedzenia go”, jak piszą autorzy oryginału) na każdym z czterech etapów — w obsłudze zdarzenia OnMessage, w metodzie WndProc(), w dedykowanej metodzie obsługi i w metodzie DefaultHandler(); owo „zjedzenie” polega po prostu na zaniechaniu jego odziedziczonej obsługi. Kod źródłowy formularza głównego projektu został przedstawiony na wydruku 3.2.

Wydruk 3.2. Kod formularza głównego projektu CatchIt.dpr

unit CIMain;

interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls, ExtCtrls, Menus;

const

SX_MYMESSAGE = WM_USER; // komunikat definiowany przez użytkownika

MessString = '%s komunikat jest teraz w %s.';

type

TMainForm = class(TForm)

GroupBox1: TGroupBox;

PostMessButton: TButton;

WndProcCB: TCheckBox;

MessProcCB: TCheckBox;

DefHandCB: TCheckBox;

SendMessButton: TButton;

AppMsgCB: TCheckBox;

EatMsgCB: TCheckBox;

EatMsgGB: TGroupBox;

OnMsgRB: TRadioButton;

WndProcRB: TRadioButton;

MsgProcRB: TRadioButton;

DefHandlerRB: TRadioButton;

procedure PostMessButtonClick(Sender: TObject);

procedure SendMessButtonClick(Sender: TObject);

procedure EatMsgCBClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure AppMsgCBClick(Sender: TObject);

private

{ obsługa komunikatu na szczeblu aplikacji }

procedure OnAppMessage(var Msg: TMsg; var Handled: Boolean);

{ obsługa komunikatu w metodzie okienkowej }

procedure WndProc(var Msg: TMessage); override;

{ obsługa komunikatu przez metodę obiektu }

procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;

{ domyślna obsługa komunikatu }

procedure DefaultHandler(var Msg); override;

end;

var

MainForm: TMainForm;

implementation

{$R *.DFM}

const

// Łańcuch określający sposób wysłania komunikatu

SendPostStrings: array[0..1] of String = ('Wysłany', 'Zakolejkowany');

procedure TMainForm.FormCreate(Sender: TObject);

begin

// przypisz metodę obsługi na szczeblu aplikacji

Application.OnMessage := OnAppMessage;

// wykorzystaj właściwość Tag pól wyboru do skojarzenia ich z opcjami

// w drugiej sekcji

AppMsgCB.Tag := Longint(OnMsgRB);

WndProcCB.Tag := Longint(WndProcRB);

MessProcCB.Tag := Longint(MsgProcRB);

DefHandCB.Tag := Longint(DefHandlerRB);

// wykorzystaj właściwość Tag opcji do skojarzenia ich z polami wyboru

// w pierwszej sekcji

OnMsgRB.Tag := Longint(AppMsgCB);

WndProcRB.Tag := Longint(WndProcCB);

MsgProcRB.Tag := Longint(MessProcCB);

DefHandlerRB.Tag := Longint(DefHandCB);

end;

procedure TMainForm.OnAppMessage(var Msg: TMsg; var Handled: Boolean);

begin

// sprawdź, czy to ten komunikat:

if Msg.Message = SX_MYMESSAGE then

begin

if AppMsgCB.Checked then

begin

// wyświetl komunikat o aktualnym etapie obsługi i ustaw

// odpowiednią flagę

ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],

'Application.OnMessage']));

Handled := OnMsgRB.Checked;

end;

end;

end;

procedure TMainForm.WndProc(var Msg: TMessage);

{ procedura okienkowa formularza }

var

CallInherited: Boolean;

begin

CallInherited := True; // załóż a priori obsługę odziedziczoną

if Msg.Msg = SX_MYMESSAGE then // czy to ten komunikat ?

begin

if WndProcCB.Checked then // gdy zaznaczono odpowiednie pole wyboru

begin

// wyświetl komunikat o aktualnym etapie obsługi i ustaw

// odpowiednią flagę

ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam], 'WndProc()']));

// wywołaj obsługę odziedziczoną, jeśli użytkownik nie postanowił

// zakończyć obsługi na niniejszym etapie

CallInherited := not WndProcRB.Checked;

end;

end;

if CallInherited then inherited WndProc(Msg);

end;

procedure TMainForm.SXMyMessage(var Msg: TMessage);

{ metoda obsługi }

var

CallInherited: Boolean;

begin

CallInherited := True; // załóż a priori obsługę odziedziczoną

if MessProcCB.Checked then // gdy zaznaczono odpowiednie pole wyboru

begin

// wyświetl komunikat o aktualnym etapie obsługi i ustaw

// odpowiednią flagę

ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],

'metodzie obsługującej']));

// wywołaj obsługę odziedziczoną, jeśli użytkownik nie postanowił

// zakończyć obsługi na niniejszym etapie

CallInherited := not MsgProcRB.Checked;

end;

if CallInherited then Inherited;

end;

procedure TMainForm.DefaultHandler(var Msg);

{ domyślna obsługa }

var

CallInherited: Boolean;

begin

CallInherited := True; // załóż a priori obsługę odziedziczoną

if TMessage(Msg).Msg = SX_MYMESSAGE then // czy to ten komunikat?

begin

if DefHandCB.Checked then // gdy zaznaczono odpowiednie pole wyboru

begin

// wyświetl komunikat o aktualnym etapie obsługi i ustaw

// odpowiednią flagę

ShowMessage(Format(MessString, [SendPostStrings[TMessage(Msg).WParam],

'DefaultHandler()']));

// wywołaj obsługę odziedziczoną, jeśli użytkownik nie postanowił

// zakończyć obsługi na niniejszym etapie

CallInherited := not DefHandlerRB.Checked;

end;

end;

if CallInherited then inherited DefaultHandler(Msg);

end;

procedure TMainForm.PostMessButtonClick(Sender: TObject);

{ zakolejkuj komunikat dla formularza }

begin

PostMessage(Handle, SX_MYMESSAGE, 1, 0);

end;

procedure TMainForm.SendMessButtonClick(Sender: TObject);

{ wyślij komunikat bezpośrednio do formularza }

begin

SendMessage(Handle, SX_MYMESSAGE, 0, 0);

end;

procedure TMainForm.AppMsgCBClick(Sender: TObject);

{ zadecyduj o dostępności opcji drugiej sekcji w ogólności }

begin

if EatMsgCB.Checked then

begin

with TRadioButton((Sender as TCheckBox).Tag) do

begin

Enabled := TCheckbox(Sender).Checked;

if not Enabled then Checked := False;

end;

end;

end;

procedure TMainForm.EatMsgCBClick(Sender: TObject);

{ zadecyduj o dostępności poszczególnych opcji w drugiej sekcji }

var

i: Integer;

DoEnable, EatEnabled: Boolean;

begin

// czy dostępne w ogólności ?

EatEnabled := EatMsgCB.Checked;

// iteracja po kontrolkach potomnych drugiej sekcji

// i ustawianie ich dostępności stosownie do stanu

// pól wyboru w pierwszej sekcji

for i := 0 to EatMsgGB.ControlCount - 1 do

with EatMsgGB.Controls[i] as TRadioButton do

begin

DoEnable := EatEnabled;

if DoEnable then DoEnable := TCheckbox(Tag).Checked;

if not DoEnable then Checked := False;

Enabled := DoEnable;

end;

end;

end.

Notatka

Zwróć uwagę, iż w ramach metod WndProc() i DefaultHandler() występuje nazwa metody; w tym przypadku „obsługa odziedziczona” polega po prostu na wywołaniu odziedziczonej metody z klasy macierzystej.

Jak zapewne zauważyłeś, metoda DefaultHandler() posiada parametr amorficzny; jest to zrozumiałe, gdyż musi być ona przygotowana na każdą postać re­kordu komunikatu: jedynym czynionym przez nią założeniem jest to, że pierwsze dwa bajty rekordu zawierają identyfikator komunikatu. Odwoływanie się do poszczególnych pól komunikatu wymaga jego rzutowania na odpowiedni typ, na przykład TMessage.

Związek komunikatów ze zdarzeniami Delphi

Wspominaliśmy już niejednokrotnie, iż większość zdarzeń VCL to „wysokopoziomowe” otoczki komunikatów Windows. Wybrane przykłady zdarzeń generowanych przez odpowiadające im komunikaty przedstawia tabela 3.3.

Tabela 3.3. Niektóre zdarzenia VCL i odpowiadające im komunikaty Windows

Zdarzenie

Komunikat(y)

OnActivate

WM_ACTIVATE

OnClick

WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN

OnCreate

WM_CREATE

OnDblClick

WM_XBUTTONDBLCLICK

OnKeyDown

WM_KEYDOWN

OnKeyPress

WM_CHAR

OnKeyUp

WM_KEYUP

OnPaint

WM_PAINT

OnResize

WM_SIZE

OnTimer

WM_TIMER

Wskazówka

Należy unikać bezpośredniego obsługiwania komunikatu w sytuacji, gdy w VCL istnieje odpowiadające mu zdarzenie. Ze względu na niekontraktowy charakter obsługi, zdarzenia są mechanizmem pewniejszym i bezpieczniejszym niż komunikaty Windows.

Podsumowanie

Delphi, oferując wygodny mechanizm zdarzeń, nie uniemożliwia bynajmniej bezpośredniego operowania komunikatami, koniecznego w niektórych sytuacjach. W niniejszym rozdziale przedstawiliśmy więc ogólną naturę komunikatów Windows oraz szczegóły ich obsługi w ramach biblioteki VCL. Z komunikatami będziemy spotykać się jeszcze wielokrotnie, w tym i następnym tomie niniejszej książki; nie należy jednak zapominać, iż są one mechanizmem charakterystycznych dla systemu Windows i nie znajdują zastosowania w aplikacjach międzyplatformowych, używających innych sposobów komunikowania się, opisanych w rozdziale 13.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 C:\HELION\DELPHI6VP\POLISH-WORK\03-komunikaty.doc

Ta kontrolka aplikacji, do której kierowane są zdarzenia pochodzące od klawiatury, nazywa się kontrolką skupioną (focused control) , posiada on bowiem pewien wyróżniony stan, zwany skupieniem (focus); w danej chwili tylko jedna kontrolka może posiadać skupienie; system operacyjny, „wywłaszczając' kontrolkę ze stanu skupienia (na rzecz innej kontrolki) wysyła do niej komunikat WM_KILLFOCUS. Kontrolka to powinna wóczas jedynie przyjąć do wiadomości, iż pozbawia się ją skupienia, nie czyniąc z tym skupieniem niczego więcej, w szczególności — nie próbując przenieść go na inną kontrolkę (weszłaby wóczas „w drogę” systemowi operacyjnemu).



Wyszukiwarka

Podobne podstrony:
Sadownictwo ćwicz 14.10.2005 i 04.11.2005, SADOWNICTWO
Rewolucja Na Talerzu s02e04 Placki 04 11 2010
04 11 88
interna egz 14,04,11
04 (11)
04.11, 04
03 04 11 2012r
04 11 12
Etyka WYKŁAD 5 ! 04 11 (ER)
04 11 09r
Korzysci oferta i jakość Loos International 04 11 08
2003 04 11 0665
5. Algorytmy (04.11.08), ALGORYTMY
5. Algorytmy (04.11.08), ALGORYTMY
notatki pracownia przebicie 10 04 11
30 04 11 A
Prezentacja 04 11 2013 plus wskazniki
04 11
16 04 11 R

więcej podobnych podstron