Delphi Kompendium Roz5

background image

Logowanie | Rejestracja | Forum | Pomoc | Reklama | Szukaj

Strona główna :: Delphi :: Kompendium

Edytuj

Historia

Rozdział 5

Komunikaty, bo to one będą tematem niniejszego rozdziału, nie są technologią zbyt często używaną przez
programistów Delphi, a to ze względu na możliwość zastąpienia ich przez zdarzenia. Jest to jednak trochę
bardziej zaawansowany element programowania, z którego nieraz możesz skorzystać w swoich aplikacjach.

Spis treści

1 Czym są komunikaty?

2 Rodzaje komunikatów

2.1 Komunikaty okienkowe

2.2 Komunikaty powiadamiające

2.3 Komunikaty definiowane przez użytkownika

3 Jak działają komunikaty?

4 Obsługa komunikatów

4.1 Przechwytywanie komunikatów

4.2 Struktura TMsg

4.3 Struktura TMessage

4.3.1 Funkcje LOWORD i HIWORD

5 Specyficzne struktury typów

6 Zdarzenie OnMessage

7 Wysyłanie komunikatów

7.1 Perform

7.1.1 Odczyt tekstu z kontrolki

7.2 Funkcje SendMessage i PostMessage

7.2.1 Pobieranie uchwytu okna

7.2.2 Różnice pomiędzy SendMessage i PostMessage

7.2.2.1 Przykład użycia

8 Komunikaty rozgłaszające

9 Deklarowanie własnych komunikatów

9.1 Obsługa komunikatu

10 Funkcje operujące na uchwytach

10.1 CloseWindow

10.2 EnableWidow

10.3 GetClientRect

10.4 GetDesktopWindow

10.5 GetForegroundWindow

10.6 GetParent

10.7 GetWindowText

10.8 IsIconic

10.9 IsWindow

10.10 IswindowVisible

10.11 MoveWindow

10.12 OpenIcon

10.13 SetForegroundWindow

10.14 SetWindowText

10.15 ShowWindow

11 'Zahaczanie' okien

11.1 Zakładanie globalnego hooka

11.2 Funkcja obsługująca hooka

12 Podsumowanie

W tym rozdziale:

dowiesz się, czym są komunikaty Windows;
nauczysz się wysyłać oraz obsługiwać komunikaty;
nauczysz się obsługiwać kilka funkcji związanych z uchwytami;
Twoja aplikacja przechwyci wszystkie klawisze, jakie są naciskane w systemie.

Czym są komunikaty?

Słowo komunikat jest często stosowane w języku użytkowników komputerów. Tematem tego rozdziału nie
będą jednak - jak początkowo mogłoby się wydawać - komunikaty w postaci okienek systemu Windows.

Wszystko to, co dzieje się w systemie (mówimy tu na razie o systemie Windows) jest wynikiem komunikatów -
czy to naciśnięcie klawisza, czy ruch myszki lub otwarcie jakiegoś programu.

Jak zapewne zauważyłeś, w Delphi odzwierciedleniem komunikatów są zdarzenia - możemy na przykład
wykorzystać w naszym programie zdarzenie OnClose, które jest wynikiem zamknięcia okna. W rzeczywistości
jest to komunikat WM_CLOSE. Nie wszystko da się zrealizować za pomocą zdarzeń Delphi - dlatego właśnie
nieraz będziesz zmuszony skorzystać z komunikatów. Oto kolejny przykład: użytkownik klika lewym klawiszem
myszy w obszarze okna aplikacji. W tym momencie system wysyła do tego okna komunikat WM_LBUTTONDOWN,
w którym zawarte są pewne informacje, takie jak np. współrzędne myszki. Zadaniem aplikacji jest
odpowiednie zareagowanie na ten komunikat.

Jeżeli tylko będziesz miał okazję, stosuj zdarzenia zamiast korzystać z komunikatów. Jest to
pewniejszy oraz szybszy sposób reagowania na określone sytuacje.

Rodzaje komunikatów

Delphi

Artykuły
Kompendium
Gotowce
FAQ
.NET

Turbo Pascal

FAQ

PHP

FAQ

Java

FAQ

C/C++

Artykuły
FAQ

C#

Wprowadzenie

Assembler

FAQ

(X)HTML
CSS
JavaScript
Z pogranicza
Algorytmy

WIĘCEJ

»

Delphi
C/C++
Turbo Pascal
Assembler
PHP

Programy
Dokumentacja
Kursy
Komponenty

WIĘCEJ

»

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

1 z 17

2009-03-14 15:32

background image

W rzeczywistości komunikaty są jedynie zwykłymi stałymi o określonych wartościach, które są jakby
identyfikatorem danego komunikatu. Komunikaty możliwe do wykorzystania w Delphi zdefiniowane są w
module Message.pas.

Komunikaty okienkowe

Podstawowym typem komunikatów są tzw. komunikaty okienkowe, które posiadają przedrostek WM_ (ang.
Windows Message). Ten typ komunikatów używany jest do komunikacji pomiędzy oknami - informują one np.
o naciśnięciu klawisza lub o przesunięciu kursora myszy w obszarze danego okna.
Najpopularniejsze (najczęściej stosowane) komunikaty przedstawione są w tabeli 5.1.

Nazwa

Opis

WM_CREATE

Komunikat jest wysyłany w momencie tworzenia okna

WM_DESTROY

Komunikat jest wysyłany w momencie zamykania okna

WM_MOVE

Komunikat stanowi informację dla aplikacji, że okno jest właśnie przemieszczane

WM_SIZE

Komunikat stanowi informację dla aplikacji, że zmieniany jest właśnie rozmiar okna

WM_ACTIVATE

Nastąpiła aktywacja okna

WM_CLOSE

Komunikat jest wysyłany w momencie, gdy okno powinno zostać zamknięte

WM_QUIT

Program zostaje zakończony

WM_CHAR

Naciśnięcie klawisza. Odpowiednik zdarzenia OnKeyPress

WM_KEYDOWN

Wciśnięcie klawisza. Odpowiednik zdarzenia OnKeyDown

WM_KEYUP

Puszczenie klawisza. Odpowiednik zdarzenia OnKeyUp

WM_MOUSEMOVE

Nastąpiło przesunięcie kursora myszy

WM_LBUTTONDOWN

Lewy przycisk myszy został naciśnięty

WM_LBUTTONUP

Lewy przycisk myszy został puszczony

Naturalnie jest to tylko część z możliwych do zastosowania komunikatów. W rzeczywistości są ich setki!
Wystarczy zajrzeć do modułu Message.pas. Nie musisz pamiętać ich wszystkich - wystarczy, że w razie
potrzeby zajrzysz do systemu pomocy Delphi lub do tego rozdziału.

Komunikaty powiadamiające

Każda kontrolka Windows posiada własne typy komunikatów, które przesyłane są z okna macierzystego
(formularz Delphi) do danej kontrolki. Komunikaty takie mogą zawierać informacje o tym, że nastąpiło
przesunięcie suwaka, naciśnięto przycisk albo np. została wyświetlona lista rozwijalna.

Jak już pisałem, każda kontrolka ma swój zestaw komunikatów - z każdą z tych kontrolek związany jest inny
przedrostek.

Tabela 5.2. Najczęściej używane komunikaty związane z kontrolkami Windows

Nazwa

Opis

Przycisk

BN_CLICKED

Kliknięto przycisk.

BN_PAINT

Obraz przycisku musi zostać odświeżony

BN_DISABLE

Przycisk został zablokowany

BN_DOUBLECLICKED

Nastąpiło podwójne kliknięcie w obszarze komponentu

BN_SETFOCUS

Obiekt stał się aktywny

BN_KILLFOCUS

Obiekt przestaje być aktywny

Lista wyboru (ListBox)

LBN_SELCHANGE

Komunikat pojawia się w momencie, gdy użytkownik zamierza zmienić zawartość
zaznaczonej pozycji

LBN_SELCANCEL

Komunikat pojawia się w momencie, gdy użytkownik rezygnuje z zaznaczenia pozycji

Kontrolka rozwijalna

CBN_SELCHANGE

Komunikat pojawia się w momencie, gdy użytkownik zaznaczył pozycję i próbuje ją
zmienić

CBN_DBLCLK

Nastąpiło podwójne kliknięcie w obszarze kontrolki

CBN_EDITCHANGE

Występuje w przypadku, gdy użytkownik zmienił treść kontrolki

CBN_DROPDOWN

Nastąpiło rozwinięcie listy rozwijalnej

CBN_CLOSEUP

Lista rozwijalna została zamknięta

CBN_SELENDOK

Komunikat występuje po wybraniu jakiegoś elementu z listy

Kontrolka edycyjna

EN_CHANGE

Zmieniony został edytowany tekst

EN_UPDATE

Komunikat zawiera informację o odświeżeniu tekstu

EN_ERRSPACE

Nie można zadeklarować wystarczającej ilości pamięci!

EN_MAXTEXT

Tekst wykracza poza kontrolkę edycyjną

EN_HSCROLL

Kliknięto poziomy pasek przewijania

EN_VSCROLL

Kliknięto pionowy pasek przewijania

RSS | Forum | Pastebin |

Regulamin | Pomoc | Usuń

cookies | Prawa autorskie |

Kontakt | Reklama

Copyright © 2000-2006 by Coyote Group 0.9.3-pre3

Czas generowania strony: 1.7169 sek. (zapytań SQL:

12)

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

2 z 17

2009-03-14 15:32

background image

W opisach z tabeli 5.2 starałem się unikać powtarzania komunikatów. Dla każdej bowiem kontrolki występuje
np. komunikat _SETFOCUS. Wystarczy tylko zmienić prefiks (czyli dodać odpowiednie EN, CBN), aby komunikat
był prawidłowo wykryty przez system.

W tabeli 5.2 zamieściłem jedynie komunikaty powiadamiające (notification messages), które zawierają
informacje o zdarzeniach związanych z kontrolkami. Istnieje wiele komunikatów służących do bezpośredniego
operowania na danych (np. do zamiany wpisanego tekstu itp.). Komunikaty te są używane podczas tworzenia
programów działających jako WinAPI, ale jeżeli jesteś ciekawy ich działania, zajrzyj do projektu ChildMsg.dpr
umieszczonego na płycie CD-ROM.

WinAPI to skrót od słów Windows Application Programming Interface. Jest to metoda
programowania polegająca na wykorzystywaniu jedynie narzędzi dostępnych w systemie -
bez korzystania z VCL. Jeżeli chcesz się dowiedzieć czegoś więcej na ten temat, zajrzyj do
rozdziału 12.

Często możesz spotkać się z określeniem

Win32

. Oznacza ono po prostu system Windows

działający w środowisku 32-bitowym (czyli Windows 95 i nowsze wersje).

Komunikaty definiowane przez użytkownika

Nieraz niezbędna okaże się komunikacja pomiędzy dwiema aplikacjami. Można ją zrealizować w dość prosty i
przejrzysty sposób - za pomocą komunikatów! Nie musimy korzystać wyłącznie z tych komunikatów, które
zadeklarowane są już w module Message.pas - możliwe jest także deklarowanie własnych. W gruncie rzeczy
sprowadza się ono jedynie do zadeklarowania stałej. Istnieje tylko jeden warunek: wartości komunikatów nie
mogą się powtarzać, aby ze sobą nie kolidowały. Komunikatami definiowanymi przez użytkownika zajmiemy się
w dalszej części tego rozdziału.

Jak działają komunikaty?

Dla nas - programistów - istotną sprawą jest obsługa samego komunikatu. To, jak komunikat działa, może
wydać się już mniej interesujące. Mimo to warto dowiedzieć się czegoś o zasadach funkcjonowania takiego
systemu. Otóż pierwszym krokiem podejmowanym po wystąpieniu określonego zdarzenia (np. kliknięcia
myszą) jest zakwalifikowanie rodzaju zdarzenia przez system. Następnie system umieszcza komunikat w tzw.
kolejce komunikatów (system Windows przechowuje dla każdej aplikacji oddzielną kolejkę). Kolejnym krokiem
jest wykonywanie pętli, która przekazuje kolejno wszystkie komunikaty do konkretnego okna. Tutaj za
obsłużenie komunikatu odpowiedzialna jest sama aplikacja.

Obsługa komunikatów

Obsługa komunikatów jest o tyle trudniejsza od zdarzeń, że trzeba samodzielnie 'rozgryzać' wartości
parametrów. W zdarzeniach wszystko podane jest jak należy - w postaci parametrów i procedur zdarzeniowych.

Przechwytywanie komunikatów

Zadaniem Twojego pierwszego programu związanego z komunikatami będzie przechwycenie komunikatu
WM_LBUTTONDOWN

, występującego w momencie naciśnięcia przez użytkownika lewego przycisku myszy!

W sekcji

private

klasy formularza dodaj następujący wiersz:

procedure WmLButtonDown

(

var Msg : TMessage

)

;

message WM_LBUTTONDOWN;

Jest to deklaracja procedury, która obsługiwać będzie zdarzenie. Zasada konstruowania procedur obsługujących
komunikaty polega na dodaniu na końcu deklaracji słowa kluczowego

message

. Po słowie tym należy wpisać

nazwę komunikatu - w naszym przypadku WM_LBUTTONDOWN.

Przyjęło się specyficzne nazewnictwo procedur obsługujących komunikaty, które polega na
nadawaniu procedurze takiej samej nazwy jak nazwa komunikatu, tyle że bez znaku
podkreślenia (_).

Nie przejmuj się na razie parametrem Msg powyższej procedury - omówię go nieco później.

Definicja procedury WmLButtonDown powinna zatem wyglądać tak:

procedure TMainForm.

WmLButtonDown

(

var Msg: TMessage

)

;

begin
lblInfo.

Caption

:=

'Użytkownik wcisnął lewy klawisz myszy!'

;

end;

Możesz już uruchomić program. Po kliknięciu lewym przyciskiem myszy w obszarze formularza wykonana
zostanie procedura WmLButtonDown, a co za tym idzie, wyświetlona zostanie także stosowna informacja w
etykiecie (TLabel). Cały kod źródłowy modułu znajduje się w listingu 5.1.

Zwróć uwagę, że w definicji procedury nie stosujemy klauzuli message - tak, jak ma to
miejsce w deklaracji.

Listing 5.1. Kod modułu przechwytującego komunikat

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm =

class

(

TForm

)

lblInfo: TLabel;

private

procedure WmLButtonDown

(

var Msg : TMessage

)

;

message WM_LBUTTONDOWN;

public

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

3 z 17

2009-03-14 15:32

background image

{ Public declarations }

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

{ TMainForm }

procedure TMainForm.

WmLButtonDown

(

var Msg: TMessage

)

;

begin
lblInfo.

Caption

:=

'Użytkownik wcisnął lewy klawisz myszy!'

;

end;

end.

Struktura TMsg

W poprzednim przykładzie procedura posiadała parametr Msg, który był typu

TMessage

. Ta struktura

identyfikuje właśnie komunikat i jego zawartość (parametry). VCL (typ TMessage należy do bibliotek VCL)
zwalnia nas w większej części od programowania bardziej zaawansowanych aspektów związanych z
komunikatami, jak pobieranie komunikatu z pętli itp. W środowisku WinAPI obowiązuje inny rekord, który jest
pierwowzorem dla TMessage:

type
TMsg =

packed record

hwnd: HWND;

message: UINT;

wParam: WPARAM;
lParam: LPARAM;

time

: DWORD;

pt:

TPoint

;

end;

Rekord ten zadeklarowany jest w module Windows.pas, i zawiera informacje dotyczące samego komunikatu.

hwnd

- uchwyt okna, do którego komunikat jest kierowany.

message

- identyfikator komunikatu.

wParam

- 32-bitowa liczba, określająca dodatkowe parametry związane z komunikatem.

lParam

- kolejne 32-bitowe pole, określające dodatkowy parametr przekazywany wraz z komunikatem.

time

- czas wysłania komunikatu.

pt

- współrzędne kursora myszy.

Przy okazji omawiania tej struktury należy się parę słów objaśnienia. Pierwszym parametrem jest tzw. uchwyt
okna. Jest to 32-bitowa liczba, określającą dane okno w systemie Windows. Każde okno posiada swój unikalny
numer - dzięki temu możemy 'porozumiewać' się z danym programem np. właśnie dzięki komunikatom.

Komunikat może zawierać w sobie pewne dane, jak np. współrzędne kursora myszy czy informacja określająca,
jaki klawisz jest w tej chwili dodatkowo wciśnięty. Jednak są też komunikaty, które nie posiadają żadnych
dodatkowych parametrów.

Struktura TMessage

W Delphi nie będziemy zajmowali się strukturą TMsg, lecz skorzystamy z dobrodziejstw VCL i rekordu TMessage,
który zadeklarowany jest w module Message.pas.

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

Na pozór struktura TMessage przedstawia się bardzo skomplikowanie, lecz nie zawiera tylu elementów, co TMsg.
Mamy tu jedynie informację przekazywaną w komunikacie, zawierającą parametry lParam i wParam.

Rekord TMessage zawiera dodatkowo parametr Result. Czasami system oczekuje od komunikatu uzyskania
informacji zwrotnej o jego przebiegu. Wówczas mamy możliwość nadania określonej wartości polu Result:

Msg.

Result

:=

1

;

Pozostało jeszcze objaśnienie kwestii specyficznej budowy rekordu, a mianowicie dodanie instrukcji case. Dla
pola LParam zadeklarowane są jeszcze dwie wartości: LParamLo i LParamHi; tak samo ma się sprawa z
pozostałymi parametrami - WParam i Result. Aby lepiej to zrozumieć, polecam wykonanie pewnego ćwiczenia.

Podczas odbierania komunikatu WM_LBUTTONDOWN parametr lParam zawiera współrzędne kursora myszy, które są
jakby 'zakodowane' w postaci 32-bitowej liczby. Sam możesz się o tym przekonać:

procedure TMainForm.

WmLButtonDown

(

var Msg: TMessage

)

;

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

4 z 17

2009-03-14 15:32

background image

begin
lblInfo.

Caption

:=

'Współrzędne myszy: '

+

IntToStr

(

Msg.

LParam

)

;

end;

W takim wypadku podczas pobierania komunikatu w etykiecie wyświetlona zostanie jakaś 'kosmiczna' liczba -
np. 2490472. Żeby uzyskać z tej liczby rzeczywiste położenie kursora, należy skorzystać z funkcji

LOWORD

i

HIWORD

.

Funkcje LOWORD i HIWORD

Funkcje LOWORD i ksłużą do uzyskiwania tzw. wyższego oraz niższego wyrazu liczby 32-bitowej. Za pomocą tych
funkcji możemy 'odkodować' parametr, który przykładowo określa współrzędne kursora myszy:

LOWORD

(

2490472

)

; { da liczbę 104 }

HIWORD

(

2490472

)

; { da liczbę 38 }

W powyższy sposób uzyskujemy rzeczywiste położenie kursora myszy - 104 (w poziomie) i 38 (w pionie).
Struktura TMessage zwalnia nas od takich zadań dzięki parametrom LParamLo i LParamHi, które zawierają już
'odkodowane' wartości. Listing 5.2 przedstawia program z listingu 5.1 już po dokonaniu modyfikacji, a sam
program w trakcie działania, przedstawiony został na rysunku 5.1

Listing 5.2. Uzyskiwanie współrzędnych kursora myszy z komunikatu

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm =

class

(

TForm

)

lblInfo: TLabel;

private

procedure WmLButtonDown

(

var Msg : TMessage

)

;

message WM_LBUTTONDOWN;

public

{ Public declarations }

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

{ TMainForm }

procedure TMainForm.

WmLButtonDown

(

var Msg: TMessage

)

;

begin
lblInfo.

Caption

:=

'Współrzędne myszy: '

+

IntToStr

(

Msg.

LParamLo

)

+

' i '

+

IntToStr

(

Msg.

LParamHi

)

;

end;

end.

Rysunek 5.1. Program w trakcie działania

Specyficzne struktury typów

Nie jest konieczne mozolne rozszyfrowanie parametrów lParam i wParam za każdym razem. Delphi udostępnia
specyficzne dla niektórych komunikatów struktury danych, dzięki którym możemy uzyskać informację na temat
parametrów. Przykładowo zamiast używać - jak w poprzednim przykładzie - rekordu TMessage, wystarczyło
skorzystać z TWMMouse:

type
TWMMouse =

packed record

Msg:

Cardinal

;

Keys:

Longint

;

case

Integer

of

0

:

(

XPos:

Smallint

;

YPos:

Smallint

)

;

1

:

(

Pos

:

TSmallPoint

;

Result

:

Longint

)

;

end;

Osobny rekord jest zadeklarowany praktycznie dla większości standardowych komunikatów Windows. Nie
musisz pamiętać typów właściwych dla nich wszystkich - wystarczy znać nazwę komunikatu, a nazwę struktury
można utworzyć, dodając na samym początku literę T i usuwając znak _. Przykładowo dla komunikatu
WM_LBUTTONDOWN

odpowiednią strukturą będzie TWmLButtonDown.

Nic nie stoi oczywiście na przeszkodzie, aby korzystać ze struktury TMessage, która pasuje do każdego rodzaju
komunikatu.

Zdarzenie OnMessage

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

5 z 17

2009-03-14 15:32

background image

Klasa TApplication zawiera zdarzenie OnMessage, dzięki któremu my, projektanci możemy kontrolować
nadsyłane do aplikacji komunikaty. Tak samo, jak w przypadku zdarzenia OnException , próżno szukać go w
Inspektorze Obiektów - jest to zdarzenie ukryte, które możemy przypisać w następujący sposób:

Application.

OnMessage

:= Procedura_Obslugi;

Procedura obsługująca zdarzenie OnMessage musi mieć specjalną budowę:

procedure MyOnMessage

(

var Msg : TMsg; var Handled:

Boolean

)

;

Pierwszym parametrem jest wskazanie struktury TMsg, którą omówiłem nieco wcześniej. Drugi parametr
informuje o tym, czy komunikat został poprawnie i w całości obsłużony.

Zdarzenie OnMessage stanowi wygodny w użyciu proces kontroli przepływu komunikatów z kolejki do okna
obsługi. Możemy dzięki niemu odpowiednio zareagować na wystąpienie określonych komunikatów, co zaraz
pokażę. Należy jednak być ostrożnym w obsłudze zdarzenia OnMessage. System kieruje do okna naprawdę wiele
komunikatów, więc należy tak zaprogramować funkcję, aby nie spowalniała działania programu ani nie
spowodowała jego zawieszenia. Przykład takiego programu znajduje się w listingu 5.3.

Listing 5.3. Przechwytywanie komunikatów

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids, ValEdit;

type
TMainForm =

class

(

TForm

)

GroupBox1: TGroupBox;
vleMsg: TValueListEditor;

procedure FormCreate

(

Sender:

TObject

)

;

procedure btnClearClick

(

Sender:

TObject

)

;

private

ID :

Integer

; // licznik komunikatów

procedure MyOnMessage

(

var Msg : TMsg; var Handled:

Boolean

)

;

public

{ Public declarations }

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

{ TForm1 }

procedure TMainForm.

MyOnMessage

(

var Msg: TMsg; var Handled:

Boolean

)

;

begin

Inc

(

ID

)

; // zwiększ licznik

{ dodaj nowy rekord do komponentu TValueListEditor }
vleMsg.

InsertRow

(

IntToStr

(

ID

)

,

IntToHex

(

Msg.

message

,

4

)

,

False

)

;

end;

procedure TMainForm.

FormCreate

(

Sender:

TObject

)

;

begin
{ wszystkie komunikaty kieruj do procedury MyOnMessage }
Application.

OnMessage

:= MyOnMessage;

end;

end.

Kluczową sprawą jest przydzielenie na starcie odpowiedniej procedury do zdarzenia OnMessage:

Application.

OnMessage

:= MyOnMessage;

W procedurze MyOnMessage następuje zwiększenie licznika ID, a następnie dodanie nowego rekordu do
komponentu TValueListEditor. Komponent ten znajduje się na zakładce Additional i służy do przedstawiania
wartości w formie dwóch kolumn (rysunek 5.2). W drugiej kolumnie wartość komunikatu zaprezentowana jest
w postaci heksadecymalnej (szesnastkowej), a to za sprawą funkcji

IntToHex

.

Rysunek 5.2. Komunikaty przekazane do zdarzenia OnMessage

Wysyłanie komunikatów

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

6 z 17

2009-03-14 15:32

background image

Czymże by była możliwość odbierania komunikatów bez możliwości ich wysyłania? Wysyłanie komunikatów jest
operacją względnie prostą, realizowaną za pomocą trzech funkcji:

SendMessage

,

PostMessage

,

Perform

. Dwie z

nich związane są z WinAPI, a ostatnia z VCL.

Perform

Każda kontrolka VCL udostępnia metodę, dzięki której można przekazać jej jakiś komunikat. Ta metoda nosi
nazwę Perform. Deklaracja odpowiedniej funkcji przedstawia się tak:

function Perform

(

Msg:

Cardinal

; WParam, LParam:

Longint

)

:

Longint

;

Pierwszym parametrem jest nazwa komunikatu przesyłanego do danej kontrolki. Kolejne dwa parametry to
wartości przekazywane wraz z parametrami.

Poniższy kod powoduje wstawienie do kontrolki tekstowej napisu Nowy tekst...

procedure TMainForm.

btnSetTextClick

(

Sender:

TObject

)

;

begin
{ ustaw tekst na kontrolce }
edtValue.

Perform

(

WM_SETTEXT,

0

,

Longint

(

PChar

(

'Nowy tekst...'

)))

;

end;

Jak widzisz, wysyłamy do komponentu edtValue komunikat WM_SETTEXT, powodujący wstawienie do okna
tekstu z parametru lParam. Parametr wParam może być pusty (mieć wartość 0).

Zastosowałem tutaj podwójne rzutowanie, gdyż parametr lParam musi być liczbą:

Longint

(

PChar

(

'Nowy tekst...'

)))

;

Jeśli nie użylibyśmy rzutowania PChar(), kompilator wyświetliłby błąd: [Error] MainFrm.pas(34): Invalid
typecast.

Typ

Longint

z punktu widzenia kompilatora jest równy typowi

Integer

. Równie dobrze

mógłbyś zamiast

Longint

wpisać

Integer

, a kod i tak zostałby skompilowany prawidłowo.

Teraz możesz zobaczyć, jak proste jest VCL w porównaniu z operowaniem na komunikatach. Taką samą
czynność jak w powyższym kodzie możemy zrealizować za pomocą jednego wiersza kodu:

edtValue.

Text

:=

'Nowy tekst...'

; // wstawianie tekstu

Równie dobrze wysyłając komunikat WM_SETTEXT możesz zamiast rzutowanej wartości przekazać po prostu
adres bufora zawierającego dany tekst - oto przykład:

procedure TMainForm.

btnSetTextClick

(

Sender:

TObject

)

;

var
Buffer :

array

[

0

..

255

]

of

char

; // tablica 255-elementowa

begin
{ ustaw tekst na kontrolce }
Buffer :=

'Nowy tekst...'

;

edtValue.

Perform

(

WM_SETTEXT,

0

,

Longint

(

@Buffer

))

; // przekazujemy wskaźnik jako parametr

lParam
end;

Odczyt tekstu z kontrolki

Aby odczytać tekst z kontrolki, możemy skorzystać z komunikatu WM_GETTEXT. Podczas wysyłania komunikatu
pierwszy parametr - wParam - musi zawierać maksymalną ilość znaków do pobrania. Drugi parametr - lParam -
musi zawierać wskazanie buforu, czyli zmiennej, której komunikat przypisze odczytane wartości.

procedure TMainForm.

btnGetTextClick

(

Sender:

TObject

)

;

var
Buffer :

array

[

0

..

255

]

of

char

;

begin
{ odczytujemy tekst wpisany w kontrolce i przypisujemy do bufora - Buffer }
edtValue.

Perform

(

WM_GETTEXT,

SizeOf

(

Buffer

)

,

Integer

(

@Buffer

))

;

Application.

MessageBox

(

PChar

(

'Tekst wpisany w kontrolce "'

+ Buffer +

'"'

)

,

''

, MB_OK +

MB_ICONINFORMATION

)

;

end;

Na samym początku zadeklarowałem tablicę 255-elementową, która po wysłaniu komunikatu będzie zawierać
tekst znajdujący się w kontrolce. Podczas wysyłania komunikatu parametr wParam zawiera ilość elementów
tablicy (funkcja

SizeOf

), a drugi parametr jest wskaźnikiem tablicy Buffer.

Kompletny kod programu zaprezentowany w tym podpunkcie znajduje się na płycie CD-ROM dołączonej do
niniejszej książki. Projekt znajduje się w katalogu ..listingi/5/Perform.

Podczas operowania na komunikatach przydatna staje się pomoc Delphi, która zawiera opis
komunikatów oraz wymaganych parametrów.

Funkcje SendMessage i PostMessage

Chcąc pokonać barierę naszej aplikacji i wysłać komunikat do innego okna, należy skorzystać z funkcji WinAPI -
SendMessage

lub PostMessage. Deklaracje tych funkcji (deklaracja znajduje się w pliku Windows.pas)

przedstawiłem poniżej:

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

7 z 17

2009-03-14 15:32

background image

function

SendMessage

(

hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM

)

: LRESULT;

stdcall;

function

PostMessage

(

hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM

)

: BOOL;

stdcall;

Działanie obydwu z nich jest bardzo podobne - różnice są nikłe, ale o tym opowiem za
chwilę.
Obydwie funkcje zostały zadeklarowane z użyciem klauzuli

stdcall

. Szczegółowo opowiem o

tym w rozdziale 9.

Budowa samych funkcji SendMessage i PostMessage jest podobna do Perform, tyle że ta ostatnia nie posiada
parametru hWnd, który określa uchwyt okna, do którego kierujemy komunikat.

Pobieranie uchwytu okna

Do pobierania uchwytu okna służy funkcja API -

FindWindow

. Funkcja ta jest raczej prosta w użyciu:

function

FindWindow

(

lpClassName, lpWindowName:

PChar

)

: HWND;

stdcall;

Wymaga podania dwóch parametrów, które umożliwią znalezienie uchwytu danego okna. Pierwszy z nich
określa nazwę klasy, a drugi określa tekst (tytuł) okna.

Przykładowo jeśli chcemy pobrać uchwyt do okna Inspektora obiektów, możemy napisać:

Uchwyt :=

FindWindow

(

nil,

'Object Inspector'

)

;

Jeżeli nie znasz klasy okna lub jego tytułu, możesz wpisać w tym miejscu wartość nil (pusty).

Na dołączonej do książki płycie CD-ROM znajduje się kod źródłowy programu mojego autorstwa, który
przedstawia graficznie wszystkie okna otwarte w danym momencie w systemie. Projekt umieszczony jest w
katalogu ..listingi/5/EnumWnd; w kodzie zawarte jest dużo komentarzy, więc nie powinieneś mieć problemów z
jego zrozumieniem. Program przedstawiony został na rysunku 5.3.

Rysunek 5.3. Rozkład okien otwartych w systemie

Istnieje możliwość rozwinięcia danej gałęzi, co spowoduje pojawienie się okien pochodnych. Przed znakiem
dwukropka (:) wpisany jest tytuł okna, a po dwukropku klasa danego okna.

Różnice pomiędzy SendMessage i PostMessage

Wcześniej powiedziałem, że dwie funkcje - SendMessage oraz PostMessage - są niemal identyczne. Podkreślam
słowo niemal, gdyż różnią się one w sposobie przekazania komunikatu. Funkcja PostMessage kieruje komunikat
bezpośrednio do kolejki komunikatów i natychmiast zwraca sterowanie do aplikacji, niezależnie od tego, co
dalej stanie się z komunikatem. Funkcja może zwrócić wartość True (komunikat został zakolejkowany) lub
False

(komunikat nie dotarł do kolejki).

Funkcja SendMessage natomiast kieruje komunikat bezpośrednio do danego okna i czeka na jego wykonanie.
Może zwrócić jakąś konkretną wartość, przekazaną przez dany komunikat.

Przykład użycia

Jeżeli chcemy przykładowo zamknąć daną aplikację, możemy wysłać do niej komunikat WM_QUIT:

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
H : HWND;
begin
H :=

FindWindow

(

nil,

'Bitmapy'

)

;

PostMessage

(

H, WM_QUIT,

0

,

0

)

;

end;

Utworzony według powyższego kodu program odnajdzie uchwyt programu, którego tytuł to Bitmapy, a
następnie wyśle do niego komunikat WM_QUIT.

Komunikaty rozgłaszające

VCL udostępnia funkcję Broadcast, która jest używana do rozsyłania tego samego komunikatu do wszystkich
kontrolek potomnych (child), czyli takich, które są umieszczone na danym obiekcie. Poniższy przykład

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

8 z 17

2009-03-14 15:32

background image

prezentuje rozsyłanie komunikatu WM_CHAR do wszystkich kontrolek umieszczonych na formularzu:

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var

Message : TMessage;

begin

Message.

Msg

:= WM_CHAR;

Message.

WParam

:=

65

;

Message.

LParam

:=

0

;

Message.

Result

:=

0

;

Broadcast

(

Message

)

;

end;

Funkcja Broadcast jest dostępna dla wszystkich obiektów wizualnych VCL.

Deklarowanie własnych komunikatów

Istnieje możliwość deklaracji własnych komunikatów oraz procesu komunikacji między dwiema aplikacjami
wykorzystującymi ten komunikat. Pierwszym krokiem jest deklaracja takiego samego komunikatu (o tej samej
wartości - identyfikatorze).

const
WM_ERROR = WM_USER +

1001

;

Może to wyglądać tak, jak powyżej; kluczową sprawą jest określenie takiego identyfikatora, który nie będzie
kolidował z innymi, zarezerwowanymi już komunikatami. Do stałej WM_USER należy dodać jakąś wartość, której
górna granica wynosi 31 734.

Obsługa komunikatu

Po zadeklarowaniu komunikatu jego obsługa jest taka sama, jak w przypadku innych komunikatów. Po pierwsze
w aplikacji należy zadeklarować taki nagłówek:

procedure WmError

(

var Msg : TMessage

)

;

message WM_ERROR;

Od tej pory procedura WmError będzie odpowiedzialna za odebranie naszego komunikatu. Listingi 5.4 oraz 5.5
przedstawiają dwa moduły - jeden odpowiedzialny jest za wysyłanie komunikatów, a drugi za ich odbieranie.

Listing 5.4. Odebranie komunikatu WM_ERROR

unit RcvMainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

const
WM_ERROR = WM_USER +

1001

;

ErrorArray :

array

[

0

..

2

]

of

String

[

25

]

=

(

'Błąd niskiego stopnia'

,

'Błąd poważny'

,

'Błąd krytyczny!'

)

;

type
TMainForm =

class

(

TForm

)

edtText: TEdit;
Label1: TLabel;

private

procedure WmError

(

var Msg : TMessage

)

;

message WM_ERROR;

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

{ TMainForm }

procedure TMainForm.

WmError

(

var Msg: TMessage

)

;

begin
edtText.

Text

:= ErrorArray

[

Msg.

wParam

]

+

' ('

+

IntToStr

(

Msg.

LParam

)

+

')'

;

Msg.

Result

:=

1

; // wartość zwrotna

end;

end.

Listing 5.5. Wysłanie komunikatu WM_ERROR

unit SndMainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

const
WM_ERROR = WM_USER +

1001

;

type
TMainForm =

class

(

TForm

)

btnSend: TButton;

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

9 z 17

2009-03-14 15:32

background image

procedure btnSendClick

(

Sender:

TObject

)

;

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.

btnSendClick

(

Sender:

TObject

)

;

var
iHandle : HWND;
begin
iHandle :=

FindWindow

(

'TMainForm'

,

'Odebranie komunikatu'

)

;

if

SendMessage

(

iHandle, WM_ERROR,

2

,

20

)

=

1

then

ShowMessage

(

'Komunikat przekazany!'

)

else

ShowMessage

(

'Błąd. Komunikat nie został

przekazany!'

)

;

end;

end.

Kompletne kody dwóch programów znajdują się w katalogu ..listingi/5/OwnMsg.

Przy okazji analizy dwóch kodów źródłowych z listingów 5.4 i 5.5 możesz zaobserwować wyraźniejszą różnicę
pomiędzy funkcjami SendMessage oraz PostMessage. Po wysłaniu komunikatu program sprawdza, czy dotarł do
miejsca przeznaczenia:

if

SendMessage

(

iHandle, WM_ERROR,

2

,

20

)

=

1

then { coś }

Jeżeli komunikat został odebrany, to zwróconą wartością powinna być liczba 1, a to za sprawą modułu
RcvMainFrm.pas:

procedure TMainForm.

WmError

(

var Msg: TMessage

)

;

begin
edtText.

Text

:= ErrorArray

[

Msg.

wParam

]

+

' ('

+

IntToStr

(

Msg.

LParam

)

+

')'

;

Msg.

Result

:=

1

; // wartość zwrotna

end;

Do pola Result struktury TMessage przypisana została wartość zwrotna - 1.

Funkcje operujące na uchwytach

Istnieje wiele funkcji, które pozwalają nam - jeśli znamy uchwyt danego okna - manipulować nim, czyli
przesuwać je, minimalizować, maksymalizować, zmieniać położenie itp. Nie jest w takim przypadku konieczne
korzystanie z komunikatów. Może w tym momencie odbiegam trochę od tematu tego rozdziału, ale podam
nazwy tych najważniejszych funkcji API, bo mogą one Ci się przydać w przyszłości.

CloseWindow

function CloseWindow

(

hWnd: HWND

)

: BOOL;

stdcall;

Wbrew pozorom ta funkcja powoduje jedynie zminimalizowanie okna określonego w parametrze, a nie - jak
mogłoby się wydawać na początku - zamykanie go.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

CloseWindow

(

hW

)

; // minimalizuj

end;

EnableWidow

function EnableWindow

(

hWnd: HWND; bEnable: BOOL

)

: BOOL;

stdcall;

Funkcja powoduje dezaktywację okna określonego w pierwszym parametrze - hWnd. Drugi parametr - bEnable -
określa, czy okno ma zostać uaktywnione (True) czy zablokowane (False). Dezaktywacja powoduje, że nie
można z danym oknem wykonać żadnych czynności, jak np. przemieszczenie okienka, minimalizacja itp.

GetClientRect

function GetClientRect

(

hWnd: HWND;

var lpRect:

TRect

)

: BOOL;

stdcall;

Funkcja podaje szerokość i wysokość okna określonego w pierwszym parametrze. Drugi parametr musi zawierać
nazwę zmiennej typu TRect, do której przypisane zostaną odczytane dane. TRect to nic innego jak zwykły
rekord:

TRect

=

record

case

Integer

of

0

:

(

Left, Top, Right, Bottom:

Integer

)

;

1

:

(

TopLeft, BottomRight:

TPoint

)

;

end;

Posiada on parametry określające jego położenie z lewej strony, od góry, od prawej i od dołu. Przykład użycia
funkcji GetClientRect:

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

10 z 17

2009-03-14 15:32

background image

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
R :

TRect

;

begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

Windows.

GetClientRect

(

hW, R

)

;

end;

Wywołując funkcję, należy podać także nazwę modułu, czyli w naszym przypadku Windows. Jest to specyficzne
wywołanie, którego będziesz używał wtedy, gdy np. nazwa danej funkcji będzie zarezerwowana przez Delphi (w
takiej sytuacji kompilator 'nie wie', do której funkcji programista chce się odwołać - należy zatem podać nazwę
modułu).
Funkcja GetClientRect dostarcza informacji jedynie o szerokości danego okna (R.Right) oraz o jego wysokości
(R.Bottom). Jeżeli chcesz uzyskać informację o położeniu okna w poziomie (odległość od lewej krawędzi
ekranu), skorzystaj z funkcji GetWindowRect, która posiada takie same parametry jak wspomniana już funkcja
GetClientRect

.

GetDesktopWindow

function GetDesktopWindow: HWND; stdcall;

Użycie tej funkcji jest proste. Zwraca ona uchwyt do pulpitu Windows. Może się zdarzyć, że będziesz
potrzebował np. namalować coś na pulpicie. Wówczas koniecznym staje się pobranie uchwytu do pulpitu. O
funkcjach graficznych Delphi będzie mowa w rozdziale 10.

GetForegroundWindow

function GetForegroundWindow: HWND; stdcall;

Dzięki tej funkcji możesz pobrać uchwyt do okna, które aktualnie znajduje się 'na wierzchu', nad innymi
oknami.

GetParent

function GetParent

(

hWnd: HWND

)

: HWND;

stdcall;

Dzięki funkcji GetParent możesz uzyskać uchwyt do okna macierzystego. Załóżmy, że masz już uchwyt do
jakiegoś okna - komponentu umieszczonego na formularzu. Chcesz jednak uzyskać uchwyt owego formularza.
Wówczas możesz skorzystać z funkcji GetParent.

GetWindowText

function GetWindowText

(

hWnd: HWND; lpString:

PChar

; nMaxCount:

Integer

)

:

Integer

;

stdcall;

Działanie funkcji GetWindowText można porównać do komunikatu WM_GETTEXT. Można się domyśleć znaczenia
tej funkcji - powoduje ona odczytanie tekstu (tytułu) okna podanego w parametrze pierwszym.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
Buffer :

array

[

0

..

255

]

of

char

;

begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

GetWindowText

(

hW, Buffer,

SizeOf

(

Buffer

))

;

{ Buffer = Unit1.pas }
end;

Z funkcji można skorzystać np. wtedy, gdy znamy klasę danego okna, a nie znamy jego tytułu.
Istnieje także funkcja GetWindowTextLength, która podaje długość tekstu (tytułu) danego okna w postaci ilości
znaków.

IsIconic

function

IsIconic

(

hWnd: HWND

)

: BOOL;

stdcall;

Funkcja zwraca True w przypadku, gdy okno podane w parametrze hWnd jest zminimalizowane.

IsWindow

function

IsWindow

(

hWnd: HWND

)

: BOOL;

stdcall;

Funkcja zwraca True, jeżeli uchwyt podany w parametrze hWnd jest oknem.

IswindowVisible

function

IsWindowVisible

(

hWnd: HWND

)

: BOOL;

stdcall;

Jeżeli okno podane w parametrze pierwszym jest oknem widocznym, funkcja zwraca True, a w przeciwnym
wypadku - False.

MoveWindow

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

11 z 17

2009-03-14 15:32

background image

function MoveWindow

(

hWnd: HWND; X, Y, nWidth, nHeight:

Integer

; bRepaint: BOOL

)

: BOOL;

stdcall;

Funkcja MoveWindow służy do określenia pozycji danego okna. Możesz przemieścić dane okno lub zmienić jego
rozmiar. Znaczenie poszczególnych parametrów jest następujące:

hWnd - uchwyt okna, którego będzie dotyczyć operacja.
X, Y - nowe pozycje X (poziom) i Y (pion) dla okna.
nWidth, nHeight - szerokość i wysokość okna.
bRepaint - określa, czy okno po zakończeniu operacji ma zostać odświeżone (True) czy też nie (False).
O odświeżaniu opowiem w rozdziale poświęconym grafice.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

MoveWindow

(

hW,

1

,

1

,

600

,

200

,

False

)

;

end;

OpenIcon

function

OpenIcon

(

hWnd: HWND

)

: BOOL;

stdcall;

Dzięki funkcji OpenIcon możesz przywrócić zminimalizowane okno do normalnego stanu.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

OpenIcon

(

hw

)

;

end;

SetForegroundWindow

function SetForegroundWindow

(

hWnd: HWND

)

: BOOL;

stdcall;

Funkcja SetForegroundWindow powoduje ustawienie aktywnego okna. Aktywne okno znajduje się na samym
wierzchu. Jeżeli operacja się powiedzie, funkcja zwróci wartość True.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

SetForegroundWindow

(

hW

)

;

end;

Jak zapewne zauważyłeś, znaczenie funkcji SetForegroundWindow jest odwrotne do GetForegroundWindow. Ta
druga funkcja pobiera aktywne okno, a pierwsza je ustawia. Podobnie jest z innymi funkcjami prezentowanymi
w tym podpunkcie.

SetWindowText

function SetWindowText

(

hWnd: HWND; lpString:

PChar

)

: BOOL;

stdcall;

Funkcja powoduje ustawienie nowej wartości tekstowej dla okna określonego w pierwszym parametrze.

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

SetWindowText

(

hW,

'Nowy tekst'

)

;

end;

ShowWindow

function

ShowWindow

(

hWnd: HWND; nCmdShow:

Integer

)

: BOOL;

stdcall;

Dzięki funkcji ShowWindow możliwe jest nadanie określonego statusu dla danego okna. Pisząc 'status' mam na
myśli pozycję okna, które określa drugi parametr (tabela 5.3).

Flaga

Opis

SW_HIDE

Powoduje ukrycie okna

SW_MAXIMIZE

Maksymalizacja danego okna

SW_MINIMIZE

Minimalizacja okna

SW_RESTORE

Przywrócenie okna do domyślnego położenia (jeżeli okno jest zminimalizowane lub
zmaksymalizowane)

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

12 z 17

2009-03-14 15:32

background image

SW_SHOW

Wyświetlenie ukrytego okna

SW_SHOWDEFAULT

Przywrócenie okna z domyślnymi wartościami (szerokość, wysokość)

SW_SHOWMAXIMIZED

Wyświetlenie okna i zmaksymalizowanie go

SW_SHOWMINIMIZED

Wyświetlenie okna i zminimalizowanie go

procedure TForm1.

Button1Click

(

Sender:

TObject

)

;

var
hW : HWND;
begin
hW :=

FindWindow

(

nil,

'Unit1.pas'

)

; // określ uchwyt

ShowWindow

(

hW, SW_MINIMIZE

)

;

end;

'Zahaczanie' okien

Pisząc 'zahaczanie' pozwoliłem sobie na małe spolszczenie. W środowisku programistycznym takie 'zahaczanie'

jest nazywane 'zakładaniem hooka'

1

. Jest to mechanizm umożliwiający monitorowanie komunikatów

przepływających przez system.

Jako przykład zaprezentuję Ci w tym rozdziale aplikację, która przechwytywać będzie dane o wszystkich
naciskanych w systemie klawiszach. Aplikacje tego typu określane są jako KeySpy (w dosłownym tłumaczeniu
szpieg klawiszy).

Zakładanie globalnego hooka

Do zakładania hooka systemowego służy funkcja API - SetWindowsHookEx. Najlepiej jest wywoływać tę funkcję
podczas startu aplikacji (zdarzenie OnCreate). Po zakończeniu działania programu hooka należy zwolnić - czyni
to funkcja UnhookWindowsHookEx.

Deklaracje obu funkcji przedstawiają się następująco:

function SetWindowsHookEx

(

idHook:

Integer

; lpfn: TFNHookProc; hmod: HINST; dwThreadId: DWORD

)

:

HHOOK;

stdcall;

function UnhookWindowsHookEx

(

hhk: HHOOK

)

: BOOL;

stdcall;

Funkcja SetWindowsHookEx zwraca wskazanie zmiennej typu HHOOK. Nazwę tej zmiennej należy podać w
parametrze UnhookWindowsHookEx, aby prawidłowo zwolnić wszystkie zasoby.

Znaczenie poszczególnych parametrów polecenia SetWindowsHookEx jest następujące:

idHook

-określa typ hooka, czyli rodzaj komunikatów, które mają być przechwytywane. W naszym

przykładzie w tym polu podamy wartość WH_JOURNALRECORD, która określa wszelkie komunikaty
kierowane do systemowej kolejki komunikatów (ang. message queue).
lpfn

- parametr ten musi określać nazwę procedury, która obsługiwać będzie 'nadlatujące' komunikaty.

Tym zajmiemy się nieco później.
hMod

- parametr ten służy do określenia modułu, w którym znajduje się procedura mająca obsłużyć nasz

hook. Procedura może być bowiem umieszczona w bibliotece DLL, dlatego jeśli piszę 'moduł', mam na
myśli albo naszą aplikację, albo bibliotekę DLL.
dwThreadId

- wątek, którego dotyczy hook. Na razie nie zawracaj sobie tym głowy - wątkami zajmiemy

się w rozdziale 8.

Założenie i zwolnienie hooka może wyglądać np. tak:

procedure TMainForm.

FormCreate

(

Sender:

TObject

)

;

begin
{ załóż hooka }
MainHook := SetWindowsHookEx

(

WH_JOURNALRECORD, KeyboardHook, hInstance,

0

)

;

if

(

MainHook = NULL

)

then

raise Exception.

Create

(

'Błąd! Hook nie został założony!'

)

;

end;

procedure TMainForm.

FormDestroy

(

Sender:

TObject

)

;

begin
UnhookWindowsHookEx

(

MainHook

)

;

end;

Drugi parametr funkcji SetWindowsHookEx określa nazwę procedury obsługującej nasz hook - procedurę tę
stworzymy za chwilę. Trzeci parametr to hInstance - ta stała informuje, że operacje dotyczą naszej aplikacji.
Pamiętasz, jak mówiłem wcześniej o tym, że hook może być umieszczony także w bibliotece DLL? W takim
wypadku w tym miejscu należałoby podać uchwyt biblioteki DLL, lecz gdy procedura obsługująca hook znajduje
się w naszym programie, wystarczy wpisać słowo hInstance.

Jeżeli zakładanie hooka powiedzie się, zmienna MainHook będzie zawierała wskazanie na niego. W przeciwnym
wypadku będzie to wartość NULL. Wartość NULL podobnie jak nil oznacza wartość pustą.

Funkcja obsługująca hooka

Nasza funkcja obsługująca hooka będzie nosiła nazwę HookKeyboard. Musi ona mieć specyficzną budowę, czyli
składać się z określonej liczby elementów o określonym typie.

function KeyboardHook

(

Code:

Integer

; wParam : WPARAM;

lParam : LPARAM

)

:

Longint

;

stdcall;

Pierwszy parametr określa typ hooka, a dwa pozostałe zawierają dodatkowe parametry. Jeżeli chcesz się
dowiedzieć czegoś więcej na ten temat, odsyłam Cię do pomocy Delphi.

Parametr lParam standardowo zawiera wskaźnik struktury TEventMsg. Tak więc na samym starcie należy

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

13 z 17

2009-03-14 15:32

background image

odczytać ten wskaźnik oraz dane struktury. Rekord TEventMsg wygląda następująco:

TEventMsg =

packed record

message: UINT;

paramL: UINT;
paramH: UINT;

time

: DWORD;

hwnd: HWND;

end;

Poszczególne pola rekordu mają praktycznie takie same znaczenie, jak omawiany na początku tego rozdziału
rekord TMsg.

Kolejnym krokiem jest 'odfiltrowanie' komunikatów i pozostawienie tylko tych, które są komunikatem
WM_KEYDOWN

.

Parametr paramL struktury TEventMsg jest kodem ASCII naciśniętego klawisza. Oto, jak będzie wyglądała cała
funkcja KeyboardHook - przypatrz się jej dokładnie; później omówię poszczególne jej elementy.

var
MainHook : HHOOK; // wskazanie na hooka
lpWnd :

PChar

; // nazwa okna, w którym użytkownik naciska klawisz

function KeyboardHook

(

Code:

Integer

; wParam : WPARAM;

lParam : LPARAM

)

:

Longint

;

stdcall;

var
Buffer : TEventMsg; // deklaracja struktury
Wnd :

array

[

0

..

255

]

of

char

;

procedure TranslateKey

(

Key :

Byte

)

;

begin

with MainForm do

begin

case Key of

13

: Memo.

Lines

.

Add

(

''

)

;

8

: Memo.

Text

:= Memo.

Text

+

'[backspace]'

;

27

: Memo.

Text

:= Memo.

Text

+

'[esc]'

;

else Memo.

Text

:= Memo.

Text

+

Chr

(

Key

)

;

end;

end;

end;


begin

Result

:=

0

; // wartość zwracana przez procedurę

Buffer := PEventMsg

(

lParam

)

^; // uzyskanie danych poprzez odczytanie wskaźnika

if Buffer.

Message

= WM_KEYDOWN

then { dotyczy tylko komunikatu WM_KEYDOWN }

begin

GetWindowText

(

Buffer.

hwnd

, Wnd,

SizeOf

(

Wnd

))

; // pobierz tekst aktywnego okna

{ jeżeli użytkownik zapisuje dane w innym oknie }

if Wnd <> lpWnd then

begin

lpWnd := Wnd;
MainForm.

Memo

.

Clear

; // wyczyść zawartość komponentu

end;

TranslateKey

(

Buffer.

paramL

)

;

end;

end;

Funkcja ta zawiera w sobie procedurę TranslateKey, która ma na celu analizę kodów ASCII. Jeżeli,
przykładowo kod ASCII jest równy 13, oznacza to, że użytkownik wcisnął klawisz Enter (liczba 13 jest kodem
ASCII klawisza Enter) i należy na to odpowiednio zareagować (dodać nowy wiersz w komponencie TMemo).

Odczytanie wskaźnika zawartego w parametrze lParam wygląda tak:

Buffer := PEventMsg

(

lParam

)

^; // uzyskanie danych poprzez odczytanie wskaźnika

Dzięki temu od tej pory rekord Buffer będzie zawierał szczegółowe informacje na temat komunikatu (kod ASCII
klawisza, nazwę komunikatu itp.).

W listingu 5.6 zaprezentowany został cały kod źródłowy modułu.
Listing 5.6. Moduł odpowiedzialny za tworzenie globalnego hooka

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;

type
TMainForm =

class

(

TForm

)

Memo: TMemo;

procedure FormCreate

(

Sender:

TObject

)

;

procedure FormDestroy

(

Sender:

TObject

)

;

end;

var
MainForm: TMainForm;

implementation

{$R *.dfm}

var
MainHook : HHOOK; // wskazanie na hooka
lpWnd :

PChar

; // nazwa okna, w którym użytkownik naciska klawisz

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

14 z 17

2009-03-14 15:32

background image

function KeyboardHook

(

Code:

Integer

; wParam : WPARAM;

lParam : LPARAM

)

:

Longint

;

stdcall;

var
Buffer : TEventMsg; // deklaracja struktury
Wnd :

array

[

0

..

255

]

of

char

;

procedure TranslateKey

(

Key :

Byte

)

;

begin

with MainForm do

begin

case Key of

13

: Memo.

Lines

.

Add

(

''

)

;

8

: Memo.

Text

:= Memo.

Text

+

'[backspace]'

;

27

: Memo.

Text

:= Memo.

Text

+

'[esc]'

;

else Memo.

Text

:= Memo.

Text

+

Chr

(

Key

)

;

end;

end;

end;


begin

Result

:=

0

; // wartość zwracana przez procedurę

Buffer := PEventMsg

(

lParam

)

^; // uzyskanie danych poprzez odczytanie wskaźnika

if Buffer.

Message

= WM_KEYDOWN

then { dotyczy tylko komunikatu WM_KEYDOWN }

begin

GetWindowText

(

Buffer.

hwnd

, Wnd,

SizeOf

(

Wnd

))

; // pobierz tekst aktywnego okna

{ jeżeli użytkownik zapisuje dane w innym oknie }

if Wnd <> lpWnd then

begin

lpWnd := Wnd;
MainForm.

Memo

.

Clear

; // wyczyść zawartość komponentu

end;

TranslateKey

(

Buffer.

paramL

)

;

end;

end;

procedure TMainForm.

FormCreate

(

Sender:

TObject

)

;

begin
{ załóż hooka }
MainHook := SetWindowsHookEx

(

WH_JOURNALRECORD, KeyboardHook, hInstance,

0

)

;

if

(

MainHook = NULL

)

then

raise Exception.

Create

(

'Błąd! Hook nie został założony!'

)

;

end;

procedure TMainForm.

FormDestroy

(

Sender:

TObject

)

;

begin
UnhookWindowsHookEx

(

MainHook

)

;

end;

end.

Możesz skompilować i sprawdzić działanie programu. Rysunek 5.4 prezentuje program w trakcie działania. Jeśli
piszemy ten tekst w edytorze Microsoft Word, tekst zostanie również dodany do komponentu TMemo.

Rysunek 5.4. Przechwytywanie naciskanych klawiszy

Podczas krótkiego zapoznania z programem możesz zauważyć, że litery z polskimi znakami
diakrytycznymi są wpisywane do TMemo jako 'krzaczki'. Twoje dodatkowe zadanie to
ulepszenie programu. Spójrz na procedurę TranslateKey, umieszczoną w funkcji
HookKeyboard

. Procedura ta ma za zadanie zastępować niektóre kody ASCII odpowiednim

tekstem. Tak samo należy postąpić z 'krzaczkami' - należy po prostu je zastąpić. Aby ułatwić
Ci zadanie, w katalogu ..listingi/5/ASCII na płycie CD-ROM umieściłem program, który
pokazuje kody ASCII naciskanych klawiszy.

Podsumowanie

Być może komunikaty nie będą zbyt często przez Ciebie stosowane, ale warto je znać. Jest to już ten aspekt
programowania, który można zaliczyć do 'bardziej zaawansowanych umiejętności' - nieraz możesz z tej wiedzy
skorzystać.

Pod koniec tego rozdziału podjąłem się, trochę ryzykownie, omówienia tematu hooków, który jest nieco
trudniejszy niż sama obsługa komunikatów w VCL, więc może na tym etapie być dla Ciebie trochę
niezrozumiały. Poznałeś już pierwsze funkcji API, które - nie ma co ukrywać - są trudniejsze w wykorzystaniu
niż zwykłe klasy VCL, ale wiedza o użyciu narzędzi WinAPI pozwoli Ci zostać o wiele lepszym programistą.

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

15 z 17

2009-03-14 15:32

background image

« Część II

Spis treści

Rejestry i pliki INI »

Załączniki:

Listingi_5.zip

(436.88 kB)

Więcej informacji

Delphi 2005. Kompendium
programisty

Adam Boduch

Format: B5, stron: 1048
oprawa twarda
Zawiera CD-ROM

©

Helion 2003. Autor:

Adam Boduch

. Zabrania się rozpowszechniania tego tekstu bez zgody autora.

Kategoria

:

Kompendium

Ostatnia modyfikacja

10-11-2008 23:45

Ostatni autor

sobol

Ilość wyświetleń

30391

Wersja

6

Ziker

dnia 16-02-2009 13:21

Capellini, prawdopodobnie dlatego, ze WM_USER to komunikat z najwyzszym identyfikatorem (po nim
nie ma wyzszych), wiec jest pewnosc, ze nie nadpiszesz innego komunikatu - to tylko przypuszczenia

oczywiscie, nie mam pojecia czy tak jest, ale zgaduje
co do hook'ow, tutaj jest pokazane zakladanie globalnego hook'a, a jakbym chcial zalozyc hook'a na
dane okno? Powiedzmy jedynie na proces gadu-gadu? I jest mozliwosc wyluskania, w podobny sposob
do uzyskania identyfikatora 'rodzica', identyfikatorow 'dzieci' danego okna? Tj. Mamy gadu-gadu i

chcemy przechwytywac rozmowy z okienek, ktore maja chyba swoj wlasny identyfikator

(to jest tylko

przyklad, lepszego nie znalazlem

)

x-fly

dnia 14-02-2008 02:46

ekstra art

bordeux

dnia 23-01-2008 15:46

ojojojo przepraszam za to ze w opisie zmian dałem od kąd a nie razem- odkąd

Autre

dnia 20-01-2008 14:57

Co robię źle? (dodałem : 44: Memo1.Text := Memo1.Text + ',';)
......
case Key of
44: Memo1.Text := Memo1.Text + ',';
13: Memo1.Lines.Add('');
.....

rfl2

dnia 18-09-2007 16:38

Ja chciałbym wiedzieć co to jest to stdcall, które pojawia się na przykład na końcu funkcji
SetWindowsHookEx. Pojawiało się już wcześniej w tym rozdziale ale chyba nie było objaśnione...

Trok

dnia 12-06-2007 18:18

Dobra znalazlem gotowy program na dole strony, on mi dziala wiec poszukam teraz jaki blad zrobilem

Trok

dnia 12-06-2007 18:14

Nie dziala mi Moduł odpowiedzialny za tworzenie globalnego hooka. Pisze cos w Wordzie czy notatniku i
nic sie na komponencie memo nie pojawia ;/

Capellini

dnia 28-03-2007 17:28

"Do stałej WM_USER należy dodać jakąś wartość, której górna granica wynosi 31 734."

Za bardzo tego nie skumałem. Dlaczego akurat WM_USER i od czego zależy jaka to ma być wartość?

Johny_Morfina

dnia 19-02-2007 11:22

Nie udalo mi sie zrealizowac przykladu z metoda BROADCAST
napisalem programik z 1 TButton i 4 TEdit. w obsludze Buttona napisalem to co jest w przykladzie.
Spodziewalem sie ze komunikat dotrze do wszystkich Edit'ow, a dotarl tylko do jednego...

Dlaczego tak jest?

Coldpeer

dnia 01-02-2006 01:05

Jest odwołanie do przypisu, a nie ma jego wyjaśnienia...

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

16 z 17

2009-03-14 15:32

background image

Dodaj komentarz

Delphi :: Kompendium :: Rozdział 5 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5

17 z 17

2009-03-14 15:32


Wyszukiwarka

Podobne podstrony:
Delphi Kompendium Roz5
Delphi Kompendium Roz8
Delphi Kompendium Roz10
Delphi Kompendium Roz6
Delphi Kompendium Roz12
Delphi Kompendium Roz14
Delphi 7 Kompendium programisty
Delphi 7 Kompendium programisty del7ko 2
Delphi 7 Kompendium programisty
Delphi Kompendium programisty 2
Delphi 7 Kompendium programisty
Delphi 7 Kompendium programisty 2
Delphi Kompendium programisty
Delphi Kompendium Roz6
Delphi Kompendium Roz12
Delphi Kompendium programisty 2
Delphi 7 Kompendium programisty del7ko
Delphi Kompendium programisty delpbb

więcej podobnych podstron