WinApi - Lekcja 2
Bardzo waznym pojeciem jest uchwyt (handle). Jest to liczba 32-bitowa identyfikujaca w systemie Windows okreslony obiekt: okno, pedzel, czcionke itd. ZAPAMIETAJ: uchwyt przydziela system w czasie tworzenia (create) danego obiektu i nie masz na niego wplywu. Typ okreslajacy uchyt latwo rozpoznac po pierwszej literze 'H'. I tak mamy: HWND, HBRUSH, HFONT, HBITMAP itd. Jesli jakis obiekt nie ma zdefiniowanego typu uchwytu to stosujemy typ ogolny THandle. Wartosc zero nie jest nigdy przydzielana i oznacza brak uchwytu. Uchwyt wykorzystywany jest prawie przy każdej operacji. Żeby przeslac komunikat nalezy znac uchwyt okna do ktorego komunikat ma dotrzec.
Skad przychodza komunikaty?
Pod pojeciem 'meldunek' albo 'komunikat' bedziesz rozumial wywolanie przez system Windows funkcji okienkowej naszego programu - na razie poprzestanmy na jednym oknie i jednej funkcji. Sa dwa zrodla meldukow: - zdarzenia zewnetrzne z punktu widzenia programu:
- komunikaty klawiaturowe (uzytkownik nacisnal/zwolnil) klawisz,
- komunikaty myszy (uzytkownik wykonal jakas czynnosc mysza),
- komunikaty od zegara oznaczajace uplyw okreslonego odcinka czasu,
- komunikaty od systemu - tworzenie okna , zmiana rozmiaru i polozenia okna, zwijanie i rozwijanie okna, zmiana kolorow systemowych itp.
- komunikaty wewnetrzne wysylane przez inne okna utworzone w naszym programie;
Glownym zrodlem sa okna potomne zwane kontrolkami:przyciski, suwadla, pola edycyjne itp.
Wyslac komunikat do okna oznacza wywolac funkcje okienkowa tego okna. Sa dwie metody:
- funkcja SendMessage - metoda 'przez poslanca';
- funkcja PostMessage - metoda 'poczta'.
Parametry kazdej z tych funkcji odpowiadaja dokladnie parametrom funkcji okienkowej. Pierwsza wywoluje bezposrednio funkcje okienkowa okna i zwraca to co funkcja okienkowa zwrocila jak LRESULT. Druga wstawia meldunek do kolejki komunikatow i niczego nie zwraca.
PCHar
Jest to typ wprowadzony w TP7.0 wskazujacy na znak/ciag znakow zakonczony zerem tzw. ASCIIZ. Powszechnie stosowany w Windows do przekazywania tekstow
(stringow) do i z procedur i funkcji. Wyroznia sie tym sposrod innych typow wskazujacych, ze mozna wykonywac na nim operacje matematyczne np.:
canst p1: PChar = 'Ala ma kota';
var p2: PChar;
begin p2 := p1 + 7;
W powyzszym przykladzie do zmienej p2 zostala przypisana stala p1 i od tej wartosci zostalo odjete 7 poczatkowych liter. Takie dzialanie da napis "kota".
W programach pisanych w API korzystac bedziemy wlasnie z typu PChar lub talibcy znakow Char - np:
var Zmianna : array[0..255] of char;
32-bitowe Delphi laczy wygode dzialania na lancuchach (typu String) w Turbo Pascalu z wymogami Windows, ktore operuja na lancuchach ASCIIZ i wskaznikach. Dlugi typ 'String' jest w istocie czterobajtowym wskaznikiem na ciag znakow zakonczony zerem i moze w prosty sposob byc konwertowany (rzutowane) na typ PChar. Jesli uzyjesz deklaracji: s:String[n]; (n<=255) to zostanie utworzony zwykly (pascalowy) string. Natomiast deklaracja: s:String; tworzy dlugi string. System przydziela pamiec na taki lancuch w sposob dynamiczny. Dlugi String mozna traktowac jak tablice znakow z wyjatkiem elementu zerowego. Zamiast tego nalezy uzywac funkcji: Length, SetLength. Standardowy 255-znakowy lancuch mozna utworzyc deklaracja: s:ShortString lub uzyc przelacznika: {$H-}
W prostych, krotkich programach nie bedziemy stosowac dlugich Stringow.
wvsprintf
Ta funkcja na pewno kojarzy się osobą piszącym wcześniej w C. Sluzy ona bowiem do konwersji tekstu. Jej dzialanie jest podobne do dzialania funkcji Format. Oto prosty przyklad jej uzycia:
uses Windows;
var Lesson : Integer;
Buff : array[0..255] of char; // bufor przechowujacy tekst...
begin
Lesson := 2; // przypisz wartosc zmienenj
wvsprintf(Buff, 'Witaj! ' + #13#13+
'Jest to %d lekcja kursu WinAPI. '+ #13#13+
'Baw się dobrze!.', @Lesson);
MessageBox(0, Buff, 'Witaj!', MB_OK); // wyswietl wartosc zmiennej
end.
W WinAPI nie ma przydatnej funkcji konwertujacej zmienne typu funkcja IntToStr, czy StrToInt. Jest za to wvsprintf. Funkcja ma prostą budowe. Pierwszym parametrem jest bufor tekstowy, ktory przechowywac bedzie tekst, kolejny to tekst do konwersji, ktory moze zawierac tak jak w przypadku polecenia Format parametry:
- %d - zmienna Integer;
- %c - znak typu char;
- %s - string;
Spis pozostalych parametrow mozesz znalezc w pliku pomocy. Trzecim parametrem jest zmienna, ktora ma byc wstawiona w miejsce znaku %d. Zamiast pojedynczej zmiennej moze to byc rekord:
uses Windows;
var
{ rekord zawierajacy informacje o szerokosci i wysokosci ekranu }
Pix : packed record
CX, CY : Integer;
end;
Buff : array[0..255] of char; // bufor przechowujacy tekst...
begin
{ pobierz szerokosc i wysokosc ekranu oraz przypisz te dane do rekordu }
with Pix do
begin
CX := GetSystemMetrics(SM_CXSCREEN);
CY := GetSystemMetrics(SM_CYSCREEN);
end;
{ przeksztalc dane do postaci tablicy znakow Buff }
wvsprintf(Buff,
'Szerokosc ekranu: %d pikseli.'#13#10+
'Wysokosc ekranu: %d pikseli.'#13#10, @Pix);
MessageBox(0, Buff, 'Wymiary ekranu', 0);
end.
W tym wypadku w okienku zostanie wyświetlona informacja o rozdzielczości ekranu.
MessageBox
MessageBox jest funkcja przeznaczona do wyswietlania krotkich komunikatow. Definicja w pliku Windows.pas:
function MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT):
Integer; stdcall;
hWnd - uchwyt 'okna rodzicielskiego'; u nas nie ma wiec zero
lpText - tekst wyswietlany w oknie
lpCaption - tekst wyswietlany w pasku tytulu
uType - styl na ktory sklada sie liczba i rodzaj przyciskow oraz rodzaj
ikony i dzwieku towarzyszacego. I tak:
MB_OK = 0;
MB_OKCANCEL = 1;
MB_ABORTRETRYIGNORE = 2;
MB_YESNOCANCEL = 3;
MB_YESNO = 4;
MB_RETRYCANCEL = 5;
MB_ICONHAND = MB_ICONERROR = MB_ICONSTOP = $10;
MB_ICONQUESTION = $20;
MB_ICONEXCLAMATION = MB_ICONWARNING = $30;
MB_ICONASTERISK = MB_ICONINFORMATION = $40;
MB_USERICON = $00000080;
MB_HELP = $4000;
Sa jeszcze inne ale niewazne.
Parametry pierwszej grupy mozna mieszac z parametrami drugiej grupy przy pomocy dodawania lub lepiej (czesciej) operatora 'OR' np:
MB_YESNO or MB_ICONQUESTION
Wartosc typu Integer zwracana przez funkcje MessageBox informuje ktory z przyciskow zostal uzyty do zamkniecia okna i moze byc:
IDOK = ID_OK = 1;
IDCANCEL = ID_CANCEL = 2;
IDABORT = ID_ABORT = 3;
IDRETRY = ID_RETRY = 4;
IDIGNORE = ID_IGNORE = 5;
IDYES = ID_YES = 6;
IDNO = ID_NO = 7;
IDCLOSE = ID_CLOSE = 8;
IDHELP = ID_HELP = 9;
Zakonczenie klawiszem Esc zwraca wynik IDCANCEL (jesli jest) lub IDOK.
Okno komunikatu wyswietlane funkcja MessageBox mozna wykorzystac w prostym programie informacyjnym.
GetSystemMetrics
Funkcja ta dostarcza rozne parametry konfiguracyjne systemu Windows. Niebawem poznamy je prawie wszystkie. Teraz uzyjemy tej funkcji aby przy pomocy MessageBox wyswietlic dwa najwazniejsze. Znaczenie poszczegolnych parametrow mozesz znalezc w pliku pomocy.
Tworzenie obiektów
Tworzenie komponentów na formularzu nie jest niczym nadzwyczajnym. Sluzy do tego funkcja CreateWindow. Tworzenie komponentow musi odbywac sie najlepiej w podczas tworzenia formularza, a wiec nalezy obsluzyc komunikat WM_CREATE. Oto jak powinno odbyć się stworzenie na formularzu przycisku:
CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, H, 0, hInstance, nil)
Funkcja ta co prawda zawiera sporo parametrów, ale bez obaw. Oto znaczenie poszczegolnych parametrow;
lpClassName - klasa. Inaczej mowiac jaki komponent ma byc umieszczony. Mozliwe sa takie oto: Button ( przycisk ), COMBOBOX ( lista rozwijalna ), EDIT ( pole tekstowe ), LISTBOX ( lista z polami ), MDICLIENT ( okno MDI ), SCROLLBAR ( pasek sluzacy do przesuwania ), STATIC ( zwykla etykieta - Label ).
lpWindowName - nazwa komponentu, czyli tekst, ktora ma na nim widniec.
dwStyle - styl komponentu. Obszerny spis mozesz znalezc w pliku pomocy. Powiem tylko, ze WS_CHILD ( komponent jest umieszczony na formie [ jest dzieckiem ] ), WS_VISIBLE [ komponent jest widoczny ].
X - polozenie w poziomie komponentu;
Y - polozenie w pionie;
nWidth - szerokosc komponentu;
nHeight - wysokosc komponentu
hWndParent - parametr ten okresla uchwyt okna - rodzica dla komponentu.
hMenu - identyfikator dla komponentu
hInstance - instancja aplikacji
pParam - wskaznik to tworzonego komponntu - to pole moze pozostac puste
Powyzszy kod mozna umiescic w komunikacie OnCreate - caly kod programu powinien wygladac tak:
program komponenty;
uses
Windows,
Messages;
const
Caption : PChar = 'Program 2';
function Window(H : HWND; Msg: UINT; wPar : WPARAM; lPar: LPARAM):
LRESULT; stdcall;
begin
Result := 0;
case Msg of
WM_DESTROY: PostQuitMessage(0);
WM_CREATE: //... podczsa uruchamiania programu...
begin
{ stworz komponent button }
CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, H, 0, hInstance, nil)
end;
else
Result := DefWindowProc(H, Msg, wPar, lPar);
end;
end;
procedure InitApp;
var
WNDCLASS : TWndClass;
WND : HWND;
begin
WndClass.style := CS_CLASSDC;
WndClass.lpfnWndProc := @Window;
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := 0;
WndClass.hInstance := hInstance;
WndClass.hIcon := LoadIcon(0, IDI_APPLICATION);
WndClass.hCursor := LoadCursor(0, IDC_ARROW);
WndClass.hbrBackground := COLOR_WINDOW;
WndClass.lpszMenuName := nil;
WndClass.lpszClassName := Caption;
RegisterClass(WNDCLASS);
Wnd := CreateWindow(Caption,
Caption,
WS_SYSMENU,
100,
100,
400,
250,
0, 0,
hInstance, nil);
ShowWindow(wnd, SW_SHOW);
end;
procedure Run;
var
Msg: TMsg;
begin
while GetMessage(msg, 0, 0, 0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
Halt(msg.wParam);
end;
begin
InitApp;
Run;
end.
Co teraz gdybysmy chcieli obsluzyc zdarzenie nacisniecia na przycisk? Do tego wlasnie sluzy ten dodatkowy parametr - identyfikator. Napiszmy jakis program. Powiedzmy bedzie on sie skladal z pola tekstowego i przycisku. Po nacisnieciu przycisku w okienku wyswietli sie zawartosc pola tekstowego.
Przede wszystkim nalezy stworzyc dodatkowy komponent typu Edit.
{ ... }
WM_CREATE: //... podczas uruchamiania programu...
begin
{ stworz komponent button }
CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 50, 100, 120, 25, H, 100, hInstance, nil);
{ stworz komponent Edit }
Edit := CreateWindow('EDIT', 'Wpisz jakiś tekst', WS_CHILD or WS_VISIBLE or WS_BORDER, 10, 50, 220, 20,
H, 0, hInstance, nil);
end;
{ ... }
Jak widzisz tym razem zadeklarowalismy osobna zmienna, ktora identyfikuje komponent Edit. Zauwaz takze, ze podczas tworzenia Edit'a przypisany zostal dodatkowy parametr - WS_BORDER, ktory doda do komponentu obramowanie. Zauwaz takze, ze stworzonemu Buttonowi przypisany zostal identyfikator o numerze 100.
Trzeba jeszcze obsluzyc zdarzenie nacisniecia na komponent. Trzeba osbluzyc komunikat WM_COMMAND:
WM_COMMAND:
case wPar of
100: // w przypadku nacisniecia na komponent Button...
begin
GetWindowText(Edit, Buff, SizeOf(Buff)); //... pobierz tekst z Edit'a i przypisz zmiennej Buff
MessageBox(H, Buff, 'Tekst z Edit''a...', MB_OK); // wyswietl w okienku zmienna
end;
end;
Jak widzisz parametr wPar zawiera informacje o identyfikatorze nacisnietego klawisza. Odbywa sie to tak: podczas nacisniecia przycisku do aplikacji zostaje wyslany komunikat WM_COMMAND, ktory zawiera w polu wPar identyfikator nacisnietego klawisza ( o szczegolach mozesz poczytac nizej ). Tak wiec my obslugujemy komunikat WM_COMMAND oraz zdarzenie w przypadku nacisniecia na klawisz, czyli inaczej mowiac zdarzenia 100. No i co ma robic program po nacisnieciu przycisku? Otoz pobiera ona tekst z pola tekstowego Edit. Pobieranie tekstu z kontrolki odbywa sie za pomoca polecenia GetWindowText. Pierwsze polecenia okresla kontrolke, z ktorej pobrany bedzie tekst, kolejny to bufor, w ktorym tekst bedzie umieszczony, no i ostatni okresla wiekosc bufora. Na koniec caly bufor zostaje wyswietlony w okienku.
Mala powtorka
Funkcja okienkowa ma cztery parametry:
hOkno - uchwyt okna, do ktorego komunikat jest skierowany,
uKomunikat - kod meldunku: WM_CosTam,
wParametr, lParametr - wykorzystywane w rozny sposob w roznych meldunkch,
czesto nie wykorzystane i rowne zero, czasem przekazujace dwie
rozne liczby jako mlodsze i starsze slowo. Bardzo czesto lParametr
jest adresem/wskaznikiem i wymaga konwersji typu (!!!).
W funkcji okienkowej zwykle stosuje sie instrukcje 'case' w celu rozgalezienia zaleznie od kodu komunikatu. Komunikaty, ktorych funkcja okienkowa nie obsluguje
musza byc skierowane do funkcji domyslnej DefWindowProc (Def - default).
Result := 0;
case uKomunikat of
WM_CREATE: {obsluga komunikatu wm_Creatte};
WM_SIZE: {obsluga komunikatu wm_Size};
WM_PAINT: {obsluga komunikatu wm_Paint};
WM_COMMAND: {osluga komunikatu wm_Command - zwykle ma taka postac}
case LoWord(wParametr) of
ID_1: {obsluga komunikatu od kontrolki 1};
ID_2: {obsluga komunikatu od kontrolki 2};
. . . .
end;
. . . .
WM_DESTROY: {obsluga komunikatu wm_Destroy (obowiazkowa)};
else Result:=DefWindowProc(hOkno, uKomunikat, wParametr, lParametr);
end;
Komunikaty klawiaturowe:
Na poczatek wystarczy nam trzy komunikaty klawiaturowe:
WM_KEYDOWN, WM_KEYUP:
wParametr zawiera kod tzw. klawisza wirtualnego,
lParametr dodatkowe dane jak licznik powtorzen, scan-code itp
raczej nie uzywany w praktyce.
WM_CHAR: meldunek wyprodukowany przez funkcje TranslateMessage
wParametr zawiera kod ASCII klawisza
lParametr - jak poprzednio.
Sa jeszcze meldunki:
WM_SYSKEYDOWN, WM_SYSKEYUP - kombinacja z klawiszem Alt,
WM_SYSCHAR, WM_DEADCHAR, WM_SYSDEADCHAR - poprzedzajacy znak
diakrytyczny na niektorych klawiaturach narodowych.
Przyklad: nacisniecie i zwolnienie klawisza 'A' wygeneruje meldunki:
WM_KEYDOWN wParametr = $41 (wirtualny kod klawisza A)
WM_CHAR wParametr = $61 - kod ASCII znaku 'a'
WM_KEYUP wParametr = $41
Klawiatura jest jedna a okien moze byc duzo. Wiec ktore okno otrzyma komunikat klawiaturowy? To, ktore ma fokus, albo mowiac inaczej, to, ktore jest 'w ognisku'. Tylko jedno okno w systemie moze miec fokus. Jest to okno aktywne lub jego potomek.
Komunikaty myszy:
WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_RBUTTONDOWN, WM_RBUTTONUP,
WM_MBUTTONDOWN, WM_MBUTTONUP: wysylane w momencie naciskania i zwalniania przyciskow myszy.
WM_MOUSEMOVE: wysylany gdy kursor myszy zmieni polozenie w oknie.
wParametr informyje o jednoczesnie wcisnietych klawiszach i przyciskach
poszczegolne bity 0..4 oznaczaja:
MK_LBUTTON = 1;
MK_RBUTTON = 2;
MK_SHIFT = 4;
MK_CONTROL = 8;
MK_MBUTTON = $10;
Oczywiscie fukcja okienkowa otrzymuje komunikaty myszy tylko wtedy, gdy kursor myszy znajduje sie w obszarze roboczym (client) okna.
Wszystkie komunikaty myszy przekazuja przy pomocy lParametr wspolrzedne kursora myszy, pozioma X w mlodszym slowie, pinowa Y w starszym slowie. Wspolrzedne liczone sa wzgledem lewego gornego rogu pola roboczego okna.
Komunikaty od zegara (timera)
To wlasciwie tylko jeden komunikat WM_TIMER. Ale zeby system wysylal ten komunikat do naszego okna nalezy funkcja SetTimer utworzyc timer. Ma ona cztery parametry:
hOkno - uchwyt okna, ktore bedzie otrzymywac meldunki WM_TIMER,
nID - numer identyfujacy timera (bo mozna utworzyc kilka),
uMiliSecs - przedzial czasy w milisekundach co jaki beda naplywaly meldunki.
lpTimerFunc - NIL lub adres innej funkcji, ktora bedzie wywolywana
zamiast glownej funkcji okienkowej w celu obslugi WM_TIMER.
wParametr komunikatu WM_TIMER zawiera numer identyfikacyjny timera, ktory
wygenerowal meldunek.
Na zakonczenie programu lub gdy juz nie jest potrzebny, timer trzeba usunac funkcja: KillTimer(hOkno, nID);
Wazniejsze komunikaty od systemu
WM_CREATE: pierwszy komunikat, jaki otrzymuje nowo utworzone okno
gdy jeszcze nie jest widoczne na ekranie. Dobre miejsce aby
na umieszczenie czynnosci inicjujacych, wykonywanych tylko raz.
WM_SIZE: gdy okno zmieni rozmiar poziomy lub pionowy. Pierwszy komunikat
pojawia sie zanim okno zostanie narysowane. lParametr zawiera
nowe wymiary okna - w mlodszym slowie szerokosc, ww starszym wysokosc.
WM_MOVE: gdy okno zmieni polozenie na ekranie. lParametr zawiera wspolrzedne
ekranowe lewego gornego rogu czesci roboczej okna.
WM_PAINT: gdy system oczekuje odmalowania zawartosci pola roboczego okna.
Obsluga tego meldunku zajmiemy sie w nastepnym odcinku. Teraz ze
wzgledu na to, ze nie umiemy umiescic niczego w polu roboczym okna
uzywamy wylacznie paska tytulu do wyswietlania tekstow funkcja
SetWindowTekst. Podobna funkcja GetWindowText mozna pobrac tekst
z paska tytulu okna.