Programowniae windows petzold Petzold04


Rozdział 3
' ' nikat
w þ komu
s
Wþ n o
Przykładowe programy z pierwszych dwóch rozdziałów odwołują się do funkcji
MessageBox, aby dostarczyć użytkownikowi wyjścia tekstowego. Funkcja Message-
Box tworzy "okno". W Windows słowo "okno" ma dokładnie określone znaczenie.
Okno to prostokątny obszar na ekranie, który odbiera polecenia użytkownika i wy-
świetla dane wyjściowe w formie tekstu i grafiki.
Funkcja MessageBox tworzy okno, ale jest to okno specjalne, o ograniczonej ela-
styczności. Okno komunikatu zawiera pasek tytułu z przyciskiem zamknięcia,
opcjonalnie ikonę, jedną albo więcej linii tekstu oraz do czterech przycisków. Ale
ikony i przyciski muszą być wybrane z małego zbioru, który udostępnia Win-
dows.
Funkcja MessageBox na pewno jest użyteczna, ale nie zajedziemy z nią daleko.
W oknie komunikatów nie możemy wyświetlać grafiki i nie możemy też dodać
menu. Dlatego nadszedł już czas, aby utworzyć własne okno.
WÅ‚asne okno
Tworzenie okna jest tak łatwe, jak wywołanie funkcji CreateWindow.
No, może nie całkiem. Chociaż funkcja tworzenia okna naprawdę nazywa się Cre-
ateWindow i możesz znaleźć jej dokumentację w /Platform SDK/Llser Interface Seroi-
ces/Windowing/Windows/Window Reference/Window Functions, to okazuje się, że pierw-
szym jej argumentem jest coś, co określone jest jako nazwa klasy okna, a ta klasa
okna jest połączona z czymś, co nazywa się procedurą okna. Zanim spróbujemy
wywołać CreateWindow, przyda nam się trochę dodatkowych informacji.
PrzeglÄ…d architektury
Kiedy programujesz w Windows, zajmujesz siÄ™ pewnym rodzajem programowa-
nia obiektowego. Najbardziej oczywistym obiektem - z którym będziesz najczę-
ściej pracował w Windows, od którego pochodzi nazwa Windows, któremu na-
wet nadaje się cechy antropomorficzne, który może nawet pojawiać się w twoich
snach -jest obiekt znany jako okno (ang. window).
Najczęściej spotykasz się z oknami aplikacji, które ozdabiają twój pulpit. Zawie-
rają one pasek tytułu pokazujący nazwę programu, menu, być może także pasek
narzędzi i pasek przewijania. Innym rodzajem okna jest okno dialogowe, które
może nie mieć paska tytułu.
40 Część I: Podstawy
Mniej oczywiste są różne rodzaje zwykłych przycisków, pól opcji, pól wyboru,
pól listy, pasków przewijania i pól tekstowych, które ozdabiają powierzchnię okien
dialogowych. Każdy z tych małych obiektów wizualnych jest oknem. Są one na-
zywane "oknami potomnymi" albo "oknami kontrolki", albo "kontrolkami okna
potomnego".
Użytkownik widzi te okna jako obiekty na ekranie i oddziałuje na nie bezpośred-
nio, używając klawiatury lub myszy. Co interesujące, punkt widzenia programi-
sty jest tu identyczny z punktem widzenia użytkownika. Okno odbiera informa-
cje wejściowe użytkownika w formie "komunikatów". Okno porozumiewa się
z innymi oknami także za pomocą komunikatów. Zrozumienie istoty komunika-
tów jest ważną częścią nauki programowania w Windows.
Oto przykład komunikatów Windows: jak wiesz, większość programów Windows
ma okno aplikacji o zmiennych rozmiarach. Oznacza to, że możesz uchwycić
myszą ramkę okna i zmienić jego wielkość. Najczęściej program odpowie na to
zmianą zawartości okna. Możesz sądzić (i będziesz miał rację), że to Windows, a
nie aplikacja, obsługuje cały kod związany ze zmianą wielkości okna przez użyt-
kownika. Jednak aplikacja "wie", że wielkość okna została zmieniona, ponieważ
potrafi zmienić format swojego wyświetlania.
Skąd aplikacja wie, że użytkownik zmienił rozmiary okna? Programista przyzwy-
czajony do konwencjonalnego programowania w trybie znakowym nie miał ni-
gdy do czynienia z żadnym mechanizmem systemu operacyjnego, który przeno-
siłby informację tego rodzaju do użytkownika. Uzyskanie odpowiedzi na to py-
tanie ma zasadnicze znaczenie dla zrozumienia architektury Windows. Kiedy
użytkownik zmienia wielkość okna, Windows wysyła do programu komunikat
z nowymi rozmiarami okna. Program może wtedy dostosować zawartość okna
do jego nowej wielkości.
Windows wysyła komunikat do programu". Mam nadzieję, że nie przeczytałeś tego
"
bez zastanowienia. Co to oznacza? Mówimy przecież o kodzie programu, a nie o sys-
temie telegraficznym. Jak system operacyjny może wysłać komunikat do programu?
Kiedy mówię, że Windows wysyła komunikat do programu, mam na myśli to, że
Windows wywołuje funkcję znajdującą się w twoim programie i będącą zasadni-
czą częścią kodu tego programu. Parametry tej funkcji opisują konkretny komu-
nikat wysyłany przez Windows i odbierany przez twój program. Ta funkcja w two-
im programie jest nazywana procedurÄ… okna (ang. window procedure).
Niewątpliwie jesteś przyzwyczajony do idei programu, który wywołuje funkcje
systemu operacyjnego. Na przykład może to być program otwierający pliki dys-
kowe. Ale możesz być zaskoczony tym, że to system operacyjny wywołuje funk-
cje programu. Jednak właśnie to stanowi podstawę architektury Windows.
Każde okno, które jest tworzone przez program, ma powiązaną procedurę okna.
Ta procedura to funkcja, która może znajdować się albo w samym programie, albo
w dołączanej dynamicznie bibliotece. Windows wysyła komunikat do okna, wy-
wohzjÄ…c procedurÄ™ okna. Procedura okna wykonuje pewne przetwarzanie zwiÄ…-
zane z komunikatem i zwraca sterowanie do Windows.
Mówiąc dokładniej, okno zawsze jest tworzone w oparciu o klasę okna (ang. win-
dow class). Klasa okna określa procedurę okna, która przetwarza komunikaty skie-
T
Rozdział 3: Windows i komunikaty 41
rowane do okna. Stosowanie klasy okna pozwala na oparcie wielu okien na tej
samej klasie okna i na użycie tej samej procedury okna. Na przykład wszystkie
przyciski we wszystkich programach Windows odwołują się do tej samej klasy
okna. Ta klasa okna jest powiÄ…zana z procedurÄ… okna, znajdujÄ…cÄ… siÄ™ w bibliotece
dynamicznej Windows, przetwarzajÄ…cÄ… komunikaty do wszystkich okien przyci-
sków.
Obiekt w programowaniu obiektowym to połączenie kodu i danych. Obiektem jest
okno. Kod to procedura okna. Dane to informacja zachowana przez procedurÄ™ okna
i przez Windows dla każdego okna i każdej klasy okna, które istnieją w systemie.
Procedura okna przetwarza komunikaty skierowane do okna. Bardzo często te
komunikaty informują okno o poleceniach użytkownika wprowadzanych z kla-
wiatury albo myszą. Na przykład stąd okno przycisku wie, że zostało "kliknię-
te". Inne komunikaty mówią oknu, że jego wielkość uległa zmianie lub że po-
wierzchnia okna wymaga ponownego narysowania (odświeżenia).
Kiedy program windowsowy rozpoczyna działanie, Windows tworzy kolejkę
komunikatów (ang. message queue) dla tego programu. Są w niej przechowywane
komunikaty do wszystkich okien utworzonych przez program. Aplikacja Win-
dows zawiera krótki fragment kodu zwany pętlą komunikatów (ang. message loop),
który pobiera komunikaty z kolejki i przesyła je do odpowiedniej procedury okna.
Inne komunikaty są wysyłane bezpośrednio do procedury okna, bez umieszcza-
nia ich w kolejce komunikatów.
Jeśli zaczynasz już przysypiać ślęcząc nad tym nadmiernie abstrakcyjnym opisem
architektury Windows, to najwyższa pora na przyjrzenie się, jak okno, klasa okna,
procedura okna, kolejka komunikatów, pętla komunikatów i komunikaty okna
współdziałają ze sobą w prawdziwym programie.
Program HELLOWIN
Utworzenie okna wymaga zarejestrowania najpierw klasy okna, co z kolei nie jest
możliwe bez procedury okna, która przetwarza komunikaty kierowane do okna.
Potrzebne są pewne dodatkowe elementy, które występują w niemal każdym
programie Windows. HELLOWIN, pokazany na rysunku 3-1, jest prostym pro-
gramem pokazującym większość tych dodatkowych elementów.
HELLOWIN.C
/*
HELLOWIN.C -- Wyświetla napis "Hello. Windows 98!"
w obszarze roboczym okna
(c) Charles Petzold, 1998
*/
llinclude
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
-
42 Część I: Podstawy
(ciÄ…g dalszy ze str. 41)
(
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW þ CS VREDRAW ; !
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEþBRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ; '
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBþICONERROR) ;
return 0 ;
(
hwnd = CreateWindow (szAppName, // nazwa klasy okna
TEXT ("The Hello Program"), // naglówek okna
WS_OVERLAPPEDWINDOW, // styl okna
CW_USEDEFAULT, // poczÄ…tkowa pozycja x
CW_USEDEFAULT, // poczdtkowa pozycja y
CW_USEDEFAULT, // poczdtkowa wielkość x
CW_USEDEFAULT, // początkowa wielkość y
NULL, // uchwyt okna nadrzędnego
NULL, // uchwyt menu okna
hInstance, // uchwyt kopii programu
NULL) ; // parametry tworzenia
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
(
case WM_CREATE:
PlaySound (TEXT ("hellowin.wav"), NULL, SNDþFILENAME þ SND ASYNC) ;
return 0 ;
Rozdział 3: Windows i komunikaty 43
,
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE þ DT CENTER þ DT VCENTER) ;
I EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostOuitMessage (0) ;
return 0 ;
l
return DefWindowProc (hwnd, message, wParam, lParam) ;
J
Rysunek 3-1. Program HELLOWIN
Ten program tworzy standardowe okno aplikacji, takie jak na rysunku 3-2, i wy-
świetla napis "Hello, Windows 98" pośrodku okna. Jeżeli masz zainstalowaną
kartę dźwiękową, możesz także usłyszeć, jak mówię ten tekst.
Rysunek 3-2. Okno programu HELLOWIN
Kilka ostrzeżeń: jeżeli używasz Microsoft Visual C++ do stworzenia nowego pro-
jektu dla tego programu, musisz dodać kilka rzeczy do bibliotek wynikowych
używanych przez konsolidator. Wybierz Settings z menu Project i przejdź na kartę
Link. Wybierz General z listy Category i dodaj WINMM.LIB (Windows multime-
dia) do pola tekstowego Object/Library Modules. Musisz to zrobić, ponieważ HEL-
LOWIN używa wywołań funkcji multimedialnych, a biblioteka multimedialna
nie jest domyśinie dołączana do projektu. Jeśli tego nie zrobisz, otrzymasz od
konsolidatora komunikat o błędzie, informujący, że nie ma funkcji PlaySound.
44 Część I: Podstawy
HELLOWIN używa pliku HELLOWIN.WAV, który znajduje się w katalogu HEL-
LOWIN na CD-ROM-ie dołączonym do tej książki. Gdy uruchamiasz HELLO-
WIN.EXE, domyślnym katalogiem musi być HELLOWIN. Tak się dzieje, gdy uru-
chamiasz program z wnętrza Visual C++, nawet jeśli pliki wykonawcze znajdują
siÄ™ w podkatalogu RELEASE lub DEBUG katalogu HELLOWIN.
Myślenie globalne
Większość HELLOWIN.C stanowią elementy dodatkowe, znajdujące się praktycz-
nie w każdym programie Windows. Nikt jednak nie uczy się na pamięć ich składni;
najczęściej programista Windows zaczyna pisanie nowego programu od skopio-
wania istniejącego i wprowadzenia w nim odpowiednich zmian. Możesz do tego
celu wykorzystywać programy z książkowego CD-ROM-u.
Wspominałem, że HELLOWIN wyświetla napis na środku swojego okna. To nie-
zupełnie prawda. W rzeczywistości tekst jest wyświetlany na środku obszaru
roboczego (ang. client area), który na rysunku 3-2 jest wielką białą płaszczyzną
zamkniętą wewnątrz paska tytułu i ramek ograniczających rozmiar okna. To roz-
różnienie jest bardzo ważne; obszar roboczy to obszar okna, w którym program
może swobodnie rysować i dostarczać użytkownikowi wizualnych danych.
Zadziwiające, ile funkcjonalności zawiera ten program zaledwie w 80 liniach kodu.
Możesz chwycić myszą pasek tytułu i przesunąć okno w inne miejsce ekranu.
Możesz chwycić ramkę okna i zmienić jego wielkość. Kiedy okno zmienia swoje
rozmiary, program automatycznie przesuwa napis na środek swojego obszaru
roboczego. Możesz kliknąć przycisk maksymalizacji i okno HELLOWIN wypełni
cały ekran. Możesz kliknąć przycisk minimalizacji i usunąć je z ekranu. Możesz
wywołać wszystkie opcje z menu systemowego (mała ikona w lewym rogu pa-
ska tytuhx). Możesz też zamknąć okno kończąc działanie programu przez wy-
branie opcji Zamknij z menu systemowego, przez kliknięcie przycisku zamknię-
cia znajdującego się w prawym rogu paska tytułu lub przez dwukrotne kliknię-
cie ikony menu systemowego.
Będziemy analizować ten program szczegółowo w dalszej części rozdziału. Jed-
nak najpierw spójrzmy bardziej globalnie.
HELLOWIN.C zawiera funkcję WinMain, podobnie jak programy przykładowe
w dwóch pierwszych rozdzialach, ale zawiera też drugą funkcję o nazwie Wnd-
Proc. To właśnie procedura okna. (W rozmowach między programistami Windows
jest ona nazywana "win prock"). Zauważ, że w HELLOWIN.C nie ma kodu, któ-
ry wywołuje WndProc. Jednak w WinMain znajduje się odniesienie do WndProc,
ponieważ funkcja jest zadeklarowana na początku programu.
Wywoływanie funkcji Windows
HELLOWIN wywołuje co najmniej 18 funkcji Windows. Oto ich lista, wraz z krót-
kim opisem (w kolejności występowania w programie):
ł LoadIcon - wczytuje do pamięci ikonę używaną przez program.
ł LoadCursor - wczytuje do pamięci kursor myszy używany przez program.
ł GetStockObject - pobiera obiekt graficzny, w tym przypadku pędzel używany
do malowania tła okna.
T
Rozdział 3: Windows i komunikaty 45
Å‚ RegisterClass - rejestruje klasÄ™ okna dla okna programu.
ł MessageBox - wyświetla okno komunikatu.
Å‚ CreateWindow - tworzy okno na podstawie klasy okna.
ł ShowWindow - wyświetla okno na ekranie.
ł UpdateWindow - wymusza aktualizację (odświeżenie) zawartości okna.
ł GetMessage - pobiera komunikat z kolejki komunikatów.
ł TranslateMessage - tłumaczy niektóre komunikaty wysyłane z klawiatury.
ł DispatchMessage - wysyła komunikat do procedury okna.
ł PlaySound - odtwarza plik dźwiękowy.
Å‚ BeginPaint - rozpoczyna blok malowania okna.
Å‚ GetClientRect - pobiera rozmiary obszaru okna roboczego.
ł DrawText - wyświetla napis.
ł EndPaint - kończy blok malowania okna.
ł PostQuitMessage - wstawia do kolejki komunikat "quit" o zakończeniu pro-
gramu.
ł DefWindowProc - wykonuje domyślne przetwarzanie komunikatów.
Te funkcje są opisane w dokumentacji Platform SDK i zadeklarowane w różnych
plikach nagłówkowych, przeważnie w WINUSER.H.
Identyfikatory pisane wielkimi literami
Zauważyłeś pewnie, że w HELLOWIN.C znajdują się identyfikatory pisane wiel-
kimi literami. Wszystkie są zdefiniowane w plikach nagłówkowych Windows.
Część z nich zawiera dwu- lub trzyliterowy przedrostek, po którym występuje
podkreślenie:
CS HREDRAW DT VCENTER SND FILENAME
CS-VREDRAW IDC ARROW WM CREATE
CW USEDEFAULT IDI aPPLICATION WM DESTROY
DT CENTER MB ICONERROR WM PAINT
DT SINGLELINE SND ŽSYNC WS OVERLAPPEDWINDOW
Są to po prostu stałe liczbowe. Przedrostek wskazuje ogólną kategorię, do której
należy stała, jak pokazuje to tabela:
Przedrostek Stała
CS opcja stylu klasy (ang. Class Style)
CW opcja tworzenia okna (ang. Create Window)
DT opcja rysowania napisów (ang. Draw Text)
IDI identyfikator (liczbowy) ikony (ang. ID for an Icon)
IDC identyfikator (liczbowy) kursora (ang. ID for a Cursor)
46 Część I: Podstawy
Przedrostek Stała
MB opcja okna komunikatów (ang. Message Box)
SND opcja dźwięku (ang. SouND)
WM komunikat okna (ang. Window Message)
WS styl okna (ang. Window Style)
Programując w Windows, zazwyczaj nie musisz pamiętać wartości liczbowej tych
stałych, gdyż prawie każda stała liczbowa ma identyfikator zdefiniowany w pli-
kach nagłówkowych.
Nowe typy danych
Niektóre identyfikatory stosowane w HELLOWIN.C to nowe typy danych, które
zostały także zdefiniowane w plikach nagłówkowych Windows przy użyciu in-
strukcji typedef lub #define. Wprowadzono je, aby uprościć przenoszenie progra-
mów z pierwotnego sytemu 16-bitowego do planowanych systemów operacyj-
nych opartych na technologii 32-bitowej. Nie działało to tak sprawnie, jak ocze-
kiwano, ale sama idea była prawidłowa.
Czasem te nazwy nowych typów danych to po prostu wygodne skróty. Na przy-
kład typ danych UINT, używany w drugim parametrze WndProc, zastępuje po
prostu unsigned int, który w Windows 98 jest wartością 32-bitową. Typ danych
PSTR, występujący w trzecim parametrze WinMain, to wskaźnik do zwykłego (nie
szerokiego) napisu, czyli char *.
Inne typy są mniej oczywiste. Na przykład trzeci i czwarty parametr WndProc są
zdefiniowane odpowiednio jako WPARAM i LPARAM. Wyjaśnienie tych nazw
wymaga sięgnięcia do historii. Kiedy Windows był systemem 16-bitowym, trzeci
parametr WndProc był zdefiniowany jako WORD, co oznaczało 16-bitową liczbę
całkowitą unsigned short, a czwarty parametr był zdefiniowany jako LONG, co ozna-
czało 32-bitową liczbę całkowitą long ze znakiem. Było to powodem umieszczenia
przedrostków "W" i "L" przed słowem "PARAM". Jednak w 32-bitowej wersji
Windows WPARAM jest zdefiniowany jako UINT, a LPARAM jest zdefiniowany
jako LONG (co w dalszym ciągu oznacza typ danych long z C), więc oba parame-
try procedury okna są wartościami 32-bitowymi. Ponieważ typ danych WORD jest
ciągle jeszcze zdefiniowany w Windows 98 jako 16-bitowa liczba całkowita unsi-
gned short, więc umieszczenie "W" przed "PARAM" tworzy mylącą nazwę.
Funkcja WndProc zwraca wartość typu LRESULT. Jest ona zdefiniowana jako LONG.
Funkcja WinMain jest typu WINAPI ( jak każde wywołanie funkcji Windows zdefi-
niowane w pliku nagłówkowym), a funkcja WndProc jest typu CALLBACK. Oba te
identyfikatory sÄ… zdefiniowane jako stdcall, co wskazuje na specjalnÄ… sekwencjÄ™
wywołania funkcji, która występuje między samym Windows i twoją aplikacją.
HELLOWIN używa też czterech struktur danych (które omówię w dalszej części
rozdziału), zdefiniowanych w pliku nagłówkowym Windows. Te struktury da-
nych pokazuje poniższa tabela:
Rozdział 3: Windows i komunikaty 47
Struktura Znaczenie
MSG struktura komunikatu (ang. Message)
WNDCLASS struktura klasy okna (ang. Window class structure)
PAIN'TSTRUCT struktura malowania (ang. Paint structure)
RECT struktura prostokÄ…ta (ang. Rectangle structure)
Pierwsze dwie struktury danych są używane w WinMain do definicji dwóch
zmiennych strukturalnych o nazwach msg i wndclass. Pozostałe są używane w Wnd-
Proc do definicji dwóch zmiennych strukturalnych o nazwach ps i rect.
Otrrymywanie uchwytu
Na koniec mamy jeszcze trzy identyfikatory pisane wielkimi literami dla różnych
rodzajów uchwytów:
Identyfikator Znaczenie
HINSTANCE uchwyt realizacji (kopii) - sam program (ang. Handle to an
instance)
HWND uchwyt okna (ang. Handle to a window)
HDC uchwyt kontekstu urzÄ…dzenia (ang. Handle to a device con-
text)
Uchwyty są stosowane w Windows bardzo często. Zanim doczytasz do końca
ten rozdziaÅ‚, spotkasz jeszcze HICON (uchwyt ikony - ang. Handle to an ICOIþ,
HCURSOR (uchwyt kursora myszy - ang. Handle to a mouse CURSOR) i HBRUSH
(uchwyt pędzla - ang. Handle to a graphics BRUSH).
Uchwyt to po prostu liczba (zwykle 32-bitowa), która wskazuje obiekt. Uchwyty
w Windows są podobne do uchwytów plików używanych przy konwencjonal-
nym programowaniu w C albo MS-DOS-ie. Program niernal zawsze otrzymuje
uchwyt przez wywołanie funkcji Windows. Program stosuje uchwyt w innych
funkcjach Windows, aby wskazać obiekt. Dla programu rzeczywista wartość
uchwytu jest nieistotna, ale moduł Windows, który dał uchwyt twojemu progra-
mowi, wie, jak go użyć do wskazania konkretnego obiektu.
Notacja węgierska
Być może zauważyłeś także, że niektóre zmienne w HELLOWIN.C mają osobli-
wie wyglądające nazwy. Jednym z przykładów jest napis szCmdLine, przekazy-
wany do WinMain.
Wielu programistów Windows stosuje konwencję nazewniczą znaną jako "nota-
cja węgierska" dla uczczenia legendarnego programisty Microsoftu, Charlesa
Symonyi. Mówiąc prosto, nazwa zmiennej zaczyna się małą literą lub literami,
48 Część I: Podstawy
które oznaczają typ danych zmiennej. Na przykład przedrostek sz w szCmdLine
oznacza "łańcuch znaków zakończony zerem" (ang. string terminated by zero).
Przedrostek h w hlnstance i w hPrevlnstance oznacza "uchwyt" (ang. handle); przed-
rostek i w iCmdShow oznacza "liczbę całkowitą" (ang. integer). Ostatnie dwa pa-
rametry WndProc także używają notacji węgierskiej, chociaż, jak wyjaśniłem wcze-
śniej, wParam powinien być nazwany uiParam (ui od ang. unsigned integer - licz-
ba całkowita bez znaku). Ponieważ jednak te dwa parametry są zdefiniowane z
użyciem typów danych WPARAM i LPARAM, zdecydowałem się pozostawić ich
tradycyjne nazwy.
Nadając nazwę zmiennym strukturalnym, możesz użyć pisanej małymi literami
pełnej nazwy struktury (albo skrótu tej nazwy) jako przedrostka albo jako całej
nazwy zmiennej. Na przykład w funkcji WinMain w HELLOWIN.C zmienna msg
oznacza strukturÄ™ typu MSG; wndclass jest strukturÄ… typu WNDCLASS. W funk-
cji WndProc ps jest strukturÄ… typu PAINTSTRUCT, a rect jest strukturÄ… typu RECT.
Notacja węgierska pomaga unikać błędów w kodzie, zanim staną się trudne do
wykrycia. Ponieważ nazwa zmiennej opisuje i przeznaczenie zmiennej, i typ da-
nych, trudniej zrobić błędy, polegające na nieodpowiednich typach danych.
Przedrostki nazw zmiennych, stosowane w tej książce, pokazuje poniższa tabela:
Przedrostek Typ danych
char lub WCHAR lub TCHAR
by BYTE (unsigned char)
n short
int
x, y int (stosowany przy współrzędnych x i y)
cx, cy int (stosowany jako długość x lub y); c oznacza liczbę
(ang. count)
b lub f BOOL (int); f oznacza znacznik (ang. flag)
w WORD (unsigned short)
LONG (long)
dw DWORD (unsigned long)
fn funkcja
łańcuch znaków (ang. string)
sz łańcuch znaków zakończony znakiem 0
uchwyt (ang. handle)
p wskaźnik (ang. pointer)
Rozdział 3: Windows i komunikaty 49
Rejestracja klasy okna
Okno zawsze jest tworzone na podstawie klasy okna. Klasa okna identyfikuje
procedurę okna, która przetwarza komunikaty wysyłane do okna.
Na podstawie pojedynczej klasy okna może być utworzone więcej niż jedno okno.
Na przykład wszystkie okna przycisków - włącznie z przyciskami, polami opcji
i polami wyboru - sÄ… utworzone na podstawie tej samej klasy okna. Klasa okna
definiuje procedurę okna i kilka innych charakterystyk okien, które są tworzone
na podstawie tej klasy. Kiedy tworzysz okno, definiujesz dodatkowÄ… charaktery-
stykę okna, który jest dla niego unikatowa.
Zanim utworzysz okno aplikacji, musisz zarejestrować klasę okna przez wywo-
łanie RegisterClass. Ta funkcja wymaga pojedynczego parametru, który jest wskaź-
nikiem do struktury typu WNDCLASS. Ta struktura zawiera dwa pola, które są
wskaźnikami do napisów, więc struktura jest zdefiniowana w pliku nagłówko-
wym WINUSER.H na dwa różne sposoby. Oto wersja ASCII - WNDCLASSA:
typedef struct tagWNDCLASSA
(
UINT style ;
WNDPROC lpfnWndProc ;
int cbClsExtra ;
int cbWndExtra ;
HINSTANCE hInstance :
HICON hIcon :
HCURSOR hCursor ;
HBRUSH hbrBackground
LPCSTR lpszMenuName
LPCSTR lpszClassName
1
WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ;
Zwróć uwagę na zastosowanie notacji węgierskiej: przedrostek Ipfn oznacza "dale-
ki wskaźnik do funkcji" (ang. long pointer to a function). (Przypominam, że w Win32
API nie ma żadnej różnicy między dalekimi i bliskimi wskaźnikami. To pozosta-
łość z 16-bitowego Windows). Przedrostek cb oznacza "licznik bajtów" (ang. count
of bytes) i jest często stosowany dla zmiennej, która oznacza rozmiar w bajtach. Przed-
rostek h oznacza "uchwyt" (ang. handle), a hbr to "uchwyt pędzla" (ang. handle to a
brush). Przedrostek Ipsz oznacza "daleki wskaźnik do łańcucha znaków zakończo-
nego zeremł (ang. long pointer to a string terminated with zero).
Wersja struktury dla unikodu jest zdefiniowana następująco:
typedef struct tagWNDCLASSW
(
UINT style ;
WNDPROC lpfnWndProc ;
int cbClsExtra ;
int cbWndExtra ;
HINSTANCE hInstance ;
HICON hIcon ;
HCURSOR hCursor ;
HBRUSH hbrBackground ;
LPCWSTR lpszMenuName ;
LPCWSTR lpszClassName ;
)
WNDCLASSW, * PWNDCLASSW, NEAR * NPWNDCLASSW, FAR * LPWNDCLASSW ;
50 Część I: Podstawy
Jedyna różnica to dwa ostatnie pola, zdefiniowane jako wskaźniki do stałych łań-
cuchów znaków szerokich, a nie wskaźniki do stałych łańcuchów znaków ASCII.
Po zdefiniowaniu przez WINUSEIZ.H struktur WNDCLASSA i WNDCLASSW
(i wskaźników do struktur), plik nagłówkowy definiuje WNDCLASS i wskaźni-
ki do WNDCLASS (niektóre z nich mają zapewnić wsteczną zgodność) na pod-
stawie definicji identyfikatora UMCODE:
llifdef UNICODE
typedef WNDCLASSW WNDCLASS ;
typedef PWNDCLASSW PWNDCLASS ;
typedef NPWNDCLASSW NPWNDCLASS
typedef LPWNDCLASSW LPWNDCLASS
llelse
typedef WNDCLASSA WNDCLASS ;
typedef PWNDCLASSA PWNDCLASS ;
typedef NPWNDCLASSA NPWNDCLASS
typedef LPWNDCLASSA LPWNDCLASS
llendi f
Przedstawiając kolejne struktury w tej książce, pokazuję funkcjonalnie równoważ-
ną definicję struktury, którą dla WNDCLASS jest:
typedef struct
(
UINT style ;
WNDPROC lpfnWndProc ;
int cbClsExtra ;
int cbWndExtra ;
HINSTANCE hInstance ;
HICON hIcon ;
HCURSOR hCursor ;
HBRUSH hbrBackground
LPCTSTR lpszMenuName
LPCTSTR lpszClassName
)
WNDCLASS, * PWNDCLASS ;
Nie będę męczył cię różnymi definicjami wskaźników. Nie ma żadnego powodu,
by zaśmiecać swój kod typami zmiennych zaczynającymi się od LP i NP.
W WinMain zdefiniuj strukturę typu WNDCLASS w następujący sposób:
WNDCLASS wndclass ;
Następnie zainicjuj 10 pól struktury i wywołaj RegisterClass.
Dwa najważniejsze pola w strukturze WNDCLASS to pole drugie i ostatnie. Dru-
gie pole (IpfnWndProc) jest adresem procedury okna, używanej przez wszystkie
okna utworzone na podstawie tej klasy. W HELLOWIN.C procedura okna to
WndProc. Ostatnie pole zawiera nazwę tekstową klasy okna. Może to być cokol-
wiek. W programach, które tworzą tylko jedno okno, w polu nazwy klasy okna
zwykle wpisywana jest nazwa programu.
Poniżej omówimy inne pola, które opisują pewne właściwości klasy okna. Przyj-
rzyjmy się kolejno każdemu polu struktury WNDCLASS.
Instrukcj a:
wndclass.style = CS HREDRAW þ CSþVREDRAW ;
Rozdział 3: Windows i komunikaty 51
łączy dwa 32-bitowe identyfikatory "stylów klas" za pomocą bitowego operato-
ra OR z C. Plik nagłówkowy WINUSER.H definiuje cały zestaw identyfikatorów
z przedrostkiem CS:
iþdefine CS_VREDRAW 0x0001
iþdefine CS_HREDRAW 0x0002
#define CS_KEYCVTWINDOW 0x0004
iþdefine CS_DBLCLKS 0x0008
þþdefine CS_OWNDC 0x0020
þþdefine CS_CLASSDC 0x0040
þþdefine CS_PARENTDC 0x0080
#define CSþNOKEYCVT 0x0100
þtdefine CS_NOCLOSE 0x0200
þþdefine CSþSAVEBITS 0x0800
þþdefine CS_BYTEALIGNCLIENT 0x1000
iþdefine CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000
þþdefine CSþIME 0x00010000
Identyfikatory definiowane w ten sposób są często zwane znacznikami bitowy-
mi, ponieważ każdy identyfikator ustawia pojedynczy bit w wartości złożonej.
Najczęściej używane są tylko niektóre z tych stylów klasy. Dwa identyfikatory,
użyte w HELLOWIN, oznaczają, że wszystkie okna stworzone na podstawie tej
klasy muszą być odświeżone, jeśli tylko zmieni się wielkość okna w poziomie
(CS HREDRAW) albo pionie (CS VREDRAW). Jeśli zmienisz rozmiar okna pro-
gramu HELLOWIN, zobaczysz, że łańcuch znaków tekstu jest odświeżany tak,
aby znalazł się na środku nowego okna. Te dwa identyfikatory odpowiadają za
ten proces. Niedługo zobaczymy, w jaki sposób procedura okna jest informowa-
na o zmianie wielkości okna.
Drugie pole struktury WNDCLASS jest inicjowane przez instrukcjÄ™:
wndclass.lpfnWndProc = WndProc :
Ustawia to procedurę okna dla tej klasy okna na WndProc, która jest drugą funk-
cją w HELLOWIN.C. Ta procedura okna będzie przetwarzała wszystkie komuni-
katy przeznaczone dla wszystkich okien utworzonych na podstawie tej klasy okna.
W C, gdy używasz nazwy funkcji w takiej instrukcji, tak naprawdę posługujesz
się wskaźnikiem do tej funkcji.
Następne dwa pola służą do rezerwacji dodatkowego miejsca w strukturze klasy
i strukturze okna, którym Windows zarządza wewnętrznie.
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
Program może użyć tej dodatkowej przestrzeni do własnych celów. Program
HELLOWIN nie wykorzystuje tej możliwości, więc ustawia wartość 0. W innym
przypadku, jak wskazuje notacja węgierska, w polu powinna być ustawiana "licz-
ba bajtów". (Użyję pola cbWndExtra w programie CHECKER3, w rozdziale 7).
Następne pole jest po prostu uchwytem kopii programu (ten uchwyt jest jednym
z parametrów WinMain):
wndclass.hInstance = hInstance :
Instrukcja:
wndclass.hIcon = LoadIcon (NULL, IDI aPPLICATION) :
52 Część I: Podstawy
określa ikonę dla wszystkich okien stworzonych na podstawie klasy okna. Ta ikona
jest małą bitmapą, która symbolizuje program. Gdy program jest uruchomiony,
ikona ta jest widoczna na pasku zadań Windows i z lewej strony paska tytułu
aplikacji. Później dowiesz się, jak tworzyć własne ikony dla swoich programów
Windows. Na razie będziemy po prostu używali predefiniowanej ikony.
Aby otrzymać uchwyt predefiniowanej ikony, wywołaj Loadlcon z pierwszym
argumentem ustawionym na NULL. Gdy ładujesz swoje własne ikony, przecho-
wywane na dysku w pliku EXE programu, ten argument powinien mieć wartość
hlnstance, czyli uchwytu kopii programu. Drugi argument identyfikuje ikonÄ™. Dla
predefiniowanych ikon ten argument jest identyfikatorem zaczynajÄ…cym siÄ™ od
przedrostka IDI ("identyfikator dla ikony") zdefiniowanym w WINUSER.H. Ikona
IDI APPLICATION jest po prostu małym obrazkiem okna. Funkcja Loadlcon
zwraca uchwyt tej ikony. Nie przejmuj się wartością tego uchwytu. Po prostu użyj
go do ustawienia wartości pola hlcon. To pole jest zdefiniowane w strukturze
WNDCLASS jako pole typu HICON, co oznacza "uchwyt ikony" (ang. handle to
an icon).
Instrukcja:
wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ;
jest podobna do poprzedniej. Funkcja Loadlcon Å‚aduje predefiniowany kursor
myszy, znany jako IDC ARROW, i zwraca uchwyt tego kursora. Ten uchwyt jest
przyporzÄ…dkowany polu hCursor struktury WNDCLASS. Gdy kursor myszy znaj-
duje siÄ™ nad obszarem roboczym okna utworzonego na podstawie tej klasy, kur-
sor staje się małą strzałką.
Następne pole określa kolor tła obszaru roboczego okna, utworzonego na pod-
stawie tej klasy. Przedrostek hbr pola hbrBackground oznacza "uchwyt pędzla" (ang.
handle to a brush). Pędzel jest określeniem, które w grafice komputerowej ozna-
cza kolorowy wzór z pikseli, używany do wypełniania obszaru. Windows ma
wiele standardowych ("szablonowych" - ang. stock) pędzli. Funkcja GetStockO-
bject, pokazana tutaj, zwraca uchwyt białego pędzla:
wndclass.hbrBackground = GetStockObject (WHITEþBRUSH) ;
Oznacza to, że pole obszaru roboczego będzie białe, co jest najczęściej stosowa-
ne.
Następne pole określa menu klasy okna. HELLOWIN nie ma menu aplikacji, więc
ustawiamy to pole na NULL:
wndclass.lpszMenuName = NULL ;
Wreszcie musimy nadać klasie nazwę. Dla małego programu może to być po pro-
stu nazwa programu, która tu jest łańcuchem znaków "HelloWin", przechowy-
wanym w zmierinej szAppName.
wndclass.lpszClassName = szAppName ;
Ten łańcuch znaków jest złożony ze znaków ASCII albo znaków unikodu, w za-
leżności od tego, czy zdefiniowany jest identyfikator UNICODE.
Gdy wszystkie 10 pól struktury zostanie zainicjowanych, HELLOWIN rejestruje
klasę okna, wywohzjąc RegisterClass. Jedynym argumentem tej funkcji jest wskaźnik
do struktury WNDCLASS. Właściwie jest to funkcja RegisterClassA, która pobie-
Rozdział 3: Windows i komunikatv 53
ra wskaźnik do struktury WNDCLASSA, i funkcja RegisterClassW, która pobiera
wskaźnik do struktury WNDCLASSW. To, której funkcji użyje program do reje-
strowania klasy okna, zależy od tego, czy komunikaty wysyłane do okna będą
zawierały tekst ASCII, czy tekst w unikodzie.
Tutaj pojawia się problem: jeśli skompilowałeś program ze zdefiniowanym iden-
tyfikatorem UNICODE, twój program wywoła RegisterClassW. Wszystko będzie
dobrze, jeśli uruchamiasz program pod Windows NT. Ale jeśli uruchamiasz pro-
gram pod Windows 98, okaże się, że funkcja RegisterClassW tak nie jest napraw-
dę zrealizowana. Istnieje punkt wejścia dla tej funkcji, ale zwraca ona zero przy
wywołaniu, co oznacza błąd. Dzięki temu program w unikodzie, uruchomiony
pod Windows 98, może poinformować użytkownika o problemie i zakończyć dzia-
łanie. Większość programów z tej książki obsługuje wywołanie funkcji RegisterC-
lass następująco:
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("Ten program wymaga Windows NT!"),
szAppName, MB ICONERROR) ;
return 0 :
Funkcja MessageBoxW działa prawidłowo, ponieważ jest to jedna z niewielu funkcji
unikodowych zrealizowanych w Windows 98.
Oczywiście ten fragment kodu zakłada, że RegisterClass nie zawiedzie z innych po-
wodów, jak na przykład wartość NULL pola lpfnWndProc struktury WNDCLASS.
W takich przypadkach funkcja GetLastError pomoże określić przyczynę błędu. Jest
to funkcja ogólnego zastosowania w Windows, która otrzymuje rozszerzoną infor-
mację o błędzie, kiedy wywołanie funkcji zawiedzie. Dokumentacja różnych funkcji
wskazuje, czy możesz stosować GetLastError, aby otrzymać tę informację. W przy-
padku niepowodzenia RegisterClassW w Windows 98, GetLastError zwraca 120. Mo-
żesz sprawdzić w WINERROR.H, że wartość 120 odpowiada identyfikatorowi ER-
ROR CALL NOT IMPLEMENTED. Możesz też poszukać znaczenia błędu w /Plat-
form SDK/Windows Base Sermces/Debugging and Error Handling/Error Codes/System Errors
- Numerical Order.
Niektórzy programiści Windows lubią sprawdzać wartość zwracaną przez każdą
funkcję, aby upewnić się, czy nie wystąpił błąd. Na pewno ma to jakiś sens: jestem
pewny, że pamiętasz o zasadzie, by zawsze sprawdzać wystąpienie błędu, gdy
przydzielasz (alokujesz) pamięć. Ale wiele funkcji Windows alokuje jakąś pamięć.
Na przykład RegisterClass potrzebuje pamięci, aby przechować informację o klasie
okna. Powinieneś więc bezwzględnie sprawdzać tę funkcję. Ale z drugiej strony,
jeśli RegisterClass zawiedzie, ponieważ nie może zaalokować potrzebnej pamięci,
Windows prawdopodobnie już się zawiesił.
W przykładowych programach w tej książce wykonuję tylko minimalne sprawdza-
nie błędów. Robię tak nie dlatego, że nie doceniam idei sprawdzania błędów; po
prostu nie chcę zaciemniać tego, co programy powinny pokazywać.
Na koniec pewna uwaga dotycząca historii: w niektórych przykładowych pro-
54 Część I: Podstawy
gramach Windows możesz spotkać następujący kod w WinMain:
if (!hPrevInstance)
(
wndclass.cbStyle = CS HREDRAW þ CS VREDRAW ;
[inne inicjacje wndclass]
RegisterClass (&wndclass) ;
Dają tu o sobie znać stare upodobania. W 16-bitowej wersji Windows, gdy urucha-
miałeś nową kopię programu przykładowego, który już działał, parametr hPrevln-
stance w WinMain powinien być uchwytem realizacji poprzedniej kopii programu.
Aby zaoszczędzić pamięć, dwie lub więcej kopii mogły używać tej samej klasy okna.
Dlatego klasa okna była rejestrowana tylko wtedy, jeśli hPrevlnstance miało war-
tość NULL, wskazując, że żadne inne kopie programu nie działają.
W 32-bitowej wersji Windows hPrevlnstance zawsze ma wartość NULL. Ten kod bę-
dzie ciągle działał właściwie, ale sprawdzanie hPrevlnstance nie jest już konieczne.
Tworzenie okna
Klasa okna definiuje ogólną charakterystykę okna, pozwalając na wykorzystanie
jej do tworzenia wielu różnych okien. Gdy pójdziesz dalej i utworzysz okno,
wywołując CreateWindow, możesz podać bardziej szczegółową informację o oknie.
Początkujący programiści Windows czasami gubią się w rozróżnieniach między
klasą okna, oknem i tym, że wszystkich charakterystyk okna nie można określić
jednocześnie. W rzeczywistości dzielenie informacji w ten sposób jest całkiem wy-
godne. Na przykład wszystkie okna przycisków są tworzone na podstawie tej sa-
mej klasy okna. Procedura okna powiÄ…zana z tÄ… klasÄ… okna znajduje siÄ™ wewnÄ…trz
samego Windows. Jest odpowiedzialna za przetwarzanie informacji wejściowej
z klawiatury i myszy do przycisku oraz definiuje wyglÄ…d przycisku na ekranie.
Wszystkie przyciski działają pod tym względem tak samo. Ale nie wszystkie są
takie same. Niemal na pewno mają różne rozmiary, inne położenie na ekranie i różne
napisy. Te ostatnie cechy są częścią definicji okna, a nie definicji klasy okna.
Podczas gdy informacja przekazywana do funkcji RegisterClass jest określona
w strukturze danych, informacja przekazywana do funkcji CreateWindow jest okre-
ślana jako oddzielne argumenty funkcji. Oto pełne wywołanie CreateWindow
z HELLOWIN.C wraz z komentarzami identyfikującymi poszczególne pola:
hwnd = CreateWindow (szAppName, // nazwa klasy okna
TEXT ("The Nello Program")"// naglówek okna
WS_OVERLAPPEDWINDOW, // styl okna
CW_USEDEFAULT, // poczdtkowa pozycja x
CW_USEDEFAULT, // poczÄ…tkowa pozycja y
CW_USEDEFAULT, // poczdtkowy rozmiar x
CW_USEDEFAULT, // poczdtkowy rozmiar y
NULL, // uchwyt okna nadrzędnego
NULL, // uchwyt menu okna
hInstance, // uchwyt kopii programu
NULL) ; // parametry tworzenia
W tym miejscu nie będę się martwił, czy w rzeczywistości jest to funkcja Create-
Rozdział 3: Windows i komunikaty 55
WindowA czy też funkcja CreateWindowW; pierwsza traktowałaby dwa pierwsze
parametry jako ASCII, druga - jako urukod.
Argument oznaczony jako "nazwa klasy okna" to szAppName, zawierający łań-
cuch znaków "HelloWin"- nazwę klasy okna, którą program właśnie zarejestro-
wał. W ten sposób tworzone okno jest wiązane z klasą okna.
Okno tworzone przez ten program to zwykłe okno, które może nakładać się na
inne. Będzie miało: pasek tytuhx, przycisk menu systemowego z lewej strony pa-
ska tytuhz, grubÄ… ramkÄ™ ustalajÄ…cÄ… rozmiary okna i przyciski minimalizacji, mak-
symalizacji i zamknięcia z prawej strony paska tytułu. Jest to standardowy styl
okien i ma nazwę WS OVERLAPPEDWINDOW, która występuje jako parametr
"styl okna" w CreateWindow. Jeśli zajrzysz do WINUSER.H, zobaczysz, że ten styl
jest kombinacją kilku znaczników bitowych:
#define WS OVERLAPPEDWINDOW (WS_OVERLAPPED þ \
WS_CAPTION þ \
WS_SYSMENU þ \
WS_THICKFRAME þ \
WS_MINIMIZEBOX þ \
WS MAXIMIZEBOX)
"Nagłówek" to tekst, który pojawi się na pasku tytułu okna.
Argumenty oznaczone jako "poczÄ…tkowa pozycja x" i "poczÄ…tkowa pozycja y'
określają początkowe położenie lewego górnego rogu okna względem lewego
górnego rogu ekranu. Podstawiając za nie identyfikator CW USEDEFAULT zga-
dzamy się, aby Windows użył domyśinego położenia dla zasłoniętego okna.
(CW USEDEFAULT jest zdefiniowany jako 0x80000000). Domyślnie Windows
umieszcza tworzone okna ze skokowo zmienianym przesunięciem w lewo i w dół
w stosunku do lewego górnego rogu ekranu. Podobnie argumenty "początkowy
rozmiar x" i "początkowy rozmiar y' określają początkową szerokość i wysokość
okna. Identyfikator CW LTSEDEFAULT ponownie wskazuje, że chcemy, by Win-
dows użył domyślnego rozmiaru okna.
Argument oznaczony "uchwyt okna nadrzędnego" jest ustawiany na NULL, kiedy
tworzysz okno "najwyższego poziomu", takie jak okno aplikacji. Zwykle jeśli mię-
dzy dwoma oknami istnieje zależność okno nadrzędne - okno potomne, okno po-
tomne zawsze pojawia się na powierzchni okna nadrzędnego. Okno aplikacji uka-
zuje się na powierzchni okna pulpitu, ale nie potrzebujesz znajdować uchwytu
okna pulpitu przy wywoływaniu CreateWindow.
"Uchwyt menu okna" też jest ustawiony na NLTLL, ponieważ okno nie ma żadnego
menu. "Uchwyt kopii programu" jest ustawiony na uehwyt realizacji (kopii), prze-
kazywany do programu jako parametr WinMain. Końcowy wskaźnik "parametry
tworzenia" jest ustawiony na NULL. Możesz wykorzystać ten parametr do wskaza-
nia pewnych danych, do których chciaþyÅ› siÄ™ później odnieść w swoim programie.
Wywołanie CreateWindow zwraca uchwyt do tworzonego okna. Ten uchwyt zapi-
sujemy w zmiennej hwnd, która jest zdefiniowana jako zmienna typu HWND
("uchwyt okna"). Każde olaþo w Windows ma uchwyt. Twój program też używa
uchwytu przy odnoszeniu siÄ™ do okna. Wiele funkcji Windows wymaga jako ar-
gumentu hwnd, więc Windows wie, do.ktbrego okna stosuje się dana funkcja.
56 Część I: Podstawy
Jeśli program tworzy dużo okien, każde ma inny uchwyt. Uchwyt okna jest jed-
nym z najważniejszych uchwytów, którymi posługuje się program w Windows.
Wyświetlanie okna
Po powrocie z wywołania CreateWindow, okno jest tworzone wewnętrznie przez
Windows. Zasadniczo oznacza to, że Windows przydzielił blok pamięci do prze-
chowywania wszystkich informacji o oknie, które określiłeś w wywołaniu Create-
Window, oraz kilku innych informacji, które później odnajdzie na podstawie uchwytu
okna.
Jednak okno nie pojawia siÄ™ jeszcze na ekranie. Do tego potrzebne sÄ… jeszcze dwa
wywołania. Pierwsze to:
ShowWindow (hwnd, iCmdShow) ;
Pierwszy argument jest uchwytem okna właśnie utworzonego przez CreateWin-
dow. Drugi argument to wartość iCmdShow, przekazana jako parametr do Win-
Main. Określa ona, w jakiej formie okno pojawi się pierwszy raz na ekranie: jako
normalne, pomniejszone czy zmaksymalizowane. Użytkownik prawdopodobnie
określił to podczas dodawania programu do menu Start. Wartością, jaką otrzy-
masz od WinMain i przekażesz do ShowWindow, może być: SW SHOWNORMAL,
jeśli okno jest wyświetlane normalnie, SW SHOWMAXIMIZED, jeśli okno jest
zmaksymalizowane i SW SHOWMINNOACTIVE, jeśli okno jest wyświetlone
tylko na pasku zadań.
Funkcja ShowWindow umieszcza okno na ekranie. Jeśli drugim argumentem Show-
Window jest SW SHOWNORMAL, to obszar roboczy okna jest czyszczony pędz-
lem tła, określonym w klasie okna. Wywołanie funkcji:
UpdateWindow (hwnd) ;
powoduje wypełnienie przez program obszaru roboczego okna. Jest to realizo-
wane przez wysłanie do procedury okna (czyli funkcji WndProc w HELLOWIN.C)
komunikatu WM PAINT. Wkrótce zobaczymy, co WndProc robi z tym komuni-
katem.
Pętla komunikatów
Po wywołaniu UpdateWindow okno jest już widoczne na ekranie. Program musi
teraz być gotowy do odbierania informacji, które użytkownik wprowadza za po-
mocą klawiatury i myszy. Dla każdego programu aktualnie działającego w sys-
temie Windows obsługuje kolejkę komunikatów. Kiedy wystąpi zdarzenie wej-
ściowe, Windows tłumaczy je na komunikat, który umieszcza w kolejce komuni-
katów programu.
Program odzyskuje te komunikaty z kolejki komunikatów wykonując blok kodu,
nazywany pętlą komunikatów:
while (GetMessage (&msg, NULL, 0. 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
Rozdział 3: Windows i komunikaty 57
Zmienna msg jest strukturą typu MSG, zdefiniowaną następująco w pliku nagłów-
kowym WINUSER.H:
typedef struct tagMSG
(
HWND hwnd ;
UINT message ;
WPARAM wParam ;
LPARAM lParam ;
DWORD time ;
POINT pt ;
)
MSG, * PMSG ;
Typ danych POINT to jeszcze inna struktura, zdefiniowana w pliku nagłówko-
wym WINDEF.H:
typedef struct tagPOINT
(
LONG x ;
LONG y ;
1
POINT, * PPOINT;
Wywołanie GetMessage, które rozpoczyna pętlę komunikatów, powoduje pobra-
nie komunikatu z kolejki komunikatów:
GetMessage (&msg, NULL, 0, 0)
To wywołanie przekazuje do Windows wskaźnik do struktury MSG o nazwie msg.
Drugi, trzeci i czwarty argument są ustawiane na NULL albo 0, wskazując, że
program żąda wszystkich komunikatów wysyłanych do wszystkich okien przez
niego utworzonych. Windows wypełnia pola struktury komunikatu następnym
komunikatem z kolejki. Polami tej struktury sÄ…:
ł hwnd - uchwyt okna, do którego skierowany jest komunikat. W programie
HELLOWIN jest on równy wartości hwnd zwracanej przez CreateWindow, po-
nieważ program ma tylko jedno okno.
ł message - identyfikator komunikatu. Jest to liczba, która identyfikuje komuni-
kat. Dla każdego komunikatu istnieje odpowiedni identyfikator zdefiniowa-
ny w plikach nagłówkowych Windows (w większości w WINUSER.H), który
zaczyna siÄ™ przedrostkiem WM ("window message" - komunikat okna). Na
przykład jeśli umieścisz wskaźnik myszy w obszarze roboczym HELLOWIN
i naciśniesz lewy przycisk myszy, Windows umieści w kolejce komunikat z po-
lem message równym WM LBUTTONDOWN, co odpowiada wartości 0x0201.
ł wParam - 32-bitowy "parametr komunikatu", którego znaczenie i wartość za-
leżą od konkretnego komunikatu.
ł lParam - inny 32-bitowy parametr komunikatu, zależny od rodzaju komuni-
katu.
ł time - czas określający moment umieszczenia komunikatu w kolejce.
ł pt - współrzędne myszy w chwili umieszczania komunikatu w kolejce.
Jeżeli pole message komunikatu uzyskanego z kolejki komunikatów jest czymś
innym niż WM-QUIT (które jest równe 0x0012), to GetMessage zwraca wartość
niezerową. Komunikat WM-QUIT powoduje, że GetMessage zwraca 0.
58 Część I: Podstawy
Instrukcja:
TranslateMessage (&msg) ;
przekazuje strukturę msg z powrotem do Windows w celu pewnego przekształ-
cenia komunikatów klawiatury. (Szerzej omówię to w rozdziale 6). Instrukcja
DispatchMessage (&msg) ;
zwraca strukturę msg do Windows. Windows wysyła wtedy komunikat do wła-
ściwej procedury okna w celu przetwarzania. Oznacza to, że Windows wywołu-
je procedurÄ™ okna. W HELLOWIN tÄ… procedurÄ… okna jest WndProc. Po przetwo-
rzeniu komunikatu w WndProc sterowanie wraca do Windows, który ciągle jesz-
cze obsługuje wywołanie DispatchMessage. Kiedy Windows powróci do HELLO-
WIN po wywołaniu DispatchMessage, pętla komunikatów wykona następne wy-
wołanie funkcji GetMessage.
Procedura okna
Wszystko, co do tej pory opisywałem, to w rzeczywistości koszty ogólne. Klasa
okna została zarejestrowana, okno wyświetlone na ekranie, a program kręci się
w pętli komunikatów, by pobierać kolejne komunikaty z kolejki.
Prawdziwe działanie odbywa się w procedurze okna. To ona decyduje, co okno
wyświetla w obszarze roboczym i jak odpowiada na poczynania użytkownika.
W HELLOWIN procedurÄ… okna jest funkcja o nazwie WndProc. Procedura okna
może mieć dowolną nazwę (oczywiście o ile nie koliduje ona z innymi nazwa-
mi). Program Windows może zawierać więcej niż jedną procedurę okna. Proce-
dura okna jest zawsze powiązana z konkretną klasą okna, którą zarejestrowałeś,
wywohxjÄ…c RegisterClass. Funkcja CreateWindow tworzy okno na podstawie kon-
kretnej klasy okna. Na podstawie tej samej klasy okna można utworzyć więcej
niż jedno okno.
Procedura okna jest zawsze zdefiniowana w ten sposób:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam. LPARAM lParam)
Cztery parametry procedury okna sÄ… identyczne z pierwszymi czterema polami
struktury MSG. Pierwszy parametr to hwnd, uchwyt okna otrzymujÄ…cego komu-
nikat. Jest to ten sam uchwyt, który zwróciła funkcja CreateWindow. Dla progra-
mu takiego jak HELLOWIN, który tworzy tylko jedno okno, jest to jedyny uchwyt
okna, o którym wie program. Jeśli program tworzy wiele okien na podstawie tej
samej klasy okna (i stÄ…d tej samej procedury okna), hwnd identyfikuje konkretne
okno otrzymujÄ…ce komunikat.
Drugi parametr jest taki sam jak pole message w strukturze MSG. Jest to liczba iden-
tyfikujÄ…ca komunikat. Ostatnie dwa parametry to 32-bitowe parametry komunika-
tu, które dostarczają dodatkowych informacji o komunikacie. Ich znaczenie zależy
od typu komunika#u. Czasem parametr komunikatu stanowiÄ… dwie umieszczone
razem wartości 16-bitowe, a czasem wskaźnik do napisu lub struktury danych.
Zasadniczo programy nie wywołuj,ą bezpośrednio procedury okna. Procedura
okna jest niemal zawsze wywoływana przez Windows. Program może pośred-
nio wywołać swoją procedurę okna przez wywołanie funkcji o nazwie SendMes-
sage, którą omówimy w dalszych rozdziałach.
Rozdział 3: Windows i komunikaty 59
Przetwarzanie komunikatów
Każdy komunikat docierający do procedury okna jest identyfikowany przez licz-
bę, która stanowi dla niej parametr komunikatu. Plik nagłówkowy Windows WIN-
USER.H definiuje identyfikatory zaczynające się od przedrostka WM, dla każde-
go rodzaju komunikatu.
Zasadniczo programiści Windows używają konstrukcji switch i case do określe-
nia, jaki komunikat otrzymała procedura okna i jak go odpowiednio przetwo-
rzyć. Gdy procedura okna przetworzy komunikat, powinna zwrócić 0. Wszyst-
kie komunikaty, które procedura okno pozostawi nie przetworzone, muszą być
przekazane do funkcji Windows o nazwie Def 4VindowProc. Wartość zwracana przez
Def4VindowProc musi być też zwrócona przez procedurę okna.
W HELLOWIN procedura WndProc wybiera do przetwarzania tylko trzy komu-
nikaty: WM CREATE, WM PAINT i 4VM-DESTROY. Procedura okna jest zbu-
dowana następująco:
switch (iMsg)
(
case WM_CREATE :
[przetwarzanie komunikatu WM CREATE]
return 0 ;
case WM_PAINT :
[przetwarzanie komunikatu WM_PAINT]
return 0 :
case WM_DESTROY :
[przetwarzanie komunikatu WM_DESTROY]
return 0 ;
)
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
Ważne jest wywołanie DefWindowProc dla domyślnego przetwarzania wszystkich
komunikatów, którymi nie zajmuje się twoja procedura okna. W przeciwnym
przypadku nie wystąpią niektóre typowe działania, np. zakończenie programu.
Odtwarzanie pliku dźwiękowego
Pierwszym komunikatem, który odbiera procedura okna - i pierwszym, który
wybiera do przetwarzania-jest WM CREATE. WndProc otrzymuje ten komuni-
kat, gdy Windows przetwarza funkcję CreateWindow w WinMain. To znaczy, że
kiedy HELLOWIN wywołuje CreateWindow, Windows robi to, co musi robić, a na-
stępnie wywołuje WndProc z pierwszym argumentem ustawionym na uchwyt
okna, a drugim argumentem ustawionym na WMþCREATE (o wartoÅ›ci 1). Wnd-
Proc przetwarza komunikat WM CREATE i zwraca sterowanie z powrotem do
Windows. Windows wtedy może powrócić z wywołania CreateWindow do HEL-
LOWIN, by kontynuować przetwarzanie z WinMain.
Często procedura okna wykonuje jednorazową inicjację okna podczas przetwa-
rzania WM-CREATE. HELLOWIN, przetwarzajÄ…c ten komunikat, odtwarza plik
dźwiękowy o nazwie HELLOWIN.WAV. Robi to za pomocą prostej funkcji Play-
Sound, opisanej w /Platform SDK/Graphics and Multimedia Services/Multimedia Au-
dio/Waveform Audio i udokumentowanej w /Platform SDK/Graphics and Multime-
dia Services/Multimedia Reference/Multimedia Functions.
w
60 Część i: Podstawy
Pierwszym argumentem PlaySound jest nazwa pliku dźwiękowego. (Może to być
także nazwa aliasu dźwięku zdefiniowana w części Dźwięk w Panelu sterowa-
nia lub w zasobach programu). Drugi argument jest używany tylko wtedy, jeśli
pliki dźwiękowe stanowią zasób programu. Trzeci argument określa szereg opcji.
W tym przypadku wskazałem, że pierwszy argument jest nazwą pliku i że dźwięk
ma być grany asynchronicznie. Oznacza to, że powrót z funkcji PIaySound nastą-
pi zaraz po rozpoczęciu odtwarzania pliku, bez czekania na jego zakończenie.
W ten sposób program może kontynuować swoją inicjację.
WndProc, po zakoÅ„czeniu przetwarzania komunikatu WMþCREATE, zwraca 0.
Komunikat WM PAINT
Drugim komunikatem przetwarzanym przez WndProc jest WM PAINT. Ten ko-
munikat jest bardzo ważny w programowaniu Windows. Informuje program, że
część lub cały obszar roboczy okna stał się "nieważny" i musi być "zaktualizo-
wany" (odświeżony), co oznacza powtórne narysowanie lub "namalowanie".
W jaki sposób obszar roboczy staje się nieważny? Kiedy okno jest tworzone po
raz pierwszy, cały obszar roboczy jest nieważny, ponieważ program nie naryso-
waÅ‚ niczego w oknie. Pierwszy komunikat WMþPAINT (które zwykle wystÄ™pu-
je, gdy program wywołuje UpdateWindow w WinMain) poleca procedurze okna
narysować coś w obszarze roboczym.
Kiedy zmienisz wielkość okna HELLOWIN, obszar roboczy staje się nieważny.
Przypomnij sobie, że pole style w strukturze wndclass HELLOWIN ma ustawione
znaczniki CS_HREDRAW i CS_VREDRAW. Oznacza to dla Windows unieważ-
nienie całego okna, kiedy jego rozmiar ulega zmianie. Procedura okna otrzymuje
wtedy komunikat WM PAINT.
Jeśli zminimalizujesz HELLOWIN, a następnie przywrócisz oknu poprzednie
rozmiary, Windows nie zachowa zawartości obszaru roboczego. W środowisku
graficznym wymagałoby to zachowania dużej ilości danych. Zamiast tego Win-
dows unieważnia okno. Procedura okna otrzymuje komunikat WM PAINT
i przywraca zawartość okna.
Kiedy przesuniesz okna na ekranie tak, że będą na siebie zachodziły, Windows
nie zachowa obszaru zasłoniętego przez inne okno. Kiedy ten obszar zostanie
później odsłonięty, jest zaznaczany jako nieważny. Procedura okna otrzymuje
komunikat WM PAINT w celu odtworzenia zawartości okna.
Przetwarzanie 4VMþPAINT niemal zawsze zaczyna siÄ™ wywoÅ‚aniem BeginPaint:
hdc = BeginPaint (hwnd, &ps) ;
a kończy wywołaniem EndPaint:
EndPaint (hwnd, &ps) ;
W obu przypadkach pierwszy argument jest uchwytem okna programu, a drugi
wskaź,nikiem do struktury typu PAINTSTRUCT. Struktura ta zawiera pewne in-
formacje, które procedura okna może wykorzystać przy malowaniu obszaru ro-
boczego. Omówię pola tej struktury w następnym rozdziale, a teraz będziemy
po prostu wykorzystywać je w funkcjach BeginPaint i EndPaint.
Rozdział 3: Windows i komunikaty 61
Podczas wywołania BeginPaint Windows czyści tło obszaru roboczego, jeśli nie
zostało to jeszcze zrobione. W tym celu wykorzystuje pędzel określony w polu
hbrBackground struktury WNDCLASS, używanej do rejestracji klasy okna. W przy-
padku HELLOWIN, jest to standardowy biały pędzel, co oznacza, że Windows
wyciera tło okna, malując je na biało. Wywołanie BeginPaint zatwierdza cały ob-
szar roboczy i zwraca uchwyt kontekstu urzÄ…dzenia (ang. handle to a device con-
text). Kontekst urządzenia odnosi się do fizycznego urządzenia wyjściowego (jak
ekran) i jego programu obsługi. Uchwyt kontekstu urządzenia jest ci potrzebny,
aby wyświetlić tekst i grafikę w obszarze roboczym okna. Używając uchwytu
kontekstu urządzenia zwróconego przez BeginPaint, nie możesz rysować poza
obszarem roboczym, choc'byś nie wiem jak próbował. Wywołanie EndPaint zwal-
nia uchwyt kontekstu urządzenia i nie można z niego więcej korzystać.
Jeśli procedura okna nie przetwarza komunikatu WM_PAINT (co zdarza się bar-
dzo rzadko), to komunikat ten musi być przekazany do Def lNindowProc. Def 4Vin-
dowProc po prostu wywołuje BeginPaint i EndPaint, w następstwie czego obszar
roboczy jest zatwierdzany.
WndProc po zakończeniu wywołania BeginPaint wywołuje z kolei GetClient-Rect:
GetClientRect (hwnd, &rect) ;
Pierwszy argument jest uchwytem okna programu. Drugi argument jest wskaź-
nikiem do struktury prostokÄ…ta typu RECT. Ta struktura zawiera cztery pola
LONG o nazwach left, top, right i bottom. Funkcja GetClientRect ustawia w tych
czterech polach rozmiary obszaru roboczego okna. Pola left i top sÄ… zawsze usta-
wiane na 0. W związku z tym pola right i bottom określają w pikselach odpowied-
nio szerokość i wysokość obszaru roboczego.
WndProc nie analizuje struktury RECT, przekazuje tylko wskaźnik do niej w czwar-
tym argumencie DrawText:
DrawText (hdc. TEXT ("Hello, Windows 98!"), -1, &rect,
DT SINGLELINE þ DT CENTER þ DT VCENTER) ;
DrawText - jak wskazuje nazwa - rysuje napisy. W zwiÄ…zku z tym pierwszym
argumentem jest uchwyt kontekstu urządzenia, zwrócony przez BeginPaint. Dru-
gim argumentem jest tekst do narysowania, a trzeci argument ma wartość -1, co
oznacza, że łańcuch znaków tekstu jest zakończony znakiem zerowym.
Ostatnim argumentem DrawText jest szereg znaczników bitowych zdefiniowanych
w WINUSER.H. (Ponieważ DrawText wyświetla dane wyjściowe, to wydaje się
być funkcją GDI, w rzeczywistości jest częścią modułu User, gdyż jest funkcją ry-
sującą dość wysokiego poziomu. Funkcja ta jest udokumentowana w /Platform
SDK/Graphics and Multimedia Services/GDI/Fonts and Text). Znaczniki wskazujÄ…,
że tekst powinien być wyświetlony jako pojedyncza linia wypośrodkowana w pio-
" nie i w poziomie wewnątrz prostokąta określonego przez czwarty argument. Wy-
wołanie tej funkcji powoduje wyświetlenie napisu "Hello, Windows 98!" na środku
obszaru roboczego.
Jeśli obszar roboczy staje się nieważny ( jak przy zmianie rozmiarów okna), Wnd-
Proc otrzymuje nowy komunikat WM-PAINT. WndProc uzyskuje aktualny roz-
miar okna, wywołując GetClientRect, i wyświetla ponownie tekst na środku okna.
62 Część I: Podstawy
Komunikat WM DESTROY
Komunikat WM DESTROY także jest ważnym komunikatem. Oznacza on, że
Windows niszczy okno na polecenie użytkownika. Komunikat ten jest wynikiem
kliknięcia przez użytkownika przycisku Zamknij albo wybrania Zamknij z menu
systemowego programu. (W dalszej części rozdziału dokładniej omówię genero-
wanie komunikatu WM DESTROY przez system).
HELLOWIN odpowiada na komunikat WM DESTROY standardowo:
PostOuitMessage (0) ;
Funkcja ta wstawia komunikat WM QUIT do kolejki komunikatów programu.
Wspominałem wcześniej, że GetMessage zwraca wartość różną od zera dla każde-
go komunikatu innego niż WM-QUIT, który pobiera z kolejki komunikatów. Gdy
GetMessage pobierze komunikat WMþQUIT, zwróci 0. Spowoduje to, że WinMain
opuści pętlę komunikatów. Wtedy program wykona następującą instrukcję:
return msg.wParam ;
Pole wParam struktury jest wartością przekazaną funkcji PostQuitMessage (zazwy-
czaj 0). Instrukcja return powoduje wyjście z WinMain i zakończenie programu.
Przeszkody w programowaniu windowsowym
Nawet z moimi objaśnieniami kodu HELLOWIN, struktura i działanie progra-
mu prawdopodobnie są nadal dość tajemnicze. W krótkim programie C, napisa-
nym dla trybu znakowego, cały program mógł być zawarty w funkcji main. Funkcja
WinMain w HELLOWIN zawiera tylko część programu odpowiedzialną za zare-
jestrowanie klasy okna, utworzenie okna, pobieranie i przesyłanie komunikatów
z kolejki komunikatów
Właściwa część programu zawarta jest w procedurze okna. W HELLOWIN nie
wykonuje ona zbyt wiele - WndProc po prostu odgrywa plik dźwiękowy i wy-
świetla napis w oknie. Ale w dalszych rozdziałach dowiesz się, że prawie wszyst-
ko, co robi program windowsowy, jest odpowiedziÄ… na komunikaty przychodzÄ…-
ce do procedury okna. Musisz się do tego przyzwyczaić, zanim zaczniesz pisać
programy dla Windows.
Nie wywołuj mnie, ja wywołam ciebie
Programiści są dobrze obeznani z ideą wywoływania funkcji systemu operacyj-
nego w celu zrobienia czegoś. Na przykład programiści C używają funkcji fopen
do otwarcia pliku. Funkcja ta jest zrealizowana za pomocą wywołania funkcji sys-
temu operacyjnego, która otwiera plik. Nie ma problemu.
Ale Windows jest inny. Chociaż posiada kilka tysięcy wywołań funkcji, wywohx-
je również funkcje z twojego programu, zwłaszcza procedurę okna, którą nazwa-
liśmy WndProc. Procedura okna jest powiązana z klasą okna, którą program za-
rejestrował wywołując RegisterClass. Okno, utworzone na podstawie danej klasy
okna, używa procedury do przetwarzania wszystkich komunikatów skierowa-
nych do niego. Windows wysyła komunikat do okna przez wywołanie procedu-
ry okna.
-
Rozdział 3: Windows i komunikaty 63
Windows wywołuje WndProc, gdy okno jest tworzone pierwszy raz. Windows
wywołuje WndProc, gdy okno jest ostatecznie niszczone. Windows wywołuje
WndProc, gdy okno zmienia rozmiary, jest przesuwane lub minimalizowane.
Windows wywołuje WndProc, gdy użytkownik klika przyciskiem myszy w ob-
szarze okna. Windows wywołuje WndProc, gdy znaki są wprowadzane z klawia-
tury. Windows wywołuje WndProc, gdy pozycja zostanie wybrana z menu. Win-
dows wywołuje WndProc w trakcie korzystania z paska przewijania. Windows
wywołuje WndProc, aby oznajmić, że trzeba odświeżyć obszar roboczy okna.
Wszystkie te wywołania WndProc mają formę komunikatów. Główna część więk-
szości programów windowsowych zajmuje się ich obsługą. Komunikaty, które
Windows wysyła do programu, są zazwyczaj identyfikowane przez nazwy, któ-
re zaczynają się przedrostkiem WM i są zdefiniowane w pliku nagłówkowym
WINUSER.H.
W rzeczywistości idea procedury wewnątrz programu, która jest wywoływana
spoza programu, nie jest całkiem nieznana w programowaniu w trybie znako-
wym. Funkcja signal w C może przechwycić przerwanie od [Ctrl+C] lub inne
przerwania systemu operacyjnego. Stare programy, pisane dla MS-DOS-a, często
przechwytywały przerwania sprzętowe.
Ale w Windows ta koncepcja rozciąga się na wszystko. Wszystko, co może dziać
siÄ™ z oknem, jest przekazywane do procedury okna w formie komunikatu. Proce-
dura okna odpowiada w pewien sposób na ten komunikat lub przekazuje go do
DefWindowProc w celu domyślnego przetworzenia.
Parametry procedury okna, wParam i lParam, nie są używane w HELLOWIN, lecz
tylko'przekazywane do DefWindowProc. Te parametry dostarczajÄ… procedurze okna
dodatkowych informacji o komunikacie. Ich znaczenie zależy od komunikatu.
Popatrzmy na przykład. Ilekroć obszar roboczy okna zmienia rozmiary, Windows
wywołuje procedurę okna dla tego okna. Parametr hwnd procedury okna jest uchwy-
tem okna zmieniającego rozmiary. (Pamiętaj, że jedna procedura okna może obsługi-
wać komunikaty dla wielu okien, które zostały utworzone na podstawie tej samej kla-
sy okna. Parametr hzvnd mówi procedurze okna, które okno otrzymuje komunikat).
Parametr message wynosi WMþSIZE. Parametr wParam dla komunikatu WMþSlZE ma
wartość SIZEþRESTOREI7, SIZEþMINávffZED, SIZEþMA?mvIIZED, SlZEþMAXSHOW
lub SIZEþMA7þþlDE (zdefiniowane w pliku nagłówkowym WIIþlUSER.H jako liczby
od 0 do 4). Oznacza to, że parametr wParam wskazuje, ezy okno wróciło do poprzed-
niej wielkości, zostało zininimalizowane, zmaksymalizowane albo ukryte.
Parametr lParam podaje riowy rozmiar okna. Nowa szerokość (wartość 16-bito-
wa) i nowa wysokość (wartość 16-bitowa) są połączone razem w 32-bitowy 1Pa-
ram. Plik nagłówkowy WINDERH definiuje kilka poręcznych makr, które pomo-
gą wyłuskać te dwie wartości z lParam. Zrobimy to w następnym rozdziale.
Czasem komunikaty generujÄ… inne komunikaty jako wynik przetwarzania Def 4Vin-
dowProc. Załóżmy, że uruchomiłeś HELLOWIN i kliknąłeś przycisk Zamknij lub
wybraÅ‚eÅ› Zamknij z menu systemowego, używajÄ…c klawiatury lub myszy. DeþVin-
dowProc przetworzy te dane wejściowe. Gdy wykryje, że wybrałeś opcję Zamknij,
wyśle komunikat WM SYSCOMMAND do procedury okna. WndProc przekaże
ten komunikat do DefWindowProc. DefWindowProc odpowie przez wysłanie ko-
w-w
64 Część I: Podstawy
munikatu WM-CLOSE do procedury okna. WndProc ponownie przekaże ten ko-
munikat do Def 4VindowProc. DefTNindowProc odpowie na komunikat WM-CLOSE
wywołaniem DestroyWindow. DestroyWindow spowoduje, że Windows wyśle ko-
munikat WM DESTROY do procedury okna. W końcu WndProc odpowie na ten
komunikat wywołaniem PostQuitMessage; wówczas w kolejce komunikatów zo-
stanie umieszczony komunikat WM-QUIT. Ten komunikat powoduje zakończe-
nie pętli komunikatów w WinMain i zamknięcie programu.
Komunikaty kolejkowane i niekolejkowane
Mówiłem o wysyłaniu przez Windows komunikatu do okna, co oznacza, że Win-
dows wywołuje procedurę okna. Ale program Windows ma także pętlę komuni-
katów, która pobiera komunikaty z kolejki komunikatów przez wywołanie Get-
Message i przekazuje do procedury okna przez wywołanie DispatchMessage.
Tak więc, czy Windows odpytuje kolejkę w celu pobrania wiadomości (tak jak
zwykły program trybu znakowego pobiera dane z klawiatury), a następnie prze-
kazuje te komunikaty do jakiegoś miejsca programu? Czy też otrzymuje komuni-
katy bezpośrednio spoza programu? I tak, i tak.
Komunikaty mogą być kolejkowane lub niekolejkowane. Komunikaty kolejkowane
to te, które Windows umieszcza w kolejce komunikatów programu. W pętli ko-
munikatów programu są one pobierane i przekazywane do procedury okna. Ko-
munikaty niekolejkowane są wynikiem bezpośredniego wywoływania procedu-
ry okna przez Windows. Mówi się, że komunikaty kolejkowane są "wstawiane"
do kolejki komunikatów, a komunikaty niekolejkowane są "wysyłane" do proce-
dury okna. Procedura okna otrzymuje wszystkie komunikaty - kolejkowane i nie-
kolejkowane - przychodzÄ…ce do okna. Procedura okna jest "centrum komunika-
tów" okna.
Komunikaty kolejkowane to głównie te, które przychodzą od użytkownika w
formie: naciśnięć klawiszy (komunikaty, takie jak: WM KEYDOWN i WM KEY-
UP), znaków będących rezultatem naciśnięć klawiszy (WM-CHAR), ruchu my-
szy (WMþMOUSEMOVE) i kliknięć przycisków myszy (WM LBUTTONDOWN).
Komunikaty kolejkowane zawierają także komunikat zegara (WM TIMER), ko-
munikat odświeżania (WM-PAINT) i komunikat wyjścia (WM QUIT).
Komunikaty niekolejkowane to wszystkie pozostałe. Są one najczęściej rezulta-
tem wywoływania pewnych funkcji Windows. Na przykład, gdy WinMain wy-
woła CreateWindow, Windows tworzy okno i podczas tego procesu wysyła proce-
durze okna komunikat WM-CREATE. Gdy WinMain wywoła funkcję ShowWin-
dow, Windows wysyła procedurze okna komunikaty WM SIZE i 4VM SHOW-
WINDOW. Gdy WinMain woła UpdateWindow, Windows wysyła procedurze okna
komunikat WM PAINT. Komunikaty kolejkowane, oznaczające wejście z myszy
lub klawiatury, mogą zaowocować także komunikatami niekolejkowanymi. Na
przykład, gdy wybierzesz pozycję menu używając myszy albo klawiatury, ko-
munikat myszy albo klawiatury jest kolejkowany, ale ostateczny komunikat
WM COMMAND, oznaczający, że wybrano pozycję menu, jest niekolejkowany.
Ten proces jest dość złożony, ale na szczęście większość komplikacji to problem
Windows, a nie naszego programu. Z perspektywy procedury okna komunikaty
Rozdział 3: Windows i komunikaty 65
te przychodzą w sposób uporządkowany i zsynchronizowany. Procedura okna
może zrobić coś z tymi komunikatami albo je zignorować.
Gdy mówię, że komunikaty przychodzą w uporządkowany i zsynchronizowany
sposób, mam na myśli, że komunikaty te nie zachowują się jak przerwania sprzę-
towe. Podczas przetwarzania jednego komunikatu w procedurze okna program
nie zostanie nagle przerywany przez przyjście innego komunikatu.
Pomimo że programy Windows mogą mieć wiele wątków wykonania, kolejka
komunikatów każdego wątku obsługuje tylko komunikaty dla okien, których
procedury okien są wykonywane w tym wątku. Innymi słowy, pętla komunika-
tów i procedura okna nie działają jednocześnie. Gdy pętla komuńikatów pobie-
rze komunikat ze swojej kolejki komunikatów i wywoła funkcję DispatchMessage,
aby przesłać komunikat do procedury okna, to powrót z DispatchMessage nie na-
stąpi, dopóki procedura okna nie zwróci kontroli z powrotem do Windows.
Jednak procedura okna może wywołać funkcję, która wyśle jej inny komunikat,
a wtedy będzie musiała zakończyć przetwarzanie drugiego komunikatu, zanim
ta funkcja zwróci sterowanie i procedura okna zacznie przetwarzać oryginalny
komunikat. Na przykład, kiedy procedura okna wywołuje UpdateWindow, Win-
dows wywołuje procedurę okna z komunikatem WM PAINT. Kiedy procedura
okna zakończy przetwarzanie komunikatu WM PAINT, wywołanie UpdateWin-
dow powinno zwrócić sterowanie z powrotem do procedury okna.
Oznacza to, że procedura okna musi być wielowejściowa. W większości przypad-
ków nie stanowi to problemu, ale powinieneś być tego świadomy. Na przykład
załóżmy, że ustawiłeś zmienną statyczną w procedurze okna w czasie przetwa-
rzania komunikatu i wtedy wywołałeś funkcję Windows. Czy jesteś przekonany,
że po powrocie z tej funkcji zmienna ma ciągle taką samą wartość? Nie zawsze -
nie, jeśli konkretna funkcja Windows, którą wywołałeś, utworzyła inny komuni-
kat i procedura okna zmieniła wartość zmiennej podczas przetwarzania drugie-
go komunikatu. To jest powodem, dla którego różne rodzaje optymalizacji kom-
pilatora muszą być wyłączane podczas kompilacji programów windowsowych.
W wielu przypadkach procedura okna musi zachowywać informację, którą otrzy-
mała w jednym komunikacie i użyć jej przy przetwarzaniu innego komunikatu.
Ta informacja musi być zachowana w zmiennej zdefiniowanej jako static w pro-
cedurze okna lub w zmiennych globalnych.
Oczywiście, będziesz to lepiej rozumiał po przeczytaniu dalszych rozdziałów, w któ-
rych procedury okna zostaną rozszerzone o przetwarzanie dalszych komunikatów.
Szybkie wejście i wyjście
Windows 98 i Windows NT to środowiska wielozadaniowe z wywłaszczaniem.
Oznacza to, że gdy jeden program działa zbyt długo, Windows pozwala użyt-
kownikowi przekazać sterowanie innemu programowi. Jest to cenna właściwość,
będąca główną zaletą obecnej wersji Windoivs w stosunku do dawnej wersji 16-
bitowej.
Jednak na skutek budowy Windows ta wielozadaniowość z wywłaszczaniem nie
zawsze działa tak, jakbyśmy tego oczekiwali. Załóżmy, że twój program prze-
66 Część I: Podstawy
twarza konkretny komunikat w ciągu minuty lub dwóch. Użytkownik może prze-
łączyć się do innego programu, ale nie może nic zrobić z twoim programem. Nie
może przesunąć okna twojego programu, zmienić jego rozmiarów, zminimalizo-
wać lub zamknąć - zupełnie ruc nie może zrobić. Dzieje się tak, ponieważ twoja
procedura okna jest odpowiedzialna za te funkcje, a przecież teraz jest zajęta
wykonywaniem długotrwałej pracy. Można tego nie zauważyć, jeśli procedura
okna wykonuje własne operacje przesuwania i zmiany rozmiarów, ale tak się dzieje
w rzeczywistości. Pracę tę wykonuje Def4VindowProc, która musi się znaleźć w
twojej procedurze okna.
Jeżeli twój program musi wykonać długotrwałą pracę przy przetwarzaniu kon-
kretnego komunikatu, istnieją lepsze sposoby wykonania tego, które opiszę w roz-
dziale 20. Nawet w systemie wielozadaniowym z wywłaszczaniem nie powinno
się pozostawiać "zamrożonego" okna na ekranie, ponieważ przeszkadza to użyt-
kownikom. Jest to tak dokuczliwe jak błędy, nietypowe zachowanie i niepełne
pliki pomocy. Umożliwiaj użytkownikowi przerywanie i szybki powrót ze wszyst-
kich komunikatów.


Wyszukiwarka

Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold20
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook

więcej podobnych podstron