Programowniae windows petzold Petzold08


Rozdział 7
Mysz jest urządzeniem wskazującym. Ma co najmniej jeden przycisk (zazwyczaj
dwa lub trzy). Wyszła zwycięsko z konfrontacji z innymi wejściowymi urządze-
niami wskazującymi, takimi jak ekrany dotykowe czy pióra świetlne. Mysz wraz
ze swoimi odmianami, takimi jak trackball zwykle stosowany w komputerach
przenośnych, stała się jedynym urządzeniem wejściowym, któremu udało się na
dobre zadomowić na rynku komputerów PC.
Jednak na samym początku perspektywy myszy wcale nie wyglądały zbyt dobrze.
Twórcy wczesnych wersji systemu Windows czuli, że nie mogą wymagać od użyt-
kowników kupienia nowego urządzenia po to tylko, aby mogli oni korzystać z ich
produktu. Dlatego też uznali mysz za urządzenie dodatkowe i umożliwili wyko-
nywanie wszelkich operacji za pomocą klawiatury. (Jeżeli na przykład przeczytasz
teksty podpowiedzi dotyczących Kalkulatora, zorientujesz się, w jaki sposób po-
szczególnym jego klawiszom i funkcjom przypisane zostały odpowiedniki z kla-
wiatury). Niezależru dostawcy oprogramowania również zachowali się w ten sam
sposób: każda z funkcji ich pro,gramów mogła zostać wykonana z klawiatury. Wcze-
śniejsze wydania tej książki również propagowały podobne podejście.
Teoretycznie, najnowsze wersje systemu Windows wymagają obecności myszy.
A przynajmniej taka informacja widnieje na opakowaniu. Możesz jednak odłą-
czyć mysz od komputera, a system i tak wystartuje poprawnie (pojawi się jedy-
nie komunikat, że mysz nie została podłączona). Jednak korzystanie z systemu
bez pomocy myszy przypomina granie na pianinie palcami nóg (przynajmniej
na początku), nic nie stoi jednak na przeszkodzie, abyś w ten sposób pracował.
Dlatego też ciągle jestem zwolennikiem umieszczania w programach odpowied-
ników klawiaturowych wszystkich funkcji dostępnych za pośrednictwem myszy.
Uźytkownicy, którzy szczególnie sprawnie poshxgują się klawiaturą, zwykle nie
lubią zbyt często odrywać od niej rąk. Przypuszczam również, że każdemu z nas
wielokrotnie zdarzyło się już "zgubić" kursor myszy na zagraconym biurku lub
mieć zdecydowanie zbyt mało miejsca na podkładce, aby sprawnie operować
myszą. Dodanie do programu możliwości uruchamiania funkcji programu za
pomocą klawiatury nie wymaga zbyt dużo wysiłku, a może znacznie ułatwić życie
tym użytkownikom, którzy wolą ten sposób komunikacji z komputerem.
Podczas gdy klawiatura wykorzystywana jest zwykle do wprowadzania i maru-
pulowania danymi, mysz pozwala na łatwe rysowanie oraz przetwarzanie obiek-
tów graficznych. Dlatego też większość programów przykładowych, które znaj-
dziesz w tym rozdziale, ma coś wspólnego z grafiką. Będziesz przy tym korzy-
stał w nich z tego, czego nauczyłeś się w rozdziale 5.
254 Część I Podstawy
Podstawy
W Windows 98 możesz posługiwać się myszą z jednym, dwoma lub trzema przy-
ciskami. Zamiast myszą możesz również poshxgiwać się joystickiem lub piórem
świetlnym. We wcześniejszych wersjach Windows, aby nie dyskryminować tych,
którzy mieli mysz z jednym przyciskiem, unikano posługiwania się drugim i trze-
cim. Jednakże wraz z upływem czasu obecność drugiego przycisku stała się stan-
dardem, nie ma więc już żadnej podstawy do jego unikania. Obecnie standardo-
wo umożliwia on wyświetlenie na ekranie "menu kontekstowego". Pojawia się
ono w oknie poza standardowym paskiem menu lub w związku z przeciąganiem
(którym zajmiemy się wkrótce). Jednakże programy nie powinny zakładać, że
drugi przycisk myszy będzie istniał w każdej sytuacji.
Teoretycznie możesz sprawdzić, czy mysz jest obecna, posługując się znajomą już
funkcją GetSystemMetrics:
fMouse = GetSystemMetrics (SM MOUSEPRESENT) ;
Zmienna lVlouse przyjmie wartość TRUE (niezerową), jeżeli mysz została zain-
stalowana, natomiast 0 - w przeciwnym wypadku. Jednak w Windows 98 funk-
cja ta zawsze zwraca wartość TRUE, niezależnie od tego, czy mysz jest podłączo-
na, czy nie. W Windows NT funkcja działa poprawnie.
Jeżeli chcesz się dowiedzieć, ile przycisków ma mysz, wykonaj:
cButtons = GetSystemMetrics (SM CMOUSEBUTTONS) ;
Również ta funkcja powinna zwrócić wartość 0, jeżeli mysz nie została zainstalo-
wana. Jednak w Windows 98 zostanie w tej sytuacji zwrócona wartość 2.
Ci z użytkowników, którzy posługują się lewą ręką, mogą zmienić funkcje przy-
pisane poszczególnym przyciskom myszy, korzystając z możliwości apletu Mysz
znajdującego się w Panelu sterowania. Mimo że aplikacja może sprawdzić, czy
przyciski zostały przełączone, wywołując funkcję GetSystemMetrics z parametrem
SM SWAPBUTTON, tak naprawdę nie jest to konieczne. Przycisk, który użytkow-
nik naciska palcem wskazującym, zawsze traktowany jest jako lewy nawet, jeżeli
fizycznie znajduje się po prawej stronie myszy. Jeżeli jednak piszesz program śle-
dzący wykorzystywanie myszy, konieczne może okazać się narysowanie na ekra-
nie jej obrazu i w tej sytuacji informacja o przełączeniu przycisków może okazać
się potrzebna.
Użytkownik może również zmieniać parametry pracy myszy posługując się aple-
tem Mysz znajdującym się w Panelu sterowariia. Z poziomu aplikacji ten sam efekt
możesz osiągnąć, wywołując funkcję SystemParameterslnfo.
Kilka szybkich definicji
Gdy użytkownik w Windows poruszy myszą, system przemieści wyświetlaną na
ekranie niewielką bitmapę. Nosi ona nazwę kursora myszy. W jego obrębie znaj-
duje się ostrze (ang. hot spot). Od tej pory, zawsze kiedy będę mówił o położeniu
kursora myszy na ekranie, będę miał na myśli położenie tego punktu.
Windows pozwala programom posługiwać się kilkoma predefiniowanymi kur-
sorami myszy. Najbardziej typowym jest pochylona strzałka o nazwie IDC AR-
Rozdział 7: Mysz 255
ROW (identyfikator ten, podobnie jak i pozostałe, zdefiniowany został w pliku
WINLTSER.H). Punkt aktywny umieszczony został na końcu jej grota. Kursor
IDC CROSS (posłużymy się nim w zaprezentowanym dalej programie BLOK-
OUT) ma punkt aktywny w miejscu przecięcia prostopadłych linii. Z kolei kur-
sor IDC WAIT ma kształt klepsydry; jest zwykle wykorzystywany przez program,
gdy chce on poinformować użytkownika, że jest czymś zajęty. Programiści mogą
również definiować swoje własne kursory. Jak to zrobić, dowiesz się w rozdziale
10. Domyślny kursor okna deklarowany jest w strukturze klasy okna:
wndclass.hCwrsor = LoadCursor (NULL, IDC ARROW) :
A oto terminy określające czynności, które możesz wykonać za pomocą myszy:
ł Kliknięcie - naciskanie i zwalnianie przycisku myszy.
ł Dwukrotne kliknięcie - dwukrotne naciśnięcie i zwolnienie przycisku myszy
w krótkim odstępie czasu.
ł Ciągnięcie - poruszanie myszą z wciśniętym jednym z przycisków.
W przypadku myszy z trzema przyciskami, przyciski noszą nazwę lewy, środko-
wy oraz prawy. Związane z nimi identyfikatory, zdefiniowane w pliku nagłówko-
wym, to LBUTTON, MBUTTON oraz RBUTTON. Mysz dwuprzyciskowa posiada
jedynie przycisk lewy i prawy, natomiast jednoprzyciskowa - wyłącznie lewy.
Komunikaty myszy dla obszaru roboczego
W poprzednim rozdziale zobaczyłeś, w jaki sposób system Windows wysyła ko-
munikaty klawiaturowe wyłącznie do okna, które ma fokus wejściowy. Komuni-
katy myszy zachowują się odmiennie: procedura okna otrzymuje takie komuni-
katy za każdym razem, gdy kursor myszy znajdzie się nad obszarem okna lub
gdy zostanie w nim kliknięty którykolwiek z przycisków. Dzieje się to nawet
wtedy, gdy okno nie jest aktywne ani nie ma fokusu. W systemie Windows zde-
finiowano 21 komunikatów związanych z myszą. Jednak 11 z nich nie dotyczy
obszaru roboczego. Noszą one nazwę "komunikatów nie związanych z obsza-
rem roboczym" (ang. nonclient-area messages), a aplikacje Windows zwykle je igno-
rują.
Gdy kursor myszy przesuwa się nad obszarem roboczym, do procedury okna wy-
syłany jest komunikat WM MOUSEMOVE. Jeżeli we wnętrzu obszaru robocze-
go zostanie naciśnięty lub zwolniony przycisk myszy, procedura okna otrzyma
jeden z komunikatów przedstawioych w poniższej tabeli:
Przycisk Naciśnięty Zwolniony Naciśnięty (drugie
kliknięcie)
Lewy WM LBUTTONDOWN 4VMLBUTTONUP WM LBUTTONDBLCLK
Środkowy WMMBUTTONDOWN WM MBUTTONUP WM MBUTTONDBLCLK
Prawv WMRBUTTONDOWN WM RBUTTONUP WMRBUTTONDBLCLK
2 Część i: Podstawy
i
Procedura okna otrzyma komunikaty MBUTTON tylko wtedy, gdy wykorzysty-
wana jest mysz z trzema przyciskami, natomiast RBUTTON - gdy wykorzysty-
wana jest mysz z dwoma przyciskami. Do procedury okna zostanie wysłany ko-
munikat DBLCLK (podwójne kliknięcie) tylko wtedy, gdy klasa okna została zde-
finiowana w sposób umożliwiający ich odbieranie (zobacz podrozdział "Kliknię-
cia dwukrotne").
Do wszystkich wymienionych wyżej komunikatów pozycja bieżąca myszy jest
przekazywana za pomocą parametrtz lParam. Młodsze jego słowo zawiera współ-
rzędną x, natomiast starsze - współrzędną y, przy czym punktem odniesienia dla
obu jest lewy górny róg ekranu. Obie wartości możesz pobrać, posługując się ma-
krami LOWORD oraz HIWORD:
x g LOWORD flParam) ;
y = HIWORD (lParam) ;
Wartość parametru wParam zawiera informacje o stanie przycisków myszy oraz
klawiszy [Shift] i [Ctrl]. Stan poszczególnych bitów tego parametru możesz spraw-
dzić, korzystając z makr zdefiniowanych w pliku nagłówkowym WINUSER.H.
Prefiks "MK" pochodzi od mouse key (przycisk myszy}.
MICLBUTTON Naciśnięty jest lewy przycisk myszy
MK MBUTfON Naciśnięty jest środkowy przycisk myszy
MIfRBUTTON Naciśnięty jest prawy przycisk myszy
MK SHIFT Naciśnięty jest klawisz [Shiftl
MK CONTROL Naciśnięty jest klawisz [CtrII
Na przykład, jeżeli odbierzesz komunikat WM LBUTTONDOWN, a
wparam & MKSHIFT
ma wartość TRUE, oznacza to, że gdy użytkownik przycisnął lewy przycisk my-
szy, naciśnięty był również klawisz [Shift].
W trakcie przesuwania kursora myszy nad obszarem roboczym okna, system Win-
dows nie generuje komunikatu WMMOUSEMOVE dla każdego możliwego po-
łożenia kursora myszy. To, ile twój program odbierze komunikatów WM MO-
USEMOVE, zależy od elektroniki samej myszy oraz od prędkości, z jaką proce-
dura okna jest w stanie przetwarzać komunikaty związane z myszą. Innymi sło-
wy, Windows nie zapełnia kolejki nie przetworzonymi komunikatami WM MO-
USEMOVE. O tym, jaka jest rzeczywista częstotliwość pojawiania się komunika-
tów, przekonasz się po uruchomieniu programu CONNECT opisanego poniżej.
Jeżeli klikniesz lewym przyciskiem myszy w obszarze roboczym okna, które nie
jest aktywne, Windows uaktywni je, a następnie prześle do jego procedury komu-
nikat WMLBUTTONDOWN. Dlatego też za każdym razem, gdy twój program
odbierze komunikat WMLBUTTONDOWN, możesz śmiało założyć, że jego okno
jest aktywne. Może się jednak tak zdarzyć, że do okna zostanie wysłany komuni-
kat WMLBUTTONUP, aIe nie zostanie poprzedzony komunikatem WM LBUT
TONDOWN. Stanie się tak, gdy Iewy przycisk myszy zostanie naciśnięty nad jed-
nym oknem, a zwolniony nad innym. Podobnie, procedura okna może odebrać ko-
munikat tNM-LBUTTONDOWN bez odpowiadającego mu komunikatu
257
R^zdział 7: Mysz
WMLBUTTONUP, jeżeli przycisk zostanie zwolniony po przesunięciu myszy do
innego okno. '
Są dwa odstępstwa od tej reguły:
ł Procedura okna może "przechwycić mysz". Będzie wtedy otrzymywała zwią-
zane z nią komunikaty, nawet jeżeli kursor znajdzie się nad obszarem robo-
czym innego okna. O tym, w jaki sposób przechwycić mysz, dowiesz się nie-
co dalej.
Jeżeli na ekranie wyświetlane jest systemowe modalne okno komunikatu (ang.
.
system modal message box) lub systemowe modalne okno dialogowe (ang. sys-
tem modal dialog box), żaden inny program nie może odbierać komunikatów
myszy. W obu przypadkach nie jest również możliwe przełączenie do innego
okna. Przykładem systemowego modalnego okna dialogowego może być to,
które pojawia się, gdy zamierzasz zakończyć pracę komputera.
Proste przetwarzanie komunikatów myszy: przykład
Przedstawiony na rysunku 7-1 program CONNECT przetwarza kilka prostych
komunikatów związanych z myszą. Pozwala ci się także zorientować, w jaki spo-
sób system Windows przesyła te komunikaty do twoich programów.
CONNECT.C
/*
CONNECT.C - Demo myszy - ldczenie punktów liniami
(c) Charles Petzold. 1998
*/
llinclude
4ldefine MAXPOINTS 1000
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) :
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppNameC7 = TEXT ("Connect") :
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 PPLICATION) :
wndclass.hCursor = LoadCursor (NULL, IDC ARROW) :
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH)
wndclass.lpszMenuName = NULL :
wndclass.lpszClassName = szAppName :
if (!RegisterClass (&wndclass))
i
258 '
Część I: Podstawy
(ciąg dalszy ze strony 257)
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) ;
I
i
hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
i
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
I
i
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static POINT ptCMAXPCINTS] ;
static int iCount ;
HDC hdc ;
int i, j :
PAINTSTRUCT ps ;
switch (message)
(
case WM_LBUTTONDOWN:
iCount = 0 ; '
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_MOUSEMOVE:
if (wParam & MK LBUTTON && iCount < 1000)
(
pt[iCount ].x = LOWORD (lParam) ;
pt[iCount++],y = HIWORD (lParam) ;
hdc = GetDC (hwnd) ;
SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0) ;
ReleaseDC (hwnd, hdc) ;
,
return 0 ;
case WMLBUTTONUP:
InvalidateRect (hwnd, NULL, FALSE) ;
return 0 ; ,
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
Rozdział l: Mysz
SetCursor (LoadCursor (NULL, IDC WAIT)) :
ShowCursor (TRUE) ;
for (i = 0 ; i < iCount - 1 ; i++)
for (j = i + 1 ; j < iCount ; j++)
.x tCi].y, NULL) ;
MoveToEx (hdc, ptCi] , p
LineTo (hdc, ptCj].x, ptCj].y) :
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC RROW)) :
EndPaint (hwnd. &ps) :
return 0 ;
case WM DESTROY:
PostOuitMessage (0) ;
return 0 ;
i
return DefWindowProc (hwnd, message, wParam, lParam) :
)
Rysunek 7-1. Program CONNECT
Program CONNECT przetwarza trzy komunikaty myszy:
WM LBUTTONDOWN - CONNECT usuwa zawartość obszaru roboczego.
WM MOUSEMOVE - jeżeli naciśnięty jest równocześnie lewy przycisk myszy,
CONNECT rysuje w obszarze roboczym czarną kropkę oraz zapamiętuje jej współ-
rzędne.
WMLBUTTONUP - program CONNECT rysuje połączenia pomiędzy wszyst-
kimi kropkami zaznaczonymi w obszarze roboczym. Czasem uda się w ten spo-
sób stw orzyć ładny wzór (zobacz rysunek 7-2).
Rysunek 7-2. Zawartość okna programu CONNECT
260
c:ęść I: Podstawy ;
;
Posługiwanie się programem CONNECT jest bardzo proste. Umieść kursor my-
szy nad obszarem roboczym, naciśnij jej lewy przycisk, przesuń w dowolny spo-
sób jej kursor, a następnie zwolnij przycisk. Program działa najlepiej, jeżeli ma
połączyć kilka kropek położonych na krzywej.
Program CONNECT posługuje się trzema funkcjami GDI, które opisałem w roz-
dziale 5: SetPixel rysuje czarny punkt po każdym odebraniu komunikatu 4VM-MO- I
USEMOVE, o ile oczywiście naciśnięty był lewy przycisk myszy. (Jeżeli korzy-
stasz z morutora pracującego w trybie o dużej rozdzielczości, punkty te mogą być
niemal niewidoczne). Z kolei rysowanie linii wymaga wywołania funkcji Move-
ToEx oraz LineTo.
Jeżeli trzymając naciśnięty lewy przycisk myszy, przesuniesz jej kursor poza ob-
szar roboczy, CONNECT nie będzie mógł połączyć punktów, ponieważ nie od-
bierze komunikatów WM-LBUTTONUP Gdy przesuniesz kursor myszy ponow-
nie nad obszar roboczy, a następnie naciśniesz ponownie lewy przycisk, zawar-
tość obszaru roboczego zostanie usunięta. Jeżeli mimo przesunięcia kursora my-
szy poza obszar roboczy chcesz kontynuować rysowanie, naciśnij lewy przycisk
myszy, a następnie przesuń kursor nad obszar roboczy.
Program CONNECT jest w stanie zapamiętać informacje o położeniu co najwy-
żej tysiąca punktów. Jeżeli założymy, że liczba punktów wynosi P, to liczba linii
,
które muszą zostać narysowane, wynosi P x (P - 1) / 2. Jeżeli okaże się, że użyt-
kownik zaznaczył tysiąc punktów, konieczne będzie narysowanie około 500 000
linii, co może zająć minutę lub dwie, zależnie od tego, jakim komputerem dyspo-
nujesz. Ponieważ Windows 98 jest systemem wielozadaniowym, możesz w tym
czasie pracować z innymi programami. Natomiast nie możesz zrobić w nim nic
innego (na przykład przesunąć okno lub zmienić jego rozmiar). W rozdziale 20
zajmiemy się metodami radzenia sobie z problemami tego typu.
Ponieważ programowi CONNECT narysowanie wszystkich linii może zająć tro-
chę czasu, to w trakcie obsługi komunikatu WM_PAINT zmierńa kształt kursora
na klepsydrę, a po zakończeniu obsługi - przywraca kształt początkowy. Wyma-
ga to dwukrotnego wywołania funkcji SetCursor dla dwóch standardowych kur-
sorów. CONNECT również dwukrotnie wywohzje funkcję ShowCursor: raz z pa-
rametrem TRUE, a za drugim razem - z parametrem FALSE. Tymi wywołaniami
bardziej szczegółowo zajmę się w dalszej części tego rozdziału pt. "Emulacja my-
szy za pomocą klawiatury .
Czasem na określenie tego, co robi program po odebraniu informacji o przemiesz-
czeniu kursora myszy, używa się słowa "śledzenie". Nie oznacza to oczywiście,
że w procedurze okna program wchodzi w pętlę i usiłuje wywnioskować, gdzie
w danym momencie znajduje się wskaźnik myszy. Zamiast tego procedura okna
przetwarza komunikaty myszy tak szybko, jak to tylko możliwe, a następnie prze-
kazuje sterowanie do systemu Windows.
Obsługa klawisry specjalnych
Gdy program CONNECT odbierze komurukat WMMOUSEMOVE, wykonuje bi-
narną operację AND na zawartości wParam oraz MK LBUTTON. Pozwala mu to
zorientować się, czy naciśnięty został lewy przycisk myszy. Parametru wParam
Rozdział 7: Mysz 261
można użyć również wtedy, gdy chcesz przekonać się, czy naciśnięty został kla-
wisz [Shift] albo [CtrI]. Możesz przy tym poshxżyć się następującą konstrukcją:
if (wParam & MK SHIFT)
(
if (wParam & MK CONTROL)
(
Cklawisze CShiftJ i CCtrlJ są naciśnięteJ
)
else
(
Cnaciśnięty jest klawisz CShiftJJ
)
else
(
if (wParam & MKCONTROL)
(
[naciśnięty jest klawisz CCtrlJJ
1
else
{
ani CCtrlJJ
Cnie jest nacinięty ani CShiftJ,
Jeżeli w swoich programach zamierzasz poshxgiwać się zarówno lewym, jak i pra-
wym przyciskiem myszy, a ponadto chcesz, aby mogli z tych programów rów-
nież korzystać ci, którzy poshzgują się myszą z jednym przyciskiem, powinieneś
tak napisać program, aby jednoczesne naciśnięcie klawisza [Shift] oraz lewego
przycisku myszy było równoznaczne z naciśnięciem prawego przycisku myszy.
Kod obsługujący komunikaty myszy mógłby wtedy wyglądać tak:
case WMLBUTTONDOWN:
if (!(wParam & MK SHIFT))
(
Cobsluga Iewego przycisku myszyJ
return 0 ;
// Kontynuacja przetwarzania
case WM_RBUTTONDOWN:
Cobsługa prawego przycisku myszy]
return 0 ;
Opisana w rozdziale 6 funkcja Windows GetKeyState może również zwrócić in-
formacje dotyczące przycisków myszy oraz klawiszy [Shift[ i [Ctrl], jeżeli jako jej
parametr podany zostanie jeden z następujących wirtualnych kodów klawiszy:
VK_LBUTTON, VK RBUTTON, VK MBUTTON, VK SHIFT oraz VK CON-
TROL. Przycisk lub klawisz jest naciśnięty, jeżeli wartość zwrócona przez Get-
KeyState jest ujemna. Ponieważ funkcja ta zwraca informację o stanie przycisków
i klawiszy dotyczącą aktualnie przetwarzanego komunikatu, jest ona dość do-
brze z nim zsynchronizowana. Jak z pewnością pamiętasz, nie można korzystać
z funkcji GetKeyState w odniesieniu do klawiszy, które dopiero mają być wciśnię-
te. Nie można również czekać w ten sposób na naciśnięcie przycisku. Dlatego
nigdy nie powinieneś pisać takich poleceń jak to:
while (GetKeyState (VKLBUTTON) >= 0) ; // ŹLE !!!
262 Część I: Podstawy
Funkcja GetKeyState tylko wtedy zwróci informacje o naciśniętym przycisku, je-
żeli został on naciśnięty już wcześniej.
Kliknięcia dwukrotne
Kliknięcie dwukrotne to dwa kliknięcia rozdzielone krótką przerwą. Aby dwa
kliknięcia mogły być ze sobą połączone, muszą nastąpić w niezbyt odległych od
siebie obszarach (domyślnie, między jednym kliknięciem a drugim kursor my-
szy może przesunąć się o około jedną szerokość znaku czcionki systemowej i o pół
jego wysokości) oraz w niezbyt długim czasie noszącym nazwę szybkości dwu-
krotnego kliknięcia (ang. double-click speed). Szybkość tą możesz modyfikować
poshxgując się apletem Mysz znajdującym się w Panelu sterowania.
Jeżeli chcesz, aby twoja procedura okna otrzymywała komunikaty związane
z dwukrotnym kliknięciem, to zanim zarejestrujesz klasę okna za pomocą funk-
cji IZegisterClass, w polu stylu struktury opisującej klasę musisz podać identyfika-
tor CS DBLCLKS:
wndclass.style = CS HREDRAW CSVREDRAW CSDBLCLKS ;
Jeżeli natomiast nie włączysz identyfikatora CS DBLCLKS, a użytkownik dwu-
krotnie kliknie lewym przyciskiem myszy, procedura okna otrzyma następujące
komunikaty:
WMLBUTTONDOWN
WM LBUTTONUP
4VMLBUTTONDOWN
WM LBUTTONUP
Może się również okazać, że pomiędzy wymienionymi powyżej komunikatami
procedura okna odbierze również inne komunikaty. Jeżeli zamierzasz zaimple-
mentować swoją własną obsługę dwukrotnego kliknięcia, za pomocą funkcji Get-
MessageTime możesz pobrać czas, na podstawie którego wyznaczysz odstęp po-
między kliknięciami. Funkcją tą dokładniej zajmiemy się w rozdziale 8.
Jeżeli do stylu rejestrowanej klasy okna włączysz identyfikator CS_DBLCLKS
,
w przypadku pojawienia się dwukrotnego kliknięcia procedura okna odbierze na-
stępującą serię komunikatów:
WMLBUTTONDOWN
WMLBUTTONUP
WMLBUTTONDBLCLK
WMLBUTTONUP
Komunikat WM LBUTTONDBLCLK zastąpił po prostu WMLBUTTONDOWN
z poprzedniego przykładu.
Obsługa dwukrotnego kliknięcia jest znacznie łatwiejsza, jeżeli pierwsze kliknię-
cie wykonuje tę samą operację co kliknięcie jednokrotne. Drugie kliknięcie (ko-
munikat 4VMLBUTTONDBLCLK) odpowiedzialne jest wtedy za operację do-
datkową w porównaniu do pierwszego kliknięcia. Zwróć na przykład uwagę,
w jaki sposób mysz wykorzystywana jest do wybierania plików z listy w Eksplo-
Rozdział 7: Mysz 263
ratorze Windows. Pojedyncze kliknięcie zaznacza plik. Powoduje to odwrócenie
kolorów liter i tła jego nazwy. Z kolei dwukrotne kliknięcie wykonuje dwie ope-
racje: w odpowiedzi na komunikat odpowiadający kliknięciu plik zostaje zazna-
czony, podczas gdy drugi komunikat, tym razem odpowiadający dwukrotnemu
kliknięciu, powoduje otworzenie zaznaczonego pliku. To bardzo proste. Nieste-
ty, wszystko się komplikuje, jeżeli przy podwójnym kliknięciu nie wykonuje tej
samej czynności, co jednokrotne kliknięcie.
Komunikaty myszy nie związane z obszarem roboczym
Omówione do tej pory 10 komunikatów myszy pojawia się, gdy jej kursor znaj-
duje się wewnątrz obszaru roboczego okna. Jeżeli natomiast kursor znajduje się
poza obszarem roboczym, ale w dalszym ciągu w oknie, system Windows wysy-
ła do procedury okna odmienny zestaw komunikatów. Obszary te to: pasek tytu-
łu, menu oraz paski przewijania.
Zwykle nie musisz zajmować się komunikatami spoza obszaru roboczego. Po pro-
stu musisz przekazać je do Def4VindowProc, a całą pracę wykona za ciebie Win-
dows. W tym sensie komunikaty te podobne są do komunikatów klawiaturowych
WM-SYSKEYDOWN, WM SYSKEYUP oraz WM-SYSCHAR.
Komunikaty myszy nie związane z obszarem roboczym są niemal dokładnym
odpowiednikiem komunikatów związanych z tym obszarem. Ich identyfikatory
zawierają litery "NC" (ang. nonclient), co oznacza, że nie dotyczą one obszaru
roboczego. Jeżeli kursor myszy przesuwany jest poza obszarem roboczym, do
procedury okna wysłany zostaje komunikat WM NCMOUSEMOVE. Przyciski
myszy generują komunikaty przedstawione w poniższej tabeli:
Przycisk Naciśnięty Zwolniony Naciśnięty (drugie
kliknięcie)
Lewy WM IVCLBUTTONDOWN WMNCLBUTTONUP WMNCLBUTTONDBLCLK
Środkowy WMNCMBUTTONDOWN WM NCMBUTTONUP WMNCMBUTTONDBLCLK
Prawy WM NCRBUTTONDOWN WM NCRBUTTONUP WM NCRBUTTONDBLCLK
Parametry wParam oraz lParam przekazywane razem z komunikatami myszy nie
dotyczącymi obszaru roboczego różnią się jednak od wykorzystywanych przez
komunikaty związane z tym obszarem. Parametr wParam wykorzystywany jest
do identyfikacji obszaru, w którym naciśnięty został przycisk lub przesunięty kur-
sor myszy. Identyfikatory poszczególnych obszarów rozpoczynają się od liter
"HT" (ang. hit-test - test na trafienie) i zostały zdefiniowane w pliku nagłówko-
wym WINUSER.H.
Młodsze słowo parametru lParam wykorzystywane jest do przekazywania współ-
rzędnej x kursora, natomiast starsze - współrzędnej y. Jednakże są to współrzęd-
ne ekranowe, a nie współrzędne obszaru roboczego, jak miało to miejsce w przy-
-
Część i: Podstawy
padku komunikatów związanych z obszarem roboczym. W przypadku współ-
rzędnych ekranowych, w lewym górnym rogu ekranu zarówno współrzędna x,
jak i y mają wartość 0. Wartość współrzędnej x zwiększa się w miarę przesuwa-
nia się w prawo, natomiast współrzędnej y - w miarę przesuwania się w dół.
(Zobacz rysunek 7-3).
Możliwe jest przekształcanie współrzędnych ekranowych na współrzędne obszaru
roboczego i odwrotnie za pomocą dwóch funkcji systemu Windows:
ScreenToClient (hwnd, &pt) ;
ClientToScreen (hwnd, &pt) ;
Parametr pt jest strukturą POINT. Obie funkcje przekształcają wartości przeka-
zywane za pomocą tej struktury bez zachowywania ich poprzednich wartości.
Zwróć uwagę, że jeżeli punkt znajduje się poza obszarem roboczym lub po jego
lewej stronie, współrzędne obszaru roboczego będą miały wartości ujemne.
Wędne ekranu
v
Rysunek 7-3. Współrzędne ekranowe oraz współrzędne obszaru roboczego
Komunikat testu na trafienie obrzeża okna
Jeżeli jeszcze się nie zgubiłeś, to wiesz, że do tej pory przedstawiłem już 20 z 21
komunikatów związanych z myszą. Ostatnim z nich jest WMNCHITTEST. Jego
nazwa pochodzi od angielskiego nonclient hit test (test na trafienie obrzeża okna).
Komunikat ten poprzedza każdy inny komunikat myszy dotyczący zarówno ob-
szaru roboczego, jak i obrzeża okna. Za pomocą parametru lParam przekazywa-
ne są ekranowe współrzędne x i y kursora myszy. Natomiast wParam nie jest wy-
korzystywany.
Rozdział 7: klysz 265
Większość aplikacji przekazuje ten komurukat do DeWndowProc. Następnie Win-
dows wykorzystuje go do wygenerowania wszystkich pozostałych komunikatów
związanych z myszą. W przypadku komunikatów myszy spoza obszaru robo-
czego wartość zwrócona przez funkcję DetNindowProc przekazywana jest do pro-
cedury okna jako parametr wParam. Może to być dowolna wartość parametru wPa-
ram towarzysząca komunikatom myszy nie związanym z obszarem roboczym lub
jedna z następująych:
HTCLIENT Obszar roboczy
HTNOWHEftE Żadne okno
HTTRANSPARENT Okno przykryte przez inne
HTERROR Def iNindowProc generuje krótki sygnał dźwiękowy
Jeżeli po przetworzeniu komunikatu WM NCHITTEST funkcja DeftNindowProc
zwróci wartość HTCLlENT, system Windows dokonuje konwersji ze współrzęd-
nych ekranowych na współrzędne obszaru roboczego, a następnie generuje ko-
munikat myszy.
Jeżeli pamiętasz jeszcze, w jaki sposób zablokowaliśmy wszystkie systemowe
hxnkcje klawiatury, to zapewne się zastanawiasz, czy można zrobić coś podob-
nego z komunikatami myszy. Oczywiście! Jeżeli dodasz do procedury okna na-
stępujące linie:
case WM NCHITTEST:
return (LRESULT) HTNOWHERE ;
spowodujesz, że w twoim oknie zostaną zablokowane wszystkie komunikaty
myszy zarówno związane z obszarem roboczym, jak i z nim nie związane. Kiedy
mysz znajdzie się nad obszarem okna, włączając w to ikonę menu systemowego,
przyciski modyfikujące wielkość okna oraz ikonę zamykającą okno, przyciski my-
szy nie będą po prostu działać.
Komunikaty generujące komunikaty
System Windows posługuje się komunikatem WM NCHITTEST do wygenerowa-
nia pozostałych komunikatów myszy. Pomysł tworzera jednych komunikatów na
podstawie innych jest w tym systemie dość powszechnie stosowany. Rozważmy
pewien przykład. Jak z pewnością wiesz, dwukrotne kliknięcie ikony menu syste-
mowego powoduje zamknięcie okna. Dwukrotne kliknięcie generuje serię komu-
nikatów WMNCHTITEST. Ponieważ kursor myszy znajduje się nad ikoną menu
systemowego, funkcja Def 4VindowProc zwraca wartość HTSYSMENU, a Windows
umieszcza w kolejce komunikat WMNCLBUTTONDBLCLK, przy czym jego pa-
rametrowi wParam nadana zostaje wartość HTSYSMENU.
Procedura okna przekazuje zwykle ten komunikat do DefWindowProc. Gdy z ko-
lei DeftNindowProc odbierze komunikat WM NCLBUTTONDBLCLK z parame-
trem wParam równym HTSYSMENU, umieszcza w kolejce komunikat WM SY-
SCOMMAND z wParam równym SC CLOSE. (Ten sam komunikat WM SY
SCOMMAND generowany jest również wtedy, gdy użytkownik wybierze pole-
cenie Zamknij z menu systemowego). Po raz kolejny procedura okna przekazuje
otrzymany komunikat do Def4VindowProc. Z kolei DefWindowProc w odpowiedzi
wysyła do funkcji okna komunikat WM CLOSE.
266 Część I: Podstawy R
Jeżeli przed zamknięciem program wymaga potwierdzenia użytkownika, to
w procedurze okna powinien zostać przechwycony komunikat WM_CLOSE.
W przeciwnym bowiem razie trafi on do funkcji Def tNindowPrac, która w odpo-
wiedzi wywoła fixnkcję DestroyWindow. Zadaruem tej funkcji jest wysłanie do pro-
cedury okna komunikatu WM DESTROY, gdzie zwykle zostaje on obsłużony w
następujący sposób:
case WM_DESTROY:
PostOuitMessage (0) ;
return 0 ;
Funkcja PostQuitMessage powoduje, że Windows umieszcza w kolejce komuni-
kat WMQUIT. Nie dotrze on nigdy do procedury okna, ponieważ po jego po-
braniu funkcja GetMessage zwróci wartość 0, co spowoduje zakończenie wykony-
wania pętli obshzgi komunikatów programu.
Testowanie trafienia w twoich programach
Wcześniej wspomniałem, w jaki sposób Eksplorator Windows reaguje na kliknięcia
oraz na dwukrotne kliknięcia. Zwykle program (lub kontrolka listy wykorzysty-
wana przez Eksplorator Windows) musi dokładnie określić, który plik lub kata-
log został wskazany przez użytkownika.
Operacja ta nosi miano testowania trafienia (ang. hit-testing). Dokładnie w ten sam
sposób, w jaki DefWindowProc po otrzymaniu komunikatu WM_NCHITTEST
sprawdza, który element okna został trafiony, procedura okna musi określić, który
element wewnątrz jej obszaru roboczego został trafiony. Dlatego też procedura
okna musi wykonać pewne obliczenia, posługując się współrzędnymi x i y kur-
sora przekazanymi za pomocą parametru lParam.
Przykład hipotetyczny
Oto przykład. Załóżmy, że twój program ma wyświetlić w kilku kolumnach listę
alfabetycznie posortowanych nazw plików. Zwykle powinieneś skorzystać z wi-
doku listy, ponieważ potrafi on wykonać za ciebie całą pracę związaną z testo-
waniem trafienia. Załóżmy jednak, że z jakiegoś powodu nie jest to możliwe.
Musisz zrobić to sam. Załóżmy również, że posortowane alfabetycznie nazwy
plików przechowywane są w postaci tablicy wskaźników do napisów o nazwie
szFileNames.
Załóżmy ponadto, że lista plików wyświetlana jest od górnej krawędzi obszaru
roboczego, który ma wielkość zapamiętaną w cxClient oraz cyClient. Poszczegól-
ne kolumny mają szerokość cxColWidth punktów, natomiast znaki wykorzysty-
wanej przez nas czcionki mają wysokość cyChar punktów. Liczba plików, które
możesz wyświetlić w jednej kolumnie, wynosi:
iNumInCol = cyClient / cxColWidth ;
Gdy program otrzymuje komurukat wywołany kliknięciem myszą, współrzędne
jej kursora cxMouse oraz cyMouse możesz pobrać z parametru IParam. Następnie,
poshzgując się przedstawionym niżej wyrażeniem, możesz wyznaczyć kolumnę,
którą wskazał użytkownik:
Rozdział 7: Mysz
iColumn = cxMouse /cxColWidth ;
Położenie nazwy pliku względem początku kolumny to
iFromTop = cyMouse /cyChar ;
Możesz teraz wyznaczyć indeks w tablicy szFileNames.
iIndex = iColumn * iNumCol + iFromTop ;
Jeżeli wartość iIndex jest większa niż liczba nazw plików przechowywanych w ta-
blicy, oznacza to, że użytkownik kliknął pusty obszar okna.
W wielu przypadkach testowanie trafienia jest bardziej złożone, niż w przedstawio-
nym przykładzie. Gdy wyświetlasz rysunek składający się z wielu części, musisz
określić położenie każdej z nich. Testowanie trafienia polega bowiem na przekształ-
ceniu współrzędnych na interesujący cię obiekt. Zadanie to może okazać się szcze-
gólnie trudne w przypadku edytorów tekstu posługujących się czcionką o zmiennej
szerokości, ponieważ należy cofnąć się i znaleźć pozycję znaku w napisie.
Przykładowy program
Przedstawiony na rysunku 7-4 program CHECKEIZI demonstruje prosty test na
trafienie. Dzieli on obszar roboczy na 25 prostokątów tworzących tablicę o wy-
miarach 5 na 5. Jeżeli klikniesz myszą dowolny z prostokątów, zostanie w nim
wpisany znak X. Jeżeli klikniesz go po raz kolejny, znak ten zostanie usunięty.
CHECKERl.C
/*
CHECKERl.C - Test trafień myszy, wersja 1
(c) Charles Petzold, 1998
*/
ilinclude
4idefine DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("Checkerl") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS llREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL IDC RROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH)
wndclass.lpszMenuName = NULL ;
wnfclass.lpszClassName = szAppName ;
Część t: Podstawy
(ciąg dalszy ze strony 267)
if (!RegisterClass (&wndclass))
,
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) ;
J
hwnd = CreateWindow (szAppName, TEXT ("Checkerl Mouse Hit-Vest Demo"), ;
WS_OVERLAPPEDWINDOW, ;
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
,
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
1
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static BOOL fState[DIVISIONS](DIVISIONS] ;
static int cxBlock, cyBlock ;
HDC hdc ;
int x, y ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
return 0 ;
case WM LBUTTONDOWN :
x = LOWORD (lParam) / cxBlock ;
y = HIWORD (lParam) l cyBlock ;
if (x < DIVISIONS && y < DIVISIONS)
(
fState [x][y] ^= 1 ;
rect.left = x * cxBlock ;
rect.top = y * cyBlock ;
rect.right = (x + 1) * cxBlock ;
rect.bottom = (y + 1) * cyBlock ;
InvalidateRect (hwnd, &rect, FALSE) ;
Rozdział 7: Mysz 269
else
MessageBeep (0) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
f
Rectangle (hdc, x * cxBlock, y * cyBlock,
(x + 1) * cxBlock, (y + 1) * cyBlock) ;
if (fState [x][y])
1
MoveToEx (hdc, x * cxBlock, y * cyBlock, NULL) ;
LineTo (hdc, (x+1) * cxBlock. (y+1) * cyBlock) ;
MoveToEx (hdc. x * cxBlock, (y+1) * cyBlock, NULL) ;
LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ;
l
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-4. Program CHECKER1
Na rysunku 7-5 przedstawione zostało okno programu CHECKERl. Wszystkie
prostokąty narysowane przez program mają takie same wymiary. Szerokość i wy-
sokość przechowywane są w zmiennych cxBlock oraz cyBlock. Ich wartości wy-
znaczane są za każdym razem, gdy zmienia się wielkość okna. W ramach obsłu-
gi komunikatu WMLBUTTONDOWN do określenia, który z prostokątów zo-
stał wskazany, wykorzystywane są współrzędne kursora myszy. Następnie zmie-
niany jest stan prostokąta przechowywany w tablicy fState. Na koniec zostaje unie-
ważniony obszar roboczy, co z kolei powoduje wygenerowanie komunikatu
WM PAINT.
270 Część I: Podstawy
Rysunek 7-5. Okno programu CHECKERI
Jeżeli okaże się, że wysokość lub szerokość okna nie jest podzielna przez 5, nie-
wielki pasek po lewej stronie lub u dołu nie zostanie pokryty przez żaden z pro-
stokątów. Kliknięcie jednego z tych obszarów traktowane jest jako błąd i sygnali-
zowane za pomocą MessageBeep.
Gdy CHECKER1 otrzyma komunikat WM PAINT, odświeży cały obszar robo-
czy, posługując się funkcją GDI ftectangle. Jeżeli flaga fState odpowiadająca dane-
mu prostokątowi jest ustawiona, program rysuje dwie przekątne prostokąta, wy-
wohzjąc funkcje MoveToEx oraz LineTo. W trakcie obsługi komunikatu WMPA-
INT program CHECKER1 nie sprawdza, czy dany prostokąt znalazł się w unie-
ważnionym obszarze roboczym. Jedna z możliwych metod sprawdzania polega
na budowaniu struktury RECT dla każdego prostokąta (można w tym celu wy-
korzystać kod zbliżony do tego, który pojawił się w obsłudze komunikatu
WMLBUTTONDOWN). Następnie, wywołując funkcję Intersetftect, należałoby
sprawdzić, czy dany prostokąt ma część wspólną z unieważnionym obszarem
(dostępny jako ps.rcPaint).
Emulacja myszy za pomocą klawiatury
Do poshzgiwania się programem CHECKER1 niezbędna jest mysz. Wkrótce uzu-
pełnimy go o interfejs klawiatury tak, jak zrobiliśmy to w programie SYSMETS
opisanym w rozdziale 6. Ponieważ jednak tym razem mysz wykorzystywana jest
do wskazywania obiektów, konieczne będzie zatroszczenie się o wyświetlanie
i przemieszczanie kursora myszy.
Nawet jeżeli mysz nie jest zainstalowana, Windows w dalszym ciągu może wy-
świetlać jej kursor. Jest z nim związane coś, co w tym systemie nosi nazwę "licz-
nika wyświetleń" (ang. display count). Jeżeli mysz jest zainstalowana, jego począt-
kową wartością jest 0, w przeciwnym wypadku jest to -1. Kursor myszy wyświe-
tla się tylko wtedy, gdy wartość liczruka nie jest ujemna. Licznik możesz zwięk-
szyć, wywołując:
Rozdział 7: Mysz 271
ShowCursor (TRUE) ;
natomiast zmniejszyć za pomocą:
ShowCursor (FALSE) ;
Zanim wywołasz funkcję ShowCursor, nie musisz sprawdzać, czy mysz jest zain-
stalowana. Jeżeli niezależnie od jej obecności chcesz wyświetlić kursor, po prostu
zwiększ wartość licznika, wywołując ShowCursor. Teraz zmniejszenie jego warto-
ści spowoduje ukrycie kursora, jeśli mysz nie jest zainstalowana. A jeśli mysz była
obecna w systemie, kursor pozostanie na ekranie
System Windows zarządza informacją o aktualnym położeniu kursora nawet
wtedy, gdy mysz nie została zainstalowana. Może nawet wyświetlić kursor w do-
wolnym punkcie ekranu. Pozostanie on tam tak dhzgo, jak dhzgo jawnie go nie
przeniesiesz. Położenie kursora możesz pobrać, wywołując:
GetCursorPos (&pt)
gdzie pt jest strukturą POINT. W jej polach umieszczone zostają współrzędne x
i y myszy. Pozycję kursora możesz ustawić, wywohxjąc
SetCursorPos (x, y) ;
W obu przypadkach x i y to współrzędne ekranowe. (Powinno to być jasne, po-
nieważ funkcja nie wymaga podania parametru hwnd). Jak już wspomniałem,
możesz konwertować współrzędne ekranowe na współrzędne obszaru robocze-
go i vice versa, wywołując funkcję ScreenToClient oraz ClientToScreen.
Jeżeli zdecydujesz się wywołać funkcję GetCursorPos w trakcie obsługiwania komu-
nikatu myszy, może się okazać, że otrzymane w ten sposób współrzędne różnią się
nieco od tych, które przekazane zostały za pomocą parametru lParam. Dzieje się tak,
ponieważ funkcja GetCursorPos zwraca aktualne współrzędne, natomiast IParam prze-
chowuje współrzędne kursora z momentu wygenerowania komunikatu.
Prawdopodobnie chcesz, aby napisany przez ciebie interfejs klawiatury pozwo-
lił na przemieszczanie kursora myszy za pomocą klawiszy strzałek, natomiast
naciśnięcie klawiszy [Spacja] lub [Enter] symulowało przyciśnięcie przycisku my-
szy. Na pewno nie chcesz, aby kursor myszy przemieszczał się o jeden punkt za
każdym naciśnięciem klawisza. Zmuszałoby to użytkownika do długiego przy-
trzymania klawisza.
Jeżeli mimo wszystko zdecydowałeś, że w zaimplementowanym przez ciebie
interfejsie klawiatury kursor będzie się przemieszczał co jeden punkt, zrób to
przynajmniej tak, aby na początku jego ruch był wolny, a później ulegał przy-
śpieszeniu. Z pewnością pamiętasz, że parametr IParam towarzyszący komuni-
katowi WMKEYDOWN zawiera informację o tym, czy komunikat ten został wy-
generowany na skutek automatycznego powtarzania klawisza. Jest to idealne za-
stosowanie tej informacji.
Dodanie interfejsu klawiatury do programu CHECKER
Przedstawiony na rysunku 7-6 program CHECKER2 jest kopią wcześniejszego
CHECKERI, z tą jednak różnicą, że został w nim zaimplementowany interfejs
klawiatury. Do przemieszczania kursora pomiędzy prostokątami możesz posłu-
żyć się klawiszami kursora. Naciśnięcie klawisza [Home] spowoduje przeniesie-
272 CzęśĆ I: Podstawy
nie kursora do górnego prawego prostokąta, natomiast klawisza [End] - do dol-
nego lewego. Naciśnięcie klawisza [Spacja] lub [Enter] powoduje zaznaczenie
wskazywanego aktualnie prostokąta.
CHECKER2.C
/*
CHECKER2.C - Test trafień myszy, wersja 2
(c) Charles Petzold, 1998
*/
include
define DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine. int iCmdShow)
static TCHAR szAppNameC] = TEXT ("Checker2") ;
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) ;
wndcla.ss.hbrBackground = (HBRUSH) GetStockObjecv (WHITE BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ; j
)
hwnd = CreateWindow (szAppName, TEXT ("Checker2 Mouse Hit-Test Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CWUSEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
;
Rozdział 7: Mvsz 273
;
return msg.wParam ;
i
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParcm, LPARAM lParam)
I
? static BOOL fStateCDIVISIONS]CDIVISIONS] ;
static int cxBlock, cyBlock ;
HDC hdc ;
int x, y ;
PAINTSTRUCT ps ;
POINT point ;
RECT rect ;
switch (message)
i f
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
return 0 ;
case WM_SETFOCUS :
ShowCursor (TRUE) ;
return 0 ;
I
i case WM_KILLFOCUS :
ShowCursor (FALSE) ;
. return 0 ;
case WM_KEYDOWN :
GetCursorPos (&point)
, ScreenToClient (hwnd, &point) ;
x = max (0, min (DIVISIONS - 1, point.x / cxBlock)) ;
y = max (0, min (DIVISIONS - 1, point.y / cyBlock)) ;
switch (wParam)
(
case VK UP :
break ;
case VK_DOWN :
y++ ;
break ;
case VK_LEFT :
x--
break ;
case VK_RIGHT :
x++ ;
break ;
case VK_HOME :
x = y = 0 ;
break ;
case UK_END :
x = y = DIVISIONS - 1 ;
274 Część I: Podstawy
(ciąg dalszy ze strony 273)
break ;
case UK_RETURN :
case VK_SPACE :
SendMessage (hwnd, WM_LBUTTONDOWN, MK LBUTTON,
MAKELONG (x * cxBlock, y * cylock)) ;
break ;
)
x = (x + DIVISIONS) % DIVISIONS ;
y = (y + DIVISIONS) % DIVISIONS ;
point.x = x * cxlock + cxlock / 2 ;
point.y = y * cylock + cyBlock / 2 ;
ClientToScreen (hwnd, &point) ;
SetCursorPos (point.x, point.y) ;
return 0 ;
case WM_LBUTTONDOWN :
x = LOWORD (lParam) / cxlock ;
y = HIWORD (lParam) / cylock ;
if (x < DIVISIONS && y < DIVISIONS)
(
fState[xCy] ^= 1 ;
rect.left = x * cxlock ;
rect.top = y * cylock ;
rect.right = (x + 1) * cxlock ;
rect.bottom = (y + 1) * cyBlock ;
InvalidateRect (hwnd, &rect, FALSE) ;
)
else
Messageeep (0) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (x = 0 ; x < DIUISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
(
Rectangle (hdc, x * cxBlock, y * cyBlock,
(x + 1) * cxlock, (y + 1) * cyBlock) ;
if (fState Cx]Cy])
(
MoveToEx (hdc, x *cxBlock, y *cyBlock, NULL) ;
LineTo (hdc, (x+1)*cxlock, (y+1)*cyBlock) ;
MoveToEx (hdc, x *cxBlock, (y+1)*cyBlock, NULL) ;
LineTo (hdc, (x+1)*cxlock, y *cyBlock) ;
)
)
EndPaint (hwnd, &ps) ;
return 0 ;
Rozdział 7: MVsz 275
case WM_DESTROY :
PostOuitMessage (0) ;
return 0 ;
l
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-6. Program CHECKER2
Obsługa komunikatu WM-KEYDOWN w programie CHECKER2 obejmuje okre-
ślenie pozycji kursora za pomocą funkcji GetCursorPos, konwersję współrzędnych
ekranowych na współrzędne obszaru roboczego za pomocą ScreenToClient oraz
podzielenie każdej z nich przez szerokość i wysokość prostokąta. W ten sposób
otrzymuje się pozycje w tablicy o rozmiarach 5 na 5. Ponieważ w momencie klik-
nięcia kursor nie musi znajdować się nad obszarem roboczym, konieczne jest prze-
filtrowanie otrzymanych współrzędnych przez makra min i max, co spowoduje,
że rezultat będzie zawierał się w przedziale od 0 do 4.
Jeżeli naciśnięty został któryś z klawiszy sterujących ruchem kursora, współrzędne
x oraz y zostają odpowiednio zwiększone lub zmniejszone. Jeżeli natomiast naci-
śnięta została [Spacja] lub [Enter], CHECKER2 wysyła sam do siebie komunikat
WMLBUTTONDOWN. Jest to metoda bardzo podobna do tej, która została za-
stosowana w programie SYSMETS z rozdziału 6. Obsługa komunikatu WM KEY
DOWN kończy się wyznaczeniem współrzędnych obszaru roboczego wskazują-
cych na środek prostokąta, przekonwertowaniu ich na współrzędne ekranowe za
pomocą funkcji ClientToScreen oraz na ustawieniu kursora myszy za pomocą Set-
CursorPos.
Wykorzystanie okien potomnych do testowania trafienia
Część programów (na przykład Windows Paint) dzieli obszar roboczy na kilka
mniejszych obszarów. Po lewej stronie okna Painta umieszczone zostały ikony
oznaczające dostępne narzędzie, natomiast u dołu - paleta kolorów. Gdy program
ten sprawdza, czy któryś z tych obszarów nie został trafiony, musi wziąć pod
uwagę fragment obszaru roboczego, zanim spróbuje określić, który właściwie ele-
ment użytkowruk miał na myśli.
A może wcale tak nie jest? W rzeczywistości Paint upraszcza sobie zarówno rysowa-
nie, jak i sprawdzanie trafionego obszaru, przez wykorzystanie okien potomnych.
Pozwalają one na podział całego obszaru roboczego na kilka mniejszych prostokąt-
nych obszarów. Każde z okien potomnych ma swój własny uchwyt, procedurę okna
oraz obszar roboczy. Każda z procedur odbiera tylko te komunikaty, które dotyczą
jej okna. W komunikatach myszy parametr IParam zawiera współrzędne kursora
względem lewego górnego rogu obszaru roboczego okna potomnego, a nie wzglę-
dem okna nadrzędnego (które w przypadku Painta jest głównym oknem programu).
Takie wykorzystanie okien potomnych ułatwia modularyzację programu. Jeżeli
okna te oparte zostały o inne klasy, każde z nich może posługiwać się swoją wła-
sną procedurą okna. Okna różnych klas mogą również mieć inne tła oraz różne
kursory. W rozdziale 9 zapoznamy się z kontrolkami okien potomnych, które są
predefiniowanymi oknami przyjmującymi postać pasków przewijania, przycisków
276 Czść I: Podstawy
lub pól edycji. Jednak teraz zajmijmy się tym, w jaki sposób możemy wykorzy-
stać okna potomne w programie CHECKER.
Okna potomne w programie CHECKER
Na rysunku 7-7 przedstawiony został program CHECKER3. Ta wersja tworzy 25
okien potomnych, których zadaniem jest reagowanie na komunikaty myszy. Nie
został w nim zaimplementowany interfejs klawiatury, jednak opierając się na tym,
co przedstawiłem w jego poprzedniej wersji, z łatwością możesz zrobić to sam.
CHECKER3.C
/*
CHECKER3.C - Test trafień myszy, wersja 3
(c) Charles Petzold, 1998
*/
include
define DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szChildClassC] = TEXT ("Checker3 Child") ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[7 = TEXT ("Checker3") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CSVREDRAW ;
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 (WHITEBRUSH)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB-ICONERROR) ;
return 0
wndclass.lpfnWndProc = ChildWndProc ;
wndclass.cbWndExtra = sizeof (long) ;
wndclass.hIcon = NULL ;
wndclass.lpszClassName = szChildClass ;
Rozial 7: Mysz 277
ł
RegisterClass (&wndclass) ;
hwnd = CreateWindow (szAppName, TEXT ("Checker3 Mouse Hit-Test Demo"),
WS_OVERLAPPEDWINDOW,
CW USEDEFAULT, CW USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
f
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
i
return msg.wParam ;
I
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
i
i
static HWND hwndChildCDIVISIONS]CDIVISIONS] ;
int cxBtock, cyBlock, x, y ;
I:
switch (message)
case WM_CREATE :
for (x = 0 ; x < DIVISIONS ; x++) i
for (y = 0 ; y < DIVISIONS ; y++)
hwndChild[x][y] = CreateWindow (szChildClass, NULL,
WS_CHILDWINDOW WS VISIBLE,
0, 0, 0, 0,
hwnd, (HMENU) (y 8 x),
(HINSTANCE) GetWindowLong (hwnd, GWL HINSTAtdCE),
NULL) ;
! return 0 ;
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
MoveWindow (hwndChildCx][y],
x * cxBlock, y * cyBlock,
cxBlock, cyBlock, TRUE) ;
return 0 ;
case WM_LBUTTONDOWN :
( MessageBeep (0) ;
I return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ;
' return 0 ;
, return DefWindowProc (hwnd, message, wParam, lParam) ;
)
278 Część I: Podstawy
i
(ciąg dalszy ze strony 277)
i
LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
(
case WM_CREATE :
SetWindowLong (hwnd, 0, 0) ; // flaga włdczenia/wyldczenia
return 0 ;
case WM_LBUTTONDOWN :
SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;
InvalidateRect (hwnd, NULL, FALSE) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
if (GetWindowLong (hwnd, 0))
MoveToEx (hdc, 0, 0, NULL) ;
LineTo (hdc, rect.right, rect.bottom) ;
MoveToEx (hdc, 0, rect.bottom, NULL)
LineTo (hdc, rect.right, 0) ;
EndPaint (hwnd, &ps) ;
return 0 ;
)
return OefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-7. Program CHECKER3
Program CHECKER3 posiada dwie funkcje okna o nazwach WndProc oraz Chil-
dWndProc. Pierwsza z nich w dalszym ciągu wykorzystywana jest przez główne
okno programu (okno nadrzędne). Z kolei ChildWndProc jest funkcją okna dla 25
okien potomnych. Obie funkcj,e muszą zostać zdefiniowane jako CALLBACK.
Ponieważ procedura okna związana jest z jedną, szczególną strukturą klasy okna,
którą rejestrujesz za pomocą funkcji RegisterClass, program CHECKER3 wymaga
zarejestrowania dwóch klas. Klasa pierwszego okna przeznaczona jest dla okna
głównego i nosi nazwę "Checker3". Natomiast nazwa drugiej klasy to "Chec-
ker3 Child". Wybrane nazwy nie muszą spełniać jakichś istotnych kryteriów.
CHECKER3 rejestruje obie klasy okna w funkcji WinMain. Po zarejestrowaniu
pierwszej, wykorzystuje tę samą strukturę wndclass w trakcie rejestracji drugiej.
Wszystkie jej pola, za wyjątkiem czterech przedstawionych niżej, nie są modyfi-
kowane:
6
Rozdział 7: Mysz
;
ł Polu lpfnWndProc przypisywana jest procedura okna potomnego ChiIdWnd-
' Proc.
ł Polu cbWndExtra zostaje nadana wartość 4, a dokładniej siezeof(long). Zawar-
tość tego pola nakazuje systemowi Windows zarezerwowanie dodatkowych
4 bajtów na wewnętrzne dane, dostępne dla każdego okna, które zostanie stwo-
rzone na podstawie tej klasy. Nie będą to jednak wspólne dane okien: każde
z nich może przechowywać w tym obszarze swoje własne informacje.
ł Pole hlcon zostaje ustawione na NULL, ponieważ takie okna potomne, jakie
wykorzystane zostały w programie CHECKER3, nie potrzebują ikon.
ł Polu pszClassName nadany zostaje łańcuch "Checker3 Child" będący nazwą klasy.
Funkcja CreateWindow na podstawie klasy Checker3 tworzy okno główne progra-
mu wywołane w WinMain. Ten fragment programu niczym nie różni się od po-
przednich. Jednak gdy funkcja WinProc otrzyma komunikat WM CREATE, wy-
wołuje CreateWindow 25 razy, aby w oparciu o klasę Checker3 Child, utworzyć
25 okien potomnych. W poniższej tabeli porównane zostały parametry przeka-
zywane do funkcji CreateWindow wywoływanej w WinMain oraz w WndProc.
Parametr Okno główne Okno potomne
klasa okna "Checker3" "Checker3 Child"
nagłówek okna "Checker3 .... NULL
styl okna WM OVERLAPPEDWINDOW WS CHILDWINDOW
I WS VISIBLE
położenie w pionie CW USEDEFAULT 0
położenie w poziomie CW USEDEFAULT 0 '
szerokość CW USEDEFAULT 0
wysokość CW USEDEFAULT 0 !
uchwyt okna nadrzędnego NULL hwnd
uchwyt menu/ NULL (HMENU)(y 8 I x)
identyfikator potomka
uchwyt realizacji hInstance (HINSTANCE) GetWindowLong
(hwnd, GWL HINSTANCE)
dodatkowe parametry NULL NULL
Zwykle okna potomne wymagają podania parametrów określających ich wiel-
kość oraz położenie. Jednak w programie CHECKER są one odpowiednio roz-
mieszczane w oknie nadrzędnym w dalszej części funkcji WndProc. W przypad-
ku okna głównego uchwyt okna nadrzędnego ma wartość NULL, ponieważ wła-
śnie to okno jest oknem nadrzędnym. Z kolei parametr ten jest obowiązkowy
w przypadku wywołania funkcji CreateWindow tworzącej okno potomne.
Okno główne nie ma menu, dlatego odpowiadający mu parametr ma wartość
NULL. W przypadku okien potomnych ten sam parametr nosi nazwę "identyfi-
kator potomka" (ang. child ID) lub "identyfikator okna potomnego" (ang. child
window ID). Jest to liczba, dzięki której możliwa jest jednoznaczna identyfikacja
okna potomnego. Identyfikator ten ma większe znaczenie, gdy pracujesz z kon-
.
280 Część I. Podstawy
trolkami umieszczonymi w oknie dialogowym, o czym przekonasz się w rozdziale
11. W programie CHECKER3 zdecydowałem, że identyfikatorem tym będzie liczba
wynikająca ze współrzędnych x i y, które określają położenie każdego okna w ta-
blicy o 5 wierszach i 5 kolumnach w oknie głównym.
Funkcja CreateWindow wymaga podamia uchwytu instancji (realizacji). W funkcji Win-
Main uchwyt ten jest bardzo łatwo dostępny, ponieważ jest jej parametrem. Gdy z kolei
tworzone jest okno potomne, CHECKEIZ3 musi posłużyć się funkcją GetWindowLong,
która umożliwia pobranie wartości hlnstance ze struktury, którą Windows przecho-
wuje dla każdego okna. (Zamiast wy'woływania GetWindowLong mógłbym zdefinio-
wać po prostu zmienną globalną i korzystać bezpośrednio z jej zawartości).
Każde z okien potomnych ma swój własny uchwyt, który przechowywany jest
w tablicy hwndChild. Gdy funkcja WndProc odbierze komurukat WMSIZE, dla
każdego z 25 okien potomnych wywołana zostanie funkcja MoveWindow. Para-
metry tej funkcji pozwalają na określenie lewego górnego rogu okna potomnego
względem obszaru roboczego okna głównego, a także jego wysokości i szeroko-
ści. Możesz również określić, czy zawartość okna potomnego powinna zostać od-
świeżona.
Przyjrzyjmy się teraz ChildWndProc - funkcji okna potomnego. Jest ona odpowie-
dzialna za przetwarzanie komunikatów docierających do każdego z 25 okien
potomnych. Przekazywany do niej parametr hwnd jest uchwytem okna potom-
nego, które odebrało komunikat. Gdy ChildWndProc przetwarza komunikat
WMCREATE (co odbywa się 25 razy, ponieważ tyle jest okien potomnych), wy-
wołuje funkcję SetWindowWord, umieszczając w obszarze danych okna wartość 0.
(Jak z pewnością pamiętasz, zarezerwowaliśmy ten obszar, korzystając z pola
cbWndExtra struktury definiującej klasę okna). Funkcja ChildWndProc poshxguje
się tą wartością, aby przechować aktualny stan prostokąta (zaznaczony lub nie).
Po kliknięciu okna potomnego, w ramach obshzgi komunikatu WMLBUTTON-
DOWN zmieniana jest po prostu wartość tej flagi (z 0 na 1 lub z 1 na 0), a następ-
nie unieważniony zostaje cały obszar okna. Obshzga komunikatu WM PAINT jest
tym razem trywialna, ponieważ wielkość prostokąta, który należy narysować, jest
dokładnie taka sama jak wielkość obszaru roboczego.
Ponieważ zarówno plik z kodem źródłowym, jak i z programem są większe niż
w przypadku programu CHECKERl, nie będę nawet próbował przekonać cię,
że CHECKER3 jest "prostszy". Zwróć jednak uwagę, że nie musimy już wykony-
wać żadnych testów na trafienie! Jeżeli okno potomne w programie CHECKER3
otrzyma jakikolwiek komunikat WM LBUTTONDOWN, oznacza to, że zostało
wybrane i nic więcej nie musi ono wiedzieć.
Okna potomne a klawiatura
Wydaje się, że dodanie do CHECKER3 interfejsu klawiatury będzie ostatnią lo-
giczną rzeczą, którą będziemy mogli zrobić. Jednak tym razem powinniśmy po-
dejść do problemu trochę inaczej. W programie CHECKER2 położenie kursora
myszy mówiło, które okno zostanie zaznaczone, gdy naciśnięty zostanie klawisz
[Spacja]. Ponieważ korzystamy z okien potomnych, możemy w pewnym stopniu
wzorować się na funkcjonowaniu okien dialogowych. Jeżeli okno potomne w oknie
Rozdzial 7: Mysz 281
dialogowym ma fokus, sygnalizuje to za pomocą migającej karetki lub naryso-
wanego wokół siebie przerywanego prostokąta.
Nie zamierzam oczywiście powielać tu wszystkich funkcji okien dialogowych,
które oczywiście są już "zaszyte" gdzieś w systemie. Zamierzam po prostu z grub-
sza symulować w aplikacji zachowanie się okna dialogowego. Gdy będę wyja-
śniał, w jaki sposób należy to zrobić, odkryjesz, że okno główne oraz okna po-
tomne powinny wspólnie obsługiwać klawiaturę. Okno potomne powinno nary-
sować lub usunąć zaznaczenie, jeżeli naciśnięty został klawisz [Spacja] lub [En-
ter]. Z kolei okno główne powinno przenosić fokus z jednego okna potomnego
do następnego, gdy naciśnięty zostanie któryś z klawiszy kursora. Czynnoci te
ulegną jednak pewnej komplikacji, ponieważ kliknięcie okna potomnego spowo-
duje, że fokus otrzyma nie ono, ale okno główne.
Program CHECKER4 przedstawiony został na rysunku 7-8.
CHECKER4.C
/*
CHECKER4.C - Test trafień myszy, wersja 4
(c) Charles Petzold, 1998
*/
include
4idefine DIVISIONS 5
LRESULT CALLBACK WndProc (HWND, UINT. WPARAM, LPARAM) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;
int idFocus = 0 ;
TCHAR szChildClassCJ = TEXT ("Checker4 Child") ;
int WINAPI WinMain (HINSTANCE hInstance, KINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppNameCJ = TEXT ("Checker4") ;
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, IDCARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE-BRUSH)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB-ICONERROR) ;
282 Część I: Podstawy
(ciąg dalszy ze strony 281)
return 0
i
wndclass.lpfnWndProc = ChildWndProc ;
wndclass.cbWndExtra = sizeof (long) ;
wndclass.hIcon = NULL ;
wndclass.lpszClassName = szChildClass ;
RegisterClass (&wndclass) ;
i
hwnd = CreateWindow (szAppName, TEXT ("Checker4 Mouse Hit-Test Demo"),
WS_OVERLAPPEDWINDOW,
CW USEDEFAULT, CW USEDEFAULT, I
- -
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ; i
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static HWND hwndChildCDIVISIONS][DIVISIONS] ;
int cxBlock, cyBlock, x, y ; ,
switch (message)
case WM_CREATE :
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
hwndChild[x]Cy] = CreateWindow (szChildClass, NULL,
WS_CHILDWINDOW WS-VISIBLE,
0, 0, 0, 0, '
hwnd, (HMENU) (y 8 x),
(HINSTANCE) GetWindowLong (hwnd, GWL-HINSTANCE),
NULL) ;
return 0 ;
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ;
- cyBlock = HIWORD (lParam) / DIVISIONS ;
for (x = 0 ; x < DIVISIONS ; x++)
for (y = 0 ; y < DIVISIONS ; y++)
MoveWindow (hwndChild[x][y],
x * cxBlock, y * cyBlock,
cxBlock, cyBlock, TRUE) ;
return 0 ;
case WM-LBUTTONDOWN :
Rozdział 7: Mysz 283
MessageBeep (0) ;
, return 0 ;
// Po odebraniu komunikatu SETFOCUS przenieś fokus
// do okna potomnego
case WM_SETFOCUS:
SetFocus (GetDlgItem (hwnd, idFocus)) ;
return 0 ;
// Po odebraniu komunikatu WM_KEYDOWN, możliwe że należy
// zmienić okno, które ma fokus
case WM_KEYDOWN:
x = idFocus & OxFF ;
y = idFocus Ż 8 ;
i
switch (wParam)
case UK_UP: y-- , break ; '
case UK_DOWN: y++ ; break ;
case VK LEFT: x-- , break ;
case VK RIGHT: x++ ; break ;
case VK HOME: x = y = 0 ; break ;
case VKEND: x = y = DIVISIONS - 1 ; break ;
default: return 0 ;
)
x = (x, + DIVISIONS) 6 DIVISIONS ;
y = (y + DIVISIONS) DIVISIONS ;
idFocus = y 8 x ;
1
SetFocus (GetDlgItem (hwnd, idFocus)) ;
return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ;
return 0 ;
t,
return DefWindowProc (hwnd, message, wParam, lParam) ;
LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message ś
WPARAM wParam, LPARAM lParam)
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
case WM_CREATE :
SetWindowLong (hwnd, 0, 0) ; // flaga wlączona/wyldczona
return 0 ;
"''jr:, ,
case WM_KEYDOWN: i,:
// Wyślij większość komunikatów klawiaturowych
// do okna 9lównego
284 Część 1: Podstawy
(ciąg dalszy ze strony 283)
if (wParam != VK RETURN && wParam != VKSPACE)
SendMessage (GetParent (hwnd), message, wParam, lParam) ;
return 0 ;
) I
// Jeżeli naciśnięto [Enter] lub spację - kontynuacja
// i zaznaczenie lub odznaczenie prostokąta
case WM_LBUTTONDOWN :
SetWindowLong (hwnd, 0, i ^ GetWindowLong (hwnd, 0)) ;
SetFocus (hwnd) ;
InvalidateRect (hwnd, NULL, FALSE) ;
return 0 ;
// Komunikaty zwidzane z fokusem - unieważnienie obszaru
// roboczego wymuszajdce ponowne wyświetlenie
case WM_SETFOCUS:
idFocus = GetWindowLong (hwnd, GWLID) ;
// kontynuacja
case WM KILLFOCUS:
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd. &ps) ;
GetClientRect (hwnd, &rect) ;
Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
// Narysuj znak "x"
if (GetWindowLong (hwnd, 0))
(
MoveToEx (hdc, 0, 0, NULL) ;
LineTo (hdc, rect.right, rect.bottom) ;
MoveToEx (hdc, 0, rect.bottom, NULL) ;
LineTo (hdc, rect.right, 0) ;
// Narysuj prostokdt oznaczajdcy fokus
if (hwnd GetFocus ())
i
rect.left += rect.right / 10 ;
rect.right -= rect.left ;
rect.top += rect.bottom / 10 ;
rect.bottom -= rect.top ;
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
SelectObject (hd.c, CreatePen (PS DASH, 0, 0)) ;
Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
Oelete0bject (Sel.ectObject (hdc, GetStockObject (BLACKPEN))) ;
Rozdziai 7: Mysz 285
EndPaint (hwnd, &ps) ;
return 0 ;
?
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-8. Program CHECKER4
Jak pamiętasz, każde z okien potomnych ma swój własny unikatowy identyfika-
tor, który został zdefiniowany podczas tworzenia okna za pomocą funkcji Create-
Window. W naszym programie jest on związany ze współrzędnymi prostokąta x i
y. Dysponując uchwytem okna, program może pobrać jego identyfikator za po-
mocą:
idChild = GetWindowLong (hwndChild, GWLID) ;
Poruższa funkcja wykonuje dokładnie tę samą operację:
idChild = GetDlgCtrlID (hwndChild) ;
Jak sugeruje nazwa funkcji, jej podstawowym zadaniem jest praca z oknami dia-
logowymi i znajdującymi się w nich kontrolkami. Jeżeli znasz uchwyt okna głów-
nego oraz identyfikator okna potomnego, z łatwością możesz wyznaczyć uchwyt
okna potomnego:
hwndChild = GetDlgItem (hwndParent, idChild) ;
Zmienna globalna idFocus wykorzystywana jest przez program CHECKER4 do
przechowywania identyfikatora tego okna potomnego, które aktualnie ma fokus.
Jak wspomniałem wcześniej, kliknięcie myszą okna potomnego nie powoduje
automatycznie, że otrzyma fokus. Dlatego też okno główne powinno w takiej sy-
tuacji obshzżyć komunikat WMSETFOCUS, wywohxjąc
SetFocus (GetDlgItem (hwnd, idFocus)) ;
co spowoduje ustawienie fokusu wejściowego na wybranym oknie potomnym.
Funkcja ChildWndProc przetwarza zarówno komunikat WM SETFOCUS, jak
i WMKILLFOCUS. W przypadku pojawienia się tego pierwszego, w zmiennej
globalnej idFocus zapamiętany zostaje identyfikator okna potomnego, które otrzy-
mało ten komunikat. Następnie niezależnie od tego, który komunikat został ode-
brany, obszar roboczy okna zostaje unieważniony, co z kolei powoduje wygene-
rowanie komunikatu WMPAINT. Jeżeli w trakcie obsługi tego komunikatu oka-
że się, że odrysowywane okno ma fokus, do narysowania prostokąta wykorzy-
stywane jest pióro PS DASH.
Funkcja ChiIdWndProc przetwarza również komunikat WMKEYDOWN. Jeżeli
nie został on spowodowany naciśnięciem [Spacji] ani [Enter], zostaje odesłany
do okna głównego. W przeciwnym wypadku wykonywane są takie same czyn-
ności jak po odebraniu komunikatu WMLBUTTONDOWN.
Przetwarzanie naciskanych klawiszy kursora przekazane zostało do okna głów-
nego. Podobnie jak miało to miejsce w CHECKER2, program pobiera współrzęd-
ne x i y tego okna potomnego, które ma fokus, a następnie przenosi fokus w spo-
sób zależny od naciśniętego klawisza, korzystając przy tym z funkcji SetFocus.
286 Część I: Podstawy
Przechwytywanie myszy
Zwykle procedura okna otrzymuje komurukaty myszy tylko wtedy, gdy jej kur-
sor znajduje się nad obszarem okna. Jednak w pewnych sytuacjach może się oka-
zać, że program musi również otrzymywać te komunikaty, nawet jeżeli mysz zna-
lazła się poza oknem. W takiej sytuacji możliwe jest przechwycenie (ang. capture)
myszy. Nie bój się, to nie będzie bolało.
Blokowanie prostokąta
Poniższy przykład pomoże ci zrozumieć, dlaczego w pewnych sytuacjach prze-
chwytywanie myszy może okazać się niezbędne. Popatrz na przedstawiony na
rysunku 7-9 program BLOKOUTI. Na pierwszy rzut oka może się wydawać, że
wszystko jest w jak najlepszym porządku. Ma on jednak pewną "wadę".
BLOKOUTl.C
/*
BLOKOUTl.C - Program demonstrujdcy użycie przycisków myszy
(c) Charles Petzold, 1998
*/
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("BlokOutl")
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CSVREDRAW ;
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 (WHITEBRUSH)
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
t !
MessageBox (NULL, TEXT ("Program requires Windows NT!"), !
szAppName, MBICONERROR) ;
return 0 :
)
hwnd = CreateWindow (szAppName, TEXT ("Mouse Button Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW USEDEFAULT, CW USEDEFAULT,
Rozdział 7: Mysz
NULL, NULL, hInstance, NULL) ;
i
ShowWindow (hwnd, kCmdShow) ; ;,
UpdateWindow (hwnd) ;
;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
?
return msg.wParam ;
void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd)
Ii
HDC hdc ;
hdc = GetDC (hwnd) ;
Ij
SetROP2 (hdc, R2_NOT) ;
SelectObject (hdc, GetStockObject (NULLBRUSH)) ; I.:
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ;
ReleaseDC (hwnd, hdc) ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
i static BOOL fBlocking, fValidBox ;
static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
(
case WM_LBUTTONDOWN :
ptBeg.x = ptEnd.x = LOWORD (lParam) ;
ptBeg.y = ptEnd.y = HIWORD (lParam) ;
DrawBoxOutline (hwnd, ptBeg, ptEnd) :
i
i SetCursor (LoadCursor (NULL, IDC CROSS)) ;
fBlocking = TRUE ;
return 0 :
case WM_MOUSEMOVE :
if (fBlocking) ^
SetCursor (LoadCursor (NULL, IDC CROSS)) ;
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ptEnd.x = LOWORD (lParam) ;
ptEnd.y = HIWORD (lParam) ;
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
return 0 ;
,
288 Część I: Podstawy
(ciąg dalszy ze strony 287)
case WM_LBUTTONUP :
if (fBlocking)
i
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ptBoxBeg = ptBeg ;
ptBoxEnd.x = LOWORD (lParam) ;
ptBoxEnd.y = HIWORD (lParam) ;
SetCursor (LoadCursor (NULL, IDC ARROW)) ;
fBlocking = FALSE ;
fValidBox = TRUE ;
InvalidateRect (hwnd, NULL, TRUE) ;
J
return 0 ;
f
case WM_CHAR :
if (fBlocking & (wParam '\x1B')) // tzn. [Esc7
(
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
r
SetCursor (LoadCursor (NULL, IDC ARROW)) ;
fBlocking = FALSE ;
)
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ; ;
if (fValidBox)
i
SelectObject (hdc, GetStockObject (BLACK BRUSH)) ;
Rectangle (hdc, ptBoxBeg.x, ptBoxBeg.y,
ptBoxEnd.x, ptBoxEnd.y) ;
)
if (fBlocking)
SetROP2 (hdc, R2_NOT) ; !
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ; ,
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-9. Program BLOKOLTTI
Rozdział 7: Mysz 289
Program ten pokazuje coś, co może zostać zaimplementowane w dowolnym pro-
gramie graficznym. Zabawę możesz zacząć od naciśnięcia lewego przycisku
myszy, aby zaznaczyć wierzchołek prostokąta. Przesuń następnie mysz. Pomię-
dzy zaznaczonym punktem a aktualnym położeniem kursora naszkicowany zo-
stał prostokąt. Po zwolnieniu przycisku myszy prostokąt zostanie wypełniony.
, Na rysunku 7-10 przedstawiony został jeden gotowy prostokąt oraz jeden w trakcie
1 tworzenia.
Rysunek 7-10. Okno programu BLOKOUT1
W czym problem?
Spróbuj zrobić coś takiego: naciśnij lewy przycisk myszy nad obszarem roboczym
okna programu BLOKOUTI, a następnie przesuń jej kursor poza krawędź okna.
Program nie otrzymuje już komunikatów WM MOUSEMOVE. A teraz zwolnij
przycisk. BLOKOUT1 nie odebrał również komunikatu WMLBUTTONUP, po-
nieważ kursor znalazł się poza obszarem roboczym. A teraz przenieś kursor po-
nownie do wnętrza okna. Procedura okna w dalszym ciągu myśli, że przycisk
jest naciśnięty.
To niedobrze. Program nie wie, o co chodzi.
Przechwycenie
Program BLOKOUTI pokazał jedną z typowych funkcji, jednak implementujący ją
kod ma wadę. Jest to jednak ten rodzaj problemu, do którego rozwiązania wymy-
ślono przechwytywanie myszy. Jeżeli użytkownik przesuwa kursor myszy, to nie
powinno mieć znaczenia, że znalazł on się chwilowo poza obszarem okna. Program
powinien stale kontrolować ruch myszy.
Przechwycenie myszy nie jest czynnością zbyt skomplikowaną. Wystarczy wy-
wołać jedynie
SetCapture (hwnd) ;
Od tego momentu Windows będzie przesyłał wszystkie komunikaty związane
z myszą do procedury tego okna, którego uchwyt przekazany został jako para-
metr funkcji, niezależnie od tego, czy kursor myszy będzie znajdował się nad ob-
290 Część I: Podstawy
szarem roboczym, czy nie. Parametr lParam w dalszym ciągu będzie przechowy-
wał pozycję kursora we współrzędnych obszaru roboczego. Jednakże, jeżeli kur-
sor myszy znajdzie się nad obszarem roboczym lub po lewej jego stronie, jedna
ze współrzędnych (albo obie) będzie miała wartość ujemną. Gdy chcesz uwolnić
kursor myszy, wystarczy wywołać funkcję
ReleaseCapture () ;
Od tego momentu przywrócone zostanie normalne przekazywanie komunikatów
myszy.
W 32-bitowej wersji Windows przechwytywanie myszy jest nieco bardziej restryk-
cyjne niż we wcześniejszych wersjach tego systemu. Szczególnie jeżeli mysz zo-
stała przechwycona, lecz jej przycisk nie jest właśnie wciśnięty, a kursor znajdzie
się nad obszarem innego okna, wtedy właśnie to okno będzie otrzymywało ko-
munikat. Jest to zabieg konieczny, aby nie blokować całego systemu, kiedy je-
den program przechwyci mysz i jej nie zwolni.
Aby uniknąć problemu, twoje programy powinny przechwytywać mysz tylko wte-
dy, gdy nad ich obszarem roboczym został wciśnięty jeden z przycisków. Mysz
powinna zostać zwolniona wraz ze zwolnieniem przycisku.
Program BLOKOUT2
Na rysunku 7-11 przedstawiony został program BLOKOUT2 ilustrujący przechwy-
tywanie myszy.
BLOKOUT2.C
/*
BLOKOUT2.C - Program demonstrujący przyciski
i przechwytywanie myszy
(c) Charles Petzold, 1998
*/
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("Blok0ut2")
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 (WHITEBRUSH)
wndclass.lpszMenuName = NULL ;
Rozdział 7: Mysz 291
wndclass.lpszClassName = szAppName :
if (!Re9isterClass (&wndclass))
(
MessageBox (NULL. TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) :
return 0 :
)
hwnd = CreateWindow (szAppName, TEXT ("Mouse Button & Capture Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) :
UpdateWindow (hwnd) :
while (GetMessage (&ms9, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
void DrawBoxOutling (HWND hwnd, POINT ptBeg, POINT ptEnd)
HDC hdc :
hdc = GetDC (hwnd) ;
SetROP2 (hdc, R2 NOT) :
SelectObject (hdc, GetStockObject (NULLBRUSH)> :
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) :
ReleaseDC (hwnd, hdc) ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static BOOL fBlocking, fUalidBox :
static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
case WM_LBUTTONDOWN :
ptBeg.x = ptEnd.x = LOWORD (lParam) ;
ptBeg.y = ptEnd.y = HIWORD (lParam) ;
DrawBoxOutline (hwnd, ptBeg. ptEnd) :
SetCapture (hwnd) :
SetCursor (LoadCursor (NULL, IDC CROSS)) :
fBlocking = TRUE ;
292 Część I Podstawy
(ciąg dalszy ze strony 291)
return 0 ;
case WM_MOUSEMOVE :
if (fBlocking)
SetCursor (LoadCursor (NULL, IDC CROSS)) ; :
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ptEnd.x = LOWORD (lParam) ;
ptEnd.y = HIWORD (lParam) ;
DrawBoxOutline (hwnd, ptBeg. ptEnd) ;
return 0 ;
case WM_LBUTTONUP :
:
if (fBlocking)
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ptBoxBeg = ptBeg ;
ptBoxEnd.x = LOWORD (lParam) ;
ptBoxEnd.y = HIWORD (lParam) ;
ReleaseCapture () ;
SetCursor (LoadCursor (NULL, IDC ARROW)) ;
fBlocking = FALSE ;
fUalidBox = TRUE ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_CHAR :
if (fBlocking & (wParam = '\x1B')) // tzn. [Esc]
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ReleaseCapture () ;
SetCursor (LoadCursor (NULL, IDC ARROW)) ;
i
fBlocking = FALSE ;
)
i
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ; ;
f
if (fValidBox)
(
SelectObject (hdc, GetStockObject (BLACK BRUSH)) ;
Rectangle (hdc, ptBoxBeg.x, ptBoxBeg.y,
ptBoxEnd.x, ptBoxEnd.y) ;
Rozdzial 7: Mysz 293
if (fBlocking)
(
SetROP2 (hdc, R2_NOT) ;
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y)
EndPaint (hwnd, &ps) ;
return 0 :
case WM DESTROY :
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-11. Program BLOKOUT2
Program BLOKOUT2 różni się od BLOKOUT1 zaledwie kilkoma liniami kodu:
wywołaniem SetCapture w ramach obsługi komunikatu WM LBUTTONDOWN
oraz wywołaniami ReleaseCapture w ramach obsługi WM LBUTTONUP. Możesz
teraz wypróbować wprowadzone zmiany. Zmniejsz okno tak, aby nie zajmowa-
ło całego ekranu i rozpocznij kreślenie prostokąta w obszarze roboczym, a na-
stępnie przemieść kursor poza ten obszar, obojętne do góry czy do dołu, i zwol-
nij przycisk myszy. Program będzie teraz miał wszelkie niezbędne informacje do
narysowania poprawnego prostokąta. Po prostu powiększ okno i sprawdź.
Przechwytywanie myszy nie jest zarezerwowane wyłącznie dla jakichś dziwnych
aplikacji. Powinieneś to robić zawsze, gdy musisz śledzić komunikaty WM MO-
USEMOVE po naciśnięciu przycisku myszy w obszarze roboczym. Przechwyty-
wanie możesz zakończyć dopiero wtedy, gdy przycisk zostanie zwolniony. Nie
dość, że dzięki temu pisane przez ciebie programy będą prostsze, to jeszcze speł-
nią wymagania użytkownika.
Kółko myszy
Pewnego dnia moja matka, nieświadomie parafrazując Emersona, powiedziała:
"Wymyśl lepszą pułapkę na myszy, a cały świat wydepcze ścieżkę do twoich
drzwi". Oczywiście, w naszych czasach większe znaczenie miałoby wynalezie-
nie lepszej myszy.
Funkcja Microsoft IntelliMouse, będąca rozszerzeniem możliwości zwykłej my-
szy, zmaterializowała się w postaci niewielkiego kółka umieszczonego pomiędzy
dwoma przyciskami. Jeżeli poruszysz je, zadziała on w taki sam sposób, jak środ-
kowy przycisk myszy. Jeżeli natomiast obrócisz je palcem wskazującym, wyge-
nerowany zostanie specjalny komunikat WMMOUSEWHEEL. Programy, które
potrafią go wykorzystać, w odpowiedzi przewijają lub powiększają wyświetla-
ny przez siebie dokument. Na pierwszy rzut oka ta innowacja może się wyda-
wać rukomu niepotrzebną sztuczką, muszę jednak przyznać, że bardzo szybko
przyzwyczaiłem się do nowego sposobu przewijania zawartości dokumentu w
programie Microsoft Word lub w Internet Explorer.
294 Część I: Podstawy
Nie zamierzam tu nawet przymierzać się do dyskusji o wszelkich możliwych zale-
tach kółka. Zamiast tego po prostu pokażę ci, w jaki sposób możesz je wykorzystać
do przewijania danych w obszarze roboczym w takim programie jak SYSMETS4.
Na rysunku 7-12 przedstawiona została ostateczna wersja tego programu.
SYSMETS.C
/*
SYSMETS.C - Ostateczna wersja programu wyświetlającego
wymiary elementów graficznych
(c) Charles Petzold, 1998
*/
include
include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("SysMets") ;
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))
i
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics"),
WS_OVERLAPPEDWINDOW WS_VSCROLL WS HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
i
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
Rozdzial 7: Mysz 295
1
return msg.wParam ;
1
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static int cxChar, cxCaps, cyChar. cxClient, cyClient, iMaxWidth ;
static int iDeltaPerLine, iAccumDelta ; // obslugd kólka myszy
HDC hdc ;
int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
PAINTSTRUCT ps ;
SCROLLINFO si ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
ULONG ulScrollLines ; // obsluga kólka myszy
switch (message)
(
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeadin9 ;
ReleaseDC (hwnd, hdc) ;
// Zapamiętaj szerokość trzech kolumn
iMaxWidth = 40 * cxChar + 22 * cxCaps ;
// Kontynuacja - pobranie informacji o kółku
CdSe WM_SETTINGCHANGE:
SystemParametersInfo (SPI GETWHEELSCROLLLINES, 0, &ulScrollLines, 0) ;
i
// ulScrollLines zwykle ma wartość 3 lub 0 (gdy bez przewijania)
// WHEEL DELTA jest równe 120, dlatego iDeltaPerLine bedzie wynosilo 40
if (ulScrollLines)
iDeltaPerLine = WHEEL DELTA / ulScrollLines ;
else
iDeltaPerLine = 0 ;
return 0 ;
case WM_SIZE:
j
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ; '
,.
// Ustaw zakres pionowego paska przewijania oraz
// wielkość strony
si.cbSize = sizeof (si> ;
si.fMask = SIF RANGE SIFPAGE ;
si.nMin = 0 :
4,
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
296 Część I: Podstawy
(ciąg dalszy ze strony 295)
SetScrollInfo (hwnd, SB VERT, &si, TRUE) ;
// Ustaw zakres poziomego paska przewijania oraz wielkość strony
si.cbSize = sizeof (si) ;
si.fMask = SIF RANGE SIFPAGE ;
si.nMin = 0 ;
si.nMax = 2 + iMaxWidth / cxChar ;
si.nPage = cxClient / cxChar ;
SetScrollInfo (hwnd, SB HORZ, &si, TRUE) ;
return 0 ;
case WM_VSCROLL:
// Pobierz wszystkie dane o przesunięciu w pionie
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
GetScrollInfo (hwnd, SB VERT, &si) ;
// Zapamiętaj polożenia dla późniejszego porównania
iVertPos = si.nPos ;
t
switch (LOWORD (wParam))
(
case SB_TOP: '
si.nPos = si.nMin ;
break ;
,.
case SB_BOTTOM:
si.nPos = si.nMax ;
break ;
case SB_LINEUP: `
f
si.nPos -= 1 ;
break ;
case SB_LINEDOWN: '
si nPos += 1 ;
i
break ;
i
i
case SB_PAGEUP: "
si.nPos -= si.nPage ;
break ;
case SB_PAGEDOWN:
si.nPos += si.nPage ;
break ;
case SB_THUMBTRACK:
si nPos = si.nTrackPos ;
break ;
default:
break ;
J
Rozdział7: Mysz
// Ustaw położenie, a następnie przywróć je. Na skutek ustawień
// Windows może to nie być taka sama wartość, jaka została ustawiona.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
GetScrollInfo (hwnd, SB VERT. &si) ;
// Jeżeli położenie uległo zmianie, przesuń zawartość i odśwież
if (si.nPos != iVertPos)
i
ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
NULL, NULL) ;
UpdateWindow (hwnd) ;
)
return 0 ;
case WM HSCROLL:
// Pobierz wszystkie dane o przesunięciu w poziomie
;
si.cbSize = sizeof (si) ;
si.fMask = SIF ALL ;
i,
// Zapamiętaj położenia, aby później porównać
GetScrollInfo (hwnd, SB HORZ, &si) ; "'
iHorzPos = si.nPos ;
switch (LOWORD (wParam))
case SB_LINELEFT:
si nPos -= 1 ;
break ;
case SB_LINERIGHT:
si.nPos += i :
break ;
i
case SB_PAGELEFT:
si.nPos -= si.nPage ;
break ;
case SBPAGERIGHT:
si.nPos += si.nPage ;
break ;
case SB_THUMBPOSITION:
si nPos = si.nTrackPos ;
break ;
default:
break ;
l
// Ustaw położenie. a następnie przywróć je. Na skutek ustawień
// Windows może to nie być taka sama wartość, jaka została ustawiona.
si.fMask = SIF_POS :
SetScrollInfo (hwnd, SB HORZ. &si, TRUE) ;
GetScrollInfo (hwnd, SB HORZ, &si) ;
I
298 Część I: Podstawy
(ciąg dalszy ze strony 297)
i
// Jeżeli polożenie uleglo zmianie, przesuń zawartość okna I
if (si.nPos != iHorzPos)
(
ScrollWindow (hwnd, cxChar * (iNorzPos - si.nPos), 0,
NULL, NULL) ;
)
return 0 ;
case WM KEYDOWN :
switch (wParam)
t
case VK_HOME :
SendMessage (hwnd, WM VSCROLL, SB TOP, 0) ;
break ;
case VK_END :
SendMessage (hwnd, WM VSCROLL, SB BOTTOM, 0) ;
break ;
case VK_PRIOR :
SendMessage (hwnd, WM VSCROLL, SBPAGEUP, 0) ;
break ;
case VK_NEXT :
SendMessage (hwnd, WM VSCROLL, SBPAGEDOWN, 0) ;
break ;
case VK_UP :
SendMessage (hwnd, WM VSCROLL, SBLINEUP, 0) ;
break ;
case VK_DOWN :
SendMessage (hwnd, WM VSCROLL, SBLINEDOWN, 0) ;
break ;
case VK_LEFT :
SendMessage (hwnd, WM HSCROLL, SBPAGEUP, 0) ;
break ;
case VK_RIGHT :
SendMessage (hwnd, WM HSCROLL, SBPAGEDOWN, 0) :
break ;
J
return 0 ;
case WM_MOUSEWHEEL:
if (iDeltaPerLine = 0)
break ;
iAccumDelta += (short) HIWORD (wParam) ; // 120 lub -120
while (iAccumDelta >= iDeltaPerLine)
f
SendMessage (hwnd, WM_VSCROLL, SBLINEUP, 0) ;
iAccumDelta -= iDeltaPerLine ;
Rozdział l: Mysz 299
)
while (iAccumDelta <= -iDeltaPerLine)
(
SendMessage (hwnd, WM_VSCROLL, SBLINEDOWN, 0) ;
iAccumDelta += iDeltaPerLine ;
)
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
// Pobierz położenie pionowego paska przewijania
si.cbSize = sizeof (si) ;
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SBVERT, &si) ;
iVertPos = si.nPos ;
// Pobierz położenie poziomego paska przewijania
GetScrollInfo (hwnd, SB HORZ. &si) ;
iHorzPos = si.nPos ;
// Określ ograniczenia rysowania
iPaintBe9 = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
iPaintEnd = min (NUMLINES - 1,
iVertPos + ps.rcPaint.bottom / cyChar) ;
for (i = iPaintBeg ; i <= iPaintEnd ; i++)
(
x = cxChar * (1 - iHorzPos) ;
y = cyChar * (i - iVertPos) ;
TextOut (hdc, x, y, I
sysmetricsCi].szLabel,
lstrlen (sysmetricsCiJ.szLabel)) ; '
TextOut (hdc, x + 22 * cxCaps, y,
sysmetricsCiJ.szDesc,
lstrlen (sysmetricsCi].szDesc)) ;
SetTextAlign (hdc, TARIGHT T TOP) ;
TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TALEFT T TOP) ;
EndPaint (hwnd, &ps) :
return 0 ;
case WM DESTROY :
PostOuitMessage (0) ;
300 Część I: Podstawy
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 7-12. Program SYSMETS
Obracanie kółka wymusza na systemie wysłanie komunikatu WM-MOUSEWHEEL
do tego okna, które aktualnie ma fokus (a nie do okna, nad którym znajduje się
kursor myszy). Jak zwykle, parametr IParam przechowuje informację o położe-
niu kursora myszy. Jednak współrzędne te podane są względem lewego górnego
rogu ekranu, a nie obszaru roboczego. Również jak zwykle młodsze słowo wPa-
ram przechowuje informację o stanie przycisków myszy oraz klawiszy [Shift] i
[Ctrl] .
Jednak w starszym słowie parametru wParam pojawiła się nowa informacja. Jest to
wartość "delta", która obecnie przyjmuje jedną z dwóch wartości: 120 lub -120 za-
leżnie od tego, czy kółko obracane jest do przodu (to znaczy w kierunku kabla
myszy), czy do tyłu. Wartości 120 i -120 oznaczają, że dokument jest przewijany
odpowiednio o trzy linie do góry lub do dołu. Być może kolejne wersje będą za-
pewniały dokładniejsze stopniowanie, a co za tym idzie wartość delta wynoszą-
ca (na przykład) 40 i -40 będzie oznaczała przewijanie o jeden wiersz.
Aby program był wystarczająco ogólny, w ramach obsługi komunikatów
4VM-CREATE oraz WM SETTINGCHANGE wywoływana jest funkcja System-
Parameterslnfo z parametrem SI GETWHEELSCROLLLINES. Otrzymana w ten
sposób wartość wskazuje, o ile linii należy przewinąć dokument dla delty wyno-
szącej WHEEL DELTA (wartość ta została zdefiniowana w pliku nagłówkowym
WINUSER.H). WHEEL DELTA ma wartość 120, a funkcja SystemParameterslnfo
domyślnie zwróci 3. Tak więc delta odpowiadająca przewinięciu dokumentu o jed-
ną linię wynosi 40. Wartość ta przechowywana jest w iDeltaPerLine.
W ramach obsługi komunikatu WM MOUSEWHEEL, program SYSMETS doda-
je wartość delty do zmiennej statycznej iAccumDelta. Następnie, jeżeli wartość
iAccumDelta jest większa lub równa iDeltaPerLine (albo mniejsza lub równa -iDel-
taPerLine), wygenerowany zostaje komunikat WM VSCROLL z parametrem
SB LINEUP lub SB LINEDOWN. Po każdym wysłaniu tego komunikatu, iAc-
cumDelta jest zmniejszana (albo zwiększana) o iDeltaPerLine.
Co dalej?
W zasadzie pozostało nam jeszcze omówienie tworzenia i dostosowywania do
swoich potrzeb kursorów myszy. Zajmę się tym, a także wstępem do zasobów
Windows w rozdziale 10.


Wyszukiwarka