Sunteți pe pagina 1din 21

Capitolul 6.

Mouse-ul
Mouse-ul este un dispozitiv de indicare cu unul sau mai multe butoane. În ciuda experimentelor făcute cu mai
multe tipuri de dispozitive de indicare, cum ar fi ecranele sensibile la atingere (touch screen) sau creioanele optice
(light pen), mouse-ul (şi alte produse asemănătoare precum bilele - trackballs - folosite pentru calculatoarele
portabile) este singurul dispozitiv care a pătruns pe scară largă pe piaţa calculatoarelor personale.
Lucrurile nu au fost, însă, întotdeauna aşa. Primii dezvoltatori ai sistemului de operare Windows şi-au dat
seama că nu pot să oblige utilizatorii să cumpere un mouse, pentru a utiliza produsul software. Ca urmare, au
considerat mouse-ul un accesoriu opţional şi au furnizat o interfaţă cu tastatura pentru toate operaţiile din
Windows şi din aplicaţiile livrate împreună cu acesta, lucru pe care l-au făcut şi alţi dezvoltatori.
Deşi mouse-ul a devenit un dispozitiv aproape universal pentru calculatoarele pe care rulează Windows, eu
cred încă în acest principiu. Cei care se descurcă foarte bine cu tastatura (şi în special dactilografele) preferă să
ţină mâinele deasupra tastaturii şi presupun că multe persoane au avut surpriza de a „pierde" uneori mouse-ul pe
un birou aglomerat. Din aceste motive, eu încă mai recomand - atunci când este posibil - includerea în program a
unei interfeţe cu tastatura, care să dubleze funcţiile mouse-ului.
Elemente de bază despre mouse
Windows 95 permite folosirea mouse-ului cu un buton, cu două butoane sau cu trei butoane, precum şi
folosirea unui joystick sau a unui creion optic, pentru simularea unui mouse cu un buton. Deoarece mouse-ul cu un
singur buton reprezintă un numitor comun, mulţi programatori Windows au evitat multă vreme să folosească
celelalte butoane ale mouse-ului. Totuşi, mouse-ul cu trei butoane a devenit un standard de facto, aşa că reticenţa
tradiţională privind folosirea celui de-al doilea buton nu mai este justificată. Al doilea buton al mouse-ului este
recomandat pentru apelarea „meniurilor de context" - meniuri care apar în fereastră în afara barei de meniu - sau
pentru operaţii speciale de tragere (despre care vom discuta în curând). Puteţi să determinaţi dacă mouse-ul este
prezent folosind funcţia GetSystemMetrics:
fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;

Variabila fMouse va avea valoarea TRUE (diferită de zero) dacă mouse-ul este instalat. Pentru determinarea
numărului de butoane ale mouse-ului instalat, folosiţi tot funcţia GetSystemMetrics:
cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;

Această funcţie returnează valoarea 0 dacă mouse-ul nu este instalat.


Utilizatorii care folosesc mâna stângă pot să inverseze butoanele mouse-ului folosind Panoul de control din
Windows. Deşi o aplicaţie poate să determine dacă butoanele mouse-ului au fost inversate apelând funcţia
GetSystemMetrics cu parametrul SM_SWAPBUTTON, de obicei acest lucru nu este necesar. Butonul apăsat cu
indexul este considerat butonul din stângă al mouse-ului, chiar dacă fizic se află în partea dreaptă a mouse-ului.
Totuşi, dacă scrieţi un program de instruire în care desenaţi un mouse pe ecran, poate că veţi avea nevoie să ştiţi
dacă butoanele au fost inversate.
Câteva scurte definiţii
Atunci când utilizatorul deplasează mouse-ul, Windows deplasează pe ecran o mică imagine bitmap numită
„indicator". Indicatorul are o „zonă senzitivă" cu dimensiunea de un pixel, care indică o poziţie precisă pe ecran.
Driverul de afişare conţine câteva indicatoare de mouse predefinite, care pot fi folosite de programe.
Indicatorul cel mai obişnuit este săgeata oblică definită în fişierele antet din Windows cu numele IDC_ARROW.
Zona senzitivă a acestui indicator este vârful săgeţii. Indicatorul IDC_CROSS (folosit în programele BLOKOUT
dm acest capitol) are zona senzitivă situată la intersecţia unor linii subţiri. Indicatorul IDC_WAIT are forma unei
clepsidre şi indică faptul că programul execută o operaţie de durată. Programatorii pot să creeze indicatoare
proprii. Indicatorul predefinit al unei ferestre este specificat la definirea structurii clasei de ferestre. De exemplu:
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

Termenii următori descriu acţiunile care pot fi executate cu mouse-ul:


 Clic - apăsarea şi eliberarea unui buton al mouse-ului.
 Dublu clic - apăsarea şi eliberarea unui buton al mouse-ului de două ori, într-o succesiune rapidă.
 Tragere - mutarea mouse-ului ţinând apăsat unul dintre butoane.
În cazul unui mouse cu trei butoane, acestea sunt desemnate: butonul din stângă, butonul din mijloc şi butonul
din dreapta. Identificatorii definiţi în fişierele antet din Windows în legătură cu mouse-ul folosesc abrevierile

Charles Petzold Page 1 13:30 26.02.2022 1


LBUTTON, MBUTTON şi RBUTTON. Mouse-ul cu două butoane are numai butonul din stânga şi butonul din
dreapta. Mouse-ul cu un singur buton are numai butonul din stânga.
MESAJE GENERATE DE MOUSE ÎN ZONA CLIENT
În capitolul anterior aţi văzut că Windows trimite mesaje de la tastatură numai ferestrei care deţine cursorul de
intrare. În cazul mouse-ului, lucrurile se petrec altfel:
o procedură de fereastră primeşte mesaje de la mouse de fiecare dată când indicatorul mouse-ului trece pe
deasupra ferestrei sau când se execută clic în fereastra respectivă, chiar dacă fereastra nu este activă sau nu deţine
cursorul de intrare. În Windows sunt definite 21 de mesaje generate de mouse. Totuşi, 11 dintre aceste mesaje nu
se referă la zona client (le vom numi de aici înainte „mesaje non-client") şi de obicei sunt ignorate de programele
Windows.
Atunci când mouse-ul este deplasat peste zona client a unei ferestre, procedura de fereastră primeşte mesajul
WM_MOUSEMOVE. Dacă un buton al mouse-ului este apăsat sau eliberat în zona client a unei ferestre,
procedura de fereastră primeşte următoarele mesaje:
Buton Apăsat Eliberat Apăsat (al doilea clic)
Stânga WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK
Mijloc WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK
Dreapta WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCLK
Procedura de fereastră primeşte mesaje „MBUTTON" numai de la un mouse cu trei butoane şi mesaje
„RBUTTON" numai de la un mouse cu două sau cu trei butoane. Procedura de fereastră primeşte mesaje
„DBLCLK" numai în cazul în care clasa de ferestre a fost definită astfel încât să recepţioneze aceste mesaje (aşa
cum vom vedea mai jos).
Pentru toate mesajele, parametrul lParam conţine poziţia mouse-ului. Cuvântul mai puţin semnificativ conţine
coordonata pe axa x, iar cuvântul mai semnificativ conţine coordonata pe axa y, relative la colţul din stânga-sus al
zonei client a ferestrei. Puteţi să extrageţi coordonatele pe axele x şi y din parametrul lParam folosind
macroinstrucţiunile LOWORD şi HIWORD, definite în fişierele antet din Windows. Valoarea parametrului
wParam indică starea butoanelor mouse-ului şi starea tastelor Shift şi Ctrl. Puteţi să testaţi parametrul wParam
folosind o serie de măşti definite în fişierele antet din Windows. Prefixul MK vine de la „mouse key" („tastă de
mouse").
MK_LBUTTON Butonul din stânga al mouse-ului este apăsat
MK_MBUTTON Butonul din mijloc al mouse-ului este apăsat
MK_RBUTTON Butonul din dreapta al mouse-ului este apăsat
MK_SHIFT Tasta Shift este apăsată
MK_CONTROL Tasta Ctrl este apăsată
Atunci când deplasaţi indicatorul mouse-ului peste zona client a unei ferestre nu se generează un mesaj
WM_MOUSEMOVE pentru fiecare pixel peste care trece indicatorul. Numărul mesajelor WM_MOUSEMOVE
depinde de componentele hardware ale mouse-ului şi de viteza cu care procedura de fereastră poate să prelucreze
aceste mesaje. Vă veţi forma o idee despre rata de transmitere a mesajelor WM_MOUSEMOVE atunci când veţi
experimenta programul CONNECT descris mai jos.
Dacă executaţi clic cu butonul din stânga al mouse-ului în zona client a unei ferestre inactive, Windows
activează fereastra respectivă şi îi transmite un mesaj WM_LBUTTONDOWN. Atunci când procedura de
fereastră primeşte un mesaj WM_LBUTTONDOWN puteţi să fiţi sigur că fereastra respectivă este activă. Totuşi,
procedura de fereastră poate să primească un mesaj WM_LBUTTONUP fără să fi primit mai întâi un mesaj
WM_LBUTTONDOWN. Acest lucru se întâmplă dacă utilizatorul apasă butonul din stânga al mouse-ului într-o
fereastră, mută indicatorul mouse-ului în fereastra programului dumneavoastră şi apoi eliberează butonul. La fel,
procedura de fereastră poate să primească un mesaj WM_LBUTTONDOWN fără să primească mesajul
WM_LBUTTONUP corespunzător atunci când butonul mouse-ului este eliberat după ce indicatorul a fost mutat în
altă fereastră.
Există două excepţii de la aceste reguli:
 O procedură de fereastră poate să „captureze mouse-ul" şi să continue să primească mesajele transmise de
acesta, chiar dacă indicatorul se află în afara zonei client.
 Dacă pe ecran este deschisă o casetă de mesaje modală sau una modală de sistem, nici un alt program nu
poate să primească mesaje de la mouse. Casetele de mesaje modale de sistem şi casetele de dialog modale de
sistem nu permit trecerea într-un alt program cât timp sunt active. (O astfel de casetă de mesaje modală de
sistem este afişată atunci când încheiaţi o sesiune de lucru în Windows.)

Charles Petzold Page 2 13:30 26.02.2022 2


Operaţii simple de prelucrare a mesajelor de la mouse: exemplu
Programul CONNECT- prezentat în Figura 6.1 execută operaţii simple de prelucrare a mesajelor de la mouse,
pentru a vă ajuta să vă formaţi o idee despre modul în care Windows trimite programului dumneavoastră mesajele
generate de mouse.

Charles Petzold Page 3 13:30 26.02.2022 3


/*-------------------------------------------------- LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg,
WPARAM wParam, LPARAM lParam)
CONNECT.C -- Connect-the-Dots Mouse Demo Program {
static POINT points[MAXPOINTS] ;
(c) Charles Petzold, 1996 static int iCount ;
HDC hdc ;
--------------------------------------------------*/
PAINTSTRUCT ps ;
int i, j ;
#include <windows.h>
switch (iMsg)
#define MAXPOINTS 1000
{
#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL)
case WM_LBUTTONDOWN :
iCount = 0 ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
case WM_MOUSEMOVE :
PSTR szCmdLine, int iCmdShow)
if (wParam & MK_LBUTTON && iCount < 1000)
{
{
static char szAppName[] = "Connect" ;
points[iCount ].x = LOWORD (lParam) ;
HWND hwnd ;
points[iCount++].y = HIWORD (lParam) ;
MSG msg ;
WNDCLASSEX wndclass ;
hdc = GetDC (hwnd) ;
SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L) ;
wndclass.cbSize = sizeof (wndclass) ;
ReleaseDC (hwnd, hdc) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
}
wndclass.lpfnWndProc = WndProc ;
return 0 ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
case WM_LBUTTONUP :
wndclass.hInstance = hInstance ;
InvalidateRect (hwnd, NULL, FALSE) ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
return 0 ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground= (HBRUSH) GetStockObject(WHITE_BRUSH) ;
case WM_PAINT :
wndclass.lpszMenuName = NULL ;
hdc = BeginPaint (hwnd, &ps) ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
RegisterClassEx (&wndclass) ;
for (i = 0 ; i < iCount - 1 ; i++)
hwnd = CreateWindow (szAppName, "Connect-the-Points Mouse Demo",
for (j = i + 1 ; j < iCount ; j++)
WS_OVERLAPPEDWINDOW,
{
CW_USEDEFAULT, CW_USEDEFAULT,
MoveTo (hdc, points[i].x, points[i].y) ;
CW_USEDEFAULT, CW_USEDEFAULT,
LineTo (hdc, points[j].x, points[j].y) ;
NULL, NULL, hInstance, NULL) ;
}
ShowWindow (hwnd, iCmdShow) ;
ShowCursor (FALSE) ;
UpdateWindow (hwnd) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
while (GetMessage (&msg, NULL, 0, 0))
EndPaint (hwnd, &ps) ;
{
return 0 ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
case WM_DESTROY :
}
PostQuitMessage (0) ;
return msg.wParam ;
return 0 ;
}
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}

Charles Petzold Page 4 13:30 26.02.2022 4


Figura 6-1. Programul CONNECT.
Programul CONNECT prelucrează trei mesaje generate de mouse:
 WM_LBUTTONDOWN - CONNECT şterge zona client a ferestrei.
 WM_MOUSEMOVE - Dacă butonul din stânga este apăsat, CONNECT desenează în poziţia
indicatorului un punct negru.
 WM_LBUTTONUP - CONNECT conectează fiecare punct desenat în zona client cu toate celelalte
puncte desenate. Uneori rezultatul este un desen drăguţ, alteori doar o mâzgăleală foarte densă (vezi
Figura 6-2).
Pentru folosirea programului CONNECT mutaţi indicatorul mouse-ului în zona client a ferestrei, apăsaţi
butonul din stânga, deplasaţi indicatorul puţin şi eliberaţi butonul apăsat. Rezultatele cele mai frumoase se obţin
dacă deplasaţi indicatorul rapid pe traiectoria unei curbe, cât timp butonul din stânga este apăsat, CONNECT
foloseşte câteva funcţii simple ale interfeţei GDI (Graphics Device Interface). Funcţia SetPixel desenează un punct
cu dimensiunea de un pixel cu o anumită culoare - negru în acest caz. (Pe ecranele cu rezoluţie mare acest punct
este abia vizibil.) Pentru desenarea liniilor sunt folosite alte două funcţii: funcţia MoveTo marchează coordonatele
x şi y ale ale începutului liniei iar funcţia LineTo desenează linia. (Remarcaţi faptul că am definit funcţia MoveTo
ca o macroinstrucţiune folosind funcţia MoveToEx.)
Dacă mutaţi indicatorul mouse-ului în afara zonei client înainte de a elibera butonul din stânga, programul
CONNECT nu mai conectează punctele, deoarece nu primeşte mesajul WM_LBUTTONUP. Dacă mutaţi
indicatorul mouse-ului înapoi în zona client a programului şi apăsaţi din nou butonul din stânga, CONNECT
şterge zona client. (Dacă vreţi să continuaţi un desen început, după ce eliberaţi butonul mouse-ului în afara zonei
client, apăsaţi din nou butonul în afara zonei client, mutaţi indicatorul în fereastra programului şi eliberaţi butonul
mouse-ului.)
Programul CONNECT stochează cel mult 1000 de puncte. Numărul de linii desenate este egal cu Px(P-1),
unde P este numărul de puncte. Pentru 1000 de puncte rezultă aproape 500.000 de linii, pentru desenarea cărora
este nevoie de aproximativ cinci minute. Deoarece Windows 95 este un mediu cu multitasking controlat, în acest
timp puteţi să treceţi la alt program. Totuşi, până la terminarea operaţiei de desenare nu puteţi să faceţi nimic cu
programul CONNECT (de exemplu, nu puteţi să mutaţi sau să redimensionaţi fereastra programului). În Capitolul
14 vom examina câteva soluţii pentru această problemă.

Figura 6.2. Fereastra afişată de programul CONNECT.


Deoarece desenarea liniilor poate să dureze destul de mult, programul CONNECT schimbă forma
indicatorului într-o clepsidră, revenind la forma normală înainte de a termina prelucrarea mesajului WM_PAINT.
Pentru aceasta este nevoie să apelaţi de două ori funcţia SetCursor, folosind două cursoare de stoc. De asemenea,
CONNECT apelează de două ori funcţia ShowCursor, prima dată cu parametrul TRUE şi a doua oară cu
parametrul FALSE.

Charles Petzold Page 5 13:30 26.02.2022 5


Dacă programul este ocupat cu desenarea liniilor, puteţi să apăsaţi butonul mouse-ului, să mutaţi mouse-ul şi
să eliberaţi butonul mouse-ului - nu se va întâmpla nimic. Programul CONNECT nu primeşte aceste mesaje,
deoarece este ocupat şi nu apelează funcţia GetMessage. După ce termină de desenat liniile, mesajele nu mai sunt
valabile, deoarece butonul din stânga al mouse-ului a fost deja eliberat. Dm acest punct de vedere mouse-ul nu
funcţionează la fel ca tastatura. Windows consideră importantă fiecare acţionare de tastă. Spre deosebire de
acestea, dacă butonul mouse-ului este apăsat şi eliberat în zona client a unui program ocupat cu o altă operaţie,
mesajele generate de mouse sunt anulate.
Faceţi următorul experiment: cât timp programul CONNECT este ocupat cu o operaţie de desenare
îndelungată ţineţi apăsat butonul din stânga al mouse-ului şi mişcaţi mouse-ul. După ce programul termină
desenul, va prelua din coada de aşteptare mesajul WM_LBUTTONDOWN (şi va şterge zona client) deoarece
butonul este încă apăsat. Totuşi, programul va recepţionă numai mesajele WM_MOUSEMOVE generate după
preluarea mesajului WM_LBUTTONDOWN.
Uneori, pentru modul în care programul tratează mişcările mouse-ului se foloseşte termenul „urmărire"
(„tracking"). Aceasta nu înseamnă că programul intră în procedura de fereastră într-un ciclu încercând să
urmărească mişcările mouse-ului pe ecran. Procedura de fereastră prelucrează fiecare mesaj generat de mouse şi
cedează controlul.
Prelucrarea tastelor de modificare
Atunci când recepţionează un mesaj WM_MOUSEMOVE, programul CONNECT face o operaţie ŞI pe biţi
între parametrul wParam al mesajului şi masca de biţi MK_LBUTTON ca să determine dacă butonul din stânga
este apăsat. Puteţi să folosiţi parametrul wParam ca să determinaţi starea tastelor de modificare (Shift şi Ctrl). De
exemplu, dacă prelucrarea mesajelor depinde de starea tastelor Shift şi Ctrl, puteţi să folosiţi o secvenţă de cod
asemănătoare cu aceasta:
if (MK_SHIFT & wParam)
if (MK_CONTROL & wParam)
{
[Tastele Shift şi Ctrl sunt apăsate]
}
else
{
[Tasta Shift este apăsata]
}
else if (MK_CONTROL&wParam)
{
[Tasta Ctrl este apăsata]
}
else
{
[Tastele Shift şi Ctrl nu sunt apăsate]
}

Dacă în program sunt folosite atât butonul din stânga cât şi cel din dreapta al mouse-ului şi vreţi să permiteţi
folosirea programului şi de către utilizatorii care folosesc un mouse cu un singur buton, puteţi să scrieţi codul
astfel încât apăsarea tastei Shift în combinaţie cu butonul din stânga să fie echivalentă cu butonul din dreapta. În
acest caz, codul de prelucrare a mesajelor generate de mouse pentru acţionarea buloanelor ar trebui să arate astfel:
case WM_ LBUTTONDOWN
if (!MK_SHIFT & wParam)
{
[codul pentru butonul din stânga al mouse-ulul]
return 0 ;
}
// trece la următoarea secvenţa case, deoarece nu am folosit instrucţiunea break
case WM_RBUTTONDOMN
[codul pentru butonul din dreapta al mouse-ului]
return 0 ;

Şi funcţia Windows GetKeyState (descrisă în Capitolul 4) returnează starea butoanelor şi a tastelor de


modificare, dacă folosiţi codurile virtuale VK_LBUTTON, VK.RBUTTON, VK_MBUTTON, VK.SHIFT şi
VK_CONTROL. Butonul sau tasta sunt apăsate dacă valoarea returnată de funcţia GetKeyState este negativă.
Deoarece funcţia GetKeyState returnează starea butoanelor sau a tastelor pentru mesajul prelucrat, informaţiile de
stare sunt sincronizate cu mesajul respectiv. Dar aşa cum nu puteţi să folosiţi funcţia GetKeyState pentru o tastă
care urmează să fie apăsată, nu puteţi să o folosiţi nici ca să aşteptaţi apăsarea unui buton. Nu faceţi aşa ceva:
while (GetKeyState (VK_LBUTTON) >= 0) ; // Greşit !!!

Charles Petzold Page 6 13:30 26.02.2022 6


Funcţia GetKeyState va raporta că butonul din stânga al mouse-ului este apăsat numai dacă butonul era deja
apăsat atunci când a fost generat mesajul în timpul prelucrării căruia apelaţi funcţia GetKeyState.
Dublu clic cu mouse-ul
Dublu clic înseamnă să executaţi de două ori clic, într-o secvenţă rapidă. Pentru a se considera că s-a executat
dublu clic, cele două clicuri trebuie să fie executate într-un interval de timp numit „interval de dublu clic" („double
click time"). Dacă vreţi ca procedura de fereastră să primească mesajele generate de dublu clic, trebuie să includeţi
identificatorul CS_DBLCLKS în stilul ferestrei, atunci când definiţi structura clasei de ferestre, înaintea apelării
funcţiei RegisterClassEx:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS

Dacă în stilul ferestrei nu este inclus identificatorul CS_DBLCLKS şi utilizatorul execută de două ori clic pe
butonul din stânga al mouse-ului într-o succesiune rapidă, procedura de fereastră va recepţionă următoarele
mesaje: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDOWN şi WM_LBUTTONUP. (Între
aceste mesaje s-ar putea ca procedura de fereastră să primească şi alte mesaje.) Dacă vreţi să implementaţi o logică
proprie pentru interpretarea mesajelor care reprezintă un dublu clic puteţi să folosiţi funcţia Windows
GetMessageTime ca să obţineţi timpul relativ de generare a mesajelor WM_LBUTTONDOWN. Despre această
funcţie vom discuta în detaliu în Capitolul 7.
Dacă în stilul ferestrei este inclus identificatorul CS_DBLCLKS, procedura de fereastră va recepţionă
următoarele mesaje: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK si
WM_LBUTTONUP. Mesajul WM_LBUTTONDBLCLK înlocuieşte al doilea mesaj WM_LBUTTONDOWN.
Prelucrarea mesajelor generate de dublu clic este mai simplă dacă primul dintre cele două clicuri declanşează
executarea aceloraşi acţiuni ca şi un clic simplu. Al doilea clic (care generează mesajul
WM_LBUTTONDBLCLK) poate să determine execuţia unor operaţii suplimentare faţă de primul clic. Ca
exemplu, gândiţi-vă la modul în care este folosit mouse-ul pentru lista de fişiere afişată de Windows Explorer. Un
singur clic selectează fişierul; Windows Explorer marchează fişierul respectiv afişându-i numele în mod video
invers. Un dublu clic execută două acţiuni: primul clic selectează fişierul, aşa cum face şi un clic simplu; al doilea
clic cere programului Windows Explorer să execute fişierul selectat. O logică destul de simplă. Codul de tratare a
mesajelor generate de mouse se complică dacă primul clic dintr-o serie de două nu execută aceleaşi operaţii ca şi
un clic simplu.
Mesaje generate de mouse în afara zonei client
Cele zece mesaje discutate până acum sunt generate atunci când mouse-ul este deplasat sau când se execută
clic în cadrul zonei client a unei ferestre. Dacă mouse-ul se află în afara zonei client, dar se află încă în fereastră,
Windows trimite procedurii de fereastră un mesaj „non-client". Zona „non-client" include bara de titlu, meniul şi
barele de derulare ale ferestrei.
În general nu este nevoie să prelucraţi mesajele generate în afara zonei client. Le transmiteţi pur şi simplu
funcţiei DefWindowProc pentru a permite sistemului de operare Windows să execute funcţiile de sistem
corespunzătoare. Din acest punct de vedere, mesajele „non-client" sunt asemănătoare cu mesajele de tastatură
WM_SYSKEYDOWN, WM_SYSKEYUP şi WM_SYSCHAR.
Mesajele generate de mouse în afara zonei client corespund mesajelor din zona client, dar includ caracterele
„NC" (de la „non-client"). Dacă mouse-ul este deplasat în afara zonei client a unei ferestre, procedura de fereastră
primeşte următoarele mesaje:

Buton Apăsat Eliberat Apăsat (al doilea clic)


Stânga WM_NCLBUTTON DOWN WM_NCLBUTTONUP WM_NCLBUTTONDBLCLK
Mijloc WM_NCMBUTTONDOWN WM_NCMBUTTONUP WM_NCMBUTTONDBLCLK
Dreapta WM_NCRBUTTONDOWN WM_NCRBUTTONUP WM_NCRBUTTONDBLCLK
Totuşi, parametrii wParam şi lParam pentru mesajele generate de mouse din afara zonei client sunt diferite de
cele generate din zona client. Parametrul wParam indică zona non-client din care a fost generat mesajul.
Parametrul wParam poate conţine unul dintre identificatorii cu prefixul HT („hit test") definiţi în fişierele antet din
Windows.
Variabila lParam conţine coordonata pe axa x în cuvântul mai puţin semnificativ şi coordonata pe axa y în
cuvântul mai semnificativ. Totuşi, aceste coordonate sunt relative la ecran, nu la zona client. Pentru coordonatele
de ecran, punctul de origine (0,0) este colţul din stânga-sus al zonei de afişare a ecranului. Valorile coordonatei x
cresc către dreapta, iar valorile coordonatei y cresc în jos (vezi Figura 6.3).

Charles Petzold Page 7 13:30 26.02.2022 7


Puteţi să transformaţi coordonatele ecranului în coordonate ale zonei client şi invers, folosind două funcţii
Windows:
ScreenToClient (hwnd, pPoint) ;
ClientToScreen (hwnd, pPoint) ;
Parametrul pPoint este un pointer la o structură de tip POINT. Aceste funcţii transformă valorile stocate în
structura transmisă ca parametru fără să păstreze vechile valori. Remarcaţi faptul că dacă un punct se află deasupra
zonei client a unei ferestre, în urma transformării coordonatelor de ecran în coordonate ale zonei client, valoarea
coordonatei pe axa y va fi negativă. Similar, dacă un punct se află în stânga zonei client a unei ferestre, în urma
transformării coordonatelor de ecran în coordonate ale zonei client, valoarea coordonatei pe axa x va fi negativă.
Mesajul de testare a poziţiei
Dacă aţi ţinut socoteala, am discutat până acum despre 20 dintre cele 21 de mesaje generate de mouse.
Ultimul mesaj este WM_NCHITTEST („non client hit test") folosit pentru verificarea poziţiei din care a fost
generat mesajul. Acest mesaj precede toate celelalte mesaje generate de mouse, din zona client sau din afara
acesteia. Parametrul IParam al mesajului conţine coordonatele x şi y ale indicatorului de mouse. Parametrul
wParam nu este folosit.

Figura 6-3. Coordonatele ecranului şi coordonatele zonei client.


De obicei, aplicaţiile Windows transmit acest mesaj funcţiei DefWindowProc. Windows foloseşte mesajul
WM_NCHITTEST ca să genereze celelalte mesaje, în funcţie de poziţia mouse-ului. Pentru mesajele din afara
zonei client, valoarea returnată de funcţia DefWindowProc în urma prelucrării mesajului WM_NCHITTEST
devine parametrul wParam al mesajului generat. Această valoare poate fi oricare dintre valorile wParam care
însoţesc mesajele generate de mouse din afara zonei client, plus următoarele:
HTCLIENT Zona client
HTNOWHERE Nici o fereastră
HTTRANSPARENT O fereastră acoperită de o altă fereastră
HTERROR Determină funcţia DefWindowProc să emită un semnal sonor
Dacă funcţia DefWindowProc generează un mesaj HTCLIENT în urma prelucrării mesajului
WM_NCHITTEST, Windows transformă coordonatele ecran în coordonate ale zonei client şi generează un mesaj
pentru zona client.
Dacă vă amintiţi cum am dezactivat toate funcţiile de sistem activate de la tastatură, prin interceptarea
mesajului WM_SYSKEYDOWN, probabil vă întrebaţi dacă puteţi să faceţi ceva asemănător şi prin interceptarea
mesajelor generate de mouse. Desigur, puteţi face acest lucru incluzând în procedura de fereastră liniile:
case WM_NCHITTEST
return (LRESULT) HTNOWHERE ;

Charles Petzold Page 8 13:30 26.02.2022 8


Veţi dezactiva toate mesajele de mouse trimise procedurii de fereastră pentru zona client şi pentru porţiunile
din afara zonei client. Butoanele mouse-ului nu vor mai funcţiona cât timp indicatorul mouse-ului se află în
fereastra dumneavoastră, inclusiv deasupra pictogramei meniului sistem, a butoanelor de redimensionare şi a
butonului pentru închiderea ferestrei.
Mesajele generează mesaje
Windows foloseşte mesajul WM_NCHITTEST ca să genereze alte mesaje de mouse. Ideea mesajelor care
generează alte mesaje este des întâlnită în Windows. După cum ştiţi, dacă executaţi dublu clic pe pictograma
meniului sistem a unui program Windows, execuţia acestuia se încheie. Executarea unui dublu clic generează o
serie de mesaje WM_NCHITTEST. Deoarece indicatorul mouse-ului se află deasupra pictogramei meniului
sistem, funcţia DefWindowProc returnează valoarea HTSYSMENU şi Windows inserează în coada de aşteptare a
programului un mesaj WM_NCLBUTTONDBLCLK care conţine în parametrul wParam valoarea
HTSYSMENU.
De obicei, procedura de fereastră retransmite acest mesaj funcţiei DefWindowProc. Atunci când recepţionează
mesajul cu parametrul wParam egal cu HTSYSMENU, funcţia DefWindowProc inserează în coada de aşteptare
un mesaj WM_SYSCOMMAND cu parametrul wParam egal cu SC_CLOSE. (Acest mesaj
WM_SYSCOMMAND este generat şi atunci când utilizatorul selectează opţiunea Close din meniul sistem.) Din
nou, procedura de fereastră transmite acest mesaj funcţiei DefWmdowProc. Funcţia DefWindowProc îl prelucrează
trimiţând procedurii de fereastră mesajul WM_CLOSE.
Dacă doriţi ca programul să ceară confirmarea utilizatorului înainte de terminare, procedura de fereastră
trebuie sunt intercepteze mesajul WM_CLOSE. În caz contrar, funcţia DefWmdowProc prelucrează mesajul
WM_CLOSE apelând funcţia DestroyWindow. Printre alte acţiuni, funcţia DestroyWindow trimite procedurii de
fereastră un mesaj WM_DESTROY. În mod normal, procedura de fereastră prelucrează mesajul WM.DESTROY
astfel:
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
Funcţia PostQuitMessage determină sistemul de operare să insereze în coada de mesaje un mesaj WM_QUIT.
Acest mesaj nu ajunge niciodată la procedura de fereastră, deoarece determină funcţia GetMessage să returneze
valoarea 0, care încheie ciclul de tratare a mesajelor.
Verificarea poziţiei în programele Dumneavoastră
Am discutat mai devreme despre modul în care programul Windows Explorer răspunde la executarea unui
clic sau a unui dublu clic. Evident, programul trebuie să determine fişierul indicat cu ajutorul mouse-ului. Această
operaţie se numeşte „verificarea poziţiei" („hit-testing"). Aşa cum funcţia DefWmdowProc execută unele operaţii
de testare a poziţiei în timpul prelucrării mesajului WM_NCHITTEST, deseori procedura de fereastră trebuie să
facă unele operaţii de verificare a poziţiei în zona client. În general, testarea poziţiei implică unele calcule pe baza
coordonatelor x şi y transmise procedurii de fereastră prin parametrul lParam al mesajului de mouse.
Exemplu
Iată un exemplu. Să presupunem că programul dumneavoastră afişează pe mai multe coloane fişiere aranjate
alfabetic. Lista de fişiere începe în partea de sus a zonei client, care are lăţimea cxClient si înălţimea cyClient -
dimensiuni măsurate în pixeli; fiecare caracter are înălţimea cyChar. Numele de fişiere sunt stocate într-o matrice
ordonată de pointeri, numită szFileNames.
Să presupunem că fiecare coloană are lăţimea cxColWidth. Numărul de fişiere care poate fi afişat în fiecare
coloană este:
iNumInCol = cyClient/cyChar ;

Recepţionaţi un mesaj generat de un clic de mouse cu coordonatele cxMouse şi cyMouse. Puteţi să determinaţi
coloana indicată de utilizator folosind formula:
iColumn = cxMouse/cxColWidth ;

Poziţia fişierului relativ la partea de sus a coloanei este:


iFromTop = cyMouse/cyChar ;

Acum puteţi să calculaţi un index în matricea szFileNames:


iIndex = iColumn * iNumlnCol + iFromTop ;

Charles Petzold Page 9 13:30 26.02.2022 9


Evident, dacă valoarea iIndex depăşeşte numărul de fişiere din matrice, înseamnă că utilizatorul a executat
clic pe o zonă liberă de pe ecran.
În multe situaţii operaţiile de testare a poziţiei sunt mai complexe decât sugerează acest exemplu. Ele pot
deveni de-a dreptul încurcate într-un procesor de texte (cum ar fi WORDPAD) care foloseşte fonturi de
dimensiuni variabile. Atunci când afişaţi ceva în zona client, trebuie să determinaţi coordonatele fiecărui element
afişat. În calculele de verificare a poziţiei trebuie să porniţi de la coordonatele obiectului. Totuşi daca obiectul este
un şir de caractere, această operaţie implică determinarea poziţiei caracterului în şir.

Un program de exemplificare
Programul CHECKER1, prezentat în Figura 6-4, ilustrează câteva operaţii simple de testare a poziţiei.
Programul împarte zona client într-o matrice de 5x5 dreptunghiuri Daca executaţi clic pe unul dintre
dreptunghiuri, în interiorul acestuia este desenat un X. Daca executaţi din nou un clic în acelaşi dreptunghi, X-ul
este şters.
/*------------------------------------------------- DispatchMessage (&msg) ;
CHECKER1.C -- Mouse Hit-Test Demo Program No. 1 }
(c) Charles Petzold, 1996 return msg.wParam ;
-------------------------------------------------*/ }

#include <windows.h> LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg,


WPARAM wParam, LPARAM lParam)
#define DIVISIONS 5 {
#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) static BOOL fState[DIVISIONS][DIVISIONS] ;
static int cxBlock, cyBlock ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, HDC hdc ;
LPARAM) ; PAINTSTRUCT ps ;
RECT rect ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE int x, y ;
hPrevInstance,
PSTR szCmdLine, int iCmdShow) switch (iMsg)
{ {
static char szAppName[] = "Checker1" ; case WM_SIZE :
HWND hwnd ; cxBlock = LOWORD (lParam) / DIVISIONS ;
MSG msg ; cyBlock = HIWORD (lParam) / DIVISIONS ;
WNDCLASSEX wndclass ; return 0 ;

wndclass.cbSize = sizeof (wndclass) ; case WM_LBUTTONDOWN :


wndclass.style = CS_HREDRAW | CS_VREDRAW ; x = LOWORD (lParam) / cxBlock ;
wndclass.lpfnWndProc = WndProc ; y = HIWORD (lParam) / cyBlock ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ; if (x < DIVISIONS && y < DIVISIONS)
wndclass.hInstance = hInstance ; {
wndclass.hIcon = LoadIcon (NULL, fState [x][y] ^= 1 ;
IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) rect.left = x * cxBlock ;
; rect.top = y * cyBlock ;
wndclass.hbrBackground = (HBRUSH) GetStockObject rect.right = (x + 1) * cxBlock ;
(WHITE_BRUSH) ; rect.bottom = (y + 1) * cyBlock ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ; InvalidateRect (hwnd, &rect, FALSE) ;
wndclass.hIconSm = LoadIcon (NULL, }
IDI_APPLICATION) ; else
MessageBeep (0) ;
RegisterClassEx (&wndclass) ; return 0 ;

hwnd = CreateWindow (szAppName, "Checker1 Mouse case WM_PAINT :


Hit-Test Demo", hdc = BeginPaint (hwnd, &ps) ;
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, for (x = 0 ; x < DIVISIONS ; x++)
CW_USEDEFAULT, CW_USEDEFAULT, for (y = 0 ; y < DIVISIONS ; y++)
NULL, NULL, hInstance, NULL) ; {
Rectangle (hdc, x * cxBlock, y * cyBlock,
ShowWindow (hwnd, iCmdShow) ; (x + 1) * cxBlock, (y + 1) * cyBlock) ;
UpdateWindow (hwnd) ;
if (fState [x][y])
while (GetMessage (&msg, NULL, 0, 0)) {
{ MoveTo (hdc, x * cxBlock, y * cyBlock) ;
TranslateMessage (&msg) ;

Charles Petzold Page 10 13:30 26.02.2022 10


LineTo (hdc, (x+1) * cxBlock, (y+1) * return 0 ;
cyBlock) ;
MoveTo (hdc, x * cxBlock, (y+1) * case WM_DESTROY :
cyBlock) ; PostQuitMessage (0) ;
LineTo (hdc, (x+1) * cxBlock, y * return 0 ;
cyBlock) ; }
} return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
} }
EndPaint (hwnd, &ps) ;

Figura 6.4. Programul CHECKER1.


Figura 6.5 prezintă fereastra afişată de programul CHECKER1. Toate cele 25 de dreptunghiuri au aceleaşi
dimensiuni. Înălţimea şi lăţimea sunt stocate în variabilele cxBlock şi cyBlock şi sunt recalculate de fiecare dată
când dimensiunile zonei client se modifică. Codul de tratare a butonului WM_LBUTTONDOWN foloseşte
coordonatele mouse-ului ca să determine dreptunghiul în care s-a executat clic, apoi marchează starea
dreptunghiului în matricea fState şi invalidează dreptunghiul ca să genereze un mesaj WM_PAINT. Dacă lăţimea
sau înălţimea zonei client nu se împarte exact la cinci, în partea stângă sau în partea de jos a zonei client rămâne o
porţiune neacoperită de un dreptunghi. Pentru prelucrarea erorilor, programul CHECKER1 răspunde la executarea
unui clic în această porţiune prin apelarea funcţiei MessageBeep.
Dacă primeşte un mesaj WM_PAINT, programul CHECKER1 actualizează întreaga zonă client, desenând
dreptunghiurile cu ajutorul funcţiei GDI Rectangle. În dreptunghiurile pentru care matricea fState conţine un 1,
CHECKER1 desenează două linii cu ajutorul funcţiilor MoveTo şi LineTo. În timpul prelucrării mesajului
WM_PAINT, CHECKER1 nu verifică validitatea fiecărei secţiuni dreptunghiulare înainte de a o redesena, dar ar
putea să facă acest lucru. O metodă de verificare a validităţii impune construirea unei structuri RECT pentru
fiecare bloc dreptunghiular (folosind aceleaşi formule ca şi în codul de prelucrare a mesajului
WM_LBUTTONDOWN) şi verificarea intersecţiei cu dreptunghiul invalid (ps.rcPaint) cu ajutorul funcţiei
IntersectRect. O altă metodă este să folosiţi funcţia PtInRect ca să determinaţi dacă oricare dintre cele patru colţuri
ale blocului dreptunghiular se situează în cadrul dreptunghiului invalid.

Figura 6-5. Fereastra afişata de programul CHECKER1.


Emularea mouse-ului cu tastatura
Programul CHECKER1 funcţionează numai dacă aveţi un mouse. Îi vom adăuga în curând o interfaţă cu
tastatura, aşa cum am făcut cu programul SYSMETS din Capitolul 5. Totuşi, includerea unei interfeţe cu tastatura
într-un program care foloseşte mouse-ul, pentru indicarea unor obiecte, înseamnă rezolvarea problemelor legate de
afişarea şi deplasarea indicatorului.
Chiar dacă mouse-ul nu este instalat, Windows poate afişa indicatorul mouse-ului. Pentru aceasta, Windows
păstrează un „contor de afişare" („display count"). Dacă mouse-ul este instalat, contorul de afişare are iniţial
valoarea 0; dacă nu, contorul are valoarea -1. Indicatorul mouse-ului este vizibil numai în cazul în care contorul de
afişare are valoarea egală cu 0 sau mai mare. Puteţi să incrementaţi contorul de afişare apelând funcţia
ShowCursor:

Charles Petzold Page 11 13:30 26.02.2022 11


ShowCursor (TRUE) ;
şi să îl decrementaţi apelând aceeaşi funcţie:
ShowCursor (FALSE) ;

Nu trebuie să determinaţi dacă mouse-ul este instalat înainte de apelarea funcţiei ShowCursor. Dacă vreţi să
afişaţi indicatorul mouse-ului indiferent dacă mouse-ul este prezent sau nu, incrementaţi contorul de afişare. În
cazul în care incrementaţi contorul o singură dată, după decrementare indicatorul va dispărea dacă mouse-ul nu
este instalat, dar va rămâne afişat dacă mouse-ul este instalat. Contorul de afişare este unic pentru toate
programele din Windows, aşa că trebuie să vă asiguraţi că îl incrementaţi şi îl decrementaţi de tot atâtea ori.
Puteţi să folosiţi în procedura de fereastră următoarea logică de prelucrare:
case WM SETFOCUS :
ShowCursor (TRUE) ;
return 0 ;
case WM_KILLFOCUS :
ShowCursor (FALSE) ;
return 0 ;

Procedura de fereastră primeşte mesajul WM_SETFOCUS atunci când fereastra obţine cursorul de intrare
(input focus) şi mesajul WM_KILLFOCUS atunci când pierde cursorul de intrare. Acestea sunt momentele cele
mai potrivite pentru afişaREA ŞI MASCAREA INDICATORULUI. ÎN PRIMUL RÂND, mesajele WM_SETFOCUS şi
WM_KILLFOCUS sunt transmise în număr egal - ceea ce înseamnă că procedura de fereastră va incrementa şi va
decrementa de tot atâtea ori contorul de afişare a indicatorului. În al doilea rând, pentru calculatoarele pe care nu
este instalat un mouse folosirea mesajelor WM_SETFOCUS şi WM_KILLFOCUS va determina afişarea
indicatorului numai atunci când fereastra dumneavoastră deţine cursorul de intrare. În acest fel, utilizatorul poate
să mute indicatorul mouse-ului folosind interfaţa cu tastatura pe care o proiectaţi.
Windows păstrează poziţia curentă a mouse-ului chiar dacă acesta nu este instalat. Dacă mouse-ul nu este
instalat şi afişaţi indicatorul, acesta poate apărea în orice punct al ecranului şi va rămâne acolo pană când îl mutaţi
în mod explicit. Puteţi să obţineţi poziţia indicatorului folosind funcţia GetCursorPos:
GetCursorPos (pPoint) ;

unde point este un pointer la o structură de tip POINT. Funcţia GetCursorPos completează câmpurile structurii
POINT cu coordonatele x si y ale mouse-ului. Puteţi să stabiliţi poziţia indicatorului folosind funcţia
SetCursorPos:
SetCursorPos (x, y) ;

În ambele cazuri, valorile x şi y reprezintă coordonate ecran, nu coordonate ale zonei clent. (Acest lucru ar
trebui să fie evident, deoarece funcţia nu primeşte un parametru hwnd). Aşa cum am arătat anterior, puteţi să
transformaţi coordonatele ecran în coordonate ale zonei client şi invers, folosind funcţiile ScreenToClient şi
ClientToScreen.
Dacă apelaţi funcţia GetCursorPos în timpul prelucrării unui mesaj generat de mouse şi transformaţi valorile
obţinute în coordonate ale zonei client, rezultatele obţinute s-ar putea să fie diferite de cele transmise prin
parametrul lParam al mesajului. Coordonatele returnate de funcţia GetCursorPos indică poziţia curentă a mouse-
ului, iar coordonatele transmise prin parametrul lParam indică poziţia mouse-ului în momentul generării
mesajului.
Probabil veţi dori să implementaţi o interfaţă cu tastatura care să permită deplasarea indicatorului mouse-ului
cu săgeţile de pe tastatură şi simularea butoanelor cu bara de spaţiu şi tasta Enter. Desigur, nu doriţi să mutaţi
indicatorul cu un singur pixel la fiecare apăsare de tastă. Aceasta ar obliga utilizatorul să ţină o tasta cu săgeată
apăsată mai mult de un minut ca să mute indicatorul mouse-ului dintr-o parte în alta a ecranului.
Dacă doriţi să implementaţi o interfaţă eficientă cu tastatura pentru indicatorul mouse-ului, dar să aveţi totuşi
posibilitatea să-l poziţionaţi cu precizie, trebuie să prelucraţi mesajele generate de acţionarea tastelor astfel încât
atunci când ţineţi apăsată o tastă, indicatorul să se deplaseze mai întâi încet, apoi din ce în ce mai repede. Vă
amintiţi că parametrul IParam al mesajului WM_KEYDOWN precizează dacă mesajul respectiv este rezultatul
autorepetării tastei apăsate. Aceasta poate fi o aplicaţie excelentă a informaţiei respective.
Adăugarea unei interfeţe cu tastatura la programul CHECKER
Programul CHECKER2, prezentat în Figura 6.6, este identic cu programul CHECKER1, exceptând faptul că
include şi o interfaţă cu tastatura. Puteţi să folosiţi săgeţile ca să deplasaţi indicatorul între cele 25 de
dreptunghiuri. Tasta Home trimite indicatorul în dreptunghiul din colţul din stânga-sus; tasta End îl trimite în

Charles Petzold Page 12 13:30 26.02.2022 12


dreptunghiul din colţul din dreapta-jos. Tasta Enter şi bara de spaţiu au acelaşi rol: şterg sau afişează semnul X din
dreptunghiul în care se află indicatorul.
#include <windows.h> ShowCursor (TRUE) ;
return 0 ;
case WM_KILLFOCUS :
#define DIVISIONS 5 ShowCursor (FALSE) ;
#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) return 0 ;
case WM_KEYDOWN :
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, GetCursorPos (&point) ;
LPARAM) ; ScreenToClient (hwnd, &point) ;
x = max (0, min (DIVISIONS - 1, point.x / cxBlock)) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE y = max (0, min (DIVISIONS - 1, point.y / cyBlock)) ;
hPrevInstance,
PSTR szCmdLine, int iCmdShow) switch (wParam)
{ {
static char szAppName[] = "Checker2" ; case VK_UP :
HWND hwnd ; y-- ;
MSG msg ; break ;
WNDCLASSEX wndclass ; case VK_DOWN :
y++ ;
wndclass.cbSize = sizeof (wndclass) ; break ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; case VK_LEFT :
wndclass.lpfnWndProc = WndProc ; x-- ;
wndclass.cbClsExtra = 0 ; break ;
wndclass.cbWndExtra = 0 ; case VK_RIGHT :
wndclass.hInstance = hInstance ; x++ ;
wndclass.hIcon = LoadIcon (NULL, break ;
IDI_APPLICATION) ; case VK_HOME :
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) x=y=0;
; break ;
wndclass.hbrBackground = (HBRUSH) GetStockObject
(WHITE_BRUSH) ; case VK_END :
wndclass.lpszMenuName = NULL ; x = y = DIVISIONS - 1 ;
wndclass.lpszClassName = szAppName ; break ;
wndclass.hIconSm = LoadIcon (NULL,
IDI_APPLICATION) ; case VK_RETURN :
case VK_SPACE :
RegisterClassEx (&wndclass) ; SendMessage (hwnd, WM_LBUTTONDOWN,
MK_LBUTTON,
hwnd = CreateWindow (szAppName, "Checker2 Mouse MAKELONG (x * cxBlock, y * cyBlock)) ;
Hit-Test Demo", break ;
WS_OVERLAPPEDWINDOW, }
CW_USEDEFAULT, CW_USEDEFAULT, x = (x + DIVISIONS) % DIVISIONS ;
CW_USEDEFAULT, CW_USEDEFAULT, y = (y + DIVISIONS) % DIVISIONS ;
NULL, NULL, hInstance, NULL) ;
point.x = x * cxBlock + cxBlock / 2 ;
ShowWindow (hwnd, iCmdShow) ; point.y = y * cyBlock + cyBlock / 2 ;
UpdateWindow (hwnd) ;
ClientToScreen (hwnd, &point) ;
while (GetMessage (&msg, NULL, 0, 0)) SetCursorPos (point.x, point.y) ;
{ return 0 ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ; case WM_LBUTTONDOWN :
} x = LOWORD (lParam) / cxBlock ;
return msg.wParam ; y = HIWORD (lParam) / cyBlock ;
}
if (x < DIVISIONS && y < DIVISIONS)
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, {
WPARAM wParam, LPARAM lParam) fState[x][y] ^= 1 ;
{
static BOOL fState[DIVISIONS][DIVISIONS] ; rect.left = x * cxBlock ;
static int cxBlock, cyBlock ; rect.top = y * cyBlock ;
HDC hdc ; rect.right = (x + 1) * cxBlock ;
PAINTSTRUCT ps ; rect.bottom = (y + 1) * cyBlock ;
POINT point ;
RECT rect ; InvalidateRect (hwnd, &rect, FALSE) ;
int x, y ; }
else
switch (iMsg) MessageBeep (0) ;
{ return 0 ;
case WM_SIZE :
cxBlock = LOWORD (lParam) / DIVISIONS ; case WM_PAINT :
cyBlock = HIWORD (lParam) / DIVISIONS ; hdc = BeginPaint (hwnd, &ps) ;
return 0 ; for (x = 0 ; x < DIVISIONS ; x++)
case WM_SETFOCUS :

Charles Petzold Page 13 13:30 26.02.2022 13


for (y = 0 ; y < DIVISIONS ; y++) LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ;
{ }
Rectangle (hdc, x * cxBlock, y * cyBlock, }
(x + 1) * cxBlock, (y + 1) * cyBlock) ; EndPaint (hwnd, &ps) ;
if (fState [x][y]) return 0 ;
{ case WM_DESTROY :
MoveTo (hdc, x * cxBlock, y * cyBlock) PostQuitMessage (0) ;
; return 0 ;
LineTo (hdc, (x+1) * cxBlock, (y+1) * }
cyBlock) ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
MoveTo (hdc, x * cxBlock, (y+1) * }
cyBlock) ;
Figura 6-6. Programul CHECKER2.
În timpul tratării mesajului WM_KEYDOWN, CHECKER2 determină poziţia indicatorului (funcţia
GetCursorPos), converteşte coordonatele ecran în coordonate ale zonei client (funcţia ScreenToClient) şi împarte
coordonatele obţinute la lăţimea şi la înălţimea unui bloc dreptunghiular. Rezultă două valori x şi y care indică
poziţia dreptunghiului într-o matrice 5x5. Cursorul mouse-ului poate să nu fie în zona client atunci când
utilizatorul apasă o tastă, aşa că valorile x şi y trebuie să fie trecute prin macroinstrucţiunile min şi max pentru a se
verifica dacă sunt în intervalul de la 0 la 4.
Pentru tastele cu săgeţi programul incrementează sau decrementează corespunzător valorile x şi y. Dacă este
apăsată tasta Enter (VK_ENTER) sau bara de spaţiu (VK_SPACE), programul CHECKER2 foloseşte funcţia
SendMessage ca să îşi trimită un mesaj WM_LBUTTONDOWN. Această tehnică este asemănătoare cu cea
folosită în programul SYSMETS din Capitolul 5 atunci când am adăugat o interfaţă cu tastatura pentru barele de
derulare. Prelucrarea mesajului WM_KEYDOWN se termină cu calcularea coordonatelor din zona client, care
indică centrul dreptunghiului. Acestea sunt transformate în coordonate ecran (funcţia ClientToScreen) şi apoi este
stabilită poziţia indicatorului (funcţia SetCursorPos).
Folosirea ferestrelor descendent pentru verificarea poziţiei
Unele programe, cum ar fi programul PAINT din Windows, împart zona client în mai multe zone logice mai
mici. Programul PAINT, prezentat în Figura 6.7, are în partea stângă o zonă pentru meniul de pictograme (bara cu
instrumente de lucru) şi o zonă în partea de jos pentru meniul de culori. Programul PAINT, atunci când verifică
poziţia pentru cele două meniuri, trebuie să ţină seama de localizarea meniurilor în cadrul zonei client înainte de a
determina meniul selectat de utilizator.
Poate că lucrurile nu stau, totuşi, chiar aşa. În realitate, programul PAINT simplifică desenarea meniurilor şi
operaţiile de verificare a poziţiei prin folosirea unor „ferestre descendent". Fiecare fereastră descendent împarte
întreaga zonă client în mai multe regiuni dreptunghiulare mai mici. Fiecare fereastră descendent are propria
variabilă handle, propria procedură de fereastră şi propria zonă client. Fiecare procedură de fereastră recepţionează
mesaje de mouse care se aplică numai respectivei ferestre descendent. Parametrul 1Param al mesajului generat de
mouse conţine coordonatele relative la colţul din stânga-sus al ferestrei descendent, nu al ferestrei părinte.

Charles Petzold Page 14 13:30 26.02.2022 14


Figura 6.7. Programul Windows PAINT.
Folosirea în acest mod a ferestrelor descendent contribuie la modularizarea şi structurarea programului. Dacă
ferestrele descendent utilizează clase diferite, fiecare poate avea o procedură de fereastră proprie. Fiecare clasă de
fereastră poate defini alte culori de fond şi diferite cursoare. În Capitolul 8 vom discuta despre controale de tip
„fereastră descendent" - ferestre descendent predefinite care pot fi bare de derulare, butoane sau casete de editare.
Pentru moment, haideţi să vedem cum putem să folosim ferestrele descendent în programul CHECKER.
Ferestre descendent în programul CHECKER
Figura 6.8 prezintă programul CHECKER3. Această versiune a programului creează 25 de ferestre
descendent pentru prelucrarea clicurilor executate cu mouse-ul. Programul nu include o interfaţă cu tastatura, dar
aceasta ar putea fi adăugată cu uşurinţă. Programul CHECKER3 are două proceduri de fereastră, numite WndProc
şi ChildWndProc. WndProc este procedura de fereastră a ferestrei principale (fereastra
părinte). ChildWndProc este procedura de fereastră folosită de toate cele 25 de ferestre descendent. Ambele
proceduri de fereastră trebuie să fie de tip CALLBACK.
Deoarece procedura de fereastră este definită de clasa de fereastră pe care o înregistraţi în Windows folosind
funcţia RegisterClassEx, pentru declararea celor două proceduri de fereastră din programul CHECKER3 trebuie să
înregistraţi două clase de fereastră. Prima clasă este folosită pentru fereastra principală şi se numeşte „Checker3".
A doua clasă este folosită pentru ferestrele descendent şi se numeşte „Checker3_Child".
În funcţia WinMain, pentru înregistrarea clasei „Checker3_Child" sunt refolosite majoritatea câmpurilor
structurii wndclass. Câmpul lpszClasssName primeşte valoarea „Checker3_Child" (numele clasei). Câmpul
IpfnWndProc primeşte valoarea ChildWndProc, procedura de fereastră pentru această clasă iar câmpurile hIcon şi
hlconSm primesc valoarea NULL, deoarece ferestrele descendent nu folosesc pictograme. Pentru clasa de fereastră
„Checker3_Child" câmpul cbWndExtra din structura vndclass primeşte valoarea 2 sau, mai precis, sizeof(WORD).
În acest fel se cere sistemului de operare să rezerve un spaţiu suplimentar de doi octeţi în structura pe care
Windows o păstrează pentru fiecare fereastră declarată pe baza acestei clase. Puteţi să folosiţi acest spaţiu pentru
stocarea unor informaţii care pot să difere de la o fereastră la alta.
/*------------------------------------------------- wndclass.hIconSm = LoadIcon (NULL,
CHECKER3.C -- Mouse Hit-Test Demo Program No. 3 IDI_APPLICATION) ;
(c) Charles Petzold, 1996
-------------------------------------------------*/ RegisterClassEx (&wndclass) ;

#include <windows.h> wndclass.lpfnWndProc = ChildWndProc ;


wndclass.cbWndExtra = sizeof (WORD) ;
#define DIVISIONS 5 wndclass.hIcon = NULL ;
#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) wndclass.lpszClassName = szChildClass ;
wndclass.hIconSm = NULL ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM,
LPARAM) ; RegisterClassEx (&wndclass) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT,
WPARAM, LPARAM) ; hwnd = CreateWindow (szAppName, "Checker3 Mouse Hit-
Test Demo",
char szChildClass[] = "Checker3_Child" ; WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE CW_USEDEFAULT, CW_USEDEFAULT,
hPrevInstance, NULL, NULL, hInstance, NULL) ;
PSTR szCmdLine, int iCmdShow)
{ ShowWindow (hwnd, iCmdShow) ;
static char szAppName[] = "Checker3" ; UpdateWindow (hwnd) ;
HWND hwnd ;
MSG msg ; while (GetMessage (&msg, NULL, 0, 0))
WNDCLASSEX wndclass ; {
TranslateMessage (&msg) ;
wndclass.cbSize = sizeof (wndclass) ; DispatchMessage (&msg) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; }
wndclass.lpfnWndProc = WndProc ; return msg.wParam ;
wndclass.cbClsExtra = 0 ; }
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ; LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg,
wndclass.hIcon = LoadIcon (NULL, WPARAM wParam, LPARAM lParam)
IDI_APPLICATION) ; {
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) static HWND hwndChild[DIVISIONS][DIVISIONS] ;
; int cxBlock, cyBlock, x, y ;
wndclass.hbrBackground = (HBRUSH) GetStockObject
(WHITE_BRUSH) ; switch (iMsg)
wndclass.lpszMenuName = NULL ; {
wndclass.lpszClassName = szAppName ; case WM_CREATE :

Charles Petzold Page 15 13:30 26.02.2022 15


for (x = 0 ; x < DIVISIONS ; x++) {
for (y = 0 ; y < DIVISIONS ; y++) HDC hdc ;
{ PAINTSTRUCT ps ;
hwndChild[x][y] = CreateWindow RECT rect ;
(szChildClass, NULL,
WS_CHILDWINDOW | WS_VISIBLE, switch (iMsg)
0, 0, 0, 0, {
hwnd, (HMENU) (y << 8 | x), case WM_CREATE :
(HINSTANCE) GetWindowLong (hwnd, SetWindowWord (hwnd, 0, 0) ; // on/off flag
GWL_HINSTANCE), return 0 ;
NULL) ;
} case WM_LBUTTONDOWN :
return 0 ; SetWindowWord (hwnd, 0, 1 ^ GetWindowWord (hwnd,
0)) ;
case WM_SIZE : InvalidateRect (hwnd, NULL, FALSE) ;
cxBlock = LOWORD (lParam) / DIVISIONS ; return 0 ;
cyBlock = HIWORD (lParam) / DIVISIONS ;
case WM_PAINT :
for (x = 0 ; x < DIVISIONS ; x++) hdc = BeginPaint (hwnd, &ps) ;
for (y = 0 ; y < DIVISIONS ; y++)
MoveWindow (hwndChild[x][y], GetClientRect (hwnd, &rect) ;
x * cxBlock, y * cyBlock, Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
cxBlock, cyBlock, TRUE) ;
return 0 ; if (GetWindowWord (hwnd, 0))
{
case WM_LBUTTONDOWN : MoveTo (hdc, 0, 0) ;
MessageBeep (0) ; LineTo (hdc, rect.right, rect.bottom) ;
return 0 ; MoveTo (hdc, 0, rect.bottom) ;
LineTo (hdc, rect.right, 0) ;
case WM_DESTROY : }
PostQuitMessage (0) ;
return 0 ; EndPaint (hwnd, &ps) ;
} return 0 ;
return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
} return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT
iMsg, WPARAM wParam, LPARAM lParam)
Funcţia WinMain apelează funcţia Create Window, care creează fereastra principală pe baza clasei
„Checkers". Totul este normal. Totuşi, atunci când primeşte mesajul WM_CREATE, procedura WndProc
apelează de 25 de ori funcţia CreateWindow ca să creeze 25 de ferestre descendent pe baza clasei
„Checker3_Child". Tabelul următor prezintă o comparaţie între parametrii funcţiei CreateWindow apelată din
funcţia WinMain pentru crearea ferestrei principale şi parametrii funcţiei Create Window apelată din funcţia
WndProc pentru crearea celor 25 de ferestre descendent:

Parametrii Fereastra principală Fereastra descendent


clasa ferestrei „Checker3" „Checker3_Child"
titlul ferestrei „Checker3..." NULL
stilul ferestrei WS_OVERLAPPED-WINDOW WS_CHILDWINDOW| WS_VISIBLE
poziţia pe orizontală CW_USEDEFAULT 0
poziţia pe verticală CW_USEDEFAULT 0
lăţime CW_USEDEFAULT 0
înălţime CW_USEDEFAULT 0
variabila handle a ferestrei părinte NULL hwnd
variabila handle a NULL (HMENU) (y << 8 | x)
meniului/identificatorul ferestrei
descendent (child ID)
variabila handle a instanţei hInstance (HINSTANCE) GetWindowLong
(hwnd,GWL_HINSTANCE)
alţi parametri NULL NULL
În mod normal, poziţia, lăţimea şi înălţimea sunt necesare pentru crearea ferestrelor descendent, dar în
programul CHECKER3 ferestrele descendent sunt dimensionate şi poziţionate mai târziu în funcţia WndProc.
Variabila handle a ferestrei părinte are valoarea NULL pentru fereastra principală, deoarece chiar aceasta este

Charles Petzold Page 16 13:30 26.02.2022 16


fereastra părinte. În schimb, pentru crearea terestrei descendent, funcţia CreateWindow are nevoie de o variabilă
handle a ferestrei părinte.
Fereastra principală nu are meniu, aşa că parametrul „variabila handle a meniului" are valoarea NULL. Pentru
ferestrele descendent, acelaşi parametru se numeşte „identificatorul ferestrei descendent (child ID)". Acesta este
un număr care identifică în mod unic fereastra descendent. Identificatorul este mai important atunci când ferestrele
descendent sunt folosite pentru controale, deoarece identificarea mesajelor trimise către fereastra părinte se face cu
ajutorul acestui identificator, aşa cum vom vedea în Capitolul 8. În programul CHECKER3, identificatorul
ferestrei descendent este egal cu poziţia ocupată de fereastra descendent într-o matrice 5x5.
Variabila handle a instanţei este hInstance în ambele cazuri. Atunci când este creată fereastra descendent,
valoarea hlnstance este obţinută prin apelarea funcţiei GetWindowLong, care citeşte valoarea hlnstance din
structura păstrată de Windows pentru fiecare fereastră. (În loc să apelăm de fiecare dată funcţia GetWindowLong
puteam să salvăm variabila hlnstance într-o variabilă globală şi să o folosim direct.)
Fiecare fereastră descendent are o variabilă handle diferită, stocată în matricea hwndChild. Atunci când
primeşte un mesaj WM_SIZE, funcţia WndProc apelează funcţia MoveWindow pentru fiecare dintre cele 25 de
ferestre descendent. Parametrii transmişi funcţiei MoveWindow indică poziţia colţului din stânga-sus al ferestrei
descendent, în coordonatele zonei client a ferestrei părinte, lăţimea şi înălţimea ferestrei descendent şi dacă
fereastra descendent trebuie să fie redesenată.
Haideţi să acordăm puţină atenţie funcţiei ChildWndProc. Această procedură de fereastră prelucrează
mesajele pentru toate cele 25 de ferestre descendent. Parametrul hwnd al funcţiei ChildWndProc este variabila
handle a ferestrei descendent care a primit mesajul. Atunci când prelucrează mesajului WM_CREATE (ceea ce se
în-tâmplă de 25 de ori, deoarece sunt 25 de ferestre descendent), funcţia ChildWndProc foloseşte funcţia
SetWindowWord ca să stocheze valoarea 0 în zona suplimentară rezervată în structura ferestrei. (Vă amintiţi că am
rezervat acest spaţiu folosind câmpul cbWndExtra atunci când am definit structura clasei de fereastră.)
ChildWndProc foloseşte această valoare ca să stocheze starea curentă a ferestrei (marcată sau nu cu un X). Atunci
când se execută clic pe fereastra descendent, codul de prelucrare a mesajului WM_LBUTTONDOWN schimbă
valoarea acestui cuvânt (din 0 în 1 sau din 1 în zero) şi invalidează întreaga zonă client a ferestrei descendent.
Această zonă reprezintă chiar dreptunghiul în care utilizatorul a executat clic. Prelucrarea mesajului WM_PAINT
este foarte simplă, deoarece dreptunghiul în care se face desenarea are aceleaşi dimensiuni ca şi zona client a
ferestrei descendent.
Deoarece codul sursă C şi fişierul executabil al programului CHECKER3 sunt mai lungi decât cele ale
programului CHECKER1 (ca să nu mai vorbim de explicaţiile mele) nu voi încerca să vă conving că CHECKER3
este mai simplu decât CHECKER1. Remarcaţi însă că nu mai avem nevoie de nici o operaţie de verificare a
poziţiei cursorului. În cazul în care o fereastră descendent din programul CHECKER3 a primit un mesaj
WM_LBUTTONDOWN, cursorul se află în fereastra respectivă, deci fereastra are toate informaţiile necesare.
Dacă vreţi să includeţi în programul CHECKER3 o interfaţă cu tastatura, reţineţi că mesajele de la tastatură
sunt primite tot de fereastra principală, deoarece aceasta deţine cursorul de intrare. Vom discuta mai mult despre
ferestrele descendent în Capitolul 8.
Capturarea mouse-ului
În mod normal, o procedură de fereastră primeşte mesaje de la mouse doar atâta timp cât indicatorul mouse-
ului se află deasupra ferestrei, în zona client sau în afara acesteia. S-ar putea ca un program să aibă nevoie să
recepţioneze mesaje de ia mouse şi atunci când indicatorul mouse-ului este în afara ferestrei programului. În
această situaţie, programul poate să „captureze" mouse-ul.
Desenarea unui dreptunghi
Pentru a vedea de ce este uneori necesară capturarea mouse-ului, haideţi să studiem programul BLOKOUT1,
prezentat în Figura 6.9.
#------------------------ (c) Charles Petzold, 1996
# BLOKOUT1.MAK make file -----------------------------------------*/
#------------------------
#include <windows.h>
blokout1.exe : blokout1.obj
$(LINKER) $(GUIFLAGS) -OUT:blokout1.exe blokout1.obj LRESULT CALLBACK WndProc (HWND, UINT, WPARAM,
$(GUILIBS) LPARAM) ;

blokout1.obj : blokout1.c int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE


$(CC) $(CFLAGS) blokout1.c hPrevInstance,
/*----------------------------------------- PSTR szCmdLine, int iCmdShow)
BLOKOUT1.C -- Mouse Button Demo Program {

Charles Petzold Page 17 13:30 26.02.2022 17


static char szAppName[] = "BlokOut1" ; fBlocking = TRUE ;
HWND hwnd ; return 0 ;
MSG msg ;
WNDCLASSEX wndclass ; case WM_MOUSEMOVE :
if (fBlocking)
wndclass.cbSize = sizeof (wndclass) ; {
wndclass.style = CS_HREDRAW | CS_VREDRAW ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ; ptEnd.x = LOWORD (lParam) ;
wndclass.hIcon = LoadIcon (NULL, ptEnd.y = HIWORD (lParam) ;
IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
; }
wndclass.hbrBackground = (HBRUSH) GetStockObject return 0 ;
(WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ; case WM_LBUTTONUP :
wndclass.lpszClassName = szAppName ; if (fBlocking)
wndclass.hIconSm = LoadIcon (NULL, {
IDI_APPLICATION) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

RegisterClassEx (&wndclass) ; ptBoxBeg = ptBeg ;


ptBoxEnd.x = LOWORD (lParam) ;
hwnd = CreateWindow (szAppName, "Mouse Button ptBoxEnd.y = HIWORD (lParam) ;
Demo",
WS_OVERLAPPEDWINDOW, SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, fBlocking = FALSE ;
NULL, NULL, hInstance, NULL) ; fValidBox = TRUE ;

ShowWindow (hwnd, iCmdShow) ; InvalidateRect (hwnd, NULL, TRUE) ;


UpdateWindow (hwnd) ; }
return 0 ;
while (GetMessage (&msg, NULL, 0, 0))
{ case WM_CHAR :
TranslateMessage (&msg) ; if (fBlocking & wParam == '\x1B') // ie, Escape
DispatchMessage (&msg) ; {
} DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
return msg.wParam ;
} SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT fBlocking = FALSE ;


ptEnd) }
{ return 0 ;
HDC hdc ; case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
hdc = GetDC (hwnd) ;
if (fValidBox)
SetROP2 (hdc, R2_NOT) ; {
SelectObject (hdc, GetStockObject (NULL_BRUSH)) ; SelectObject (hdc, GetStockObject
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ; (BLACK_BRUSH)) ;
Rectangle (hdc, ptBoxBeg.x, ptBoxBeg.y,
ReleaseDC (hwnd, hdc) ; ptBoxEnd.x, ptBoxEnd.y) ;
} }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, if (fBlocking)


WPARAM wParam, LPARAM lParam) {
{ SetROP2 (hdc, R2_NOT) ;
static BOOL fBlocking, fValidBox ; SelectObject (hdc, GetStockObject
static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ; (NULL_BRUSH)) ;
HDC hdc ; Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x,
PAINTSTRUCT ps ; ptEnd.y) ;
}
switch (iMsg)
{ EndPaint (hwnd, &ps) ;
case WM_LBUTTONDOWN : return 0 ;
ptBeg.x = ptEnd.x = LOWORD (lParam) ;
ptBeg.y = ptEnd.y = HIWORD (lParam) ; case WM_DESTROY :
PostQuitMessage (0) ;
DrawBoxOutline (hwnd, ptBeg, ptEnd) ; return 0 ;
}
SetCursor (LoadCursor (NULL, IDC_CROSS)) ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
Figura 6.9. Programul BLOKOUT1.

Charles Petzold Page 18 13:30 26.02.2022 18


Acest program ilustrează o metodă care poate fi implementată de un program de desenare sub Windows.
Începeţi cu apăsarea butonului din stânga al mouse-ului, ca să marcaţi primul colţ al unui dreptunghi. Trageţi apoi
mouse-ul. Programul desenează conturul unui dreptunghi, poziţia curentă a indicatorului marcând colţul opus
primului colţ marcat. Figura 6.10 prezintă un dreptunghi deja desenat şi un dreptunghi în curs de desenare.

Figura 6-10. Fereastra afişată de programul BLOKOUT1.


Atunci când butonul din stânga al mouse-ului este apăsat, BLOKOUT1 salvează coordonatele mouse-ului şi
apelează funcţia DrawBoxOutline pentru prima dată. Funcţia DrawBoxOutline desenează un dreptunghi folosind
operaţia rastru R2_NOT. Aceasta inversează culoarea zonei client a ferestrei. În timpul mesajelor
WM_MOUSEMOVE care urmează, programul desenează din nou acelaşi dreptunghi, ştergând versiunile
anterioare. De fiecare dată programul foloseşte coordonatele mouse-ului ca să deseneze un nou dreptunghi. In
sfârşit, atunci când primeşte un mesaj WM_LBUTTONUP, programul BLOKOUT1 salvează coordonatele
mouse-ului şi invalidează fereastra, generând un mesaj WM_PAINT pentru desenarea unui dreptunghi fix.
Aşadar, care este problema?
Faceţi următorul experiment: apăsaţi butonul din stânga al mouse-ului în zona client a programului
BLOKOUT1, apoi mutaţi indicatorul mouse-ului în afara zonei client. Din acest moment, programul nu mai
primeşte mesajele WM_MOUSEMOVE generate de mouse. Eliberaţi butonul din stânga al mouse-ului. Programul
BLOKOUT1 nu primeşte mesajul WM_LBUTTONUP, deoarece indicatorul mouse-ului se află în afara zonei
client a ferestrei. Mutaţi din nou indicatorul în zona client a programului. Procedura de fereastră încă mai crede că
butonul mouse-ului este apăsat. Şi nu este bine. Programul nu mai ştie ce se întâmplă.
Soluţia capturării
Programul BLOKOUT1 prezintă o funcţie frecvent întâlnită în programele Windows, dar este evident că în
cod s-a strecurat o greşeală. Pentru acest gen de probleme a fost inventată metoda numită „capturarea mouse-ului".
În cazul în care utilizatorul trage mouse-ul, ar trebui să nu fie nici o problemă dacă indicatorul acestuia iese în
afara ferestrei. Ar trebui ca programul să păstreze controlul asupra mouse-ului.
Capturarea mouse-ului se face foarte simplu. Nu trebuie decât să apelaţi funcţia SetCapture:
SetCapture (hwnd) ;

După apelarea funcţiei SetCapture, Windows trimite toate mesajele generate de mouse către fereastra indicată
de variabila handle hwnd. Mesajele trimise sunt generate pentru zona client a ferestrei, chiar dacă mouse-ul se află
în afara acesteia. Parametrul lParam indică poziţia indicatorului mouse-ului în coordonatele zonei client. Aceste
coordonate pot avea valori negative dacă mouse-ul se află în stânga sau deasupra zonei client a ferestrei.
După capturarea mouse-ului, funcţiile de sistem ale tastaturii sunt dezactivate. Atunci când doriţi să eliberaţi
mouse-ul, apelaţi funcţia ReleaseCapture:
ReleaseCapture () ;

Funcţia ReleaseCapture determină revenirea la normal a operaţiilor de prelucrare.

Charles Petzold Page 19 13:30 26.02.2022 19


În Windows 95, capturarea mouse-ului este puţin mai restrictivă decât în versiunile anterioare ale sistemului
de operare Windows. Mai precis, dacă mouse-ul a fost capturat, dar nici unul dintre butoanele acestuia nu este
apăsat şi indicatorul lui trece peste o altă fereastră, mesajele generate de acesta sunt trimise ferestrei deasupra
căruia se află indicatorul, nu ferestrei care a capturat mouse-ul. Acest lucru este necesar pentru a împiedica
blocarea întregului sistem atunci când un program capturează mouse-ul şi „uită" să îl mai elibereze.
Cu alte cuvinte, trebuie să capturaţi mouse-ul numai atunci când butonul este apăsat în zona client a ferestrei
dumneavoastră. Eliberaţi mouse-ul după ce butonul mouse-ului este eliberat.
Programul BLOKOUT2
Programul BLOKOUT1 ilustrează metoda capturării mouse-ului şi este prezentat în Figura 6.11.
#------------------------ DispatchMessage (&msg) ;
# BLOKOUT2.MAK make file }
#------------------------ return msg.wParam ;
}
blokout2.exe : blokout2.obj
$(LINKER) $(GUIFLAGS) -OUT:blokout2.exe blokout2.obj void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT
$(GUILIBS) ptEnd)
{
blokout2.obj : blokout2.c HDC hdc ;
$(CC) $(CFLAGS) blokout2.c
/*--------------------------------------------------- hdc = GetDC (hwnd) ;
BLOKOUT2.C -- Mouse Button & Capture Demo Program
(c) Charles Petzold, 1996 SetROP2 (hdc, R2_NOT) ;
---------------------------------------------------*/ SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ;
#include <windows.h>
ReleaseDC (hwnd, hdc) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, }
LPARAM) ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg,
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE WPARAM wParam, LPARAM lParam)
hPrevInstance, {
PSTR szCmdLine, int iCmdShow) static BOOL fBlocking, fValidBox ;
{ static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;
static char szAppName[] = "BlokOut2" ; HDC hdc ;
HWND hwnd ; PAINTSTRUCT ps ;
MSG msg ;
WNDCLASSEX wndclass ; switch (iMsg)
{
wndclass.cbSize = sizeof (wndclass) ; case WM_LBUTTONDOWN :
wndclass.style = CS_HREDRAW | CS_VREDRAW ; ptBeg.x = ptEnd.x = LOWORD (lParam) ;
wndclass.lpfnWndProc = WndProc ; ptBeg.y = ptEnd.y = HIWORD (lParam) ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, SetCapture (hwnd) ;
IDI_APPLICATION) ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW)
; fBlocking = TRUE ;
wndclass.hbrBackground = (HBRUSH) GetStockObject return 0 ;
(WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ; case WM_MOUSEMOVE :
wndclass.lpszClassName = szAppName ; if (fBlocking)
wndclass.hIconSm = LoadIcon (NULL, {
IDI_APPLICATION) ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ;

RegisterClassEx (&wndclass) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

hwnd = CreateWindow (szAppName, "Mouse Button & ptEnd.x = LOWORD (lParam) ;


Capture Demo", ptEnd.y = HIWORD (lParam) ;
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
CW_USEDEFAULT, CW_USEDEFAULT, }
NULL, NULL, hInstance, NULL) ; return 0 ;

ShowWindow (hwnd, iCmdShow) ; case WM_LBUTTONUP :


UpdateWindow (hwnd) ; if (fBlocking)
{
while (GetMessage (&msg, NULL, 0, 0)) DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
{
TranslateMessage (&msg) ; ptBoxBeg = ptBeg ;

Charles Petzold Page 20 13:30 26.02.2022 20


ptBoxEnd.x = LOWORD (lParam) ; if (fValidBox)
ptBoxEnd.y = HIWORD (lParam) ; {
SelectObject (hdc, GetStockObject
ReleaseCapture () ; (BLACK_BRUSH)) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ; Rectangle (hdc, ptBoxBeg.x, ptBoxBeg.y,
ptBoxEnd.x, ptBoxEnd.y) ;
fBlocking = FALSE ; }
fValidBox = TRUE ;
if (fBlocking)
InvalidateRect (hwnd, NULL, TRUE) ; {
} SetROP2 (hdc, R2_NOT) ;
return 0 ; SelectObject (hdc, GetStockObject
(NULL_BRUSH)) ;
case WM_CHAR : Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x,
if (fBlocking & wParam == '\x1B') // i.e., Escape ptEnd.y) ;
{ }
DrawBoxOutline (hwnd, ptBeg, ptEnd) ;
ReleaseCapture () ; EndPaint (hwnd, &ps) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ; return 0 ;

fBlocking = FALSE ; case WM_DESTROY :


} PostQuitMessage (0) ;
return 0 ; return 0 ;
}
case WM_PAINT : return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
hdc = BeginPaint (hwnd, &ps) ; }

Figura 6.11. Programul BLOKOUT2.


Programul BLOKOUT2 este acelaşi cu programul BLOKOUT1, dar are trei linii de cod în plus: un apel al
funcţiei SetCapture în timpul prelucrării mesajului WM_LBUTTONDOWN si două apeluri ale funcţiei
ReleaseCapture, în timpul prelucrării mesajelor WM_LBUTTONUP şi WM_CHAR. (Prelucrarea mesajului
WM_CHAR permite eliberarea mouse-ului atunci când utilizatorul apasă tasta Esc.)
Faceţi următorul experiment redimensionaţi fereastra astfel încât să fie mai mică decât ecranul, marcaţi
începutul unui dreptunghi în zona client, mutaţi indicatorul în afara zonei client, către dreapta sau în jos, apoi
eliberaţi butonul mouse-ului ca să marcaţi al doilea colţ al dreptunghiului. Programul va păstra coordonatele
întregului dreptunghi. Măriţi din nou fereastra ca să vă convingeţi.
Capturarea mouse-ului nu este o funcţie care se poate realiza numai în aplicaţiile mai ciudate. Ar trebui să o
folosiţi de fiecare dată când trebuie să urmăriţi mesajele WM_MOUSEMOVE după ce butonul mouse-ului a fost
apăsat în zona client a programului dumneavoastră, pană când butonul mouse-ului este eliberat. Programul va fi
mai simplu şi va satisface aşteptările utilizatorilor.

Charles Petzold Page 21 13:30 26.02.2022 21

S-ar putea să vă placă și