Sunteți pe pagina 1din 313

Cuprins

Ce este PPE......................................................................................................................................................4
Scurt istoric.......................................................................................................................................................5
Limbaje de programare pentru PPE...............................................................................................................7
Cum funcționează PPE...................................................................................................................................8
Domenii de aplicare.....................................................................................................................................10
Rezumat.....................................................................................................................................................12
Dicționar......................................................................................................................................................13
Totul a pornit de la GUI...............................................................................................................................13
Schimbarea paradigmei...............................................................................................................................15
Evenimentele conduc programul................................................................................................................16
HelloWin modificat (de la Charles Petzold).................................................................................................16
Punctul de intrare în program - inițializarea................................................................................................22
Punctul de intrare în program - bucla de mesaje........................................................................................25
Proprietățile dispecerului............................................................................................................................26
Procedura de fereastră................................................................................................................................26
Tratarea mesajelor......................................................................................................................................30
Gestiunea evenimentelor și programele secvențiale..................................................................................32
Soluția.........................................................................................................................................................35
Interfața grafică a utilizatorului...................................................................................................................37
Conceptele şi fundamentele GUI.................................................................................................................37
Consecvenţa privind interfaţă cu utilizatorul...............................................................................................37
Avantajul oferit de multitasking..................................................................................................................38
Gestionarea memoriei.................................................................................................................................38
Interfaţa grafică independentă de dispozitiv...............................................................................................39
Dacă optaţi pentru Windows.......................................................................................................................40
Apelurile de funcţii......................................................................................................................................40
Programarea orientată pe obiecte..............................................................................................................41
Arhitectura bazată pe mesaje......................................................................................................................41
Procedura de fereastră................................................................................................................................42
PRIMUL DUMNEAVOASTRĂ PROGRAM windows........................................................................................43
Ce este greşit în acest program?.................................................................................................................43
Fişierele programului HELLOWIN................................................................................................................44
Fişierul de construcţie.................................................................................................................................49
Fişierul sursă C.............................................................................................................................................50
Apelurile de funcţii Windows......................................................................................................................51
Identificatori cu majuscule..........................................................................................................................52
Noi tipuri de date........................................................................................................................................52
Câteva cuvinte despre variabilele handle....................................................................................................54
Notaţia ungară.............................................................................................................................................54
Punctul de intrare în program.....................................................................................................................55
Înregistrarea clasei de fereastră..................................................................................................................57
Crearea terestrei.........................................................................................................................................61
Afişarea ferestrei.........................................................................................................................................62
Ciclul de mesaje...........................................................................................................................................63
Procedura de fereastră................................................................................................................................65
Prelucrarea mesajelor.................................................................................................................................66
Redarea unui fişier de sunet........................................................................................................................67
Mesajul WM_PAINT....................................................................................................................................68
Mesajul WM_DESTROY...............................................................................................................................70
PROBLEME LEGATE DE PROGRAMAREA SUB WINDOWS............................................................................70
Nu ne apela, te apelăm noi!........................................................................................................................70
Mesaje în coada de aşteptare şi în afara acesteia.......................................................................................72
Nu fiţi egoist!...............................................................................................................................................73
Ciclul de învăţare.........................................................................................................................................74
Afișarea textului..........................................................................................................................................75
Mesajul WM_PAINT................................................................................................................................75
Dreptunghiuri valide şi invalide.......................................................................................................................76
Contextul de dispozitiv................................................................................................................................77
Obţinerea unei variabile handle a contextului de dispozitiv: prima metodă...................................................78
Fomatarea textului..........................................................................................................................................87
Nu există destul spaţiu!...................................................................................................................................98
Dimensiunea zonei client................................................................................................................................98
Mesaje de la barele de derulare....................................................................................................................102
Elemente de grafică...................................................................................................................................123
Dreptunghiuri de încadrare...................................................................................................................150
Totul despre tastatură...............................................................................................................................197
Ignorarea tastaturii....................................................................................................................................197
Acţionări de taste şi caractere...................................................................................................................198
Mesaje pentru acţionări de taste..........................................................................................................199
Contorul de repetare.................................................................................................................................200
Codul de context.......................................................................................................................................201
Starea anterioară a tastei..........................................................................................................................202
Starea de tranziţie.....................................................................................................................................202
VK_LBUTTON.............................................................................................................................................202
Starea tastelor de modificare....................................................................................................................204
ADĂUGAREA INTERFEŢEI CU TASTATURA..................................................................................................206
Transmiterea mesajelor.............................................................................................................................208
mesaje CARACTER.....................................................................................................................................221
Mesaje pentru „caractere moarte"...........................................................................................................224
Funcţii pentru cursorul de editare.........................................................................................................234
PROBLEME LEGATE DE INTERNAŢIONALIZARE..........................................................................................253
Folosirea seturilor de caractere.................................................................................................................253
Folosirea blocului de taste numerice.........................................................................................................255
Totul despre mouse...................................................................................................................................256
Prelucrarea tastelor de modificare................................................................................................................266
Dublu clic cu mouse-ul..................................................................................................................................267
Apăsat.......................................................................................................................................................268
Eliberat..................................................................................................................................................268
Mesajul de testare a poziţiei.........................................................................................................................269
Verificarea poziţiei în programele Dumneavoastră.......................................................................................272
Emularea mouse-ului cu tastatura................................................................................................................279
Capturarea mouse-ului..................................................................................................................................298
Ce este PPE
Programarea pilotată de evenimente (eng. event-driven programming, EDP) este o paradigmă de
programare în care execuția programului este determinată de evenimente - o acțiune a unui utilizator
cum ar fi un click de mouse, apăsarea unei taste, un mesaj de la sistemul de operare sau de la un alt
program, etc. O aplicație bazată pe evenimente este concepută pentru a detecta evenimentele pe
măsură ce acestea apar și apoi pentru a le procesa, folosind o procedură adecvată de tratare. Cu alte
cuvinte, spre deosebire de programele tradiționale, care-și urmează propria execuție, schimbându-și
câte o data ordinea de execuție în puncte de ramificație (instrucțiuni test, ciclare, etc), cursul
execuției unui program din categoria PPE este condus în mare parte de evenimente (externe sau
interne). Este o extensie a programării controlate de întreruperi, de tipul celor folosite în sistemele
de operare sau sistemele încorporate.

PPE poate fi definită ca metodă de construire a programului de calculator în codul căruia (de obicei,
în funcția principală a programului) în mod explicit este evidențiat un ciclu, numit ciclul principal al
aplicației. Corpul acestui ciclu este alcătuit din două părți: selectarea evenimentului și direcționarea
evenimentului pentru tratare.

Aplicațiile orientate pe evenimente sunt formate de obicei din fragmente mici de cod, numite
operator de eveniment (event handler), apelate ca răspuns la evenimente externe de un coordonator
(dispecer), care apelează handlerele de evenimente, folosind de obicei o coadă a evenimentelor, care
să rețină evenimentele ce nu au fost încă procesate.

În multe cazuri, handlerele de evenimente pot declanșa ele însele evenimente, ducând la o cascadă
de evenimente. De regulă, în cadrul unor sarcini reale, nu este recomandat ca procesorul să fie alocat
unui eveniment pentru o perioadă lungă de timp, deoarece în acest caz aplicația nu va putea
reacționa la alte evenimente.

Un mediu integrat de dezvoltare (IDE) vizuală oferă o mare parte a codului pentru manipularea
evenimentelor atunci când este creată o nouă aplicație. Programatorul se poate concentra, prin
urmare, pe aspecte precum proiectarea interfeței, care implică adăugarea de comenzi, cum ar fi
butoanele sau, casetele de text și etichetele la formele standard (o formă reprezintă spațiul de lucru
al unei aplicații sau o fereastră). Odată ce interfața cu utilizatorul este în mod substanțial realizată,
programatorul poate adăuga codul de tratare a unui eveniment, după cum este necesar. Multe medii
integrate de programare vizuală furnizează chiar șabloane de cod pentru event-handlere, astfel încât
programatorul trebuie doar să concretizeze codul care realizează acțiunea pe care o va executa
programul ca răspuns la producerea evenimentului. Fiecare operator de eveniment este legat, de
obicei, de un obiect specific sau de un control pe formă. Orice subrutine suplimentare, metode sau
proceduri funcționale necesare sunt de obicei plasate într-un modul de cod separat și pot fi apelate
din alte părți ale programului atunci când este necesar. În acest sens mediile integrate de dezvoltare
reprezintă un nivel suplimentar de abstractizare, facilitând munca programatorului, dar în același
timp ascunzând multe dintre secretele programării de nivel jos, cunoașterea cărora este obligatorie
pentru un inginer IT. Din acest motiv vom începe cursul apelând la programarea C brută, adică
folosind instrumentele de dezvoltare interactivă, dar fără a exploata la maximum posibilitățile
limbajelor de programare obiect orientate sau avantajele oferite de IDE.

Programarea pilotată de evenimente accentuează ca virtuți flexibilitatea și asincronismul. Aplicațiile


dotate cu interfețe grafice sunt de obicei programate într-o modalitate gestionată de evenimente.
Sistemele de operare sunt un alt exemplu clasic de programe pilotate de evenimente (pe cel puțin
două nivele). La cel mai de jos nivel, codul de tratare a unei întreruperi se comportă ca handlere de
evenimente hardware, cu procesorul în rol de coordonator (dispecer). Sistemul de operare, de
asemenea este coordonator pentru procese, transmițând datele și întreruperile soft către procese user,
care de multe ori sunt programate sub forma unor handlere de eveniment.

Scurt istoric
Înainte de apariția limbajelor de programare obiect orientată, event-handlerele erau implementate ca
subrutine în cadrul unui program procedural. Fluxul de execuție a programului era determinat de
către programator și controlat de rutina principală a aplicației. Complexitatea logicii realizate
solicita implementarea unui program foarte bine structurat. Codul programului era scris de către
programator, inclusiv codul necesar pentru a se asigura că evenimentele și excepțiile erau corect
gestionate, precum și codul necesar pentru a gestiona fluxul de execuție a programului. 

Într-un program controlat de evenimente nu există un flux de control. Programul principal conține o
buclă de dispecerizare a evenimentelor (dispecer de evenimente), care atunci când apare un
eveniment apelează procedura adecvată de tratare a acestuia. Deoarece codul pentru bucla de
dispecerizare a evenimentelor este furnizat de obicei de mediul sau cadrul de dezvoltare bazat pe
evenimente și în mare parte este invizibil pentru programator, percepția programatorului asupra
aplicației este cea a unei colecții de rutine de tratare a evenimentelor. Programatorii obișnuiți cu
programarea procedurală uneori consideră că trecerea la un mediu pilotat de evenimente solicită un
efort mental considerabil.

Schimbarea accentului de la programarea procedurală la programarea pilotată de evenimente a


pornit odată cu introducerea limbajelor de programare orientate pe obiecte (OO) și a noilor
metodologii de dezvoltare de la sfârșitul anilor 1970 și a fost accelerată de introducerea interfeței
grafice de utilizator (GUI), adoptată pe scară largă pentru utilizare în sistemele de operare și
aplicațiile utilizatorilor finali. Până în anii 1990, tehnologiile obiect orientate au înlocuit în mare
măsură limbajele de programare procedurală și metodele de dezvoltare structurată, populare în anii
1970 - 1980 (fig. 1).
Fig. 1. Trendurile programării

Catalizator suplimentar al acestui proces a fost schimbarea atitudinii față de bazele de date (BD),
inclusiv utilizarea acestora în scopuri comerciale. Dezvoltatorii de sisteme informatice au început să
considere baza de date componentă centrală a unui sistem informatic. Software-ul utilizat pentru
accesarea bazei de date era privit ca o simplă interfață a utilizatorului la BD, care conține un set de
proceduri de tratare a evenimentelor asociate tratării interpelărilor sau actualizării BD.

Una dintre ideile fundamentale care stau în spatele programării orientate pe obiecte este aceea de a
reprezenta entitățile programabile ca obiecte. De exemplu, un program care modelează executarea
comenzilor clienților într-o companie de vânzări include obiecte precum "client", "comandă", "item
al comenzii", etc. Un obiect include atât datele (atributele) atașate obiectului, acțiunile (metodele)
care pot fi utilizate pentru a accesa sau modifica atributele acestuia, cât și evenimentele care pot
determina invocarea metodelor obiectului. Structura unui obiect și relația acestuia cu aplicația de
care aparține este ilustrată în diagrama din figura 2.

Fig. 2.Relația
dintre obiect și aplicație

Putem spune că o clasă, în termeni foarte generali, este un șablon care definește proprietățile
(atributele), metodele și evenimentele ce vor fi implementate în orice exemplar creat în baza acestei
clase. Un obiect este o instanță (reprezentant) a clasei. Există o strânsă legătura între programarea
orientată pe obiecte și programarea pilotată de evenimente. De exemplu, obiectele de pe o formă
Visual C++, denumite în mod obișnuit controale, pot fi clasificate în clase - clasa Buton, clasa
TextBox, etc. - și mai multe instanțe pot apărea pe una și aceeași formă. Fiecare clasă va avea
atribute (proprietăți), care pot fi comune tuturor obiectelor de tipul respectiv (de exemplu
BackgroundColour, Width, etc.) și va defini o listă de evenimente la care un obiect va răspunde.
Metodele (event-handlerele) pentru gestiunea evenimentelor sunt furnizate ca șabloane la care
programatorul trebuie doar să adauge codul care efectuează acțiunea necesară.

Limbaje de programare pentru PPE


Programele bazate pe evenimente pot fi scrise în orice limbaj de programare, deși unele limbaje sunt
proiectate special pentru a facilita acest tip de programare și există medii integrate de dezvoltare
(IDE) care automatizează, parțial sau aproape total, producerea codului. Astfel de aplicații includ
obiecte și comenzi încorporate, fiecare dintre acestea fiind gândite pentru o serie de evenimente
prestabilite. Practic, toate limbajele obiect orientate și vizuale susțin programarea pilotată de
evenimente. Visual Basic, C# și Java sunt exemple de astfel de limbaje.

Limbajele de programare contemporane susțin evenimentele ca mecanism pentru tratarea


excepțiilor. De exemplu, în C# evenimentele sunt implementate ca element al limbajului și sunt
membri ai clasei. Mecanismul evenimentului implementează șablonul Publisher/Subscriber.
Exemplu de declarație a unui eveniment în limbajul C#:

          public class MyClass

          {

                    public event EventHandler MyEvent;

          }

Aici, EventHandler este un delegat care definește tipul procedurii de tratare a evenimentului.
Abonarea la eveniment este realizată astfel:

myClass.MyEvent += new EventHandler (Handler);

unde myClass este o instanță a clasei MyClass,  iar Handler este procedura de tratare a


evenimentului. Un eveniment poate avea un număr nelimitat de proceduri de tratare. Când este
adăugată o prcedură de tratare, aceasta este adăugată într-o stivă specială, iar la producerea
evenimentului este apelată procedura din topul stivei. Dezabonarea de la eveniment, adică
eliminarea procedurii de tratare, se face în mod similar, dar folosind operatorul "- =".

Cum funcționează PPE


Dispecerele de evenimente

Elementul central al unei aplicații pilotate de evenimente este partea programului ce recepționează
evenimentele – dispecerul, care transmite fiecare eveniment handler-ului propriu. Dispecerul rămâne
activ până când va întâlni un eveniment (de exemplu, "End_Program") care îl va determina să
închidă aplicația. În anumite circumstanțe, dispecerul poate întâlni un eveniment pentru care nu
există un handler adecvat. În funcție de natura evenimentului, dispecerul poate fie să îl ignore, fie să
genereze o excepție. Într-un mediu de programare pilotat de evenimente, evenimentele standard sunt
de obicei identificate utilizând ID-ul obiectului afectat de eveniment (de exemplu, numele unui
buton dintr-o formă) și ID-ul evenimentului (de exemplu, "clic-stânga"). Informația transmisă event-
handlerului poate include date suplimentare, cum ar fi coordonatele x și y ale indicatorului de mouse
în momentul producerii evenimentului sau starea tastei Shift, dacă evenimentul în cauză este o
apăsare de tastă sau de buton de mouse.

Ce reprezintă evenimentele
Evenimentele sunt deseori acțiuni efectuate de utilizator în timpul folosirii programului, dar pot fi și
mesaje generate de sistemul de operare, de o altă aplicație sau o întrerupere generată de un
dispozitiv periferic sau hardware de sistem. Dacă utilizatorul apasă un buton de mouse sau tasta
Enter, va fi generat un eveniment. Dacă descărcarea unui fișier s-a terminat, aceasta declanșează un
eveniment. Dacă există o eroare hardware sau software, va fi generat un eveniment. Evenimentele
sunt gestionate de operatorul central de evenimente (dispecer) - ciclu care rulează continuu în fundal
și așteaptă să se întâmple evenimente. Când are loc un eveniment, dispecerul trebuie să determine
tipul evenimentului și să apeleze handlerul corespunzător. Informațiile transmise de dispecer către
event handler vor varia, dar vor include date suficiente pentru a permite codului de tratare să ia toate
măsurile necesare.

Handlerul de eveniment

Handler-ul de eveniment este un mic fragment de cod procedural care tratează evenimentul. De


obicei, acesta va produce un răspuns vizual pentru a informa sau direcționa utilizatorul și poate
adesea schimba starea sistemului. Starea sistemului cuprinde atât datele utilizate de sistem (de
exemplu, valoarea unui câmp al BD), cât și starea interfeței utilizatorului (de exemplu, care obiect
de pe ecran deține cursorul de intrare sau care este culoarea de fundal a unei casete de text). Un
event-handler poate chiar să declanșeze un alt eveniment care va solicita un alt event-handler
(atenție la bucle infinite). În mod similar, un event-handler poate determina ca în anumite situații
așteptarea să fie eliminată (de exemplu, când utilizatorul face clic pe butonul Quit pentru a închide
programul).

Relația dintre evenimente, planificator și operatorii de evenimente

Figura 3 ilustrează relația dintre evenimente, planificator/dispecer și codul care tratează un


eveniment.

Fig. 3. Reprezentarea schematică a paradigmei event-driven programming

Pseudo-codul de mai jos ilustrează cum ar putea funcționa un planificator. El este compus dintr-o
buclă principală care rulează continuu până când apare o condiție de terminare. La apariția unui
eveniment, planificatorul determină tipul evenimentului și selectează un handler adecvat (sau decide
cum se va proceda, dacă handlerul necesar nu există).

do forever: // the main scheduler loop


   get event from input stream

    if event type == EndProgram:

        quit // break out of event loop

     else if event type == event_01:

        call event-handler for event_01 with event parameters

     else if event type == event_02:

        call event-handler for event_02 with event parameters

      .

      .

      .

     else if event type == event_nn:

        call event-handler for event_nn with event parameters

     else handle unrecognized event // ignore or raise exception

end loop

Într-o aplicație bazată pe evenimente, mai multe evenimente pot fi declanșate într-un timp relativ
scurt. Dispecerul și event-handlerele s-ar putea să nu face față tuturor evenimentelor imediat ce
acestea au fost declanșate. Soluția evidentă este de a plasa evenimentele neprocesate într-un fir de
așteptare până când acestea vor putea fi preluate pentru a fi tratate. Evenimentele sunt introduse în
coada firului de așteptare și vor fi preluate de către planificator odată ce ajung în topul firului. Este
posibil să existe și scheme prioritare în care anumite tipuri de evenimente au prioritate față de altele.
Astfel de evenimente vor fi urmărite rapid de dispecer, care le va deplasa în topul firului de
așteptare. Poate chiar exista un fir de așteptare separat pentru evenimentele prioritare. Existența
firului de așteptare garantează că toate evenimentele vor fi tratate la un moment dat și într-o anumită
ordine. Lungimea firului și timpul necesar procesării evenimentelor depind de viteza procesorului,
capacitatea memoriei operative și numărul de alte aplicații executate în același timp (nivelul de
multitasking - concurența pentru aceleași resurse de sistem). O mare parte din timp, însă, firul de
așteptare este vid și dispecerul va fi în stare de așteptare pentru următorul eveniment.

Domenii de aplicare
Programarea pilotată de evenimente, de regulă, este utilizată în trei cazuri:
o pentru crearea aplicațiilor server;

o atunci când sunt construite interfețe utilizator, inclusiv cele grafice pentru aplicații
desktop;

o pentru programarea jocurilor în care trebuie controlate multe obiecte.

Utilizarea în aplicații server

Programarea pilotată de evenimente este utilizată în aplicațiile server pentru a rezolva problema
scalării atunci când există mai mult de 10.000 de conexiuni simultane. În serverele construite pe
modelul "un fir - o conexiune", problemele de scalabilitate apar datorită solicitărilor excesive pentru
structurile de date ale sistemului de operare, necesare pentru a descrie o sarcină (segmentul de stare
a sarcinii, stiva) sau din cauza cheltuielilor excesive pentru comutarea contextelor.

Condiția filosofică pentru refuzarea modelului serverelor cu fire de execuție poate fi declarația lui
Alan Cox: "Un computer este un automat finit. Programarea cu fire de execuție este necesară pentru
cei care nu știu cum sa programeze automate finite".

Aplicația server folosind programarea pilotată de evenimente este implementată într-un apel de


sistem, care primește evenimentele simultan din mai mulți descriptori (multiplexare). La procesarea
evenimentelor sunt utilizate doar operațiile de intrare/ieșire fără blocare, astfel încât nici un
descriptor nu împiedică procesarea evenimentelor din alți descriptori. Pentru multiplexarea
conexiunilor pot fi utilizate instrumente ale sistemului de operare, cum ar fi select din UNIX
(scalare slabă, deoarece lista descriptorilor este reprezentată ca imagine
bitmap), poll și epoll (Linux), kqueue (FreeBSD), /dev/poll (Solaris), IO completion
port (Windows), POSIX AIO sau io submit și eventfd pentru operațiile I/O de disc.

Utilizarea în aplicații desktop

În anii 1950 utilizatorii calculatoarelor erau şi programatori. Primele programe lucrau în felul
următor: citeau datele de intrare de pe cartele, banda perforată sau dintr-un fișier, le prelucrau atât
cât era necesar, apoi extrăgeau rezultatele pe hârtie sau le salvau într-un fișier de ieșire. Utilizatorii
nu intrau în calcule. Chiar atunci când programele au devenit mai interactive permițând folosirea
linei de comandă pe un monitor video, utilizatorii erau nevoiți să învețe o mulțime de comenzi şi
opțiuni criptice pentru a controla execuția acelor programe.

Adevărata revoluție în crearea programelor de calculator au constituit-o primele procesoare de texte


şi primele programe de calcul tabelar interactive, care includeau forme primitive ale elementului
fundamental de proiectare a interfețelor moderne - meniul. Chiar dacă meniurile acelor aplicații
interactive nu erau prea bine implementate, ideea apărută a luat forme tot mai complexe. Privind
retrospectiv, meniul pare acum un lucru elementar - un mijloc prin care utilizatorului îi sunt
prezentate posibilitățile programului.

În limbajele moderne de programare evenimentele și gestionarea evenimentelor sunt esențiale pentru


implementarea interfeței grafice de utilizator. Ca exemplu poate fi interacțiunea programului cu
evenimentele de la mouse. Apăsarea butonului stâng al mouse-ului determină o întrerupere a
sistemului care declanșează o procedură specifică în interiorul sistemului de operare. În această
procedură, fereastra de sub cursorul mouse-ului este căutată. Dacă se găsește o fereastră, atunci acest
eveniment este trimis la coada de procesare a mesajelor din această fereastră. În plus, în funcție de
tipul ferestrei, pot fi generate evenimente suplimentare. De exemplu, dacă fereastra este un buton (în
Windows, toate elementele grafice sunt ferestre), atunci este generat un eveniment click pe buton.
Ultimul eveniment este mai abstract, și anume, nu conține coordonatele indicatorului de mouse, ci
pur și simplu spune că butonul a fost apăsat. În C# codul de tratare a evenimentului poate arăta
astfel:

          private void button1_Click(object sender, EventArgs e)

          {

                    MessageBox.Show("Butonul a fost apăsat");

          }

În acest caz, codul pentru tratarea evenimentului este o procedură la care este transmis parametrul
sender, de obicei conținând un pointer la sursa evenimentului. Aceasta permite utilizarea aceleași
proceduri pentru a gestiona evenimente de la mai multe butoane, acest parametru producând
diferența.

Astăzi cele mai populare aplicații software moderne au o interfață grafică de utilizator plăcută și
prietenoasă. Totuși nu ar trebui să credem că programarea pilotată de evenimente este unica soluție
potrivită pentru crearea sistemelor informatice. Unele sisteme pot avea un rol foarte specific care le
obligă să îndeplinească anumite sarcini de la lansare până la oprire cu intervenție minimă sau fără
intervenția utilizatorilor (de exemplu, un compilator C). Astfel de aplicații vor fi în continuare
elaborate în conformitate cu paradigma procedurală.

Majoritatea software-ului comercial presupune existența unei interfețe grafice, iar cea mai mare
parte a acesteia este pilotat de evenimente. Un limbaj de programare vizuală, cum ar fi Visual Basic
sau Visual C++, vine cu un mediu integrat de dezvoltare (IDE), care oferă o gamă largă de comenzi
standard, fiecare cu seturi proprii de evenimente și cu șabloane de coduri pentru event-handlere.
Sarcina programatorului GUI este astfel dublă: (1) de a crea interfața cu utilizatorul și (2) de a scrie
codul de manipulare a evenimentelor și eventualele module de cod suplimentare care ar putea fi
necesare. IDE furnizează dispecerul și firul de așteptare și, în mare măsură, are grijă de modul în
care programul va fi executat.

Un programator este liber să se concentreze asupra codului specific aplicației, inclusiv GUI. El va
scrie codul necesar fiecărui control sau obiect pentru a permite acestuia să răspundă la un anumit
eveniment, dar nu obligator trebuie să știe cum să creeze aceste elemente. Nu este aceeași situația
unui inginer IT, care este obligat să cunoască cum aceste elemente pot fi create.

Rezumat
1. PPE este paradigmă de programare în care execuția programului este determinată de
evenimente: o acțiune a unui utilizator cum ar fi un click de mouse, apăsarea de taste, un
mesaj din sistemul de operare sau din alt program. O aplicație bazată pe evenimente este
concepută pentru a recepționa evenimentele pe măsura apariției și a le procesa, folosind o
procedură adecvată de tratare - event-handlerul. Programele bazate pe evenimente pot fi
scrise în orice limbaj de programare, deși unele limbaje sunt proiectate special pentru a
facilita acest tip de programare.
2. Înainte event-handlerele erau implementate ca subrutine în cadrul programului procedural.
Fluxul de execuție a programului era determinat de către programator și controlat de rutina
principală a aplicației. Programul trebuia foarte bine structurat. Într-un program controlat de
evenimente nu există un flux de control. Rutina principală conține o buclă de dispecerizare a
evenimentelor (dispecer de evenimente), care atunci când apare un eveniment apelează
procedura adecvată de tratare a acestuia.
3. Elementul central al unei aplicații pilotate de evenimente este partea programului care
recepționează  evenimentele (dispecerul) și transmite fiecare eveniment operatorului
(handler, procedură de tratare) propriu. Dispecerul rămâne activ până când va întâlni un
eveniment (de exemplu, "End_Program") care îl va determina să închidă aplicația. În
anumite circumstanțe, dispecerul poate întâlni un eveniment pentru care nu există un handler
adecvat. În funcție de natura evenimentului, dispecerul poate fie să îl ignore, fie să genereze
o excepție.
4. Într-un sistem bazat pe evenimente, frecvența apariției evenimentelor poate fi mare.
Aplicația s-ar putea să nu poată asigura tratarea imediată a unui eveniment. Evenimentele
neprocesate sunt plasate într-un fir de așteptare până când vor putea fi preluate pentru a fi
tratate.
5. În programarea procedurală (imperativă) programul este centrat pe calculator, executat
conform intenției programatorului, care stabilește ordinea acțiunilor și controlează fluxul de
calcul. În PPE programul este centrat pe utilizator, este interactiv, iar fluxul de calcul este
determinat în timpul execuției.
6. PPE, de regulă, este utilizată în trei cazuri: (1) atunci când sunt construite interfețe utilizator
(inclusiv cele grafice pentru aplicații desktop); (2) pentru crearea aplicațiilor server; (3)
pentru programarea jocurilor.
7. Diferite limbaje de programare susțin PPE în grad diferit. Cel mai complet nivel de susținere
a evenimentelor poate fi găsit la Perl, Java, C#, Python, ActionScript 3.0 etc. Multe alte
limbaje susțin evenimentele ca mecanism pentru tratarea excepțiilor.
8. PPE are anumite dezavantaje: (1) testarea programelor PPE este mult mai complicată
deoarece programatorul nu cunoaște care va fi fluxul de execuție - fluxul poate fi diferit de la
o execuție la alta - este dificil de prognozat acțiunile utilizatorului. Din această cauză este
folosită metoda testării incrementale; (2) pentru aplicații mici și simple, care nu au nevoie de
interfețe grafice sofisticate este recomandată utilizarea programării procedurale.

Dicționar
Event listener (Ascultător de evenimente) - o comandă care poate fi configurată pentru a declanșa o
funcție atunci când un anumit eveniment are loc.

Funcție callback - funcție specificată ca parte a unui Event Listener; este scrisă de programator, dar
apelată de sistem ca rezultat al declanșării unui eveniment.

Eveniment (dex.ro) - ceea ce are loc, se produce într-un punct arbitrar în spațiu-timp. În informatică
și programare: un eveniment este un mesaj care indică ce s-a întâmplat.
Program pilotat de evenimente - un program conceput pentru a rula blocuri de cod sau funcții ca
răspuns la evenimente specificate.

Gestionarea evenimentelor - termen general pentru sarcinile de programare implicate în realizarea


unui program care să răspundă evenimentelor prin declanșarea funcțiilor asociate.

Elemente GUI - obiecte pe ecran, cum ar fi butoane, imagini, casete de text, meniuri, ecrane etc.

GUI (Interfața grafică a utilizatorului) - Elementele vizuale ale unui program prin care un utilizator
controlează sau comunică cu aplicația.

Totul a pornit de la GUI


În partea 2 a lecției 1 veți face cunoștință cu principalele concepte ale programării pilotate de
evenimente cu origini în interfața grafică a utilizatorului, concepte care au ținut pasul cu revoluția
calculatoarelor personale din anii 80, dar care au debutat încă la sfârșitul anilor 1960.

Planul de astăzi este de a explica, încă o dată, de ce interfața de utilizator grafică, prescurtat GUI, a
dat naștere unei noi paradigme de programare și cum funcționează cu adevărat această nouă
paradigmă, cunoscută sub numele de programare pilotată de (sau bazată pe) evenimente. Mai exact,
în această lecție veți face cunoștință cu cele mai importante caracteristici ale programării bazate pe
evenimente, exemplificate prin API-ul Win32 original, care a fost proiectat încă în anii 1980 și care
funcționează și astăzi, chiar și pe cele mai recente versiuni de Windows pe 64 de biți.

M-am bazat pe lecția dlui dr Miro Samek în care au fost introduse mici modificări. Mi s-a părut cea
mai utilă lecție din cele la care "am fost prezent", luând în considerație disciplinele care vor urma în
programul vostru de învățământ.  Cine dorește poate asista la această lecție aici: 

De la prima transmisie între două calculatoare care legau un laborator al Universității California din
Los Angeles (UCLA) cu Institutul de Cercetare Stanford (acum cunoscut sub numele de SRI
International sau SRI) a trecut ceva timp. Deși legătura a cedat la scurt timp după începerea
transmisiei, revoluția în conectivitatea digitală luase start. Cercetătorii au continuat să lucreze asupra
rețelelor de calculatoare, concomitent dezvoltând și alte tehnologii complementare. Dintre cele mai
importante au fost, probabil, cele propuse de Douglas Engelbart, creatorul Centrului de Cercetări
Augmentate de la SRI, și colegii săi. De acolo au pornit atât de cunoscutele azi realizări cum ar fi
mouse-ul, hipertextul, instrumentele de colaborare și ecranele cu imagini bitmap - ferestrele -
precursorii interfeței grafice cu utilizatorul.

Interesant este că, în solicitarea sa de brevet, Engelbart se referea la cursorul mouse-ului ca la un


„indicator de poziție X-Y pentru un sistem de afișare” (fig. 1). Dispozitivul a fost poreclit „mouse”
din cauza “cozii” care-l lega de calculator. Cursorul de pe ecran sugerase inițial denumirea „bug”
(gândac), însă termenul mouse a ieșit învingător.

Toate acestea au fost prezentate de Doug Engelbart și echipa sa la conferința comună ACM/IEEE
din Menlo Park, California, pe 9 decembrie 1968. Un fragment din acea prezentare, numită
retroactiv „The Mother of All Demos”, poate fi privit în continuare. 
Pentru cei care nu se grăbesc, prezentarea integrală, care durează peste 90 minute, poate fi accesată
la  

Prezentarea live, în care era descris sistemul de operare numit NLS (On-Line System), a demonstrat
în esență aproape toate elementele fundamentale ale tehnologiilor moderne: mouse,  ferestre,
hipertext, grafică, navigare eficientă, conferință video, procesarea textului, conectarea dinamică a
fișierelor, controlul versiunilor și colaborarea reală. 

Ideile laboratorului SRI au fost extrem de influente și au dat naștere la o serie de proiecte similare.
Dar a fost nevoie de un deceniu aproape complet pentru a pune la punct detaliile tehnice care au
implementat ideile. Acest lucru s-a întâmplat la Centrul de Cercetare Xerox Palo Alto (Xerox Palo
Alto Research Center, PARC) la mijlocul anilor 1970. Clipul de mai jos, care este un material
publicitar pentru promovarea calculatorului Xerox Alto, fixează situația de atunci privind
implementarea acelor idei. 

 Era anul 1973: primul calculator cu GUI, dezvoltat la PARC afișa o fereastră, utilizatorul o putea
situa deasupra altor ferestre, cum ar fi așezarea unei foi de hârtie peste alte foi pe un birou, puteau fi
alese opțiuni dintr-o listă de posibilități, chiar dacă se lucra mai mult (încă) din linia de comandă.
Dar mouse-ul era alături!  

Ca și concluzie: sistemul de operare era un prim reprezentant al programelor conduse de


evenimente, deși la acel moment nu se vorbea încă de programarea pilotată de evenimente. Trebuia
să mai treacă mai mult de un deceniu ca să ajungem la PPE.

Schimbarea paradigmei
Comparativ cu interfața liniei de comandă pentru care este folosit un terminal (o tastatură și un
display), implementarea unei interfețe grafice de utilizator a necesitat o schimbare de paradigmă.

Iată principala problemă: într-o interfață a sistemului de operare de tipul linie de comandă, singurul
dispozitiv de intrare este tastatura, iar singurul dispozitiv de ieșire este display-ul cu linia din partea
de jos a ecranului, care derulează ca un teletype electronic.

Codul pentru lucrul cu terminalul arată aproximativ așa:

int main(int arg, char *argv[]) {


     while (1) {
          int ch = getch(); /* wait for key press… */
          putchar(ch); /* echo the key press to the screen */
           … /* process the key press. . . */
          printf(…); /* output the result to the sceen */
     }
}

Atunci când are loc o apăsare de tastă, codul afișează caracterul care corespunde tastei (ecoul) și
procesează tasta, ceea ce ar putea conduce la afișarea a ceva suplimentar pe ecran. Afișarea se
produce întotdeauna în partea de jos a ecranului unde se află cursorul de editare.
Dar, cu o interfață grafică de utilizator, situația este fundamental diferită. Întâi de toate, acum avem
mai multe surse de intrare: tastatura și mouse-ul. Apare imediat o problemă: dacă programul este
configurat pentru intrare implicită de la tastatură, nu sunt posibile intrări de la mouse și invers.
Pseudocodul de mai jos ne introduce în problemă:

int main(int arg, char *argv[]) {


     while (1) {
          int ch = getch(); /* wait for key press… */
          struct mouse_data msg = mouse_input(); /* wait for mouse ???*/
          if (key-press) {
              putchar(ch); /* echo the key press to the screen */
              … /* process the key press. . . */
              printf(…); /* output the result to the sceen */
          }
          if (mouse-move) {
              int x = m.x; /* get the mouse coordinates */
              int y = m.y;
              int buttons = m.buttons /* get the state of mouse buttons */
              /* determine over which object the mouse currently is */
              int obj_id = getShape(x, y);
              /* process the mouse depending on the object… */
              switch (obj_id) {
. . .
             }
          }
     }                  
}

Deci, mai întâi trebuie de găsit o modalitate de a lucra simultan cu mai multe dispozitive de intrare,
programul știind la fiecare moment de timp care intrare este folosită, iar celelalte să fie blocate. Plus
că, atunci când este vorba de tastatură nu mai știi unde trebuie să se producă ieșirea pe ecran. Pentru
aceasta, ar trebui să știți care parte a ecranului a fost "în treburi", cea ce se numește "input focus" în
programarea pilotată de evenimente de astăzi.

Dar legate de mouse mai apr și alte probleme, mai multe și mai complicate. Mouse-ul furnizează o
intrare bidimensională, astfel încât aveți pe ecran coordonatele x și y, plus starea butoanelor mouse-
ului. Dar coordonatele brute nu sunt suficiente pentru a lua măsuri semnificative: trebuie să știți ce
obiect de pe ecran este la aceste coordonate. Pentru aceasta, în mod obișnuit apelați la un serviciu al
interfeței grafice. Deoarece acest lucru se întâmplă pentru orice intrare de la mouse, vom sări peste
acest moment, dar vom reveni în capitolul consacrat mouse-ului cu explicațiile de rigoare. Lăsăm
interfeței grafice să găsească obiectul la coordonatele actuale ale mouse-ului.

Acest lucru înseamnă efectiv că mouse-ul poate produce multe alte tipuri de intrări, cum ar fi
„obiect_id” în pseudocodul de mai sus, toate acestea în funcție de situația în continuă schimbare de
pe ecran.

Evenimentele conduc programul


Am ajuns la concluzia că, două dispozitive de intrare plus interfața grafică de utilizator introduc noi
niveluri de complexitate, care diferă mult de situația când în calitate de interfață folosim linia de
comandă. De fapt, programarea GUI este o cu totul altă muzică. Nu este de mirare că a solicitat un
mod diferit de gândire.
Perspectiva cheie care a generat o soluție viabilă a fost focalizarea pe intrări, care se numesc
*evenimente* sau *mesaje*, cum ar fi apăsările de taste, mișcările mouse-ului și intrările secundare
de la obiecte de pe ecran, cum ar fi butoanele, pictograme pentru desktop, bare de derulare
etc. Concentrarea pe evenimente înseamnă că evenimentele conduc software-ul, nu invers, așa cum
era în programele secvențiale. 

HelloWin modificat (de la Charles Petzold)


Pentru a vedea ce înseamnă cu adevărat această paradigmă bazată pe evenimente și cum
funcționează, vom considera o aplicație GUI simplă „HelloWin” care rulează pe Windows. Aplicația
este scrisă în C, folosind antica Interfață pentru Programarea Aplicațiilor Windows (API), pe care
Microsoft a dezvoltat-o încă din anii '80. Spre deosebire de alte API-uri Windows, mai moderne și
alte limbaje de programare, această API Win32 de nivel scăzut în C demonstrează conceptele
principale ale programării bazate pe evenimente în forma lor cea mai simplă și directă.

Așadar, haideți să parcurgem această aplicație Windows simplă, care este adaptarea programului
„Hello, Windows 95” din cartea „Programarea în Windows” a lui Charles Petzold. Această carte,
publicată pentru prima dată în 1988, a devenit biblia pentru programarea Windows în acea vreme.

/*******************************************************************

* HelloWin.c -- Displays "Hello, Windows" in client area

*               (c) Charles Petzold, 1996

* ©Miro Samek, 2020

* Adaptat pentru cursul "Programarea pilotata de evenimente"

*******************************************************************/

#include <windows.h> // Windows API

LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam);

#define WIN_HANDLED   ((LRESULT)0)

/*----------------------------------------------------------------

* The main entry point to the Windows apllication

*/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PTSTR szCmdLine,


int iShowCmd)

{
    WNDCLASSEX wnd; // instance of the Window class ('wnd' object)

    HWND hwnd;  // "handle" to the window object

    int status; // status of the processing to report back to Windows

    UNREFERENCED_PARAMETER(hPrevInstance);

    UNREFERENCED_PARAMETER(szCmdLine);

    // set attributes of the 'wnd' object

    wnd.cbSize = sizeof(wnd);

    wnd.style = CS_HREDRAW | CS_VREDRAW;

    wnd.cbClsExtra = 0;

    wnd.cbWndExtra = 0;

    wnd.hInstance = hInstance;

    wnd.hIcon = NULL; // LoadIcon(NULL, IDI_APPLICATION);

    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);

    wnd.hbrBackground = GetStockObject(WHITE_BRUSH);

    wnd.lpszMenuName = NULL;

    wnd.hIconSm = NULL;

    wnd.lpszClassName = "HelloWin";

    // set "virtual" function of the 'wnd' object

    wnd.lpfnWndProc = &WndProc; // attach the "window proc"

    // register the Window Class with Windows

    RegisterClassEx(&wnd);

    // constructor of the 'wnd' class

    hwnd = CreateWindow(
        wnd.lpszClassName,    // window class name

        "Hello, Windows!",    // window caption

        WS_OVERLAPPEDWINDOW,  // window style

        CW_USEDEFAULT,        // initial x position

        CW_USEDEFAULT,        // initial y position

        300,                  // initial x size

        200,                  // initial y size

        NULL,                 // parent window handle

        NULL,                 // window menu handle

        hInstance,            // program instance handle

        NULL);                // creation parameters

    ShowWindow(hwnd, iShowCmd);

    UpdateWindow(hwnd);

    // event loop ("message pump")

    while (1) {

        MSG msg; // message object to receive

        // generically WAIT for any message to arrive in the queue

        status = GetMessage(&msg, NULL, 0, 0);

        if (status == 0) { // message NOT to be processed?

            status = msg.wParam;

            break; // terminate the event loop

    }
        TranslateMessage(&msg);

        // dispatch to the appropriate "window proc"

        DispatchMessage(&msg);

  }

    return status; // return to Windows with the status of processing

/*----------------------------------------------------------------

* The "windows proc" registered for the main window

* of this application

*/

LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam)

    static int wm_keydown_ctr   = 0; // counter incremented in WM_KEYDOWN

    static int wm_mousemove_ctr = 0; // counter incremented in WM_MOUSEMOVE

    static char const* led_text = "OFF";

    LRESULT status; // status of the processing to report back to Windows

    switch (sig) {       // signal of the message

        case WM_CREATE: { // window was created

            status = WIN_HANDLED; // report event handled

            break;

    }
        case WM_DESTROY: { // windows is about to be destroyed

            PostQuitMessage(0);

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_PAINT: { // window needs to be repainted

            PAINTSTRUCT ps;

            HDC hdc;

            RECT rect;

            char cBuffer[100];

            wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s ",

                (wm_keydown_ctr % 1000),

                (wm_mousemove_ctr % 1000),

                led_text);

            // painting performed between BeginPain() and EndPaint()...

            hdc = BeginPaint(me, &ps);

            GetClientRect(me, &rect);

            DrawText(hdc, cBuffer, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

            EndPaint(me, &ps);

            status = WIN_HANDLED; // report event handled

            break;

    }

                case WM_KEYDOWN: {  // keyboard key has been pressed


            ++wm_keydown_ctr;

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_MOUSEMOVE: { // mouse has been moved over the window

            ++wm_mousemove_ctr;

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

        default: { // default behavior (characteristic "look and feel")

            // report the status of default processing

            status = DefWindowProc(me, sig, wParam, lParam);

            break;

    }

  }

    return status; // return to Windows with the status of processing

Explicațiile vin în pagina următoare.

Punctul de intrare în program - inițializarea


Codul începe cu #include windows.h, unde windows.h este un fișier antet care definește în API-ul
Windows tipurile și constantele. 

În continuare, puteți vedea declarația în avans a funcției WndProc (prototipul), necesară pentru
funcția WinMain.
Urmează funcția WinMain(), care este punctul de intrare în program și joacă același rol ca și funcția
main() în mediul C convențional, cu excepția că are mai mulți parametri, or o aplicație Windows
GUI este mai complexă. Aici, în această aplicație foarte simplă, unii dintre acești parametri nici
măcar nu sunt folosiți.

/*******************************************************************

* HelloWin.c -- Displays "Hello, Windows" in client area

*               (c) Charles Petzold, 1996

* ©Miro Samek, 2020

* Adaptat pentru cursul "Programarea pilotata de evenimente"

*******************************************************************/

#include <windows.h> // Windows API

LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam);

#define WIN_HANDLED   ((LRESULT)0)

/*----------------------------------------------------------------

* The main entry point to the Windows apllication

*/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PTSTR szCmdLine,


int iShowCmd)

    WNDCLASSEX wnd; // instance of the Window class ('wnd' object)

    HWND hwnd;  // "handle" to the window object

    int status; // status of the processing to report back to Windows

    UNREFERENCED_PARAMETER(hPrevInstance);

    UNREFERENCED_PARAMETER(szCmdLine);

Dar urmează o parte interesantă, în care pregătim clasa de fereastră (tipul WNDCLASSEX) pentru a
fi înregistrată în Windows.
Am formatat și comentat în mod special inițializarea, astfel încât să recunoașteți că aici vă angajați
într-un fel de programare orientată obiect, așa cum ați procedat în OOP cu limbajul C. Deci, în mod
special, vedem mai întâi setarea atributelor unei instanțe din clasa ferestrei 'wnd', cum ar fi stilul
ferestrei, cursorul mouse-ului pentru fereastră și numele clasei ferestrei.

    // set attributes of the 'wnd' object

    wnd.cbSize = sizeof(wnd);

    wnd.style = CS_HREDRAW | CS_VREDRAW;

    wnd.cbClsExtra = 0;

    wnd.cbWndExtra = 0;

    wnd.hInstance = hInstance;

    wnd.hIcon = NULL; // LoadIcon(NULL, IDI_APPLICATION);

    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);

    wnd.hbrBackground = GetStockObject(WHITE_BRUSH);

    wnd.lpszMenuName = NULL;

    wnd.hIconSm = NULL;

    wnd.lpszClassName = "HelloWin";

    wnd.lpfnWndProc = &WndProc; // attach the "window proc"     // set "virtual" function of the
'wnd' object

    RegisterClassEx(&wnd);    // register the Window Class with Windows

    // constructor of the 'wnd' class

    hwnd = CreateWindow(

        wnd.lpszClassName,    // window class name

        "Hello, Windows!",    // window caption

        WS_OVERLAPPEDWINDOW,  // window style

        CW_USEDEFAULT,        // initial x position

        CW_USEDEFAULT,        // initial y position


        300,                  // initial x size

        200,                  // initial y size

        NULL,                 // parent window handle

        NULL,                 // window menu handle

        hInstance,            // program instance handle

        NULL);                // creation parameters

    ShowWindow(hwnd, iShowCmd);

    UpdateWindow(hwnd);

Urmează atribuirea funcției “virtuale” - procedura de fereastră - WndProc este funcția care va trata
mesajele pentru această clasă de ferestre. Aici, designerii API Windows au folosit tehnica simplă de
implementare a încorporării unui pointer la funcția virtuală direct în structura atributelor.

Apoi, este apelată funcția Windows API – RegisterClassEx(&wnd), pentru a înregistra clasa de
fereastră cu valorile setate ale atributelor.

Și în sfârșit, apelul la CreateWindow joacă rolul unui constructor, deoarece creează un obiect
fereastră bazat pe clasa de ferestre chiar acum înregistrată.

Odată creată fereastra aplicației, aceasta este afișată pe ecran (ShowWindow) și actualizată
(UpdateWindow).

Punctul de intrare în program - bucla de mesaje


După inițializare, funcția WinMain intră în bucla de evenimente, cunoscută și sub denumirea de
bucla de mesaje sau pompa de mesaje, unde programul efectuează adevărata lucrare. Aceasta este
partea cea mai importantă a fiecărui program bazat pe evenimente.

 // event loop ("message pump")

    while (1) {

        MSG msg; // message object to receive       

        status = GetMessage(&msg, NULL, 0, 0);  // generically WAIT for any message to arrive in
the queue

        if (status == 0) { // message NOT to be processed?

            status = msg.wParam;
            break; // terminate the event loop

    }

        TranslateMessage(&msg);

        DispatchMessage(&msg);        // dispatch to the appropriate "window proc"

  }

    return status; // return to Windows with the status of processing

Bucla de evenimente are o structură foarte specifică și conține doi pași principali:

În primul rând observăm funcția GetMessage() care așteaptă orice intrare de la tastatură, mouse sau
ecran. Când se întâmplă oricare dintre astfel de evenimente, sistemul de operare Windows îl
plasează în coada de mesaje pentru această aplicație. GetMessage() apoi deblochează și copiază
mesajul de la coadă la obiectul msg.

Acesta este modul în care dispecerul-buclă rezolvă problema de așteptare simultan pentru mai multe
evenimente, ceea ce era imposibil folosind pseudocodul anterior.

Dacă starea returnată de GetMessage() este zero, înseamnă că aplicația a fost închisă de utilizator,
deci bucla de evenimente trebuie încheiată executând instrucțiunea break. Aplicația își va încheia
execuția.

În caz contrar, mesajul este transmis funcției DispatchMessage(), care apelează „procedura de
fereastră WndProc” înregistrată pentru fereastra curentă.

Vom analiza „procedura de fereastră” un pic mai târziu, dar înainte de a părăsi acest fragment de
cod, în rezumat despre proprietățile cheie ale dispecerului (bucla de repartizare a evenimentelor).

Proprietățile dispecerului
Dispecerul posedă câteva proprietăți foarte importante din punctul de vedere al programării pilotate
de evenimente.  Prima proprietate este aceea că dispecerul folosește pentru comunicare mesajele.
Fiecărui eveniment îi corespunde un mesaj. Atunci când are loc un eveniment, sistemul de operare
introduce în coada de evenimente a sistemului mesajul asociat, de unde ulterior va fi preluat pentru
procesare. Datorită cozii de mesaje, evenimentele pot fi livrate și atunci când dispecerul este în
așteptare, cât și atunci când este ocupat procesând evenimente anterioare. Sistemul de operare
interceptează evenimentul, îl transformă în mesaj și îl plasează în coada de mesaje, dar NU așteaptă
procesarea efectivă a evenimentului. Acest tip de livrare a evenimentelor se numește *asincron* și
înseamnă că producătorul de evenimente (SO Windows) este independent de consumatorul de
evenimente (aplicația). Cu alte cuvinte, aceste două activități sunt asincrone, ceea ce înseamnă că nu
sunt sincronizate. Mai târziu în această lecție veți vedea un exemplu, care va demonstra această
natură asincronă a livrării evenimentelor.
A doua proprietate a dispecerului: execuția funcției DispatchMessage() trebuie să fie obligator
finalizată. Doar după această se va începe “lucrul” cu următorul eveniment. Aceasta înseamnă că
dispecerul procesează evenimentele în mod atomar (Run-to-Completion, RTC) – procesarea unui
eveniment nu poate fi întreruptă și lansată procesarea altui eveniment.

Iar a treia proprietate cheie a dispecerului este că apelează codul aplicației – funcția WndProc.
Aceasta diferă de programarea imperativă în care doar aplicația putea apela funcții ale sistemului de
operare. Acum și sistemul de operare apelează funcții ale aplicației.

Această ultimă proprietate duce la "inversarea controlului" în comparație cu programarea


secvențială tradițională. Este o caracteristica cheie a tuturor programelor controlate de evenimente și
este esența PPE. Inversarea controlului înseamnă că evenimentele conduc aplicația și nu invers (cum
era în programarea imperativă). 

Dacă am compara cu schemele-bloc de descriere a algoritmilor, prezentându-le sub formă de graf


orientat, asta ar însemna că, spre deosebire de programarea imperativă, în care operațiile erau plasate
în vârfurile grafului, iar tranzițiile erau reprezentate prin arce, aici este exact invers: activitățile
(operațiile) sunt reprezentate prin arce, iar în vârfurile grafului sunt plasate stările aplicației. Cu alte
cuvinte, tranzițiile dintr-o stare în alta sunt determinate de evenimentele importante pentru aplicație
și sunt reprezentate prin arcele grafului orientat. Pe arce vor fi indicate acțiunile asociate fiecărui
eveniment. Iar vârfurile grafului conțin stările în care se poate afla aplicația.

Procedura de fereastră
Iar acuma a venit timpul să analizăm procedura de fereastră – funcția WndProc. În primul rând,
pentru a înțelege signatura acestei funcții, trebuie să vedem declarația structurii mesajului (tipul
MSG). Structura MSG este definită în fișierul antet WinUser.h, care este inclus în Windows.h (a
vedea în Visual Studio).

După cum puteți vedea, tipurile celor patru parametri ai funcției WndProc sunt identice cu primele
patru atribute ale structurii MSG. Am redenumit doar primul parametru din hwnd în me, pentru a
face mai clar că WndProc este o funcție membru al clasei de ferestre. Aceasta este convenția de
denumire pentru funcțiile membru (implementarea claselor în C).

/*----------------------------------------------------------------

* The "windows proc" registered for the main window

* of this application

*/

LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam)

    static int wm_keydown_ctr   = 0; // counter incremented in WM_KEYDOWN


    static int wm_mousemove_ctr = 0; // counter incremented in WM_MOUSEMOVE

    static char const* led_text = "OFF";

    LRESULT status; // status of the processing to report back to Windows

    switch (sig) {       // signal of the message

        case WM_CREATE: { // window was created

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_DESTROY: { // windows is about to be destroyed

            PostQuitMessage(0);

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_PAINT: { // window needs to be repainted

            PAINTSTRUCT ps;

            HDC hdc;

            RECT rect;

            char cBuffer[100];

            wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s ", (wm_keydown_ctr %


1000), (wm_mousemove_ctr % 1000), led_text);

            // painting performed between BeginPain() and EndPaint()...

            hdc = BeginPaint(me, &ps);


            GetClientRect(me, &rect);

            DrawText(hdc, cBuffer, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

            EndPaint(me, &ps);

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_KEYDOWN: { // keyboard key has been pressed

            ++wm_keydown_ctr;

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

        case WM_MOUSEMOVE: { // mouse has been moved over the window

            ++wm_mousemove_ctr;

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

        default: { // default behavior (characteristic "look and feel")

            // report the status of default processing

            status = DefWindowProc(me, sig, wParam, lParam);

            break;

    }

  }
    return status; // return to Windows with the status of processing

Așa cum am văzut în WinMain, funcția WinProc nu este doar o funcție membru, ci este o funcție
”virtuală”, moment specific tipului clasei de ferestre înregistrate cu Windows pentru această
aplicație.

De asemenea, cel de-al doilea parametru al funcției WndProc a fost redenumit în  „sig” în loc de
msg, deoarece aceasta este partea din mesaj care vă informează despre ceea ce numim eveniment
asociat acestui mesaj. În programarea modernă bazată pe evenimente, această informație se numește
*semnal* de eveniment, așa că am numit-o „sig”.

Și în sfârșit, ultimii doi parametri, wParam și lParam, sunt parametrii de eveniment care furnizează
informații suplimentare despre eveniment. Sensul acestor parametri depinde de semnalul
evenimentului.

Destinația principală a funcției WndProc este să proceseze mesajele primite. Pentru această
programatorul va trebui să decidă care mesaje vor fi procesate. Pentru prelucrarea mesajelor utilizăm
instrucțiunea switch, care folosește ca expresie de control semnalul mesajului.

Instrucțiunile case sunt etichetate cu numele simbolice ale diferitelor semnale de mesaj, listate în
fișierul antet WinUser.h.

De exemplu, semnalul WM_CREATE corespunde valorii numerice 1 și este trimis la WndProc la


crearea ferestrei. În mod similar, WM_DESTROY corespunde valorii numerice 2 și este trimis la
WndProc atunci când fereastra urmează să fie distrusă. În acest caz, WndProc apelează
PostQuitMessage, care introduce mesajul WM_QUIT în coada de mesaje a programului, care, la
rândul său, va cauza ieșirea din bucla de evenimente. Este un exemplu interesant care arată că o
aplicație poate genera asincron evenimente pentru necesități proprii.

În toate cazurile, WndProc stabilește valoarea variabilei locale „status” care duce evidența stării
procesării. De exemplu, când WndProc tratează un mesaj dat, aceasta stabilește starea la
WIN_HANDLED. Această stare este apoi returnată înapoi la sistemul de operare, astfel încât există
o comunicare bidirecțională: sistemul de operare spune funcției WndProc ce mesaj să proceseze, iar
WndProc raportează starea procesării înapoi la Windows.

Tratarea mesajelor
Poate cel mai important mesaj pe care WndProc trebuie să îl gestioneze este mesajul WM_PAINT,
pe care sistemul Windows îl generează atunci când o parte sau toată zona client a ferestrei trebuie să
fie actualizată.

        case WM_PAINT: { // window needs to be repainted

            PAINTSTRUCT ps;

            HDC hdc;
            RECT rect;

            char cBuffer[100];

            wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s ", (wm_keydown_ctr


% 1000), (wm_mousemove_ctr % 1000),

                            led_text);

            hdc = BeginPaint(me, &ps);      // painting performed between BeginPain() and


EndPaint()...
            GetClientRect(me, &rect);

            DrawText(hdc, cBuffer, -1, &rect, DT_SINGLELINE | DT_CENTER |


DT_VCENTER);

            EndPaint(me, &ps);

            status = WIN_HANDLED; // report event handled

            break;

    }

Aplicația noastră gestionează acest lucru prin pictarea unui text în centrul zonei client a ferestrei.
Detaliile nu sunt atât de relevante la moment, vom spune doar că sunt afișate valorile curente ale
unor contoare pentru apăsarea tastelor și mișcările mouse-ului. Mai important este faptul că aceste
contoare sunt definite ca variabile *statice*, deoarece trebuie să înlăture multe invocări și "returnări"
din WndProc.

Contorul wm_keydown este incrementat în mesajul WM_KEYDOWN și contorul wm_mousemove


este incrementat în mesajul WM_MOUSEMOVE. În ambele cazuri, procesarea trebuie să includă
apelarea InvalidateRect() pentru a spune sistemului de operare că zona client a ferestrei trebuie
actualizată deoarece altfel noua valoare a contorului nu va fi actualizată imediat. Acesta este un alt
aspect al comunicării bidirecționale între codul aplicației și Windows.

În final, mesajele care nu sunt tratate de WndProc vor fi returnate sistemului de operare, care le va
trata așa cum “știe” el.

Este un design foarte interesant, care are implicații uriașe, deoarece aici vedem „aspectul și senzația
caracteristică”, generate de aplicațiile înzestrate cu GUI. Pentru a explica ce vreau să spun, hai să
rulăm această aplicație Hello-Windows și să vedem ce poate face.

Aplicația are fereastra obișnuită, cu bara de ferestre și un titlu. Poți redimensiona fereastra, o poți
muta, minimiza, restaura și maximiza. Dar noi am scris cod implicit doar pentru numărarea
mișcărilor mouse-ului și a apăsărilor tastelor. Cu toate acestea, aplicația poate face multe alte lucruri
și arată și se comportă cu adevărat ca orice altă aplicație Windows. Totul este datorat funcției
WndProc, care, împreună cu WinMain, oferă aceste alte comportamente.
Cazul implicit din WndProc ar putea să nu pară deloc impresionant, dar pentru a-l aprecia, trebuie
doar să navigați prin sutele de mesaje Windows definite în WinUser.h. Cele mai multe dintre aceste
semnale de WM_mesaj trec prin WndProc fără a fi procesate. De procesarea lor se ocupă sistemul
de operare.

Un programator nu este obligat să cunoască toate aceste complexități, deoarece tot ce trebuie să știe
sunt câteva mesaje pe care le gestionează. Un inginer IT trebuie să cunoască mult mai multe mesaje,
dar și mai important este să înțeleagă abordarea orientată pe mesaje.

Un mod bun de a înțelege această arhitectură este de a o privi sub unghiul abordării ordonării
ierarhice. La cel mai mic nivel al ierarhiei se află codul aplicației: este primul care poate răspunde la
un eveniment, deoarece fiecare eveniment este trimis mai întâi la WndProc. În cazul în care un
mesaj nu este tratat de WndProc în mod explicit, acesta nu este ignorat, ci este transmis sistemului
de operare, care se află la nivelul superior al ierarhiei (fig. ).

Fig.  2. Ordonarea ierarhică

Acest design ierarhic mai este adesea numit „Ultimate Hook” sau „Programming by Difference”.
Aceste două nume înseamnă exact același lucru, dar subliniază diferitele aspecte ale acestuia.
„Ultimate Hook” subliniază ușurința de a atașa sau „conecta” codul nostru la fiecare eveniment.
„Programming by Difference” subliniază faptul că trebuie doar să programați în mod explicit
*diferențele* de la comportamentul implicit.

Din perspectiva OOP, puteți considera acest design ca o ierarhie de clase în care SO Windows este
clasa de bază cu sute de funcții virtuale, una pentru fiecare semnal de mesaj. Aplicațiile Windows,
cum ar fi HelloWin sau Microsoft Word sunt subclase care înlocuiesc funcțiile virtuale selectate în
WndProc.
A fost o introducere rapidă în PPE pe exemplul unei aplicații simple cu GUI, scopul fiind explicarea
ideilor principale despre modul în care funcționează această paradigmă de programare. Dar nici-o
introducere în PPE nu vă va oferi o înțelegere corectă, fără a o pune în contrast cu programarea
tradițională, secvențială și cu problemele blocărilor inerente care pot avea loc încercând să punem
împreună PPE și programarea clasică. Asta este exact ceea ce vom încerca să înțelegem în ultimele
minute ale acestei lecții.

Gestiunea evenimentelor și programele secvențiale


Vom complica un pic problema. Fie că utilizatorul vrea ca aplicația noastră să controleze un LED,
adică să-l aprindă pentru câteva fracțiuni de secundă, apoi să-l stingă, folosind pentru aceasta
tastatura. Vom folosi în acest scop un fragment de cod clasic pentru Sistemele de operare în timp
real – RTOS: cod secvențial, care face ca un LED roșu de pe placa TivaC LaunchPad să clipească.
Ceva de tipul:

              BSP_ledRedOn();

              QXThread_delay(BSP_TIKS_PER_SEC / 3U);

              BSP_ledRedOff();

Vom introduce în WndProc, ceva analogic, care să oblige LED-ul să clipească la apăsarea oricărei
taste de pe tastatură. Locul de introducere a acestui fragment de cod este acolo unde sunt tratate
mesajele de la tastatură, adică CASE WM_KEYDOWN. Cu alte cuvinte, va trebui să modificăm
acest fragment de cod:

        case WM_KEYDOWN: { // keyboard key has been pressed

            ++wm_keydown_ctr;

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

O implementare tradițională, secvențială, ar fi să pornim LED-ul, după care să așteptăm, adică să


blocăm, pentru, să zicem 200 de milisecunde, pentru ca să vedem culoarea roșie și apoi să-l stingem.

API-ul Windows oferă un echivalent al serviciului RTOS de întârziere, pe nume Sleep(), care
blochează LED-ul în roșu și așteaptă  expirarea numărului specificat de milisecunde.

Deoarece nu putem utiliza un LED pe calculatorul nostru, vom afișa starea LED-ului ca text în
centrul zonei client a ferestrei.
Ca și în toate celelalte cazuri, după schimbarea textului de afișat, trebuie să invalidăm dreptunghiul
zonei client a ferestrei pentru a forța actualizarea ei.

"Dormim" timp de 200 de milisecunde. După întârzierea introdusă de Sleep(), schimbăm textul
LED-ului din RED în „OFF” și invalidăm din nou dreptunghiul zonei client a ferestrei.

Ca și celelalte variabile de stare, indicatorul de text LED trebuie definit fiind static în Sleep().
Suplimentar, trebuie să adăugăm textul LED în buffer pentru a putea fi afișat.

După toate aceste modificări, fragmentul de cod pentru tratarea mesajului WM_KEYDOWN va
arăta astfel:

        case WM_KEYDOWN: { // keyboard key has been pressed

            ++wm_keydown_ctr;

            led_text = "RED";    // BSP_ledRedOn();

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            Sleep(200);    // sleep and block for 200 ms

            led_text = "OFF";    // BSP_ledRedOff();


            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

Suplimentar, vom adăuga la declarații linia static char const* led_text = "OFF"; iar funcția wsprintf din
tratarea mesajului PAINT va arăta astfel: wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s
", (wm_keydown_ctr % 1000), (wm_mousemove_ctr % 1000), led_text); pentru ca pe ecran să fie afișată și
informația despre LED.

Hai să rulăm acest program. Când apăsați tastatura o dată starea LED-ului nu se schimbă așa cum
era de așteptat, chiar dacă contorul tastaturii crește. Deci, codul dvs. pentru LED NU funcționează
așa cum v-ați imaginat.

Dar situația devine și mai precară. Când apăsați mai multe taste rapid, programul îngheață și nu
actualizează imediat contorul tastaturii. Apoi, după o perioadă de timp, contorul de tastatură sare la o
valoare mare.

De fapt, atunci când apăsați mai multe taste sau deplasați mouse-ul, nu se întâmplă nimic în aplicație
și niciun contor nu se mărește până când atât contorul tastaturii, cât și cel al mouse-ului sar la valori
noi mari.
Toate acestea, evident, nu sunt bune, deoarece aplicația pare înghețată - nu răspunde. Iar salturile
masive din contoarele afișate sunt destul de ciudate. Se pare că acest comportament este o
consecință a evenimentelor asincrone și a cozii de evenimente în Windows.

Problema este că întârzierea Sleep() blochează WndProc și nu permite revenirea rapidă la bucla de
evenimente. Mesajele WM_KEYDOWN de la tastatură se acumulează în coada de evenimente, dar
vor putea fi procesate doar după expirarea celor 200 ms. De aici saltul brusc al valorilor contoarelor.

Este o problemă bine cunoscută, iar programatorii PPE i-au dat chiar și un nume:  ei numesc o astfel
de aplicație "pig" (porc) și nimeni nu ar vrea să fie numit așa.

Regulă generală pentru programele Windows este că, dacă sunt necesare mai mult de aproximativ
100 de milisecunde pentru procesarea unui eveniment, acesta trebuie divizat în câteva evenimente,
tratarea fiecăruia având o durată mai mică.

Așadar, blocarea introdusă de Sleep() explică lipsa de reacție și „înghețarea” programului. Dar să ne
amintim: actualizarea LED-ului după apăsarea unei taste nu a funcționat efectiv. Și motivul în acest
caz este și mai interesant.

Din perspectiva evenimentului, orice apel de blocare din codul nostru, cum ar fi Sleep(), conduce la
așteptarea expirării celor 200 ms. Deblocarea înseamnă că acest eveniment a avut loc. Evenimentul
pe care îl obțineți la deblocare s-ar putea să nu fie numit explicit, dar totuși, acesta este livrat în
mijlocul procesării unui alt eveniment - WM_KEYDOWN în acest caz. Dar acest lucru încalcă
semantica Run-to-Completion de procesare a evenimentelor, pe care sistemul Windows, bazat pe
eveniment, și-l asumă.

Mai precis, apelarea funcției InvalidateRect() chiar înainte de blocarea Sleep() nu are efect, deoarece
WndProc nu se întoarce la Windows în acest moment. Prin urmare, Windows nu are nicio șansă să
trimită mesajul WM_PAINT la WndProc pentru a actualiza efectiv starea LED-ului. Prin urmare, nu
vom vedea niciodată actualizarea.

În consecință, utilizarea paradigmei de programare secvențială în sistemele bazate pe evenimente, în


special când este vorba de blocare, nu este o idee prea bună din două motive: (1) înfundă bucla
evenimentului și distruge receptivitatea programului la toate evenimentele, nu doar la cele care se
blochează pentru o perioadă și (2) încalcă semantica Run-to-Completion asumată universal în toate
sistemele bazate pe evenimente.

Aceasta este cea mai importantă concluzie din această lecție de care vreau să vă amintiți:
programarea secvențială și programarea bazată pe evenimente sunt două paradigme distincte care nu
se amestecă bine - deci păstrați-le separate.

Soluția
 Pagina precedentă se termina cu afirmația: programarea secvențială și programarea bazată pe
evenimente sunt două paradigme distincte care nu se amestecă bine - deci păstrați-le separate.

Acest lucru înseamnă că într-un program bazat pe evenimente, trebuie să utilizați o soluție cu
adevărat „condusă de evenimente” pentru a face ca LED-ul să clipească la apăsarea unei taste.
În acest scop, în loc să blocați secvențial cu Sleep() execuția, puteți utiliza funcția Windows special
concepută în acest scop numită „cronometru”, cu care puteți să generați în viitor un eveniment
special numit WM_TIMER după expirarea unui  număr dat de milisecunde. Și aceasta este într-
adevăr tot ce trebuie să faci în cazul WM_KEYDOWN, pentru că restul procesării se duce la CASE
WM_TIMER. Desigur, finalizați procesarea în mod obișnuit, cu singurul pas suplimentar pentru a
apela KillTimer, deoarece în caz contrar, cronometrul Windows va continua să trimită periodic
mesaje la intervalul programat:

 case WM_PAINT: {                      // window needs to be repainted

            PAINTSTRUCT ps;

            HDC hdc;

            RECT rect;

            char cBuffer[100];

            wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s ", (wm_keydown_ctr %


1000), (wm_mousemove_ctr % 1000), led_text);

            hdc = BeginPaint(me, &ps);                                      // painting performed between


BeginPain() and EndPaint()...

            GetClientRect(me, &rect);

            DrawText(hdc, cBuffer, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

            EndPaint(me, &ps);

            status = WIN_HANDLED;                                          // report event handled

            break;

    }

        case WM_TIMER: {

            led_text = "OFF";                                                           // BSP_ledRedOff();

            InvalidateRect(me, NULL, FALSE);                                  // force re-paining of the window

            KillTimer(me, wParam);                                                  // stop the timer

            status = WIN_HANDLED;                                               // report event handled

            break;
    }

        case WM_KEYDOWN: {                                                       // keyboard key has been pressed

            ++wm_keydown_ctr;

            led_text = "RED";                                                            // BSP_ledRedOn();

            InvalidateRect(me, NULL, FALSE);                                   // force re-paining of the window

            SetTimer(me, 1, 200, NULL);                                           // 200ms timer interval

            status = WIN_HANDLED;                                               // report event handled

            break;

    }

Deci, să vedem acum cum funcționează acest lucru (în Visual Studio). Să începem mai întâi cu
tastatura izolată. După cum puteți vedea, după fiecare apăsare a tastei, LED-ul își schimbă acum
starea în RED.

Acum, să încercăm explozii de apăsări de taste, plus să manipulăm și mouse-ul în același


timp ... După cum puteți vedea starea LED-urilor se schimbă corect și contoarele de evenimente
continuă să se actualizeze, astfel încât aplicația rămâne receptivă.

Interfața grafică a utilizatorului


Windows este o interfaţă grafică cu utilizatorul (GUI - graphical user interface), numită uneori şi
„interfaţă vizuală" sau „mediu grafic cu ferestre" („graphical windowing environment"). Conceptele
care stau la baza acestui tip de interfaţă cu utilizatorul datează de la mijlocul anilor '70, având la
origine munca de pionierat a celor de la Centrul de cercetare al firmei Xerox de la Palo Alto (PARC
- Palo Alto Research Center) pentru calculatoare precum Alto şi Star şi medii ca Smalltalk. Ideile au
fost readuse în prim-plan şi popularizate de Apple Computer, Inc., mai întâi cu ghinionistul sistem
de operare Lisa şi un an mai târziu cu Macintosh, un sistem de operare de succes, prezentat în
ianuarie 1984.

De la apariţia sistemului de operare Macintosh, interfeţele grafice au cunoscut o răspândire


extraordinară în industria calculatoarelor. Acum este evident faptul că interfaţă grafică cu
utilizatorul este (citându-l pe Charles Simonyi de la Microsoft) singurul „consens general" important
din industria calculatoarelor personale.

Conceptele şi fundamentele GUI


Toate tipurile de interfeţe grafice cu utilizatorul folosesc elemente grafice afişate într-o imagine de
tip bitmap. Elementele grafice asigură o utilizare mai eficientă a spaţiului de afişare, un mediu bogat
din punct de vedere vizual pentru transferul de informaţii şi posibilitatea de afişare a rezultatelor aşa
cum vor arăta în realitate pe hârtie (WYSIWYG - what you see is what you get).

La început, ecranul era folosit numai pentru afişarea informaţiilor pe care utilizatorul le introducea
de la tastatură. Într-o interfaţă grafică, ecranul devine chiar o sursă pentru celelalte intrări ale
utilizatorului. Pe ecran sunt afişate diferite obiecte grafice sub forma pictogramelor şi a unor
dispozitive de intrare, precum butoanele şi barele de derulare. Folosind tastatura (sau, mai direct, un
dispozitiv de indicare precum mouse-ul) utilizatorul poate să manipuleze direct aceste obiecte de pe
ecran. Obiectele grafice pot fi deplasate, butoanele pot fi apăsate şi barele de derulare pot fi derulate.

Interacţiunea dintre program şi utilizator devine mult mai directă. În locul unui ciclu unidirectional
al informaţiilor de la tastatură la program şi de la program la ecran, utilizatorul interacţionează direct
cu obiectele de pe ecran.

Consecvenţa privind interfaţă cu utilizatorul


Utilizatorii nu mai sunt dispuşi să piardă prea mult timp pentru a învăţa cum se foloseşte
calculatorul sau cum să stăpânească un anumit program. Windows îi ajută în acest sens, deoarece
toate programele pentru Windows au un aspect asemănător şi se comportă fundamental la fel.
Programele ocupă o fereastră - o suprafaţă dreptunghiulară de pe ecran. Fereastra poate fi
identificată datorită unei bare de titlu. Majoritatea funcţiilor oferite de program sunt apelate cu
ajutorul unui meniu. Informaţiile afişate care nu încap pe un singur ecran pot fi vizualizate cu
ajutorul barelor de derulare. Unele articole de meniu apelează casete de dialog în care utilizatorul
introduce informaţii suplimentare. În majoritatea programelor mai mari pentru Windows există o
casetă de dialog care deschide un fişier. Această casetă de dialog arată la fel (sau foarte asemănător)
în mai multe programe Windows şi este apelată aproape întotdeauna cu aceeaşi opţiune de meniu.

După ce învăţaţi să folosiţi un program Windows, veţi fi capabil deja să utilizaţi orice alt program
pentru Windows. Meniurile şi casetele de dialog permit utilizatorului să experimenteze diferite
funcţii ale programului şi să îi exploreze posibilităţile. Majoritatea programelor Windows au
interfaţă atât cu mouse-ul, cât şi cu tastatura. Deşi majoritatea funcţiilor unui program pentru
Windows pot fi controlate şi cu ajutorul tastaturii, mouse-ul este deseori mai convenabil pentru
diferite sarcini.

Din punctul de vedere al programatorului, consecvenţa în realizarea interfeţei cu utilizatorul este


rezultatul folosirii procedurilor integrate în Windows pentru construirea meniurilor şi a casetelor de
dialog. Toate meniurile au aceeaşi interfaţă cu tastatura şi cu mouse-ul deoarece aceste operaţii sunt
manipulate mai degrabă de Windows, decât de programul respectiv.

Avantajul oferit de multitasking


Deşi unii continuă încă să se întrebe dacă multitaskingul este cu adevărat necesar pe un calculator
folosit de un singur utilizator, majoritatea utilizatorilor se bucură de existenţa acestei facilităţi şi pot
să-i folosească avantajele. Acest lucru a fost dovedit de popularitatea unor programe rezidente în
memoria RAM sub MS-DOS, precum Sidekick. Deşi programele derulante, în sens strict, nu sunt
acelaşi lucru cu multitaskingul, acestea permit comutarea rapidă de la un context la altul, proces care
implică multe dintre conceptele multitaskingului.
Sub Windows, toate programele sunt, de fapt, programe rezidente în memoria RAM. Mai multe
programe Windows pot fi afişate şi rulate în acelaşi timp. Fiecare program ocupă o fereastră
dreptunghiulară de pe ecran. Utilizatorul poate să mute ferestrele pe ecran, poate să le schimbe
dimensiunile, să treacă de la un program la altul şi să transfere date de la un program la altul.
Deoarece aceasta creează uneori aspectul spaţiului de lucru de pe un birou (desktop) - pe vremea
când birourile nu erau dominate încă de calculator - se spune uneori despre Windows că foloseşte
pentru afişarea mai multor programe „metafora suprafeţei de lucru (desktop)".

Versiunile anterioare ale sistemului de operare Windows foloseau un tip de multitasking numit
„necontrolat". Aceasta înseamnă că Windows nu folosea ceasul sistemului ca să aloce timpi de
procesare diferitelor programe care rulează în sistem. Programele trebuie să cedeze voluntar
controlul, astfel încât alte programe să-şi poată continua execuţia*. În Windows 95 multitaskingul
este controlat şi programele se pot împărţi în mai multe fire de execuţie, care par să ruleze în acelaşi
timp.

Gestionarea memoriei
Un sistem de operare nu poate să implementeze multitaskingul fără o gestionare adecvată a
memoriei. Pe măsură ce noi programe sunt încărcate în memorie şi altele vechi îşi încheie execuţia,
memoria se poate fragmenta. Sistemul de operare trebuie să aibă posibilitatea să consolideze
memoria liberă. Pentru aceasta, sistemul trebuie să poată muta blocuri de cod şi de date în memorie.

Chiar şi prima versiune Windows, rulată pe un procesor 8088, putea să gestioneze memoria în acest
mod. Ţinând seama de faptul că microprocesorul 8088 lucra în mod real, aceasta trebuie privită ca o
realizare uimitoare a ingineriei software. Programele rulate sub Windows pot să depăşească
memoria existentă; un program poate conţine mai mult cod decât încape în memorie la un moment
dat. Windows poate să elimine din memorie o parte din cod şi ulterior să reîncarce codul respectiv
din fişierul executabil de pe hard-disc. Un utilizator poate rula simultan mai multe copii ale aceluiaşi
program, numite „instanţe"; toate aceste instanţe folosesc acelaşi cod din memorie. Programele
rulate sub Windows pot să partajeze rutine stocate în alte fişiere numite „biblioteci cu legături
dinamice" (dynamic link libraries). Windows conţine un mecanism de editare a legăturilor dintre
program şi rutinele din bibliotecile cu legături dinamice, în timpul execuţiei. Chiar sistemul de
operare este, în esenţă, o colecţie de biblioteci cu legături dinamice.

În acest fel, chiar şi în Windows 1.0, limita de 640 kiloocteţi impusă de arhitectura calculatoarelor
personale a fost extinsă fără să fie nevoie de memorie suplimentară. Dar Microsoft nu s-a oprit aici:
Windows 2 a permis aplicaţiilor Windows accesul la memoria expandată (EMS) iar Windows 3
rulează în mod protejat, ceea ce permite aplicaţiilor Windows accesul la 16 megaocteţi de memorie.
Windows 95 a depăşit toate aceste restricţii, fiind un sistem de operare pe 32 de biţi, cu un spaţiu de
memorie plată.

Interfaţa grafică independentă de dispozitiv


Windows este o interfaţă grafică şi programele pentru Windows pot folosi toate avantajele oferite de
elementele grafice şi de textul formatat atât pentru ecran, cât şi pentru imprimantă. O interfaţă
grafică nu are numai avantajul de a fi mai atractivă, ci şi pe acela de a furniza utilizatorului o
cantitate mai mare de informaţii.
Programele scrise pentru Windows nu au acces direct la componentele hardware ale dispozitivelor
de afişare, cum ar fi ecranul sau imprimanta. În schimb, Windows foloseşte un limbaj de programare
pentru grafică (numit GDI - Graphics Device Interface) care simplifică afişarea textului formatat şi a
elementelor grafice. Windows transformă componentele hardware de afişare în dispozitive virtuale.
Un program scris pentru Windows va rula cu orice placă video şi cu orice imprimanta pentru care
Windows are un driver de dispozitiv. Programul nu trebuie să determine ce tip de dispozitiv fizic
este ataşat la calculator.

Crearea unei interfeţe grafice independente de dispozitiv pentru calculatoarele IBM PC nu a fost o
sarcină uşoară pentru dezvoltatorii sistemului de operare Windows. Proiectarea calculatoarelor
personale s-a făcut pe baza principiului arhitecturilor deschise. Producătorii terţi de componente
hardware au fost încurajaţi să dezvolte dispozitive periferice, şi datorită acestui fapt au apărut o
mulţime de astfel de dispozitive. Deşi au fost create anumite standarde, programele convenţionale
pentru MS-DOS trebuie să asigure suportul individual pentru diferite configuraţii standard. De
exemplu, este un lucru destul de obişnuit ca un program de procesare de texte pentru MS-DOS să fie
livrat împreună cu câteva dischete care conţin mai multe fişiere mici, pentru adaptarea la diferite
tipuri de imprimante. Programele Windows 95 nu au nevoie de drivere proprii, deoarece acestea
sunt asigurate chiar de sistemul de operare.

Dacă optaţi pentru Windows...


Programarea sub Windows 95 este o propunere de tip „totul sau nimic". De exemplu, nu puteţi să
scrieţi aplicaţii pentru MS-DOS şi să utilizaţi facultăţile oferite de Windows numai pentru anumite
grafice. Dacă vreţi să aveţi acces la toate posibilităţile pe care le are mediul Windows, trebuie să vă
hotărâţi şi să scrieţi programe complete pentru Windows.

Logica acestei opţiuni va deveni evidentă după ce veţi afla mai multe despre structura unui program
pentru Windows. Toate componentele sistemului Windows sunt interconectate. Dacă vreţi să
desenaţi ceva pe ecran aveţi nevoie de un element numit „variabilă handle a unui context de
dispozitiv". Pentru aceasta aveţi nevoie de o „variabilă handle a unei ferestre". Pentru a o obţine,
trebuie să creaţi o fereastră şi să fiţi pregătit să recepţionaţi „mesajele" trimise către fereastră. Pentru
recepţionarea şi prelucrarea mesajelor aveţi nevoie de o „procedură de fereastră". A face toate aceste
operaţii înseamnă a scrie un program pentru Windows. Nu puteţi să zburaţi decât dacă vă dezlipiţi
de pământ.

Apelurile de funcţii
Windows 95 asigură suportul pentru mai mult de o mie de apeluri de funcţii pe care le pot folosi
aplicaţiile. Este foarte puţin probabil ca cineva să memoreze sintaxa tuturor acestor apeluri.
Majoritatea programatorilor Windows petrec cea mai mare parte din timpul de lucru căutând diferite
apeluri de funcţii, în documentaţia scrisă sau în surse.

Fiecare funcţie Windows are un nume sugestiv, scris atât cu majuscule, cât şi cu minuscule, cum ar
fi CreateWindow. Această funcţie, aşa cum probabil v-aţi dat seama, creează o fereastră. Un alt
exemplu: funcţia IsClipboardFormatAvailable determină dacă în memoria temporară (clipboard)
sunt stocate date într-un anumit format.
Toate funcţiile Windows importante sunt declarate în fişiere antet. Principalul fişier antet se numeşte
WINDOWS.H şi include multe alte fişiere antet. Aceste fişiere antet sunt furnizate de orice mediu
de programare pentru Windows 95, aşa cum este, de pildă, C. Fişierele antet sunt o parte importantă
a documentaţiei Windows. Puteţi să tipăriţi o copie a fişierelor antet sau să folosiţi un browser
pentru o căutare rapidă.

În programele pentru Windows folosiţi apelurile de funcţii la fel cum folosiţi funcţiile de bibiotecă
C, cum ar fi strlen. Principala diferenţă este faptul că în cazul funcţiilor C codul funcţiilor este legat
direct de codul programului, pe când codul funcţiilor Windows este stocat în fişiere din afara
programului, numite biblioteci cu legături dinamice (DLL - dynamic link libraries).

Atunci când rulaţi un program pentru Windows, interconectarea acestuia cu sistemul de operare se
face printr-un proces numit „legare dinamică" (dynamic linking). Un fişier executabil al unui
program Windows conţine referinţe la diferite biblioteci cu legături dinamice, pe care le foloseşte şi
la funcţiile conţinute de acestea. Majoritatea bibliotecilor DLL se află în subdirectorul SYSTEM din
directorul Windows. Atunci când un program pentru Windows este încărcat în memorie, apelurile de
funcţii din program sunt rezolvate astfel încât să indice intrările funcţiilor din bibliotecile cu legături
dinamice, care sunt şi ele încărcate în memorie, dacă este nevoie.

Atunci când editaţi legăturile unui program ca să creaţi un fişier executabil, trebuie să editaţi şi
legăturile cu bibliotecile speciale „de import" furnizate de mediul de programare folosit. Aceste
biblioteci de import conţin numele funcţiilor Windows folosite în bibliotecile cu legături dinamice şi
informaţii de referinţă pentru acestea. Programul de editare a legăturilor foloseşte aceste informaţii
ca să construiască în fişierul executabil tabelul folosit de Windows pentru rezolvarea apelurilor de
funcţii Windows la încărcarea programului.

Programarea orientată pe obiecte


Programarea sub Windows este un tip de programare orientată pe obiecte (OOP -object-oriented
programming). Acest lucru devine evident dacă vă gândiţi la obiectul pe care îl veţi folosi cel mai
mult în Windows - obiectul care dă numele sistemului de operare, obsedanta „fereastră".

Aşa cum am menţionat mai devreme, ferestrele sunt nişte suprafeţe dreptunghiulare de pe ecran. O
fereastră primeşte intrări de la tastatură sau de la mouse şi afişează rezultate în zona proprie de pe
suprafaţa ecranului.

Fereastra unei aplicaţii conţine, de obicei, bara de titlu a programului, meniul, chenarul de
dimensionare şi, eventual, barele de derulare. Casetele de dialog sunt ferestre adiţionale. Mai mult,
suprafaţa unei casete de dialog conţine întotdeauna câteva ferestre suplimentare, numite „ferestre
descendent" („child windows"). Ferestrele descendent apar sub forma butoanelor de apăsare, a
butoanelor radio, a casetelor de validare, a câmpurilor pentru introducerea textului, casetelor-listă şi
a barelor de derulare.

Utilizatorul vede ferestrele ca obiecte pe ecran şi poate acţiona direct asupra lor, apăsând un buton
sau manipulând o bară de derulare. Interesant este faptul că perspectiva programatorului este foarte
asemănătoare cu cea a utilizatorului. Fereastra recepţionează intenţiile utilizatorului sub forma unor
„mesaje". De asemenea, ferestrele folosesc mesaje pentru comunicarea cu alte ferestre.
Înţelegerea acestor mesaje este unul dintre pragurile pe care va trebui să le depăşiţi ca să ajungeţi
programator Windows.

Arhitectura bazată pe mesaje


Atunci când am văzut pentru prima dată în acţiune o interfaţă grafică cu utilizatorul, am fost uimit.
Demonstraţia includea rularea unui procesor de texte rudimentar într-o fereastră. Procesorul de texte
rearanja textul atunci când dimensiunile ferestrei erau modificate.

Era evident că sistemul de operare manipula detaliile „logicii" de redimensionare a ferestrei şi că


programul putea să răspundă la această funcţie a sistemului. Dar de unde ştia programul că fereastra
a fost redimensionată? Ce mecanism folosea sistemul de operare ca să transmită această informaţie
către fereastra programului? Experienţa pe care o aveam atunci nu mă ajuta să înţeleg acest mod de
funcţionare.

S-a dovedit că răspunsul la aceste întrebări este esenţial pentru înţelegerea arhitecturii folosite la
construirea interfeţelor grafice cu utilizatorul. În Windows, atunci când utilizatorul redimensionează
o fereastră, sistemul de operare trimite programului un mesaj prin care îi comunică noile dimensiuni
ale ferestrei. Programul poate apoi să modifice conţinutul ferestrei, astfel încât acesta să se adapteze
la noile dimensiuni.

„Sistemul de operare trimite programului un mesaj." Sper că nu aţi luat ad-litteram sensul acestei
propoziţii. Ce poate să însemne acest lucru? Pentru că nu este vorba despre un sistem de poştă
electronică, ci despre cod, cum poate un sistem de operare să „trimită" mesaje către un program?

Atunci când am spus că „sistemul de operare trimite programului un mesaj" m-am referit la faptul că
Windows apelează o funcţie din program. Parametrii acestei funcţii descriu mesajul respectiv.
Această funcţie din programul Windows este cunoscută sub numele de „procedură de fereastră"
(„window procedure").

Procedura de fereastră
Desigur, sunteţi obişnuit cu ideea că un program apelează funcţii ale sistemului de operare. Acest
lucru este făcut de un program atunci când, de exemplu, deschide un fişier de pe hard-disc. S-ar
putea să nu fiţi la fel de obişnuit, însă, şi cu ideea că sistemul de operare apelează o funcţie a
programului. Totuşi, aceasta este o idee fundamentală a arhitecturii orientate pe obiecte a sistemului
de operare Windows 95.

Orice fereastră creată de un program are asociată o procedură de fereastră. Procedura de fereastră
este e funcţie care se poate afla chiar în program sau într-o bibliotecă cu legături dinamice (DLL).
Windows trimite un mesaj către o fereastră prin apelarea procedurii de fereastră. Procedura de
fereastră prelucrează anumite informaţii pe baza mesajului primit, apoi returnează controlul
sistemului de operare.

Mai precis, o fereastră este creată întotdeauna pe baza unei „clase de fereastră". Clasa specifică
procedura de fereastră care prelucrează mesajele trimise către fereastră. Folosirea unei clase de
fereastră permite ca pe baza aceleiaşi clase să se creeze mai multe ferestre care, ca urmare, folosesc
aceeaşi procedură de fereastră. De exemplu, toate butoanele din toate programele pentru Windows
sunt create pe baza aceleiaşi clase de fereastră. Această clasă are o procedură de fereastră (aflată
într-una dintre bibliotecile cu legături dinamice ale sistemului de operare) care prelucrează mesajele
trimise către ferestrele butoanelor.

În programarea orientată pe obiecte, un „obiect" este o combinaţie de cod şi date. O fereastră este un
obiect. Codul obiectului este procedura de fereastră. Datele obiectului sunt informaţiile păstrate de
fereastră şi informaţiile păstrate de Windows pentru fiecare fereastră şi pentru fiecare clasă de
fereastră existentă în sistem.

O procedură de fereastră prelucrează mesajele trimise ferestrei respective. Deseori, aceste mesaje
informează fereastra cu privire la acţiunile executate de utilizator cu ajutorul mouse-ului sau al
tastaturii. Aceasta este calea prin care o fereastră „află", de exemplu, că un buton a fost apăsat. Alte
mesaje comunică ferestrei că a fost redimensionată sau că trebuie să fie refăcută.

Atunci când un program este lansat în execuţie, Windows creează o „coadă de mesaje" („message
queue") pentru programul respectiv, în această coadă de mesaje sunt păstrate mesajele trimise către
toate ferestrele pe care le creează programul. Programul conţine o mică secvenţă de cod numită
„ciclu de mesaje" („message loop") care preia mesajele din coada de aşteptare şi le distribuie
procedurilor de fereastră corespunzătoare. Alte mesaje sunt trimise direct procedurii de fereastră,
fără să mai fie plasate în coada de mesaje.

Dacă simţiţi că această descriere excesiv de abstractă a arhitecturii Windows vă depăşeşte, s-ar putea
să fi sosit momentul să vedeţi concret cum fereastra, clasa de fereastră, procedura de fereastră, coada
de mesaje şi mesajele trimise ferestrei se integrează în contextul real al unui program.

Să-i dăm drumul!

PRIMUL DUMNEAVOASTRĂ PROGRAM windows


În cartea devenită clasică Limbajul de programare C, Brian Kernighan şi Dennis Ritchie încep
descrierea limbajului C cu de-acum faimosul program „Hello, world!":

include <stdio.h>

main ()

printf ("Hello, world\n");

 
În acest capitol vă voi prezenta un program asemănător, scris pentru Microsoft Windows 95.
Programul se numeşte HELLOWIN, afişează pe ecran şirul de caractere „Hello, Windows 95!" şi
redă un fişier de sunet cu vocea mea rostind aceleaşi cuvinte.

Dacă nu v-aţi prăbuşit atunci când aţi văzut pentru prima dată codul programului HELLOWIN, vă
avertizez că are mai mult de 80 de linii. Majoritatea acestor linii reprezintă cod de întreţinere
(overhead code) şi veţi include un asemenea cod în toate programele Windows pe care le veţi scrie.

În loc să ne întrebăm de ce programul „Hello, Windows 95!" este atât de lung şi de complex, haideţi
să vedem de ce programul „Hello, world!" tradiţional este atât de scurt şi de simplu.

Ce este greşit în acest program?


Modelul de afişare pentru programul „Hello, world!" şi pentru alte programe C tradiţionale este o
componentă hardware antică numită teleimprimator. Teleimprimatorul seamănă cu o maşină de scris
cu o rolă de hârtie continuă. Într-un trecut nu prea îndepărtat, programatorii stăteau în faţa unui
astfel de teleimprimator şi scriau comenzi al căror „ecou" apărea pe hârtie. Calculatorul răspundea la
comenzi prin tipărirea răspunsurilor tot pe hârtie.

Odată cu apariţia terminalelor sistemelor mainframe şi a calculatoarelor personale, ideea


teleimprimatorului a fost extinsă şi pentru monitoarele video. Monitoarele video erau folosite ca un
„teleimprimator de sticlă" care, pur şi simplu, derula imaginea în momentul când textul scris ajungea
în partea de jos a ecranului.

Cum poate programul tradiţional „Hello, world!" să afişeze text fără să îi comunice sistemului de
operare la ce dispozitiv de ieşire să fie afişat textul? Simplu, deoarece există un singur dispozitiv de
ieşire - ecranul, folosit ca teleimprimator. Dacă utilizatorul doreşte ca rezultatele să fie trimise în altă
parte, trebuie să le redirecţioneze folosind linia de comandă.

Cum poate programul să afişeze text fără să îi comunice sistemului de operare în ce poziţie urmează
să fie afişat acesta? Textul este afişat întotdeauna acolo unde se află cursorul - de obicei pe
următoarea linie după comanda de executare a programului. Dacă vreţi să afişaţi mesajul „Hello,
world!" în centrul ecranului, trebuie să folosiţi o serie de coduri dependente de dispozitiv ca să
poziţionaţi mai întâi cursorul în poziţia dorită.

Să presupunem că vreţi să rulaţi mai multe programe „Hello, world!" în acelaşi timp. Ce mizerie!
Copiile programului se vor amesteca unele cu altele. Teleimprimatorul nu furnizează nici o metodă
de separare a ieşirilor de la mai multe programe rulate simultan.

Un alt lucru interesant este faptul că textul „Hello, world!" rămâne pe ecran chiar şi după terminarea
programului, în loc să cureţe ecranul după terminare, programul lasă „urme" ale existenţei sale.

Programul „Hello, world!" este atât de simplu pentru că este proiectat pentru calculatoare mai
simple şi pentru dispozitive de ieşire mai simple. Nu numai că nu este ceea ce numim astăzi un
produs software modern, dar nici nu se află măcar pe acelaşi teren.

Fişierele programului HELLOWIN


Două dintre cele trei fişiere necesare pentru crearea programului HELLOWIN sunt prezentate în
Figura 2-1. Acestea sunt fişierul „de construcţie " HELLOWIN.MAK şi fişierul sursă
HELLOWIN.C. Cel de-al treilea fişier este stocat pe discheta ataşată cărţii, cu numele
HELLOWIN.WAV şi conţine varianta sonoră a textului afişat de program.

#------------------------

# HELLOWIN.MAK make file

#------------------------

hellowin.exe : hellowin.obj

     $(LINKER) $(GUIFLAGS) -OUT:hellowin.exe hellowin.obj $(GUILIBS)

hellowin.obj : hellowin.c

     $(CC) $(CFLAGS) hellowin.c

/*------------------------------------------------------------

   HELLOWIN.C -- Displays "Hello, Windows 95!" in client area

                 (c) Charles Petzold, 1996

  ------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "HelloWin" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName,         // window class name

                                   "The Hello Program",     // window caption

                    WS_OVERLAPPEDWINDOW,     // window style

                    CW_USEDEFAULT,           // initial x position

                    CW_USEDEFAULT,           // initial y position

                    CW_USEDEFAULT,           // initial x size

                    CW_USEDEFAULT,           // initial y size

                    NULL,                    // parent window handle

                    NULL,                    // window menu handle

                    hInstance,               // program instance handle

                                   NULL) ;                               // creation parameters

     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;


     while (GetMessage (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     HDC         hdc ;

     PAINTSTRUCT ps ;

     RECT        rect ;

     switch (iMsg)

          {

         // case WM_CREATE :

               //PlaySound ("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC) ;//

               //return 0 ;

          case WM_PAINT :

                   hdc = BeginPaint (hwnd, &ps) ;

               GetClientRect (hwnd, &rect) ;

               DrawText (hdc, "Hello, Windows 95! ", -1, &rect,

                                                   DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

                   EndPaint (hwnd, &ps) ;

               return 0 ;
          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 2-1. Programul HELLOWIN.

În Capitolul 9 veţi întâlni un alt tip de fişier frecvent folosit pentru programele Windows, numit
„fişier script" („resource script") şi având extensia .RC. Până acolo, majoritatea programelor, fiind
mai simple, vor folosi un fişier de construcţie, un fişier sursă şi, eventual, un fişier antet.

Aşa cum am menţionat anterior, cea mai mare parte din codul programului HELLOWIN.C este cod
de întreţinere (overhead code) inclus în aproape toate programele Windows. Nimeni nu se
străduieşte să ţină minte sintaxa acestui cod; în general, programatorii Windows creează un nou
program copiind un program existent şi făcând modificările necesare în acesta. Sunteţi liber să
folosiţi în acest mod oricare dintre programele de pe discheta care însoţeşte cartea.

Dacă aţi instalat pe calculatorul dumneavoastră sistemul de operare Windows 95 si mediul


Microsoft Visual C++ 4.0 şi aţi rulat fişierul de comenzi VCVARS32.BAT inclus în Visual C++ şi
fişierul de comenzi MSC.BAT prezentat în acest capitol, ar trebui să puteţi crea fişierul executabil
HELLOWIN.EXE cu comanda:

               Hello, Windows 95!

NMAKE HELLOWIN.MAK

din linia de comandă MS-DOS.

Dacă totul merge bine, puteţi să rulaţi programul din linia de comandă MS-DOS cu comanda:

HELLOWIN

Programul creează o fereastră normală, prezentată în Figura 2-2. În centrul zonei client a ferestrei
este afişat textul „Hello, Windows 95!". Dacă aveţi pe calculator o placă de sunet, veţi auzi şi
mesajul vorbit. (Iar dacă nu aveţi încă, ce mai aşteptaţi?)
Figura 2-2. Rularea programului HELLOWIN sub Windows

De fapt, această fereastră are o mulţime de funcţii pentru cele numai 80 de linii de cod. Puteţi să
„agăţaţi" bara de titlu cu mouse-ul şi să mutaţi fereastra pe ecran sau, trăgând chenarul ferestrei,
puteţi redimensiona fereastra. Atunci când fereastra îşi schimbă dimensiunea, programul
repoziţionează automat textul în noul centru al zonei client. Puteţi să executaţi clic pe butonul de
mărire a ferestrei (maximize) si fereastra va umple ecranul. Puteţi să executaţi clic pe butonul de
micşorare a ferestrei (minimize) şi fereastra va elibera ecranul. Toate aceste opţiuni pot fi apelate şi
din meniul sistem. De asemenea, puteţi să închideţi programul selectând opţiunea Close din meniul
sistem, executând clic pe butonul de închidere a ferestrei din partea dreaptă a barei de titlu sau
executând dublu clic pe pictograma din partea stângă a barei de titlu.

Deşi s-ar putea să vă placă faptul că programul HELLOWIN are toate funcţiile unui program
Windows normal, probabil nu veţi fi la fel de fericit atunci când veţi examina codul sursă necesar
pentru crearea programului. Dar haideţi să ne luăm inima în dinţi şi să „disecăm" acest program
bucată cu bucată.

Fişierul de construcţie
Pentru simplificarea compilării programelor Windows puteţi să folosiţi utilitarul NMAKE, inclus în
setul de programe Microsoft Visual C++ 4.0. De fiecare dată când vreţi să modificaţi ceva în codul
sursă al programului HELLOWIN.C trebuie să rulaţi programul NMAKE, aşa cum am arătat mai
sus, ca să creaţi un fişier executabil actualizat.

Un fişier de construcţie este format din una sau mai multe secţiuni, fiecare începând cu un rând de
text aliniat la stânga, care conţine un fişier destinaţie, urmat de două puncte şi de unul sau mai multe
fişiere dependente. Această linie este urmată de una sau mai multe linii de comandă aliniate mai în
interior. Aceste comenzi creează fişierul destinaţie din fişierele dependente. Dacă data sau ora de
modificare ale oricăruia dintre fişierele dependente este ulterioară datei sau orei de creare a fişierului
destinaţie, programul NMAKE execută liniile de comandă.

În mod normal, programul NMAKE actualizează numai fişierul destinaţie din prima secţiune a
fişierului de construcţie. Totuşi, dacă unul sau mai multe dintre fişierele dependente sunt la rândul
lor fişiere destinaţie în alte secţiuni ale fişierului de construcţie, NMAKE le va actualiza întâi pe
acestea.

Fişierul HELLOWIN.MAK conţine două secţiuni. Prima secţiune rulează programul de editare a
legăturilor dacă HELLOWIN.OBJ a fost modificat mai recent decât HELLOWIN.EXE. A doua
secţiune rulează compilatorul C dacă HELLOWIN.C a fost modificat mai recent decât
HELLOWIN.OBJ. Deoarece fişierul HELLOWIN.OBJ este fişier dependent în prima secţiune şi
fişier destinaţie în a doua secţiune, programul NMAKE va verifica mai întâi dacă HELLOWIN.OBJ
trebuie să fie actualizat înainte ca fişierul HELLOWIN.EXE să fie re-creat. În această situaţie,
fişierul de construcţie este de fapt executat de la un capăt la celălalt. Rularea compilatorului C
creează modulul obiect HELLOWIN.OBJ din fişierul sursă HELLOWIN.C. Rularea programului de
editare a legăturilor creează fişierul executabil HELLOWIN.EXE din HELLOWIN.OBJ.

În Capitolul 1 am arătat cum sunt furnizaţi macro-identificatorii din fişierul de construcţie de către
variabilele de mediu stabilite de fişierele de comenzi prezentate în capitolul respectiv. Aceasta
înseamnă stabilirea unor indicatori flag de compilare şi a numelor de biblioteci folosite de
programul de editare a legăturilor. Dacă doriţi amănunte, reveniţi la secţiunea respectivă din
Capitolul 1.

Fişierul sursă C
Al doilea fişier prezentat în Figura 2-1 este HELLOWIN.C, fişierul care conţine codul sursă C. Va
trece un timp pană când vă veţi da seama că acest program este într-adevăr scris în limbaj C!

Înainte de a intra în detalii, haideţi să aruncăm o privire generală asupra fişierului HELLOWIN.C.
Fişierul conţine numai două funcţii: WinMain şi WndProc. Funcţia WinMain reprezintă punctul de
intrare în program. Aceasta este echivalentul funcţiei main din programele scrise în limbajul C.
Orice program pentru Windows trebuie să aibă o funcţie WinMain.

WndProc este „procedura de fereastră" a ferestrei create de programul HELLOWIN. Orice fereastră
- indiferent dacă este fereastra principală a aplicaţiei sau fereastra unui mic buton de apăsare - are o
procedură de fereastră. Procedura de fereastră este un mod de încapsulare a codului care răspunde
intrărilor (în general de la tastatură şi de la mouse) şi afişează elementele grafice pe ecran. Aşa cum
veţi vedea, procedura de fereastră face acest lucru prin prelucrarea „mesajelor" trimise către
fereastră. Pentru moment nu vă bateţi capul cu modul de funcţionare a acestui mecanism. Veţi avea
destul timp să vă luptaţi cu acest concept.
Nici o instrucţiune din fişierul HELLOWIN.C nu apelează direct funcţia WndProc: WndProc este
apelată de sistemul de operare Windows. Totuşi, în funcţia WinMain apare o referire la funcţia
WndProc, acesta fiind motivul pentru care WndProc este declarată în partea de început a
programului, înainte de WinMain.

Apelurile de funcţii Windows


HELLOWIN apelează nu mai puţin de 17 funcţii Windows. Aceste funcţii, împreună cu o scurtă
descriere, sunt prezentate în continuare, în ordinea apariţiei în programul HELLOWIN:

·        LoadIcon - încarcă o pictogramă care urmează să fie folosită de un program.

·        LoadCursor - încarcă un indicator pentru mouse, care urmează să fie folosit de un program.

·        GetStockObject - obţine un obiect grafic (în acest caz o pensulă folosită pentru desenarea
fondului ferestrei).

·        RegisterClassEx - înregistrează o clasă de fereastră pentru fereastra programului.

·        CreateWindow - creează o fereastră pe baza unei clase de fereastră.

·        ShowWindow - afişează o fereastră pe ecran.

·        UpdateWindow - cere unei ferestre să se redeseneze.

·        GetMessage - preia un mesaj din coada de mesaje.

·        TranslateMessage - converteşte unele dintre mesajele de la tastatură.

·        DispatchMessage - trimite un mesaj către o procedură de fereastră.

·        PlaySound - redă un fişier de sunet.

·        BeginPaint - iniţiază o operaţie de desenare a ferestrei.

·        GetClientRect - obţine dimensiunile zonei client a ferestrei.

·        DrawText - afişează un text.

·        EndPaint - încheie o operaţie de desenare.

·        PostQuitMessage - inserează un mesaj de încheiere în coada de aşteptare.

·        DefWindowProc - execută operaţiile prestabilite de prelucrare a mesajelor.

Aceste funcţii sunt documentate în cărţi şi în sursele incluse de compilator şi sunt declarate în
diferite fişiere antet incluse în fişierul WINDOWS.H.
Identificatori cu majuscule
Veţi observa că în fişierul HELLOWIN.C sunt folosiţi câţiva identificatori scrişi cu majuscule.
Aceşti identificatori sunt definiţi în fişierele antet din Windows. Câţiva dintre ei conţin un prefix de
două sau trei litere, urmat de o liniuţă de subliniere:

CS_HREDRAW                                DT_VCENTER                                                WM_CREATE

CS_VREDRAW                                IDC_ARROW                                                  WM_DESTROY

CW_USEDEFAULT                         IDI_APPLICATION                                         WM_PAINT

DT_CENTER                                    SND_ASYNC                                                           
WS_OVERLAPPEDWINDOW

DT_SINGLELINE                             SND_FILENAME

Acestea sunt simple constante numerice. Prefixul indică o categorie generală căreia îi aparţine
constanta respectivă, aşa cum se arată în tabelul următor:

Prefix Categorie
CS Opţiune pentru stilul clasei

IDI Număr de identificare pentru o pictogramă

IDC Număr de identificare pentru un cursor

WS Stil de fereastră

CW Opţiune de creare a unei ferestre

WM Mesaj de fereastră

SND Opţiune pentru sunete

DT Opţiune de desenare a textului

În mod normal nu este nevoie să reţineţi nici o constantă numerică pentru scrierea unui program
pentru Windows. De fapt, toate constantele numerice folosite în Windows au un identificator definit
în fişierele antet.

Noi tipuri de date


Alţi identificatori folosiţi în fişierul HELLOWIN.C sunt noi tipuri de date, definite în fişierele antet
cu ajutorul instrucţiunii typedef sau #define. Aceste definiţii au fost stabilite iniţial pentru a
simplifica tranziţia programelor Windows originale de la sistemele pe 16 biţi originale la viitoarele
sisteme pe 32 de biţi, sau la cele bazate pe o altă tehnologie. Această tranziţie nu s-a făcut chiar atât
de uşor şi de transparent cum ar fi crezut unii la momentul respectiv, dar conceptul este destul de
bun.

Uneori aceste noi tipuri de date sunt doar abrevieri mai uşor de folosit. De exemplu, tipul de date
UINT folosit pentru al doilea parametru al funcţiei WndProc este prescurtarea de ia unsigned int,
ceea ce în Windows 95 înseamnă o valoare întreagă pe 32 de biţi. Tipul de date PSTR folosit pentru
al treilea parametru al funcţiei WinMain este un pointer la un şir de caractere, adică înlocuieşte tipul
char*.

Alte tipuri de date nu sunt la fel de evidente. De exemplu, al treilea şi al patrulea parametri ai
funcţiei WndProc sunt definiţi cu tipurile de date WPARAM si LPARAM. Originea acestor nume
ţine oarecum de istoria sistemului Windows: pe când Windows era un sistem pe 16 biţi, al treilea
parametru al funcţiei WndProc era definit ca WORD, adică un număr întreg scurt, fără semn, pe 16
biţi (unsigned short) iar al patrulea parametru era definit ca LONG, adică un număr întreg pe 32 de
biţi (long) - de aici provenind prefixele „W" şi „L" ale cuvântului „PARAM". În Windows 95,
WPARAM este definit ca UINT iar LPARAM este definit ca LONG (care este chiar tipul long din
C), aşa că ambii parametri ai procedurii de fereastră sunt valori pe 32 de biţi. Deoarece tipul de date
WORD este definit în Windows 95 ca un număr întreg fără semn pe 16 biţi (unsigned short),
prefixul „W" din WPARAM este oarecum impropriu şi poate crea confuzii.

Funcţia WndProc returnează o valoare LRESULT, definit ca un număr de tip LONG. Funcţia
WinMain este de tipul WINAPI, iar funcţia WndProc este de tipul CALLBACK. Ambii
identificatori sunt definiţi ca __stdcall, care defineşte o secvenţă specială de apelare pentru apelurile
de funcţii dintre Windows şi aplicaţii.

Programul HELLOWIN foloseşte patru structuri de date (despre care vom discuta mai târziu în acest
capitol) definite în fişierele antet din Windows. Aceste structuri de date sunt:

Structura                     Semnificaţie

MSG                             Structura mesajului

WNDCLASSEX         Structura clasei de fereastră

PAINTSTRUCT          Structură pentru desenare

RECT                           Dreptunghi

Primele două structuri sunt folosite în funcţia WinMain pentru definirea a două structuri numite msg
şi wndclass. Celelalte două sunt folosite în funcţia WndProc pentru definirea altor două structuri,
numite ps şi rect.
Câteva cuvinte despre variabilele handle
În sfârşit, în program sunt folosiţi trei identificatori cu majuscule pentru diferite tipuri de variabile
handle:

Identificator           Semnificaţie

HINSTANCE              Variabilă handle a unei „instanţe" - programul însuşi

HWND                         Variabilă handle a unei ferestre

HDC                             Variabilă handle a unui context de dispozitiv

Variabilele handle sunt folosite destul de des în Windows. În acest capitol veţi face cunoştinţă cu
variabila handle a unei pictograme (HICON), variabila handle a unui cursor (HCURSOR) şi cea a
unei pensule (HBRUSH).

O variabilă handle este pur şi simplu un număr (de obicei pe 32 de biţi) care face trimitere la un
obiect. Variabilele handle din Windows sunt asemănătoare cu cele folosite pentru fişiere (file
handles) în programele convenţionale C sau MS-DOS. Un program obţine aproape întotdeauna o
variabilă apelând o funcţie Windows. Programul foloseşte apoi variabila handle obţinută pentru
trimiterea la obiect în alte funcţii. Valoarea reală a variabilei handle nu este importantă pentru
program, dar modulul Windows care o furnizează programului ştie cum să îl manipuleze pentru
trimiterea la obiect.

Notaţia ungară
S-ar putea să fi observat că unele dintre variabilele folosite în programul HELLOWIN.C au nume
ciudate. Un astfel de exemplu este szCmdLine, transmis ca parametru funcţiei WinMain.

Mulţi programatori Windows folosesc „notaţia ungară", o convenţie de denumire a variabilelor


intitulată astfel în onoarea legendarului programator de la Microsoft, Charles Simonyi. Convenţia
este foarte simplă - fiecare nume de variabilă începe cu una sau mai multe litere mici care specifică
tipul de date al variabilei. De exemplu, prefixul sz al variabilei szCmdLine semnifică „şir de
caractere terminat cu zero". Prefixul h al variabilelor hInstance şi hPrevInstance înseamnă „variabilă
handle"; prefixul i al variabilei iCmdShow înseamnă „întreg". Şi ultimii doi parametri ai funcţiei
WndProc respectă notaţia ungară, deşi, aşa cum am arătat mai devreme, wParam ar fi trebuit să se
numească uiParam (de la „unsigned integer"). Totuşi, deoarece aceşti doi parametri se definesc
folosind tipurile de date WPARAM şi LPARAM, am păstrat denumirile originale.

Atunci când denumiţi variabilele de tip structură, puteţi să folosiţi numele de tip al structurii (sau o
abreviere a acestuia) ca prefix al numelui variabilei sau chiar ca nume al variabilei. De exemplu, în
funcţia WinMain din programul HELLOWIN.C, variabila msg este o structură de tip MSG iar
wndclass este o variabilă de tip WNDCLASSEX. În funcţia WndProc, ps este o structură de tip
PAINTSTRUCT iar rect este o structură de tip RECT.
Notaţia ungară vă ajută să descoperiţi erorile înainte ca acestea să ajungă în programele finale.
Deoarece numele unei variabile descrie atât modul de folosire a acesteia, cât şi tipul de date, este
mai puţin probabil să faceţi erori de programare care implică amestecarea unor tipuri de date
incompatibile.

Prefixele folosite pentru variabilele din această carte sunt prezentate în tabelul următor:

Prefix   Tip de date

c             char

by               BYTE (unsigned char)

n             short

i                  int

x, y            int (folosit pentru coordonate)

cx, cy         int (folosit pentru dimensiuni pe axele x si y, c vine de la „contor")

b sau f        BOOL (int); f vine de la „flag" (indicator)

w                WORD (unsigned short)

l                  LONG (long)

dw              DWORD (unsigned long)

fn               funcţie

s                 şir de caractere

sz               sir de caractere terminat cu zero

h                 variabilă handle

p                 pointer

Punctul de intrare în program


După ce v-aţi format o idee generală despre programul HELLOWIN.C, putem să disecăm programul
linie cu linie. Codul începe cu o instrucţiune #include pentru includerea fişierului antet
WINDOWS.H:
 

#include <windows.h>

Fişierul WINDOWS.H include la rândul lui mai multe fişiere antet care conţin declaraţiile funcţiilor,
structurilor, noilor tipuri de date şi constantelor numerice din Windows.

Instrucţiunea #include este urmată de declaraţia avansată a funcţiei WndProc*:

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

Declararea în avans a acestei funcţii este necesară deoarece WndProc este referită în cadrul funcţiei
WinMain.

Punctul de intrare al unui program C scris pentru un mediu convenţional este o funcţie numită main.
De la această funcţie începe execuţia programului. (De fapt, funcţia main este punctul de intrare la
acea parte a programului scrisă de programator. De obicei, compilatorul C inserează în fişierul
executabil unele secvenţe de cod pentru lansarea în execuţie. Funcţia main este apoi apelată de acest
cod de lansare). Punctul de intrare într-un program Windows este o funcţie numită WinMain.
Funcţia WinMain este întotdeauna definită astfel:

int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance, PSTR szCmdLine,


int iCmdShow)

Această funcţie foloseşte secvenţa de apelare WINAPI şi la terminare returnează sistemului de


operare o valoare întreagă. Numele funcţiei trebuie să fie WinMain. Această funcţie are patru
parametri:

Parametrul hlnstance este numit „variabilă handle a instanţei" („instance handle"). Acesta este un
număr care identifică în mod unic toate programele rulate în Windows. Utilizatorul poate rula
simultan mai multe copii ale aceluiaşi program. Aceste copii se numesc „instanţe" şi fiecare are o
valoare diferită pentru parametrul hlnstance. Variabila handle a instanţei este asemănătoare cu
„identificatorul de operaţie" („task ID") sau „identificatorul de proces" („process ID") din alte
sisteme de operare multitasking.

hPrevInstance („previous instance" - instanţa anterioară) este un parametru învechit. În versiunile


Windows anterioare acest parametru conţinea variabila handle a celei mai recente instanţe încă
activă a aceluiaşi program. Dacă nu erau încărcate alte instanţe ale programului, hPrevInstance avea
valoarea 0 sau NULL. În Windows 95, parametrul hPrevInstance are întotdeauna valoarea NULL.
Parametrul szCmdLine este un pointer la un şir de caractere terminat cu zero care conţine eventualii
parametri transmişi programului în linia de comandă. Puteţi să rulaţi un program Windows cu
parametri incluzând parametrii respectivi după numele programului în linia de comandă MS-DOS
sau specificându-i în caseta de dialog Run apelată din meniul Start.

Parametrul iCmdShow este un număr care indică modul iniţial de afişare a ferestrei în Windows.
Acest număr este atribuit de programul care lansează în execuţie programul aflat în discuţie.
Programele verifică rareori valoarea acestui parametru, dar o pot face dacă este nevoie. În
majoritatea cazurilor, iCmdShow are valoarea 1 sau 7. Dar cel mai bine este să nu vă gândiţi la
aceste valori numerice. Mai sugestivi sunt identificatorii SW_SHOWNORMAL (definit în Windows
ca 1) şi SW_SHOWMINNOACTIVE (definit cu valoarea 7). Prefixul SW vine de la „show
window" (afişare fereastră). Acest parametru specifică dacă fereastra programului este afişată
normal sau dacă este afişată iniţial doar ca o pictogramă.

Înregistrarea clasei de fereastră


O fereastră este întotdeauna creată pe baza unei clase de fereastră. Aceasta identifică procedura de
fereastră care prelucrează toate mesajele trimise către fereastră.

Pe baza aceleiaşi clase pot fi create mai multe ferestre. De exemplu, toate butoanele din Windows
sunt create pe baza unei singure clase de fereastră. Aceasta defineşte procedura de fereastră şi alte
caracteristici ale ferestrei create pe baza clasei respective. Atunci când creaţi o fereastră, definiţi şi
atributele suplimentare ale acesteia, care sunt unice pentru fereastra respectivă.

Înainte de a crea fereastra programului trebuie să înregistraţi o clasă de fereastră, apelând funcţia
RegisterClassEx. Aceasta este o versiune extinsă (de aici sufixul „Ex") a funcţiei RegisterClass din
versiunile anterioare ale sistemului de operare Windows. Totuşi, funcţia RegisterClass poate fi încă
folosită în Windows 95.

Funcţia RegisterClassEx acceptă un singur parametru: un pointer la o structură de tipul


WNDCLASSEX. Structura WNDCLASSEX este definită în fişierele antet din Windows astfel:

typedef  struct tagWNDCLASSEX

UINT                    cbSize ;

UINT                    style ;

WNDPROC         lpfnWndProc ;

int                         cbClsExtra ;

int                         cbWnExtra ;

HINSTANCE       hinstance ;
HICON                 hicon ;

HCURSOR           hCursor ;

HBRUSH             hbrBackground ;

LPCSTR               lpszMenuName ;

LPCSTR               lpszClassName ;

HICON                 hIconSm ;

WNDCLASSEX ;

Sunt necesare câteva observaţii privind tipurile de date şi notaţia ungară folosită în această structură:
prefixele LP şi lp sunt prescurtări pentru „long pointer" şi sunt „rămăşiţe" din versiunile Windows
pe 16 biţi, în cazul cărora programatorii trebuie să facă diferenţa între pointerii de tip short (sau
near) pe 16 biţi şi pointerii de tip long (sau far) pe 32 de biţi. În Windows 95 toţi pointerii au valori
pe 32 de biţi. Am încercat să elimin toate prefixele l ale tipurilor de pointeri din exemplele de
programe pe care le-am ales pentru această carte, dar cu siguranţă că le veţi mai întâlni în alte
programe.

Remarcaţi şi alte moduri de folosire a notaţiei ungare: lpfn vine de la „long pointer to a function"
(„pointer de tip long la o funcţie"). Prefixul cb provine de la „count of bytes" („contor de octeţi").
Prefixul hbr vine de la „handle to a brush" („variabilă handle a unei pensule").

În funcţia WinMain trebuie să definiţi o structură de tipul WNDCLASSEX, cum ar fi:

WNDCLASSEX wndclass ;

Apoi definiţi cele 12 câmpuri ale structurii şi apelaţi funcţia RegisterClassEx:

RegisterClassEx (&wndclass) ;

Cele mai importante câmpuri ale structurii sunt al treilea şi penultimul. Penultimul câmp conţine
numele clasei de fereastră (şi în programele care creează o singură fereastră are, de obicei, acelaşi
nume ca şi programul). Al treilea câmp (lpfnWndProc) este adresa procedurii de fereastră folosită
pentru toate ferestrele create pe baza acestei clase (care este funcţia WndProc din programul
HELLOWIN.C). Celelalte câmpuri descriu caracteristicile tuturor ferestrelor create pe baza acestei
clase.
Câmpul cbSize reprezintă dimensiunea structurii. Instrucţiunea: wndclass.style = CS_HREDRAW |
CS_VREDRAW ; combină doi identificatori pentru „stilul de clasă" („class style") folosind
operatorul SAU orientat pe biţi din limbajul C. În fişierele antet din Windows sunt definiţi mai mulţi
identificatori cu prefixul CS_. Acestea sunt constante pe 32 de biţi în care un singur bit are valoarea
1. De exemplu, identificatorul CS_VREDRAW este definit ca 0x0001 iar CS_HREDRAW este
definit ca 0x0002. Identificatorii definiţi în acest fel sunt numiţi uneori „identificatori pe biţi".
Aceştia pot fi combinaţi cu ajutorul operatorului SAU orientat pe biţi din limbajul C.

Cei doi identificatori pentru stilul clasei indică faptul că toate ferestrele create pe baza acestei clase
sunt redesenate complet, ori de câte ori se modifică dimensiunea pe orizontală (CS_HREDRAW)
sau cea pe verticală (CS_VREDRAW) a ferestrei. Dacă redimensionaţi fereastra programului
HELLOWIN veţi vedea că textul este redesenat, astfel încât să apară în centrul noii ferestre. Acest
lucru este asigurat de cei doi identificatori de stil. Vom vedea imediat cum este informată procedura
de fereastră privind modificarea dimensiunii ferestrei.

Al treilea câmp al structurii WNDCLASSEX este iniţializat prin instrucţiunea:

wndclass.lpfnWndProc = WndProc ;

Această instrucţiune stabileşte ca procedură de fereastră funcţia WndProc, adică a doua funcţie
definită în fişierul HELLOWIN.C. Această procedură va prelucra toate mesajele trimise către toate
ferestrele create pe baza acestei clase de fereastră. Aşa cum am arătat mai sus, prefixul lpfn
înseamnă, în notaţia ungară, „pointer de tip long la o funcţie".

Următoarele două instrucţiuni:

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

rezervă un spaţiu suplimentar în structura clasei şi în structura ferestrei, păstrată în interiorul


sistemului de Windows. Un program poate să utilizeze spaţiul suplimentar în scopuri proprii.
Programul HELLOWIN nu foloseşte această posibilitate, aşa că nu se rezervă nici un spaţiu
suplimentar. În alte situaţii, aşa cum indică şi notaţia ungară, câmpurile vor avea rolul de „contor de
octeţi".

Următorul câmp este variabila handle a instanţei (care este chiar unul dintre parametrii funcţiei
WinMain):

wndclass.hInstance = hinstance ;

Instrucţiunea:

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

şi instrucţiunea:

wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;


definesc o pictogramă pentru ferestrele create pe baza acestei clase. Pictograma este o mică imagine
de tip bitmap care apare în bara de taskuri a sistemului de operare şi în bara de titlu a ferestrei. Mai
târziu veţi învăţa cum să creaţi pictograme proprii pentru programele Windows. Pentru moment,
vom aborda o metodă mai simplă şi vom folosi o pictogramă predefinită.

Pentru obţinerea unei variabile handle a unei pictograme predefinite apelaţi funcţia LoadIcon cu
primul parametru având valoarea NULL. (Atunci când încărcaţi o pictogramă proprie, acest
parametru va conţine variabila handle a instanţei programului.) Al doilea parametru este un
identificator cu prefixul IDI_ definit în fişierele antet din Windows. Pictograma
IDI_APPLICATION este o mică imagine a unei ferestre. Funcţia LoadIcon returnează o variabilă
handle a acestei pictograme. Nu ne interesează valoarea reală a acestei variabile, ci doar o stocăm în
câmpurile hIcon şi hIconSm. Aceste câmpuri sunt definite în structura WNDCLASSEX de tipul
HICON („handle to an icon").

Instrucţiunea:

wndclass.hCursor = LoadCursor (NULL, IDC_ ARROW) ;

este foarte asemănătoare cu cele două instrucţiuni anterioare. Funcţia LoadCursor încarcă un cursor
predefinit pentru mouse, numit IDC_ARROW, şi returnează o variabilă handle a acestui cursor.
Atunci când este deplasat deasupra zonei client a ferestrei create pe baza acestei clase, indicatorul
mouse-ului este afişat sub forma unei săgeţi.

Următorul câmp precizează culoarea fondului zonei client a ferestrelor create pe baza acestei clase.
Prefixul hbr al numelui hbrBackground vine de la „handle to a brush" („variabilă handle a unei
pensule"). O pensulă este un termen grafic care se referă la un model colorat de pixeli folosit pentru
umplerea unei suprafeţe. Windows are mai multe pensule standard (sau pensule „de stoc"). Funcţia
GetStockObject returnează o variabilă handle a unei pensule albe:

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

Aceasta înseamnă că fondul zonei client a ferestrei va fi colorat cu alb, ceea ce este un lucru
obişnuit.

Următorul câmp specifică meniul ferestrei. Programul HELLOWIN nu are nici un meniu, aşa că
acest câmp are valoarea NULL:

wndclass.lpszMenuName = NULL ;

În sfârşit, clasei trebuie să i se dea un nume. Pentru programele mai mici, acesta poate fi chiar
numele programului, deci în variabila szAppName este stocat şirul de caractere „HelloWin":

wndclass.IpszClassName = szAppName ;

După iniţializarea celor 12 câmpuri ale structurii, HELLOWIN înregistrează clasa de ferestre prin
apelarea funcţiei RegisterClassEx. Singurul parametru al funcţiei este un pointer către structura
WNDCLASSEX:
RegisterClassEx (&wndclass) ;

Crearea terestrei
Clasa de fereastră defineşte caracteristicile generale ale unei ferestre, permiţând astfel folosirea
aceleiaşi clase pentru crearea mai multor ferestre. Atunci când creaţi o fereastră apelând funcţia
Create Window, puteţi să specificaţi mai multe detalii despre fereastra respectivă.

Programatorii Windows mai noi sunt uneori derutaţi de diferenţele între o clasă de fereastră şi o
fereastră, şi nu înţeleg de ce nu pot să specifice toate caracteristicile unei ferestre printr-o singură
operaţie. De fapt, împărţirea informaţiilor în acest fel este foarte convenabilă. De exemplu, toate
butoanele de apăsare sunt create pe baza unei singure clase de fereastră. Procedura de fereastră
asociată acestor butoane este localizată chiar în Windows. Clasa de fereastră are ca sarcini
prelucrarea tuturor mesajelor de la tastatură şi de la mouse trimise către butonul de apăsare şi
definirea aspectului vizual al butonului pe ecran. Din acest punct de vedere, toate butoanele de
apăsare funcţionează în acelaşi mod. Acestea însă pot avea diferite dimensiuni, diferite poziţii pe
ecran şi diferite etichete. Aceste caracteristici fac parte din definiţia ferestrei.

În loc să folosească o structură de date, aşa cum face funcţia RegisterClassEx, funcţia
CreateWindow cere ca toate informaţiile să fie transmise ca parametri. Iată cum este apelată funcţia
CreateWindow în programul HELLOWIN.C:

hwnd =CreateWindow (szAppName,                    // numele clasei de fereastra

"The Hello Program",                                             // titlul ferestrei

WS_OVERLAPPEDWINDOW,                            // stilul ferestrei

CW_USEDEFAULT,                                              // poziţia iniţiala pe axa x

CW_USEDEFAULT,                                              // poziţia iniţiala pe axa y

CW_USE DEFAULT,                                             // dimensiunea iniţiala pe axa x

CW_USEDEFAULT,                                              // dimensiunea iniţiala pe axa y

NULL,                                                                       // variabila handle a ferestrei părinte

NULL,                                                                       // variabila handle a meniului

hlnstance,                                                                 // variabila handle a instanţei programului

NULL) ;                                                                    // parametri de creare

 
Pentru o citire mai uşoară, am folosit simbolul // pentru notarea comentariilor pe o singură linie care
descriu parametrii funcţiei Create Window.

Parametrul notat cu „numele clasei de fereastră" este szAppName, care conţine şirul de caractere
„HelloWin" - numele clasei de fereastră pe care tocmai am înregistrat-o. În acest fel, fereastra este
asociată unei clase de fereastră.

Fereastra creată de acest program este o fereastră normală suprapusă, cu o bară de titlu; în partea
stângă a barei de titlu se află caseta cu meniul sistem; în partea dreaptă se află butoanele de mărire,
de micşorare şi de închidere; fereastra are un chenar îngroşat, care permite redimensionarea. Acesta
este stilul standard al ferestrelor, numit WS_OVERLAPPEDWINDOW; în funcţia CreateWindow îi
corespunde comentariul „stilul ferestrei". „Titlul ferestrei" este textul afişat în bara de titlu.

Parametrii notaţi cu „poziţia iniţială pe axa x" şi „poziţia iniţială pe axa y" specifică poziţia iniţială a
colţului din stânga-sus al ferestrei, relativ la colţul din stânga-sus al ecranului. Prin folosirea
identificatorului CW_USEDEFAULT pentru aceşti parametri indicăm sistemului de operare să
folosească valorile prestabilite pentru o fereastră suprapusă. (CW_USEDEFAULT este definit ca
0x80000000.) În mod prestabilit, Windows poziţionează mai multe ferestre suprapuse succesive la o
distanţă crescătoare pe verticală şi pe orizontală faţă de colţul din stânga-sus al ecranului. La fel,
parametrii „dimensiunea iniţială pe axa x" şi „dimensiunea iniţială pe axa y" specifică dimensiunile
iniţiale ale ferestrei. Identificatorul CW_USEDEFAULT indică sistemului de operare să folosească
valorile prestabilite.

Parametrul indicat ca „variabilă handle a ferestrei părinte" are valoarea NULL, deoarece această
fereastră nu are nici o fereastră părinte. Atunci când între două ferestre există o relaţie părinte-
descendent, fereastra descendent este afişată întotdeauna pe suprafaţa ferestrei părinte. Parametrul
indicat ca „variabilă handle a meniului" are tot valoarea NULL, deoarece fereastra nu are meniu.
Parametrul indicat ca „variabilă handle a instanţei programului" are ca valoare variabila handle
transmisă programului ca parametru la apelarea funcţiei WinMain. În sfârşit, „parametrul de creare"
are valoarea NULL. Puteţi să folosiţi acest parametru pentru adresarea unor date folosite ulterior în
program.

Funcţia Create Window returnează o variabilă handle a ferestrei create. Aceasta este salvată în
variabila hwnd, definită ca fiind de tipul HWND (variabilă handle a unei ferestre). Orice fereastră
din Windows are o variabilă handle. Programul foloseşte variabila handle pentru indicarea ferestrei.
Multe funcţii Windows au un parametru hwnd, care specifică fereastra la care se referă funcţia
respectivă. Dacă un program creează mai multe ferestre, fiecare are o variabilă handle diferită.
Variabila handle a unei ferestre este una dintre cele mai importante variabile folosite în Windows.

Afişarea ferestrei
După executarea funcţiei CreateWindow, fereastra a fost creată de Windows, dar încă nu este afişată
pe ecran. Pentru aceasta mai sunt necesare încă două apeluri de funcţii. Primul este:

ShowWindow (hwnd, iCmdShow) ;

Primul parametru este o variabilă handle a ferestrei create de funcţia CreateWindow. Al doilea
parametru este variabila iCmdShow, transmisă funcţiei WinMain. Dacă iCmdShow este
SW_SHOWNORMAL (egal cu 1), fereastra este afişată normal. Dacă iCmdShow este
SW_SHOWMINNOACTIVE (egal cu 7), atunci fereastra nu este afişată, dar numele şi pictograma
acesteia apar pe bara de taskuri.

În programul HELLOWIN funcţia ShowWindow afişează fereastra pe ecran. Dacă al doilea


parametru al funcţiei este SW_SHOWNORMAL, Windows şterge zona client a ferestrei folosind
pensula specificată în clasa ferestrei. Apelul:

UpdateWindow (hwnd) ;

determină redesenarea zonei client. Acest lucru se face prin trimiterea către procedura de fereastră
(funcţia WndProc din HELLOWIN.C) a unui mesaj WM_PAINT. Vom vedea imediat cum tratează
funcţia WndProc aceste mesaje.

Ciclul de mesaje
După apelarea funcţiei UpdateWindow, fereastra devine vizibilă pe ecran. Programul trebuie să fie
acum pregătit să citească intrările de la mouse şi de la tastatură. Windows formează o „coadă de
mesaje" pentru fiecare program rulat concurenţial. Atunci când apare un eveniment exterior,
Windows converteşte acest eveniment într-un mesaj pe care îl plasează în coada de aşteptare.

Un program preia mesajele din coada de aşteptare prin executarea unei secvenţe de cod numită
„ciclu de mesaje" („message loop"):

while (GetMessage (&msg, NULL, 0, 0))

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

return msg.wParam ;

Variabila msg este o structură de tip MSG, definită în fişierele antet din Windows astfel:

typedef struct tagMSG

HWND        hwnd ;
UINT          message ;

WPARAM   wParam ;

LPARAM    lParam ;

DWORD     time ;

POINT        pt ;

MSG ,

Tipul de date POINT este tot o structură, definită astfel:

typedef    struct tagPOINT

LONG x ;

LONG y ;

POINT ;

Funcţia GetMessage apelată la începutul ciclului de mesaje preia un mesaj din coada de aşteptare:

GetMessage (&msg, NULL, 0, 0)

Acest apel transmite sistemului de operare un pointer, numit msg, la o structură de tip MSG. Al
doilea, al treilea şi al patrulea parametru au valoarea NULL sau 0, ceea ce indică faptul că
programul vrea să preia toate mesajele, pentru toate ferestrele create de program. Windows
completează câmpurile structurii de mesaje cu următorul mesaj din coada de aşteptare. Câmpurile
acestei structuri sunt:

·        hwnd - variabila handle a ferestrei căreia îi este destinat mesajul. În programul HELLOWIN,
aceasta este aceeaşi cu valoarea hwnd returnată de funcţia CreateWindow, deoarece aceasta este
singura fereastră a programului.

·        message - identificatorul mesajului. Acesta este un număr folosit pentru identificarea
mesajului. Pentru fiecare mesaj în fişierele antet din Windows este definit un identificator care
începe cu prefixul WM_ („window message"). De exemplu, dacă poziţionaţi indicatorul mouse-ului
în zona client a programului HELLOWIN şi apăsaţi butonul din stânga, Windows va insera în coada
de aşteptare un mesaj pentru care câmpul message conţine identificatorul WM_LBUTTONDOWN,
adică valoarea 0x0201.

·        wParam - un parametru pe 32 de biţi a cărui valoare depinde de mesajul trimis.

·        lParam - un alt parametru pe 32 de biţi dependent de mesaj.

·        time - momentul inserării mesajului în coada de mesaje.

·        pt - coordonatele poziţiei mouse-ului în momentul inserării mesajului în coada de mesaje.

Dacă în câmpul message este transmisă orice altă valoare decât WM_QUIT (egală cu 0x0012),
funcţia GetMessage returnează o valoare diferită de zero. Mesajul WM_QUIT determină ieşirea din
ciclul de mesaje. Programul se încheie, returnând valoarea parametrului wParam al structurii msg.

Instrucţiunea:

TranslateMessage (&msg) ;

retransmite structura msg sistemului de operare, pentru convertirea unor mesaje de la tastatură.
(Vom discuta mai multe despre aceasta în Capitolul 5.)

Instrucţiunea:

DispatchMessage (&msg) ;

ca şi funcţia TranslateMessage, retransmite structura msg sistemului de operare. Windows trimite


apoi mesajul către procedura de fereastră corespunzătoare, în vederea prelucrării - cu alte cuvinte,
Windows apelează procedura de fereastră. În programul HELLOWIN, procedura de fereastră este
WndProc. După ce prelucrează mesajul, funcţia WndProc predă controlul sistemului de operare,
care încă elaborează răspunsul la apelul DispatchMessage. Atunci când Windows returnează
controlul programului HELLOWIN, după executarea apelului DispatchMessage, ciclul de tratare a
mesajelor continuă cu următorul apel al funcţiei GetMessage.

Procedura de fereastră
Tot codul descris până în acest moment este cod de întreţinere: a fost înregistrată clasa de fereastră,
a fost creată fereastra, care apoi a fost afişată pe ecran şi programul a intrat în ciclul de tratare a
mesajelor în vederea preluării mesajelor din coada de aşteptare.

Operaţiile reale au loc însă în procedura de fereastră. Procedura de fereastră arată ce afişează
fereastra în zona client şi cum răspunde fereastra la intrările utilizatorului.

În programul HELLOWIN procedura de fereastră este funcţia WndProc. O procedură de fereastră


poate avea orice nume (cu condiţia ca numele respectiv să nu existe deja). Un program pentru
Windows poate conţine mai multe proceduri de fereastră. O procedură de fereastră este întotdeauna
asociată unei clase de fereastră, înregistrată cu ajutorul funcţiei RegisterClassEx. Funcţia
CreateWindow creează o fereastră pe baza unei anumite clase. Pe baza aceleiaşi clase pot fi create
mai multe ferestre.

Procedura de fereastră este definită întotdeauna astfel:

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam. LPARAM
lParam)

Remarcaţi faptul că cei patru parametri ai procedurii de fereastră sunt identici cu primele patru
câmpuri ale structurii MSG.

Primul parametru este hwnd, variabila handle a ferestrei care recepţionează mesajul. Aceasta este
aceeaşi variabilă cu cea returnată de funcţia CreateWindow. Pentru un program care creează o
singură fereastră, precum HELLOWIN, aceasta este singura variabilă handle cunoscută de program.
Dacă programul creează mai multe ferestre pe baza aceleiaşi clase (şi deci foloseşte aceeaşi
procedură de fereastră), atunci hwnd identifică fereastra care primeşte mesajul.

Al doilea parametru este un număr (mai precis, un întreg fără semn - UINT - pe 32 de biţi) care
identifică mesajul. Ultimii doi parametri (wParam, de tipul WPARAM si lParam, de tipul
LPARAM) furnizează mai multe informaţii despre mesaj. Aceştia sunt numiţi „parametri de mesaj".
Conţinutul acestor parametri este specific fiecărui tip de mesaj.

Prelucrarea mesajelor
Fiecare mesaj recepţionat de o procedură de fereastră este identificat printr-un număr, acesta fiind
parametrul iMsg al procedurii de fereastră. În fişierele antet din Windows sunt definiţi identificatori,
cu prefixul WM („window message"), pentru fiecare parametru de mesaje.

În general, programatorii Windows folosesc o construcţie switch sau case ca să determine ce mesaje
a primit fereastra şi să stabilească modul de prelucrare a fiecărui mesaj. Atunci când prelucrează un
mesaj, procedura de fereastră trebuie să returneze valoarea 0. Orice mesaj pe care procedura de
fereastră nu-l prelucrează este transmis unei funcţii Windows numită DefWindowProc. Valoarea
returnată de funcţia DefWindowProc trebuie să fie returnată şi de procedura de fereastră.

În programul HELLOWIN, procedura de fereastră (WndProc) prelucrează numai trei mesaje:


WM_CREATE, WM_PAINT şi WM_DESTROY. Procedura de fereastră este structurată astfel:

switch (iMsg)

case WM_CREATE :

[prelucrează mesajul WM_CREATE]


return 0 ;

case WM_PAINT :

[prelucrează mesajul WM_PAINT]

return 0 ;

case WM_DESTROY :

[prelucrează mesajul WM_DESTROY]

return 0 ;

return DefWindowProc(hwnd, iMsg, wParam, lParam) ;

Este esenţial să apelaţi funcţia DefWindowProc pentru prelucrarea tuturor mesajelor ignorate de
procedura de fereastră a programului.

Redarea unui fişier de sunet


Primul mesaj pe care îl primeşte o procedură de fereastră - şi primul mesaj prelucrat de funcţia
WndProc - este WM_CREATE. WndProc recepţionează acest mesaj în timp ce Windows execută
funcţia CreateWindow din WinMain. Aceasta înseamnă că atunci când HELLOWIN apelează
funcţia CreateWindow, Windows face ce are de făcut şi apelează funcţia WndProc, transmiţându-i
variabila handle a ferestrei şi mesajul WM_CREATE. WndProc prelucrează mesajul WM_CREATE
şi returnează controlul sistemului de operare. Windows poate apoi să încheie execuţia funcţiei
CreateWindow şi să se întoarcă la programul HELLOWIN pentru alte operaţii din funcţia WinMain.

Deseori, procedurile de fereastră fac toate operaţiile de iniţializare a ferestrei în timpul prelucrării
mesajului WM_CREATE. În timpul prelucrării acestui mesaj, programul HELLOWIN redă un fişier
de sunet, numit HELLOWIN.WAV. Acest lucru se face prin apelarea funcţiei PlaySound. Primul
parametru al funcţiei este numele fişierului. Acesta ar putea să fie şi un alias (nume de înlocuire)
definit în secţiunea Sounds a panoului de control (Control Panel) sau o resursă de program. Al
doilea parametru este folosit numai dacă fişierul de sunet este o resursă. Al treilea parametru
specifică un set de opţiuni. În acest caz am indicat faptul că primul parametru este un nume de fişier
şi că sunetul trebuie să fie redat asincron, adică funcţia PlaySound trebuie să returneze controlul
imediat după începerea operaţiei de redare, fără să aştepte terminarea acesteia.
WndProc încheie prelucrarea mesajului WM_CREATE cu returnarea valorii zero din procedura de
fereastră.

Mesajul WM_PAINT
Al doilea mesaj prelucrat de funcţia WndProc este WM_PAINT. Acest mesaj este foarte important
în programarea sub Windows, deoarece informează fereastra privind faptul că o parte sau întreaga
zonă client a acesteia este „invalidă" şi trebuie să fie redesenată.

Cum poate să devină invalidă o zonă client? Atunci când fereastra este creată, întreaga zonă client a
ferestrei este invalidă, deoarece programul nu a desenat încă nimic în fereastră. Primul mesaj
WM_PAINT (care apare, în mod normal, atunci când programul apelează funcţia UpdateWindow
din WinMain) cere procedurii de fereastră să deseneze ceva în zona client.

Zona client devine invalidă şi atunci când redimensionaţi fereastra programului HELLOWIN. Vă
amintiţi că parametrul de stil al structurii wndclass din HELLOWIN conţine identificatorii
CS_VREDRAW şi CS_HREDRAW. Aceşti identificatori determină sistemul de operare Windows
să invalideze întreaga fereastră atunci când aceasta îşi schimbă dimensiunea şi apoi să trimită un
mesaj WM_PAINT către procedura de fereastră.

Dacă micşoraţi la o pictogramă fereastra programului HELLOWIN şi apoi îi refaceţi dimensiunea


iniţială. Windows nu salvează conţinutul zonei client. Într-un mediu grafic ar fi mult prea multe date
de salvat. De aceea, Windows invalidează fereastra. Procedura de fereastră primeşte mesajul
WM_PAINT şi reface conţinutul acesteia.

Atunci când mutaţi ferestrele astfel încât să se suprapună. Windows nu salvează porţiunea unei
ferestre care a fost acoperită de o altă fereastră. În schimb, atunci când fereastra respectivă este
readusă la suprafaţă, porţiunea anterior acoperită este invalidată. Procedura de fereastră primeşte un
mesaj WM_PAINT pentru redesenarea porţiunii respective.

Aproape întotdeauna, prelucrarea mesajului WM_PAINT începe prin apelarea funcţiei BeginPaint:

hdc = BeginPaint (hwnd, &ps) ;

şi se termină cu apelarea funcţiei EndPaint:

EndPaint (hwnd, &ps) ;

În ambele situaţii, primul parametru este variabila handle a ferestrei programului, iar al doilea
parametru este un pointer la o structură de tip PAINTSTRUCT. Structura PAINTSTRUCT conţine
unele informaţii pe care programul le poate folosi pentru redesenarea zonei client. (Vom discuta
despre acest câmp al structurii în următorul capitol.)
În timpul apelării funcţiei BeginPaint, Windows şterge fondul zonei client, dacă acesta nu a fost deja
şters. Fondul este şters folosindu-se pensula specificată în câmpul hbrBackground al structurii
WNDCLASSEX, folosită pentru înregistrarea clasei de fereastră, în cazul programului
HELLOWIN, aceasta este o pensulă de stoc albă, ceea ce înseamnă că Windows şterge fondul
ferestrei colorându-l, în acelaşi timp, cu alb. Funcţia BeginPaint validează întreaga zonă client şi
returnează o „variabilă handle a contextului de dispozitiv". Un context de dispozitiv se referă la un
dispozitiv fizic de ieşire (cum ar fi un monitor video) împreună cu driverul care controlează acest
dispozitiv. Aveţi nevoie de o variabilă handle a contextului de dispozitiv ca să afişaţi text şi
elemente grafice în zona client a ferestrei. Folosind variabila handle a contextului de dispozitiv
returnată de funcţia BeginPaint nu puteţi să desenaţi în afara zonei client a ferestrei, chiar dacă
încercaţi. Funcţia EndPaint eliberează variabila handle a contextului de dispozitiv, astfel încât
aceasta nu mai este validă.

Dacă procedura de fereastră nu prelucrează mesajele WM_PAINT - ceea ce se întâmplă foarte rar -
acestea trebuie să fie transmise funcţiei DefWindowProc. Funcţia DefWindowProc apelează funcţiile
BeginPaint şi EndPaint, astfel încât zona client a ferestrei să fie din nou validată.

După funcţia BeginPaint, WndProc apelează funcţia GetClientRect:

GetClientRect (hwnd, &rect) ;

Primul parametru al funcţiei GetClientRect este variabila handle a ferestrei programului. Al doilea
parametru al funcţiei este un pointer la o variabilă numită rect, de tipul RECT, definită în WndProc.

RECT este o structură pentru un „dreptunghi" definit în fişierele antet din Windows. Structura
conţine patru câmpuri de tip LONG, numite left, top, right şi bottom. Funcţia GetClientRect atribuie
acestor câmpuri valorile corespunzătoare dimensiunilor zonei client a ferestrei. Câmpurile left şi top
au întotdeauna valoarea 0. Câmpurile right şi bottom conţin lăţimea şi înălţimea zonei client, în
pixeli.

WndProc transmite funcţiei DrawText un pointer către structura RECT, prin cel de-al patrulea
parametru al funcţiei:

DrawText (hdc, "Hello, Windows 95!", -1, &rect,

DT_SINGLELINE : DT_CENTER ! DT_VCENTER) ;

Funcţia DrawText, aşa cum arată şi numele, este folosită pentru „desenarea" unui text. Deoarece
funcţia desenează ceva, primul parametru este o variabilă handle a contextului de dispozitiv,
returnatâ de funcţia BeginPaint. Al doilea parametru este textul care urmează să fie afişat, iar al
treilea parametru are valoarea -1, ceea ce indică faptul că textul de afişat se termină cu un octet 0.

Ultimul parametru al funcţiei este o combinaţie de indicatori flag pe biţi, definiţi în fişierele antet
din Windows. Indicatorii flag folosiţi determină afişarea textului pe o singură linie, centrată
orizontal şi vertical relativ la dreptunghiul specificat prin al patrulea parametru. Ca urmare, funcţia
DrawText afişează textul „Hello, Windows 95!" în centrul zonei client a ferestrei.
De fiecare dată când zona client devine invalidă (aşa cum se întâmplă atunci când modificaţi
dimensiunea ferestrei), funcţia WndProc recepţionează un mesaj WM_PAINT. WndProc obţine
noua dimensiune a ferestrei şi afişează din nou textul în centrul acesteia.

Mesajul WM_DESTROY
Un alt mesaj important este WM_DESTROY. Acest mesaj indică faptul că sistemul de operare
desfăşoară un proces de distrugere a ferestrei pe baza unei comenzi de la utilizator. Mesajul este
trimis atunci când utilizatorul execută clic pe butonul Close, selectează opţiunea Close din meniul
sistem sau apasă fastele Alt+F4.

Programul HELLOWIN răspunde la acest mesaj printr-o metodă standard, apelând funcţia
PostQuitMessage:

PostQuitMessage (0) ;

Această funcţie inserează în coada de aşteptare a programului un mesaj WM_QUIT. Am spus


anterior că funcţia GetMessage returnează o valoare diferită de zero în cazul preluării oricărui mesaj
în afară de WM_QUIT. Atunci când preia din coada de aşteptare un mesaj WM_QUIT, funcţia
GetMessage returnează valoarea 0, ceea ce determină ieşirea din ciclul de tratare a mesajelor din
funcţia WinMain şi închiderea programului.

PROBLEME LEGATE DE PROGRAMAREA SUB


WINDOWS
Chiar şi după explicaţiile legate de programul HELLOWIN, structura şi modul de funcţionare ale
programelor Windows s-ar putea să fi rămas ceva misterios. Într-un scurt program C scris pentru un
mediu convenţional, întregul program poate fi conţinut de funcţia main. În programul HELLOWIN,
funcţia WinMain conţine numai codul de întreţinere (overhead) necesar pentru înregistrarea clasei,
crearea ferestrei şi obţinerea şi livrarea mesajelor din coada de mesaje.

Toată „acţiunea" programului se desfăşoară în procedura de fereastră. În programul HELLOWIN,


aceasta nu face prea multe - redă un fişier de sunet şi afişează un text în fereastră. În următoarele
capitole veţi vedea cam tot ce poate face un program Windows ca răspuns la un mesaj. Prelucrarea
mesajelor este unul dintre principalele obstacole pe care trebuie să le depăşiţi atunci când scrieţi un
program pentru Windows.

Nu ne apela, te apelăm noi!


Aşa cum am menţionat mai devreme, programatorii sunt obişnuiţi cu ideea apelării unor funcţii ale
sistemului de operare, pentru îndeplinirea unei anumite operaţii. De exemplu, programatorii C
folosesc funcţia fopen ca să deschidă un fişier. Funcţiile de bibliotecă livrate odată cu compilatorul
conţin secvenţe de cod care apelează funcţii ale sistemului de operare pentru deschiderea unui fişier.
Nici o problemă.

În Windows, însă, lucrurile se petrec altfel. Deşi sistemul de operare are mai mult de o mie de
funcţii pe care programele le pot apela, şi Windows poate să apeleze funcţii ale programului. Mai
precis, Windows poate să apeleze procedurile de fereastră, precum WndProc. Procedura de fereastră
este asociată unei clase de fereastră pe care programul o înregistrează prin apelarea funcţiei
RegisterClassEx. O fereastră creată pe baza acestei clase foloseşte procedura de fereastră specificată
de clasă pentru prelucrarea tuturor mesajelor recepţionate. Windows trimite un mesaj către o
fereastră prin apelarea procedurii de fereastră a acesteia.

Windows apelează funcţia WndProc atunci când fereastra este creată. Windows apelează funcţia
WndProc atunci când fereastra este distrusă. Windows apelează funcţia WndProc atunci când
fereastra este redimensionată, mutată sau redusă la o pictogramă. Windows apelează funcţia
WndProc atunci când utilizatorul selectează o opţiune dintr-un meniu. Windows apelează funcţia
WndProc atunci când o bară de derulare este manipulată sau când utilizatorul execută clic pe
aceasta. Windows apelează funcţia WndProc pentru a-i comunica ferestrei că trebuie să redeseneze
zona client.

Toate aceste apeluri sunt făcute sub forma unor mesaje. În majoritatea programelor Windows, cea
mai mare parte a codului este folosit pentru tratarea mesajelor. Windows poate trimite unei
proceduri de fereastră peste 200 de mesaje. Aceste mesaje sunt identificate prin nume care încep cu
literele WM şi sunt definite în fişierele antet din Windows.

De fapt, ideea includerii într-un program a unei proceduri care să poată fi apelată din exterior există
şi în programarea convenţională. Funcţia signal din limbajul C interceptează codul combinaţiei de
taste Ctrl+Break. Probabil aţi folosit metode de interceptare a întreruperilor hardware în limbaj de
asamblare sau una dintre construcţiile ON din Microsoft BASIC. Driverul Microsoft Mouse conţine
o metodă pe care o pot folosi programele non-Windows pentru a fi „la curent" cu activitatea mouse-
ului.

În Windows, acest concept este extins, acoperind orice operaţie. Toate evenimentele legate de o
fereastră sunt transmise procedurii de fereastră sub forma unor mesaje. Procedura de fereastră
răspunde la aceste mesaje sau le retransmite către procedura DefWindowProc, pentru operaţii de
prelucrare prestabilite.

Parametrii wParam şi lParam ai procedurii de fereastră nu sunt folosiţi în programul HELLOWIN


decât pentru a fi retransmişi funcţiei DefWindowProc. Aceşti parametri furnizează procedurii de
fereastră informaţii suplimentare despre mesajul primit. Semnificaţia acestor parametri este
dependentă de mesaj.

Haideţi să vedem un exemplu. Ori de câte ori zona client a unei ferestre îşi modifică dimensiunea,
Windows apelează procedura ferestrei respective. Parametrul hwnd al procedurii de fereastră este
variabila handle a ferestrei care şi-a modificat dimensiunea. Parametrul iMsg are valoarea
WM_SIZE. Parametrul wParam poate conţine unul dintre identificatorii SIZENORMAL,
SIZEICONIC, SIZEFULLSCREEN, SIZEZOOMSHOW şi SIZEZOOMHIDE (definiţi în fişierele
antet din Windows cu valori de la 0 la 4). Parametrul wParam arată dacă fereastra a fost redusă la o
pictogramă, a fost mărită la dimensiunea ecranului sau a fost mascată (în urma acoperirii de către o
altă fereastră). Parametrul lParam conţine noua dimensiune a ferestrei. Noua lăţime (o valoare pe 16
biţi) şi noua înălţime (tot o valoare pe 16 biţi) sunt împachetate în parametrul lParam, care este o
valoare pe 32 de biţi. În fişierele antet din Windows este definită o macroinstrucţiune care vă ajută
să extrageţi cele două valori din parametrul lParam. Vom face acest lucru în capitolul următor.
Uneori mesajele generează alte mesaje ca rezultat al prelucrării în funcţia DefWindowProc. De
exemplu, să presupunem că rulaţi programul HELLOWIN şi selectaţi opţiunea Close din meniul
sistem, folosind tastatura sau mouse-ul. Funcţia DefWindowProc prelucrează intrările de la tastatură
sau de la mouse. Atunci când detectează selectarea opţiunii Close, trimite un mesaj
WM_SYSCOMMAND către procedura de fereastră. WndProc retransmite acest mesaj funcţiei
DefWindowProc. Funcţia DefWindowProc răspunde prin trimiterea unui mesaj WM_CLOSE către
procedura de fereastră. WndProc retransmite şi acest mesaj către funcţia DefWindowProc. Funcţia
DefWindowProc răspunde la acest mesaj prin apelarea funcţiei Destroy Window. Funcţia Destroy
Window determină sistemul de operare să trimită un mesaj WM_DESTROY către procedura de
fereastră. WndProc răspunde la acest mesaj apelând funcţia PostQuitMessage, care inserează un
mesaj WM_QUIT în coada de aşteptare. Acest mesaj determină ieşirea din ciclul de tratare a
mesajelor şi închiderea programului.

Mesaje în coada de aşteptare şi în afara acesteia


Am vorbit despre faptul că Windows trimite mesaje către o fereastră, ceea ce înseamnă că Windows
apelează procedura de fereastră. Dar un program Windows are si un ciclu de tratare a mesajelor care
preia mesajele din coada de aşteptare apelând funcţia GetMessage şi le distribuie procedurii de
fereastră apelând funcţia DispatchMessage.

Aşadar, un program Windows interoghează coada de aşteptare (aşa cum un program convenţional
interoghează bufferul de intrare de la tastatură) şi apoi retransmite mesajele în anumite locuri? Sau
primeşte mesajele direct din exterior? Ei bine, sunt folosite ambele metode.

Mesajele pot fi trimise prin coada de aşteptare sau direct. Mesajele trimise prin coada de aşteptare
sunt preluate de program şi sunt distribuite în ciclul de tratare a mesajelor. Mesajele care nu trec prin
coada de aşteptare sunt trimise ferestrei direct, atunci când Windows apelează procedura de
fereastră. Rezultatul este faptul că procedura de fereastră primeşte toate mesajele, indiferent dacă
sunt trimise prin coada de aşteptare sau direct. Din punct de vedere structural, programele Windows
sunt foarte clare, deoarece prelucrarea mesajelor se face centralizat, într-un singur punct. Aşadar,
mesajele preluate de funcţia GetMessage sunt inserate în coada de aşteptare, iar mesajele care nu
trec prin coada de aşteptare sunt trimise procedurii de fereastră.

Mesajele din coada de aşteptare sunt, în principal, rezultatul acţionării tastelor (aşa sunt, de pildă,
WM_KEYDOWN şi WM_KEYUP) sau caractere trimise de la tastatură (WM_CHAR), rezultatul
deplasării mouse-ului (WM_MOUSEMOVE) şi al apăsării butoanelor mouse-ului
(WM_LBUTTONDOWN). Tot în categoria mesajelor inserate în coada de aşteptare intră şi
mesajele trimise de la ceas (WM_TIMER), mesajele de redesenare (WM_PAINT) şi mesajele de
încheiere (WM_QUIT). Mesajele trimise direct sunt cele rezultate din alte evenimente. În multe
cazuri, mesajele trimise direct sunt rezultatul unor mesaje din coada de aşteptare. Atunci când
transmiteţi un mesaj din procedura de fereastră către funcţia DefWindowProc, de multe ori Windows
prelucrează mesajul prin trimiterea altor mesaje către procedura de fereastră.

Evident, acest proces este foarte complex dar, din fericire, de cea mai mare parte răspunde sistemul
de operare, nu programul. Din perspectiva procedurii de fereastră, aceste mesaje sunt recepţionate în
ordine, şi sincronizat. Procedura de fereastră poate să prelucreze mesajele sau să le ignore. Din acest
motiv, este numită uneori „ultimul hop". Prin mesaje, procedura de fereastră este informată privind
aproape toate evenimentele care afectează fereastra.
Mesajele care nu trec prin coada de aşteptare rezultă din apelarea unor funcţii Windows sau din
expedierea în mod explicit a unui mesaj, prin apelarea funcţiei SendMessage. (Mesajele din cealaltă
categorie sunt inserate în coada de aşteptare prin apelarea funcţiei PostMessage.)

De exemplu, atunci când WinMain apelează funcţia CreateWindow, Windows creează fereastra şi
trimite procedurii de fereastră un mesaj WM_CREATE. Atunci când WinMain apelează funcţia
ShowWindow, Windows trimite procedurii de fereastră mesajele WM_SIZE şi
WM_SHOWWINDOW. Atunci când WinMain apelează funcţia UpdateWindow, Windows trimite
procedurii de fereastră un mesaj WM_PAINT.

Mesajele nu seamănă cu întreruperile hardware. În timpul prelucrării unui mesaj într-o procedură de
fereastră, programul nu poate fi întrerupt de un alt mesaj. Procedura de fereastră va prelucra un alt
mesaj înainte de returnarea controlului numai dacă apelează o funcţie care generează un nou mesaj.

Ciclul de mesaje şi procedura de fereastră nu sunt executate simultan. Atunci când procedura de
fereastră prelucrează un mesaj extras din coada de aşteptare, acest mesaj este rezultat în urma
apelării funcţiei DispatchMessage în WinMain. Funcţia DispatchMessage nu returnează controlul
decât după ce procedura de fereastră termină prelucrarea mesajului.

Remarcaţi faptul că procedura de fereastră trebuie să fie reentrantă. Windows trimite deseori funcţiei
WndProc un nou mesaj atunci când aceasta apelează funcţia DefWindowProc pentru mesajul
anterior. În majoritatea cazurilor reentranţa procedurii de fereastră nu este o problemă, dar trebuie să
fiţi conştient de această posibilitate.

De exemplu, să presupunem că în procedura de fereastră stabiliţi valoarea unei variabile în timpul


prelucrării unui mesaj, apoi apelaţi o funcţie Windows. După returnarea din funcţia apelată, puteţi fi
sigur că valoarea variabilei a rămas aceeaşi? Nu este obligatoriu - de exemplu, este posibil ca funcţia
Windows apelată să genereze un alt mesaj, iar procedura de fereastră să modifice valoarea aceleiaşi
variabile în timpul prelucrării acestui mesaj. Acesta este unul dintre motivele pentru care unele
forme de optimizare ale compilatorului trebuie să fie dezactivate atunci când compilaţi programe
Windows.

În multe situaţii, procedura de fereastră poate să reţină anumite informaţii în timpul prelucrării unor
mesaje şi să le folosească pentru prelucrarea altor mesaje. Aceste informaţii trebuie să fie stocate fie
în variabile statice definite în procedura de fereastră, fie în variabile globale.

Desigur, veţi înţelege mai bine toate aceste lucruri în următoarele capitole, pe măsură ce vom
extinde procedurile de fereastră astfel încât să prelucreze şi alte mesaje.

Nu fiţi egoist!
Windows 95 este un mediu care asigură multitaskingul controlat. Aceasta înseamnă că atunci când
un program execută o operaţie de durată, sistemul de operare permite utilizatorului să treacă în alt
program. Acesta este un lucru bun şi este unul dintre avantajele oferite de Windows 95 faţă de
versiunile Windows bazate pe MS-DOS.

Totuşi, datorită structurii Windows, multitaskingul controlat nu funcţionează întotdeauna aşa cum ar
trebui. De exemplu, să presupunem că programul are nevoie de mai multe minute pentru prelucrarea
unui mesaj. Este adevărat că utilizatorul poate trece într-un alt program, dar nu poate face nimic cu
programul respectiv. Utilizatorul nu poate să mute fereastra programului, să o redimensioneze sau să
o închidă. Nu poate face absolut nimic, deoarece aceste operaţii trebuie să fie executate de procedura
de fereastră, iar procedura de fereastră este ocupată cu o operaţie de durată, chiar dacă nu ea pare a fi
cea care execută operaţiile de mutare şi de redimensionare. Aceste operaţii sunt executate de funcţia
DefWindowProc, care trebuie considerată o parte a procedurii de fereastră.

Dacă programul trebuie să execute o operaţie de durată în timpul prelucrării unui anumit mesaj,
puteţi să faceţi acest lucru şi într-un mod mai politicos, aşa cum vom vedea în Capitolul 14. Chiar şi
într-un mediu cu multitasking controlat, nu este o idee prea bună să blocaţi o fereastră pe ecran.
Acest lucru deranjează utilizatorii şi îi face să gândească lucruri urâte despre programul
dumneavoastră.

 
Ciclul de învăţare
Aşa cum v-aţi dat seama din acest capitol, programarea sub Windows este foarte diferită de
programarea pentru un mediu convenţional, cum ar fi MS-DOS, şi nu pretinde nimeni că
programarea sub Windows este uşoară.

Atunci când am început să învăţ programarea sub Windows, am vrut să fac ceea ce făceam de obicei
pentru a învăţa un nou sistem de operare sau un nou limbaj de programare - să scriu un program
simplu, care să afişeze conţinutul hexazecimal al unui fişier. Sub MS-DOS, un astfel de program
implică lucrul de la linia de comandă, operaţii rudimentare de intrare/ieşire cu fişiere şi formatarea
rezultatelor pe ecran. Programul de afişare sub Windows s-a dovedit a fi ceva monstruos. A trebuit
să învăţ o mulţime de lucruri despre meniuri, casete de dialog, bare de derulare şi altele. Desigur,
acest prim efort de învăţare care se impunea nu era un punct în favoarea programării sub Windows.

Cu toate acestea, odată terminat, programul de afişare hexazecimală nu semăna cu nici unul dintre
programele pe care le scrisesem până atunci în acelaşi scop. În loc să obţină numele fişierului din
linia de comandă, WINDUMP (acesta era numele pe care l-am dat programului) folosea o casetă de
dialog în care erau prezentate toate fişierele din directorul curent, în loc să scrie rezultatele pe ecran
după modelul unui teleimprimator, WINDUMP avea bare de derulare ce permiteau deplasarea în
orice parte a fişierului. Ca un avantaj suplimentar, puteam să rulez simultan două copii ale
programului WINDUMP, ceea ce îmi permitea să compar două fişiere afişate în două ferestre
alăturate. Pe scurt, WINDUMP a fost primul program de afişare hexazecimală de care am fost cu
adevărat mândru.

Trebuie să vă puneţi următoarea întrebare: doresc să folosesc o interfaţă modernă şi eficientă, care să
includă meniuri, casete de dialog, bare de derulare şi elemente grafice? Dacă răspunsul este
afirmativ, trebuie să vă puneţi o altă întrebare: doresc să scriu chiar eu codul pentru aceste meniuri,
casete de dialog, bare de derulare şi elemente grafice? Sau voi profita de faptul că acest cod există
deja în Windows? Cu alte cuvinte, ce este mai uşor - să învăţaţi să folosiţi 1000 de funcţii, sau să le
scrieţi chiar dumneavoastră? Este mai uşor să vă orientaţi gândirea către arhitectura bazată pe
mesaje a sistemului de operare Windows, sau preferaţi să luptaţi cu aceasta folosind diverse surse de
intrare, după modelul tradiţional?
Dacă doriţi să scrieţi codul unei interfeţe proprii, mai bine închideţi această carte şi apucaţi-vă de
treabă. În acest timp, noi ceilalţi vom învăţa cum să afişăm şi să derulăm textul într-o fereastră.

* Acest tip de multitasking se mai numeşte şi „multitasking cooperativ", (n.t)

* Într-un program C funcţiile pot fi declarate înainte de a fi definite. Declaraţia unei funcţii
precizează tipul parametrilor primiţi de funcţie şi tipul valorii returnate de aceasta, fără să definească
şi corpul acesteia. Acest lucru este necesar atunci când funcţia este indicată înainte de a fi definită,
dar uşurează şi citirea programului, (n.t.)

Afișarea textului
Desenarea şi redesenarea

Sub MS-DOS, un program care foloseşte tot ecranul (full-screen mode) poate să scrie în orice parte
a acestuia. Elementele afişate rămân pe ecran şi nu dispar în mod misterios. Programul poate să
renunţe apoi la informaţiile necesare pentru redesenarea ecranului. Dacă un alt program (cum ar fi
programele rezidente în memorie) afişează ceva peste o parte a ecranului, atunci programul respectiv
trebuie să-şi refacă ecranul.

În Windows nu puteţi să afişaţi decât în zona client a ferestrei şi nu puteţi să presupuneţi că ceea ce
afişaţi în zona client va rămâne acolo pană când programul afişează explicit altceva. De exemplu, o
casetă de dialog a unei alte aplicaţii poate să se suprapună peste o parte a zonei client a programului
dumneavoastră. Deşi sistemul de operare încearcă să salveze şi să refacă zona de ecran de sub caseta
de dialog, uneori nu poate face acest lucru. După ce caseta de dialog va fi închisă, Windows va cere
programului să redeseneze porţiunea afectată din zona client.

Windows este un sistem de operare bazat pe mesaje. El informează aplicaţiile privind apariţia
evenimentelor, prin trimiterea unor mesaje în coada de mesaje a aplicaţiei, sau prin trimiterea unor
mesaje către procedurile de fereastră corespunzătoare. De pildă, Windows informează o procedură
de fereastră că o parte a zonei client trebuie să fie reactualizată prin trimiterea unui mesaj
WM_PAINT.

Mesajul WM_PAINT

Majoritatea programelor Windows apelează funcţia UpdateWindow în timpul procesului de


iniţializare din procedura WinMain, chiar înainte de intrarea în ciclul de tratare a mesajelor.
Windows profită de această ocazie ca să trimită către procedura ferestrei primul mesaj WM_PAINT.
Acest mesaj informează procedura ferestrei că zona client este pregătită pentru desen. Din acest
moment, procedura ferestrei ar trebui să fie pregătită să prelucreze orice mesaj WM_PAINT în
următoarele situaţii:
·     O zonă anterior acoperită a ferestrei este adusă la suprafaţă atunci când utilizatorul mută o
fereastră.

·     Utilizatorul redimensionează fereastra (dacă stilul clasei ferestrei include seturile de biţi
CS_HREDRAW si CS_VREDRAW).

·     Programul foloseşte funcţiile ScrollWindow sau ScrollDC ca să deruleze o parte din zona client
a ferestrei.

·     Programul foloseşte funcţiile InvalidateRect sau InvalidateRgn pentru a genera în mod explicit
un mesaj WM_PAINT.

În anumite cazuri, când zona client este acoperită parţial cu text, Windows încearcă să salveze o
zonă a ecranului, pe care o va restaura mai târziu. Această metodă, însă, nu dă întotdeauna rezultate
bune. Windows poate trimite, de aceea, un mesaj WM_PAINT, în situaţiile în care:

·                    Windows a şters o casetă de dialog sau casetă de mesaje care acoperea o parte a
ferestrei.

·                    Un meniu este tras în jos şi apoi eliberat.

Windows salvează zona de ecran pe care a scris şi apoi o restaurează, în cazurile în care:

·                    Indicatorul mouse-ului este mişcat în zona client.

·                     O pictogramă este trasă în zona client.

Tratarea mesajelor WM_PAINT implică revizuirea modului de scriere pe ecran. Programul trebuie
structurat astfel încât să acumuleze toate informaţiile necesare pentru redesenarea zonei client, dar să
facă această operaţie numai „la cerere" - atunci când Windows îi trimite un mesaj WM_PAINT.
Dacă programul trebuie să actualizeze zona client, poate forţa sistemul de operare să îi trimită un
mesaj WM_PAINT. Deşi această metodă de afişare pare ocolitoare, ea contribuie la structurarea
programului.

Dreptunghiuri valide şi invalide


Deşi procedura unei ferestre trebuie să fie pregătită să actualizeze întreaga zonă client a ferestrei
atunci când primeşte mesajul WM_PAINT, deseori este necesară numai reactualizarea unei porţiuni
mai mici (de cele mai multe ori o suprafaţă dreptunghiulară din zona client). O astfel de situaţie
apare atunci când o parte a zonei client este acoperită de o casetă de dialog. Redesenarea este
necesară numai pentru zona dreptunghiulară adusă la suprafaţă după închiderea casetei de dialog.

Această zonă este numită „regiune invalidă" („invalid region") sau „regiune de actualizare" („update
region"). Prezenţa unei regiuni invalide în cadrul zonei client determină sistemul de operare să
plaseze un mesaj WM_PAINT în coada de aşteptare a aplicaţiei. Procedura de fereastră a unui
program recepţionează un mesaj WM_PAINT numai dacă o parte a zonei client a ferestrei este
invalidă.
Windows păstrează în interior o „structură cu informaţii pentru desenare" (paint information
structure) pentru fiecare fereastră. Această structură conţine (printre alte informaţii) coordonatele
celui mai mic dreptunghi în care se încadrează regiunea invalidă. Acesta este cunoscut sub numele
de „dreptunghi invalid", dar uneori este numit tot „regiune invalidă". Dacă o altă regiune a zonei
client devine invalidă înainte ca mesajul WM_PAINT să fie prelucrat, Windows calculează un nou
dreptunghi invalid care cuprinde ambele regiuni şi stochează informaţiile actualizate în structura de
informaţii pentru desenare, fără să plaseze un nou mesaj WM_PAINT în coada de aşteptare a
aplicaţiei.

Procedura unei ferestre poate să invalideze un dreptunghi din zona client proprie prin apelarea
funcţiei InvalidateRect. Dacă în coada de aşteptare există deja un mesaj WM_PAINT, Windows
calculează un nou dreptunghi invalid. În caz contrar, plasează în coada de aşteptare un nou mesaj
WM_PAINT. La recepţionarea mesajului WM_PAINT, procedura ferestrei poate obţine
coordonatele dreptunghiului invalid (aşa cum vom vedea ceva mai târziu în acest capitol). De
asemenea, poate obţine aceste coordonate în orice alt moment, apelând funcţia GetUpdateRect.

După ce procedura de fereastră apelează funcţia BeginPaint în timpul prelucrării mesajului


WM_PAINT, întreaga zonă client este validată. De asemenea, programul poate să valideze orice
porţiune dreptunghiulară din zona client, apelând funcţia ValidateRect. Dacă în urma acestui apel
întreaga zonă invalidă este validată, toate mesajele WM_PAINT aflate în coada de aşteptare sunt
şterse.

Interfaţa GDI

Pentru desenarea zonei client a ferestrei folosiţi funcţiile din interfaţa Windows pentru dispozitivele
grafice (GDI - Graphics Device Interface). (Vom face o prezentare generală a interfeţei GDI în
capitolul următor.) Aţi întâlnit deja funcţia DrawText în Capitolul 2, dar cea mai cunoscută funcţie
este TextOut. Această funcţie are următorul format:

TextOut (hdc, x, y, psString, iLength) ;

Funcţia TextOut afişează pe ecran un şir de caractere. Parametrul psString este un pointer la şirul de
caractere iar iLength este lungimea acestui şir, în caractere. Parametrii x şi y definesc poziţia de
început a şirului de caractere. (Vom prezenta în curând mai multe detalii.) Parametrul hdc este o
variabilă handle a contextului de dispozitiv şi reprezintă o parte importantă a interfeţei GDI. De fapt,
toate funcţiile GDI au nevoie de acest parametru la apelare.

Contextul de dispozitiv
O variabilă handle, aşa cum am mai spus, este pur şi simplu un număr pe care Windows îl foloseşte
pentru indicarea unui obiect. Puteţi să obţineţi această variabilă din Windows şi apoi să o folosiţi în
alte funcţii. Variabila handle a contextului de dispozitiv este calea de acces a ferestrei
dumneavoastră la funcţiile GDI. Folosind această variabilă sunteţi liber să desenaţi zona client a
ferestrei şi să o faceţi aşa cum doriţi.

Contextul de dispozitiv (prescurtat DC - device context) este o structură de date întreţinută intern de
interfaţa GDI. Fiecare context de dispozitiv este asociat unui anumit dispozitiv de afişare, cum ar fi
imprimanta, plotterul sau monitorul video. În cazul monitoarelor video, un context de dispozitiv este
de obicei asociat unei anumite ferestre de pe ecran.

O parte dintre valorile din contextul de dispozitiv sunt atribute grafice. Aceste atribute definesc
unele particularităţi privind modul de lucru al unor funcţii de desenare din interfaţa GDI. În cazul
funcţiei TextOut, de exemplu, atributele contextului de dispozitiv determină culoarea textului,
culoarea fondului, modul de mapare a coordonatelor x şi y în zona client a ferestrei şi fontul folosit
de Windows pentru afişarea textului.

Atunci când vrea să deseneze, programul trebuie să obţină mai întâi o variabilă handle a unui
context de dispozitiv. După terminarea operaţiilor de desenare, programul ar trebui să elibereze
variabila. După eliberarea variabilei handle, aceasta nu mai este validă şi, deci, nu mai poate fi
folosită. Programul trebuie să obţină şi să elibereze variabila handle în timpul prelucrării unui singur
mesaj. Cu excepţia contextelor de dispozitiv create cu funcţia CreateDC (despre care nu vom
discuta în acest capitol) este recomandat să nu păstraţi variabilele handle ale contextelor de
dispozitiv de la un mesaj la altul.

În general, aplicaţiile Windows folosesc două metode pentru obţinerea variabilelor handle ale
contextelor de dispozitiv, atunci când se pregătesc pentru desenarea ecranului.

Obţinerea unei variabile handle a contextului de dispozitiv:


prima metodă
Această metodă este folosită în timpul prelucrării mesajelor WM_PAINT. Sunt implicate două
funcţii: BeginPaint şi EndPaint. Aceste funcţii au nevoie de variabila handle a ferestrei (transmisă
procedurii de fereastră ca parametru) şi de adresa unei variabile de tipul PAINTSTRUCT. De obicei,
programatorii Windows numesc această variabilă ps şi o definesc în procedura de fereastră astfel:

PAINTSTRUCT                       ps;

În timpul prelucrării mesajului WM_PAINT, procedura de fereastră apelează mai întâi funcţia
BeginPaint ca să completeze câmpurile structurii ps. Valoarea returnată de funcţia BeginPaint este
variabila handle a contextului de dispozitiv. În general, aceasta este salvată într-o variabilă numită
hdc. În prcedura de fereastră definiţi această variabilă astfel:

HDC                 hdc;

Tipul de date HDC este definit ca un întreg fără semn, pe 32 de biţi. Programul poate apoi să
folosească funcţii GDI, cum ar fi TextOut. Apelul funcţiei EndPaint eliberează variabila handle a
contextului de dispozitiv.

În general, prelucrarea mesajului WM_PAINT se face astfel:

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

[apelarea unor funcţii GDI]


EndPaint (hwnd, &ps) ;

return 0 ;

În timpul prelucrării mesajului WM_PAINT, procedura de fereastră trebuie să apeleze funcţiile


BeginPaint şi EndPaint. Dacă o procedură de fereastră nu prelucrează mesajele WM_PAINT,
trebuie să le retransmită procedurii DefWindowProc (procedura de fereastră prestabilită).

DefWindowProc prelucrează mesajele WM_PAINT în următoarea secvenţă de cod:

case WM_PAINT:

BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

return 0 ;

Apelarea în secvenţă a funcţiilor BeginPaint şi EndPaint fără nici o altă instrucţiune intermediară nu
face decât să valideze regiunea invalidată anterior. Aşadar, nu procedaţi astfel:

case WM_PAINT:

return 0 ;     // GREŞIT !!!

Windows plasează un mesaj WM_PAINT în coada de aşteptare, deoarece o parte a zonei client este
invalidă. Dacă nu apelaţi funcţiile BeginPaint şi EndPaint (sau ValidateRect) Windows nu validează
zona de fereastră respectivă, ci vă trimite în continuare mesaje WM_PAINT.

Structura de informaţii pentru desenare

Am vorbit anterior despre „structura de informaţii pentru desenare" („paint information structure")
păstrată de Windows pentru fiecare fereastră. Această structură este definită astfel:

typedef struct tagPAINTSTRUCT

HDC       hdc;

BOOL      fErase;

RECT      rcPaint;

BOOL      fRestore;

BOOL      fIncUpdate;
BYTE      rgbReserved[32];

PAINTSTRCUT;

Windows completează câmpurile acestei structuri atunci când programul dumneavoastră apelează
funcţia BeginPaint. Programul poate să folosească numai primele trei câmpuri, celelalte fiind
folosite intern de sistemul de operare.

Câmpul hdc reprezintă variabila handle a contextului de dispozitiv. Deoarece redundanţa este tipică
pentru sistemul de operare Windows, valoarea returnată de funcţia BeginPaint este aceeaşi variabilă
handle.

În majoritatea cazurilor, câmpul fErase va avea valoarea TRUE (diferită de zero), ceea ce înseamnă
că Windows a şters fondul dreptunghiului invalid. Pentru ştergerea fondului, Windows foloseşte
pensula specificată în câmpul hbrBackground al structurii WNDCLASSEX, pe care aţi folosit-o la
înregistrarea clasei în timpul iniţializărilor, din funcţia WinMain. Multe programe Windows folosesc
o pensulă de culoare albă:

wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

Totuşi, dacă programul invalidează un dreptunghi din zona client apelând funcţia InvalidateRect,
ultimul parametru al funcţiei specifică dacă vreţi ca fondul să fie şters. Dacă acest parametru este
FALSE (0), Windows nu va şterge fondul şi câmpul fErase va avea valoarea FALSE.

Câmpul rcPaint al structurii PAINTSTRUCT este o structură de tip RECT. Aşa cum aţi aflat din
Capitolul 2, structura RECT defineşte un dreptunghi. Cele patru câmpuri ale structurii sunt left, top,
right şi bottom. Câmpul rcPaint al structurii PAINTSTRUCT defineşte limitele unui dreptunghi
invalid, aşa cum se poate vedea în figura 3.1. Valorile sunt date în pixeli, şi se raportează la colţul
din stânga-sus al zonei client a ferestrei. Dreptunghiul invalid este suprafaţa pe care ar trebui să o
redesenaţi. Deşi un program Windows ar putea să redeseneze întreaga zonă client a ferestrei de
fiecare dată când primeşte un mesaj WM_PAINT, redesenând numai porţiunea ferestrei definită de
dreptunghi programul economiseşte timp.

Dreptunghiul rcPaint din structura PAINTSTRUCT nu este un simplu dreptunghi invalid, ci un


dreptunghi „de decupare" (clipping rectangle). Aceasta înseamnă că Windows restricţionează
desenarea în interiorul dreptunghiului. (Mai precis, dacă regiunea invalidă nu este dreptunghiulară,
Windows restricţionează desenarea numai la regiunea respectivă.) Atunci când folosiţi variabila
handle a contextului de dispozitiv din structura PAINTSTRUCT, Windows nu desenează în afara
dreptunghiului rcPaint.

 
Figura 3-1. Limitele unui dreptunghi invalid.

Pentru desenarea în afara dreptunghiului rcPaint în timpul prelucrării mesajului WM_PAINT, faceţi
următorul apel:   InvalidateRect (hWnd, NULL, TRUE); înaintea apelării funcţiei BeginPaint.
Apelul de mai sus invalidează întreaga zonă client şi şterge fondul acesteia. Dacă ultimul parametru
are valoarea FALSE, fondul nu este şters şi desenul va fi făcut peste ceea ce există deja.

În programul HELLOWIN prezentat în Capitolul 2 nu am ţinut seama de dreptunghiurile invalide


sau de dreptunghiurile de decupare în timpul prelucrării mesajului WM_PAINT. Dacă zona în care
era afişat textul se afla în dreptunghiul invalid, funcţia DrawText o refăcea. Dacă nu, în timpul
prelucrării apelului DrawText, Windows determina faptul că pe ecran nu trebuie să afişeze nimic.
Dar aceste operaţii de determinare durează. Un programator interesat de performanţă şi viteză va
dori să folosească dimensiunile dreptunghiului invalid în timpul prelucrării mesajului WM_PAINT,
astfel încât să evite apelarea inutilă a unor funcţii GDI.

Obţinerea unei variabile handle a contextului de dispozitiv: a doua metodă

Puteţi să obţineţi o variabilă handle a unui context de dispozitiv şi în timpul prelucrării altor mesaje
decât WM_PAINT, sau atunci când aveţi nevoie de variabila handle a contextului în alte scopuri,
cum ar fi obţinerea unor informaţii despre contextul de dispozitiv. Apelaţi funcţia GetDC pentru a
obţine variabila handle şi apoi apelaţi funcţia ReleaseDC atunci când nu mai aveţi nevoie de aceasta:
hdc = GetDC(hwnd) ;

[apelarea unor funcţii GDI]

ReleaseDC(hwnd, hdc) ;

La fel ca funcţiile BeginPaint şi EndPaint, şi funcţiile GetDC şi ReleaseDC ar trebui apelate în


pereche. Atunci când apelaţi funcţia GetDC în timpul prelucrării unui mesaj, este recomandat să
apelaţi funcţia ReleaseDC înainte de a ieşi din procedura de fereastră. Nu apelaţi funcţia GetDC ca
răspuns la un mesaj şi funcţia ReleaseDC ca răspuns la un alt mesaj.

Spre deosebire de variabila handle a contextului de dispozitiv obţinută din structura


PAINTSTRUCT, variabila handle returnată de funcţia GetDC se referă la un dreptunghi cu întreaga
zonă client a ferestrei. Puteţi să desenaţi în orice parte a zonei client, nu doar în dreptunghiul invalid
(dacă există un dreptunghi invalid). Spre deosebire de funcţia BeginPaint, GetDC nu validează nici
o regiune invalidă.

În general, folosiţi funcţiile GetDC şi ReleaseDC ca răspuns la mesajele de la tastatură (de exemplu,
într-un procesor de texte) sau la mesajele de la mouse (de exemplu, într-un program pentru desen).
În acest fel, programul poate să actualizeze zona client ca reacţie la informaţiile introduse de
utilizator de la tastatură sau cu ajutorul mouse-ului, fără să invalideze în mod deliberat o zonă a
ferestrei pentru generarea unui mesaj WM_PAINT. Totuşi, programul trebuie să acumuleze
suficiente informaţii pentru a putea să reactualizeze ecranul ori de câte ori primeşte un mesaj
WM_PAINT.

Funcţia TextOut: detalii

Atunci când obţineţi o variabilă handle a contextului de dispozitiv, Windows completează structura
contextului de dispozitiv, păstrată intern, cu valorile prestabilite. Aşa cum veţi vedea în capitolele
următoare, puteţi să modificaţi valorile prestabilite cu ajutorul funcţiilor GDI. Funcţia GDI care ne
interesează în acest moment este TextOut:

TextOut (hdc, x, y, psString, iLength) ;

Haideţi să examinăm în detaliu această funcţie.

Primul parametru este o variabilă handle a contextului de dispozitiv - valoarea hdc returnată de
funcţia GetDC sau valoarea hdc returnată de funcţia BeginPaint în timpul prelucrării mesajului
WM_PAINT.

Atributele contextului de dispozitiv controlează caracteristicile textului afişat. De exemplu, unul


dintre atributele contextului de dispozitiv stabileşte culoarea textului. Culoarea prestabilită este
negru. De asemenea, contextul prestabilit de dispozitiv stabileşte ca fondul să fie alb. Atunci când
un program afişează un text pe ecran, Windows foloseşte această culoare de fond ca să umple spaţiul
dreptunghiular care înconjoară fiecare caracter, spaţiu numit „casetă caracter" („character box").

Fondul textului nu este acelaşi lucru cu fondul pe care îl stabiliţi atunci când definiţi clasa ferestrei.
Fondul din clasa ferestrei este o pensulă - adică un model care poate să fie sau să nu fie o culoare
pură - pe care sistemul de operare Windows o foloseşte ca să şteargă zona client. La definirea
structurii de clasă a ferestrei, multe aplicaţii Windows folosesc identificatorul WHITE_BRUSH,
astfel încât culoarea de fond din contextul de dispozitiv prestabilit să fie aceeaşi cu culoarea pensulei
pe care Windows o foloseşte pentru ştergerea zonei client.

Parametrul psString este un pointer la un şir de caractere, iar iLength este lungimea acestui şir de
caractere, adică numărul de caractere conţinut de şir. Şirul de caractere nu poate conţine caractere
ASCII de control, cum ar fi CR (carriage return - retur de car), LF (line feed - salt la linie nouă), tab
sau backspace. Windows afişează aceste caractere ca blocuri negre sau mici casete. Funcţia TextOut
nu recunoaşte caracterul 0 ca terminator de şir si are nevoie de parametrul iLength pentru precizarea
lungimii şirului de caractere.

Valorile x şi y din apelul funcţiei TextOut definesc începutul şirului de caractere în zona client a
ferestrei. Valoarea x indică poziţia pe orizontală, iar valoarea y indică poziţia pe verticală. Colţul din
stânga-sus al primului caracter se află în poziţia de coordonate (x,y). În contextul de dispozitiv
prestabilit, originea sistemului de coordonate, adică punctul în care x şi y au valoarea 0, se află în
colţul din stânga-sus al zonei client. Dacă în funcţia TextOut folosiţi valoarea 0 pentru parametrii x
şi y, şirul de caractere este afişat începând din colţul stânga-sus al zonei client. În documentaţie,
coordonatele GDI sunt numite „coordonate logice". Vom vorbi mai mult despre semnificaţia acestui
termen în capitolul următor. Pentru moment, reţineţi că în Windows există diferite moduri de
mapare care controlează transformarea coordonatelor logice transmise funcţiilor GDI în coordonate
fizice ale pixelilor afişaţi pe ecran. Modul de mapare este definit în contextul de dispozitiv. Modul
de mapare prestabilit este MM_TEXT (folosind identificatorul definit în fişierele antet Windows). În
modul de mapare MM_TEXT, unităţile logice sunt aceleaşi cu unităţile fizice, adică pixelii; ele se
raportează la colţul din stânga-sus al zonei client, iar valorile coordonatei y cresc pe măsură ce
coborâţi în zona client a ferestrei (vezi Figura 3.2). Sistemul de coordonate MM_TEXT este acelaşi
cu sistemul de coordonate folosit de Windows pentru definirea dreptunghiului invalid din structura
PAINTSTRUCT. Foarte convenabil. (Nu la fel se întâmplă însă dacă folosiţi alte moduri de
mapare.)
Figura 3.2. Coordonatele x şi y în modul de mapare MM_TEXT.

Contextul de dispozitiv defineşte şi o regiune de decupare (clipping region). Aşa cum aţi văzut,
regiunea prestabilită de decupare este întreaga zonă client, pentru o variabilă handle a contextului de
dispozitiv obţinută prin apelarea funcţiei GetDC, sau numai regiunea invalidă, pentru o variabilă
handle a contextului de dispozitiv obţinută prin apelarea funcţiei BeginPaint. Windows nu afişează
partea care se află în afara regiunii de decupare şi care aparţine şirului de caractere, ci numai
porţiunile care sunt cuprinse în regiunea de decupare. Scrierea în afara zonei client a unei ferestre
este o operaţiune dificilă, aşa că nu vă speriaţi - nu este posibil să faceţi acest lucru din greşeală.

Fontul sistem

Tot în contextul de dispozitiv este definit şi fontul pe care sistemul de operare Windows îl foloseşte
pentru scrierea textului în zona client. Fontul prestabilit este numit „font sistem" sau (folosind
identificatorul definit în fişierele antet Windows) SYSTEM_FONT. Fontul sistem este fontul pe
care Windows îl foloseşte pentru textul din barele de titlu, barele de meniu şi casetele de dialog.

La începuturile sistemului de operare Windows, fontul sistem era un font cu dimensiune fixă, ceea
ce înseamnă că toate caracterele aveau aceeaşi lăţime, ca la maşinile de scris. Începând cu versiunea
Windows 3.0 şi continuând pană la Windows 95, fontul sistem este un font cu dimensiune variabilă,
ceea ce înseamnă că fiecare caracter are o altă dimensiune. De exemplu, „W" este mai lat decât „i".
Este foarte clar că un text scris cu un font având dimensiune variabilă este mai uşor de citit decât un
font cu dimensiune fixă. Aşa cum vă puteţi imagina, însă, această schimbare a dat peste cap multe
programe Windows scrise pentru primele versiuni şi programatorii au fost nevoiţi să înveţe noi
tehnici de lucru cu text.

Fontul sistem este un font de tip „rastru", ceea ce înseamnă că fiecare caracter este definit ca un bloc
de pixeli. Versiunile Windows aflate pe piaţă includ mai multe fonturi sistem, de diferite
dimensiuni, folosite pentru diferite tipuri de plăci video. O firmă care produce un nou driver de
afişare trebuie să creeze şi fontul sistem potrivit cu rezoluţia de afişare respectivă. O altă soluţie este
ca producătorul să specifice unul dintre fonturile sistem furnizate împreună cu sistemul de operare
Windows. Fontul sistem trebuie proiectat astfel încât pe ecran să încapă cel puţin 25 de linii cu câte
80 de caractere. Aceasta este singura garanţie privind compatibilitatea între dimensiunea ecranului şi
dimensiunea fontului.

Dimensiunea unui caracter

Pentru afişarea mai multor linii de text cu ajutorul funcţiei TextOut trebuie să determinaţi
dimensiunile caracterelor din fontul folosit. Puteţi să stabiliţi spaţiul dintre liniile succesive de text
pe baza înălţimii unui caracter şi spaţiul dintre coloane pe baza lăţimii medii a caracterelor din font.

Dimensiunile caracterelor sunt obţinute prin apelarea funcţiei GetTextMetrics. Funcţia


GetTextMetrics are nevoie de o variabilă handle a contextului de dispozitiv, deoarece returnează
informaţii despre fontul selectat în contextul de dispozitiv. Windows copiază valorile referitoare la
dimensiunile caracterelor într-o structură de tip TEXTMETRIC. Valorile sunt exprimate în unităţi
de măsură care depind de modul de mapare selectat în contextul de dispozitiv. În contextul
prestabilit de dispozitiv, modul de mapare este MM_TEXT, aşa că dimensiunile sunt date în pixeli.

Pentru folosirea funcţiei GetTextMetrics trebuie să definiţi mai întâi o variabilă de tipul
TEXTMETRIC (numită, de obicei, tm):

TEXTMETRIC    tm;

În continuare obţineţi o variabilă handle a contextului de dispozitiv şi apelaţi funcţia


GetTextMetrics:

hdc = GetDC(hwnd);

GetTextMetrics(hdc, &tm) ;

ReleaseDC(hwnd, hdc);

Apoi puteţi să examinaţi valorile din structura de dimensiuni a textului şi, dacă doriţi, să salvaţi
unele dintre aceste dimensiuni pentru utilizarea în viitor.

Dimensiunile textului: detalii

Structura TEXTMETRIC furnizează o mulţime de informaţii despre fontul curent selectat în


contextul de dispozitiv. Totuşi, aşa cum puteţi vedea în Figura 3-3, dimensiunea verticală a unui font
este definită de numai cinci valori.
Figura 3-3. Cele cinci valori care definesc dimensiunea verticala a unui caracter.

Acestea sunt uşor de înţeles. Valoarea tmInternalLeading se referă la spaţiul păstrat deasupra unui
caracter pentru semnele de accentuare. Dacă această valoare este zero, literele mari cu accent sunt
afişate ceva mai mici, astfel încât accentul să încapă în partea de sus a caracterului. Valoarea
tmExternalLeading se referă la spaţiul recomandat de către proiectantul fontului a fi lăsat între
rândurile de text. Puteţi să acceptaţi sau nu această recomandare atunci când stabiliţi distanţa dintre
rândurile de text.

Structura TEXTMETRIC conţine două câmpuri care descriu lăţimea unui caracter:

tmAveCharWidth (lăţimea medie a literelor mici) şi tmMaxCharWidth (lăţimea celui mai mare
caracter al fontului). Pentru fonturile cu dimensiune fixă cele două valori sunt egale.

În exemplul de programe din acest capitol am avut nevoie şi de o altă dimensiune - lăţimea medie a
majusculelor. O valoare destul de precisă poate fi obţinută calculând 150% din valoarea
tmAveCharWidth.

Este important să reţineţi faptul că dimensiunile fontului sistem depind de rezoluţia ecranului pe
care rulează sistemul de operare Windows. Windows furnizează o interfaţă grafică independentă de
dispozitiv, dar este nevoie de un mic efort şi din partea dumneavoastră. Nu scrieţi programe
Windows care se bazează pe ghicirea dimensiunilor unui caracter. Nu introduceţi în cod valori fixe.
Folosiţi funcţia GetTextMetrics ca să obţineţi valorile de care aveţi nevoie.
Fomatarea textului
Deoarece dimensiunile fontului sistem nu se modifică în timpul unei sesiuni Windows, trebuie să
apelaţi funcţia GetTextMetrics o singură dată după lansarea în execuţie a programului. Un loc
potrivit pentru acest apel este codul de prelucrare a mesajului WM_CREATE din procedura de
fereastră. Mesajul WM_CREATE este primul mesaj pe care îl primeşte procedura de fereastră.
Windows trimite un mesaj WM_CREATE procedurii de fereastră atunci când apelaţi funcţia
CreateWindow din funcţia WinMain.

Să presupunem că scrieţi un program Windows care afişează mai multe linii de text una sub alta, în
zona client a ferestrei. Trebuie să obţineţi valorile pentru înălţimea şi lăţimea caracterelor. În
procedura de fereastră puteţi să definiţi două variabile în care să salvaţi lăţimea medie (cxChar) şi
înălţimea totală a caracterelor (cyChar):

static int cxChar, cyChar ;

Prefixul c adăugat la numele variabilelor provine de la „contor" şi în combinaţie cu x sau y se referă


la dimensiunea pe orizontală sau pe verticală a caracterelor. Aceste variabile sunt declarate ca
statice deoarece trebuie să fie valide atunci când procedura de fereastră prelucrează alte mesaje (cum
ar fi WM_PAINT). Dacă variabilele sunt declarate ca globale în alte funcţii nu mai este necesar să
fie declarate statice. Iată codul de prelucrare a mesajului WM_CREATE:

case WH_CREATE:

hdc = GetDC (hwnd);

GetTextMetrics (hdc, &tm);

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC (hwnd, hdc);

return 0;

Dacă nu vreţi să folosiţi spaţiul suplimentar extern pentru spaţierea liniilor de text, puteţi să utilizaţi
instrucţiunea:

cyChar = tm.tmHeight;

Dumneavoastră stabiliţi modul în care folosiţi dimensiunile caracterelor pentru calcularea


coordonatelor de afişare. O metodă simplă este să lăsaţi o margine de dimensiunea cyChar în partea
de sus a zonei client şi una de mărimea cxChar în partea stângă. Pentru afişarea mai multor linii de
text aliniate la stânga, folosiţi valoarea cxChar pentru parametrul care reprezintă coordonata pe axa
x în apelul funcţiei TextOut. Valoarea coordonatei pe axa y la apelarea funcţiei TextOut este:

cyChar * (1 + i);
unde i reprezintă numărul liniei, începând de la 0.

Deseori este necesar să afişaţi atât numere formatate, cat şi şiruri simple de caractere. Dacă aţi scris
programe DOS folosind funcţiile standard C de bibliotecă, probabil aţi folosit pentru formatarea
numerelor funcţia printf. În Windows nu puteţi să folosiţi funcţia printf, deoarece ea determină
afişarea la dispozitivul standard de ieşire, acesta fiind un concept care în Windows nu are nici un
sens.

În schimb, puteţi să folosiţi funcţia sprintf. Funcţia sprintf lucrează la fel ca şi funcţia printf, cu
excepţia faptului că şirul de caractere formatat este stocat într-o matrice de caractere. Apoi puteţi
utiliza funcţia TextOut pentru a scrie şirul ce trebuie afişat. Funcţia sprintf returnează lungimea
şirului de caractere, ceea ce este foarte convenabil - puteţi să transmiteţi valoarea returnată în locul
parametrului iLength din apelul funcţiei TextOut. Secvenţa de cod de mai jos prezintă o combinaţie
tipică a funcţiilor TextOut şi sprintf:

int iLenght;

char szBuffer [40];

[alte Iinii de program]

iLenght = sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB);

TextOut (hdc, x, y, szBuffer, iLength);

Pentru operaţiile simple de afişare precum cea de mai sus puteţi să renunţaţi la definirea variabilei
iLength şi să combinaţi cele două instrucţiuni rămase într-una singură:

TextOut (hdc, x, y, szBuffer, sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB));

Nu arată prea frumos, dar funcţionează.

Dacă nu trebuie să afişaţi numere în virgulă mobilă, puteţi folosi funcţia wsprintf în locul funcţiei
sprintf. Funcţia wsprintf are aceeaşi sintaxă ca şi funcţia sprintf, dar este inclusă în Windows, aşa că
nu va mări dimensiunea fişierului executabil.

Să punem totul la un loc

Acum se pare că avem toate elementele necesare pentru scrierea unui program simplu, care afişează
mai multe linii de text. Ştim cum să obţinem o variabilă handle a contextului de dispozitiv, cum să
folosim funcţia TextOut şi cum să spaţiem textul în funcţie de dimensiunea caracterelor. Singurul
lucru care a mai rămas este să găsim ceva interesant de afişat.

Informaţiile returnate de funcţia GetSystemMetrics par destul de interesante. Funcţia


GetSystemMetrics returnează informaţii despre dimensiunea unor elemente grafice din Windows,
cum ar fi pictograme, cursoare, bare de titlu şi bare de derulare. Aceste dimensiuni depind de placa
video şi de driverul de afişare. Funcţia GetSystemMetrics acceptă un singur parametru, numit
„index". Indexul este unul dintre cei 73 de identificatori de tip întreg definiţi în fişierele antet din
Windows. GetSystemMetrics returnează o valoare întreagă care reprezintă, de obicei, dimensiunea
elementului transmis ca parametru.

Haideţi să scriem un program care afişează o parte dintre informaţiile returnate de funcţia
GetSystemMetrics într-un format simplu, cu o linie de text pentru fiecare element. Folosirea acestor
informaţii este simplificată prin crearea unui fişier antet care defineşte o structură conţinând atât
identificatorii din fişierele antet Windows, folosiţi pentru indexul transmis funcţiei
GetSystemMetrics, cât şi textul pe care vrem să îl afişeze programul pentru fiecare valoare returnată
de funcţie. Acest fişier antet este numit SYSMETS.H şi este prezentat în Figura 3-4.

/*-----------------------------------------------

   SYSMETS.H -- System metrics display structure

  -----------------------------------------------*/

#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))

struct

int  iIndex ;

char *szLabel ;

char *szDesc ;

sysmetrics [ ] =

SM_CXSCREEN,                               "SM_CXSCREEN",                                  "Screen width in


pixels",

SM_CYSCREEN,                               "SM_CYSCREEN",                                  "Screen height in


pixels",

SM_CXVSCROLL,                           "SM_CXVSCROLL",                               "Vertical scroll arrow


width",

SM_CYHSCROLL,                           "SM_CYHSCROLL",                               "Horizontal scroll


arrow height",

SM_CYCAPTION,                            "SM_CYCAPTION",                                "Caption bar height",


SM_CXBORDER,                              "SM_CXBORDER",                                 "Window border
width",

SM_CYBORDER,                              "SM_CYBORDER",                                 "Window border


height",

SM_CXDLGFRAME,                     "SM_CXDLGFRAME",                           "Dialog window frame


width",

SM_CYDLGFRAME,                     "SM_CYDLGFRAME",                           "Dialog window frame


height",

SM_CYVTHUMB,                          "SM_CYVTHUMB",                                "Vertical scroll thumb


height",

SM_CXHTHUMB,                          "SM_CXHTHUMB",                                "Horizontal scroll


thumb width",

SM_CXICON,                                  "SM_CXICON",                                       "Icon width",

SM_CYICON,                                  "SM_CYICON",                                       "Icon height",

SM_CXCURSOR,                            "SM_CXCURSOR",                                  "Cursor width",

SM_CYCURSOR,                            "SM_CYCURSOR",                                  "Cursor height",

SM_CYMENU,                                "SM_CYMENU",                                     "Menu bar height",

SM_CXFULLSCREEN,                  "SM_CXFULLSCREEN",                        "Full screen client area


width",

SM_CYFULLSCREEN,                  "SM_CYFULLSCREEN",                        "Full screen client area


height",

SM_CYKANJIWINDOW,              "SM_CYKANJIWINDOW",                    "Kanji window


height",

SM_MOUSEPRESENT,                 "SM_MOUSEPRESENT",                       "Mouse present flag",

SM_CYVSCROLL,                         "SM_CYVSCROLL",                              "Vertical scroll arrow


height",

SM_CXHSCROLL,                         "SM_CXHSCROLL",                              "Horizontal scroll


arrow width",

SM_DEBUG,                                    "SM_DEBUG",                                        "Debug version flag",


SM_SWAPBUTTON,                      "SM_SWAPBUTTON",                            "Mouse buttons
swapped flag",

SM_RESERVED1,                           "SM_RESERVED1",                                "Reserved",

SM_RESERVED2,                           "SM_RESERVED2",                                "Reserved",

SM_RESERVED3,                            "SM_RESERVED3",                                "Reserved",

SM_RESERVED4,                            "SM_RESERVED4",                                "Reserved",

SM_CXMIN,                                      "SM_CXMIN",                                          "Minimum window


width",

SM_CYMIN,                                      "SM_CYMIN",                                          "Minimum window


height",

SM_CXSIZE,                                      "SM_CXSIZE",                                         
"Minimize/Maximize icon width",

SM_CYSIZE,                                      "SM_CYSIZE",                                         
"Minimize/Maximize icon height",

SM_CXFRAME,                                "SM_CXFRAME",                                    "Window frame


width",

SM_CYFRAME,                                "SM_CYFRAME",                                    "Window frame


height",

SM_CXMINTRACK,                        "SM_CXMINTRACK",                            "Minimum window


tracking width",

SM_CYMINTRACK,                        "SM_CYMINTRACK",                            "Minimum window


tracking height",

SM_CXDOUBLECLK,                     "SM_CXDOUBLECLK",                         "Double click x


tolerance",

SM_CYDOUBLECLK,                     "SM_CYDOUBLECLK",                         "Double click y


tolerance",

SM_CXICONSPACING,                  "SM_CXICONSPACING",                      "Horizontal icon


spacing",

SM_CYICONSPACING,                  "SM_CYICONSPACING",                      "Vertical icon


spacing",
SM_MENUDROPALIGNMENT,   "SM_MENUDROPALIGNMENT",       "Left or right menu
drop",

SM_PENWINDOWS,                        "SM_PENWINDOWS",                            "Pen extensions


installed",

SM_DBCSENABLED,                      "SM_DBCSENABLED",                          "Double-Byte Char


Set enabled",

SM_CMOUSEBUTTONS,               "SM_CMOUSEBUTTONS",                   "Number of mouse


buttons",

SM_SHOWSOUNDS,                        "SM_SHOWSOUNDS",                           "Present sounds


visually"

};

Figura 3.4. Fişierul SYSMETS.H

Programul care afişează aceste informaţii se numeşte SYSMETS1. Fişierele necesare pentru crearea
fişierului executabil SYSMETS1.EXE (fişierul de construcţie şi codul sursă C) sunt prezentate în
Figura 3-5. Cea mai mare parte a codului ar trebui să vi se pară deja cunoscută. Cu excepţia numelui
programului, fişierul de construcţie este identic cu cel al programului HELLOWIN. Funcţia
WinMain din fişierul SYSMETS1.C este foarte asemănătoare cu cea din programul HELLOWIN.

#include <windows.h>

#include <string.h>

#include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SysMets1" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Get System Metrics No. 1",

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

          }
     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxChar, cxCaps, cyChar ;

     char        szBuffer[10] ;

     HDC         hdc ;

     int         i ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC(hwnd) ;

               GetTextMetrics(hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;

               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               for (i = 0 ; i < NUMLINES ; i++)

                 {
                 TextOut (hdc, cxChar, cyChar * (1 + i), sysmetrics[i].szLabel, strlen
(sysmetrics[i].szLabel)) ;

                  TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i), sysmetrics[i].szDesc, strlen


(sysmetrics[i].szDesc)) ;

                  SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

                  TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar * (1 + i), szBuffer, wsprintf
(szBuffer, "%5d",

                                  GetSystemMetrics (sysmetrics[i].iIndex))) ;

                  SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

                  }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 3.5. Programul SYSMTES1

Figura 3-6 prezintă fereastra afişată de programul SYSMETSl pe un monitor VGA. Aşa cum se
poate vedea din textul afişat în fereastra programului, ecranul are lăţimea de 640 de pixeli şi
înălţimea de 480 de pixeli. Cele două valori, ca şi multe altele afişate de program, pot fi diferite
pentru alte tipuri de monitoare video.

Procedura de fereastră a programului SYSMETS1.C

Procedura de fereastră WndProc din fişierul SYSMETS1.C tratează trei mesaje: WM_CREATE,
WM_PAINT şi WM_DESTROY. Mesajul WM_DESTROY este tratat la fel ca şi în programul
HELLOWIN din Capitolul 2.
Figura 3-6. Fereastra afişată de programul SYSMETS1.

WM_CREATE este primul mesaj pe care îl primeşte procedura de fereastră. Acest mesaj este
generat de Windows atunci când funcţia CreateWindow creează fereastra. În timpul prelucrării
mesajului WM_CREATE, SYSMETS1 obţine un context de dispozitiv pentru fereastră, apelând
funcţia GetDC, şi dimensiunile fontului sistem prestabilit, apelând funcţia GetTextMetrics.
SYSMETS1 salvează lăţimea medie a caracterelor în variabila cxChar şi înălţimea totală (inclusiv
spaţiul suplimentar extern) a caracterelor în variabila cyChar.

De asemenea, SYSMETS1 salvează lăţimea medie a literelor mari în variabila statică cxCaps.
Pentru fonturile cu dimensiune fixă, cxCaps este egală cu cxChar. Pentru fonturile cu dimensiune
variabilă, cxCaps este 150% din cxChar. Bitul cel mai puţin semnificativ al câmpului
tmPitchAndFamily din structura TEXTMETRIC are valoarea 1 pentru fonturile cu dimensiune
variabilă şi valoarea 0 pentru fonturile cu dimensiune fixă. Programul SYSMETS1 foloseşte acest
bit ca să calculeze valoarea cxCaps din cxChar:

cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) *  cxChar / 2 ;

Toate operaţiile de desenare în fereastră sunt făcute în timpul prelucrării mesajului WM_PAINT.
Aşa cum este normal, procedura de fereastră obţine mai întâi o variabilă handle a contextului de
dispozitiv prin apelarea funcţiei BeginPaint. Un ciclu for parcurge toate liniile structurii sysmetrics
definită în fişierul antet SYSMETS.H. Cele trei coloane de text sunt afişate prin apelarea de trei ori a
funcţiei TextOut. La fiecare apel, cel de-al treilea parametru al funcţiei TextOut are valoarea:
cyChar * (1 + i)

Acest parametru indică în pixeli poziţia părţii de sus a şirului de caractere, relativ la marginea
superioară a zonei client. În acest fel, programul lasă în partea superioară a zonei client o margine de
mărimea cyChar. Prima linie (pentru care i are valoarea 0) începe cu cyChar pixeli mai jos de
marginea de sus a zonei client.

Prima instrucţiune TextOut afişează cu litere mari un identificator în prima dintre cele trei coloane.
Al doilea parametru al funcţiei are o valoare egală cu cxChar. În acest fel, în partea stângă a zonei
client este lăsat liber un spaţiu egal cu cxChar, deci de mărimea unui caracter. Textul este obţinut
din câmpul szLabel al structurii sysmetrics. Lungimea şirului de caractere, necesară pentru ultimul
parametru al funcţiei TextOut, este obţinută prin apelarea funcţiei strlen.

A doua instrucţiune TextOut afişează descrierea sistemului de valori metrice. Aceste descrieri sunt
stocate în câmpul szDesc al structurii sysmetrics. În acest caz, al doilea parametru al funcţiei
TextOut are valoarea:

cxChar + 22 * cxCaps

Cel mai lung identificator afişat în prima coloană are 20 de caractere, aşa că a doua coloană trebuie
să înceapă la o distanţă cel puţin egală cu valoarea de 20*cxCaps faţă de începutul primei coloane.

A treia instrucţiune TextOut afişează valoarea numerică obţinută prin apelarea funcţiei
GetSystemMetrics. Fonturile de dimensiune variabilă fac destul de dificilă formatarea unei coloane
de numere aliniate la dreapta. Toate cifrele de la 0 la 9 au aceeaşi lăţime, dar mai mare decât cea a
unui spaţiu. Numerele pot fi formate din una sau mai multe cifre, aşa că două numere diferite pot
începe din poziţii diferite pe orizontală.

Nu ar fi mai simplu dacă am putea să afişăm o coloană de numere aliniate la dreapta prin
specificarea poziţiei în care se termină numerele, în locul poziţiei la care încep acestea? După ce
programul SYSMETS apelează funcţia:

SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

coordonatele transmise funcţiilor TextOut care urmează specifică localizarea colţului din dreapta-sus
al şirului de caractere, în locul colţului din stânga-sus.

Funcţia TextOut care afişează coloana de numere are ca al doilea parametru următoarea expresie:

cxChar + 22 * cxCaps + 40 * cxChar

Valoarea 40 x cxChar reprezintă lăţimea însumată a coloanelor doi şi trei. După apelarea funcţiei
TextOut este apelată din nou funcţia SetTextAlign, pentru a readuce la normal modul de aliniere a
textului.
Nu există destul spaţiu!
Programul SYSMETS are un mic neajuns: dacă nu aveţi un ecran gigantic şi o placă video de înaltă
rezoluţie, nu puteţi vedea ultima parte a listei de valori metrice. Dacă îngustaţi fereastra, nu puteţi
vedea nici valorile din partea dreaptă.

Programul SYSMETS1 nu ştie cât de mare este zona client a ferestrei. De aceea, el începe afişarea
valorilor din partea de sus a ferestrei şi lasă în seama sistemului de operare decuparea textului care
depăşeşte marginile zonei client. Pentru că nu este cel mai convenabil mod de afişare, prima
recomandare este aceea de a determina cât de mult din ceea ce afişează programul poate încăpea în
zona client.

Dimensiunea zonei client


Dacă faceţi câteva experimente cu aplicaţiile Windows existente, veţi vedea că dimensiunea
ferestrelor poate să varieze foarte mult. Dacă nu are meniu şi bare de derulare, fereastra poate fi
mărită la maximum şi zona client ocupă întregul ecran, cu excepţia barei de titlu a programului.
Dimensiunile totale ale ferestrei mărite pot fi obţinute prin apelarea funcţiei GetSystemMetrics cu
parametrii SM_CXFULLSCREEN şi SM_CYFULLSCREEN. Pentru un monitor VGA valorile
returnate sunt 640 şi 461 de pixeli. Dimensiunile minime ale ferestrei pot fi destul de mici, uneori
apropiate de zero, eliminând de fapt zona client.

O metodă obişnuită de determinare a dimensiunilor zonei client a unei ferestre este prelucrarea
mesajului WM_SIZE în procedura de fereastră. Windows trimite un mesaj WM_SIZE către
procedura de fereastră, de fiecare dată când se modifică dimensiunile ferestrei. Parametrul lParam
transmis procedurii de fereastră conţine lăţimea zonei client în cuvântul mai puţin semnificativ
(LOWORD) şi înălţimea zonei client în cuvântul mai semnificativ (HIWORD). Codul pentru
prelucrarea acestui mesaj arată astfel:

static int cxClient, cyClient ;

[alte linii de program] case WM_SIZE :

      cxClient = LOWORD (lParam) ;

      cyClient = HIWORD (lParam) ;

      return 0 ;

Macroinstrucţiunile LOWORD şi HIWORD sunt definite în fişierele antet din Windows. Ca şi


variabilele cxChar şi cyChar, variabilele cxClient şi cyClient sunt definite ca statice în cadrul
procedurii de fereastră, deoarece vor fi folosite ulterior pentru prelucrarea altor mesaje.

Mesajul WM_SIZE este urmat de un mesaj WM_PAINT, deoarece la definirea clasei am specificat
următorul stil de fereastră:

      CS_HREDRAW | CS_VREDRAW


Acest stil cere sistemului de operare să forţeze redesenarea ferestrei de fiecare dată când se modifică
dimensiunea verticală sau orizontală a acesteia.

Puteţi să calculaţi numărul de linii de text care pot fi afişate în zona client folosind formula:

      cyClient / cyChar

Această valoare poate fi zero dacă zona client este prea mică pentru afişarea unui caracter. La fel,
puteţi să calculaţi numărul aproximativ de caractere care pot fi afişate pe orizontală în zona client,
folosind formula:

      cxClient / cxChar

Dacă determinaţi valorile cxChar şi cyChar în timpul prelucrării mesajului WM_CREATE, nu vă


faceţi griji privind eventualitatea unei împărţiri la zero în formulele de mai sus. Procedura de
fereastră primeşte mesajul WM_CREATE atunci când funcţia WinMain apelează funcţia
CreateWindow. Primul mesaj WM_SIZE este primit puţin mai târziu, atunci când funcţia WinMain
apelează funcţia ShowWindow, moment în care variabilelor cxChar şi cyChar le-au fost deja
atribuite valori pozitive diferite de zero.

Aflarea dimensiunilor zonei client a ferestrei este primul pas pentru furnizarea unei metode de
deplasare a textului în zona client atunci când aceasta nu este suficient de mare pentru a cuprinde tot
textul afişat. Dacă aţi mai lucrat cu alte aplicaţii Windows şi aţi întâmpinat aceleaşi probleme,
probabil ştiţi deja de ce aveţi nevoie -minunată invenţie cunoscută sub numele de bară de derulare.

Barele de derulare

Barele de derulare se numără printre cele mai reuşite componente ale unei interfeţe grafice pentru
mouse; sunt uşor de folosit şi determină o reacţie vizuală foarte rapidă. Puteţi să folosiţi bare de
derulare oriunde afişaţi ceva - text, imagini, foi de calcul tabelar, înregistrări din baze de date -
pentru care aveţi nevoie de mai mult spaţiu decât este disponibil în zona client a ferestrei.

Barele de derulare sunt poziţionate vertical (pentru deplasări în sus şi în jos) sau orizontal (pentru
deplasări la stânga şi la dreapta). Puteţi să executaţi clic pe săgeţile de la capetele barei de derulare
sau pe zona dintre acestea. Bara de derulare este parcursă longitudinal de o „casetă de derulare" care
indică poziţia aproximativă a părţii afişate pe ecran faţă de întregul document. De asemenea, puteţi
să trageţi caseta de derulare cu ajutorul mouse-ului, ca să o mutaţi într-o anumită poziţie. Figura 3-7
prezintă modul recomandat de folosire a unei bare de derulare verticale pentru text.

Programatorii au uneori probleme cu terminologia legată de barele de derulare, deoarece perspectiva


lor este diferită de cea a utilizatorilor: un utilizator care derulează un document în jos vrea să aducă
pe ecran o parte a documentului aflată mai jos; pentru aceasta, programul mută de fapt documentul
mai sus în raport cu fereastra de pe ecran. Documentaţia Windows şi identificatorii definiţi în
fişierele antet se bazează, însă, pe perspectiva utilizatorului: derularea în sus înseamnă parcurgerea
documentului către început; derularea în jos înseamnă parcurgerea documentului către sfârşit.
Figura 3-7. Bara de derulare verticală.

Este foarte uşor să includeţi în fereastra aplicaţiei o bară de derulare orizontală sau verticală. Tot ce
trebuie să faceţi este să includeţi identificatorul WS_VSCROLL (derulare verticală) şi/sau
WS_HSCROLL (derulare orizontală) în stilul de fereastră din apelul funcţiei CreateWindow. Barele
de derulare sunt plasate întotdeauna la marginea de jos sau la cea din dreapta a ferestrei şi se întind
pe toată lăţimea, respectiv înălţimea zonei client. Zona client nu include spaţiul ocupat de barele de
derulare. Lăţimea unei bare de derulare verticale şi înălţimea uneia orizontale sunt constante pentru
un driver de afişare dat. Dacă aveţi nevoie de aceste valori, puteţi să le obţineţi (aşa cum aţi văzut)
prin apelarea funcţiei GetSystemMetrics.

Windows se ocupă de modul de utilizare a mouse-ului pentru barele de derulare, dar barele de
derulare ale ferestrelor nu au o interfaţă automatizată cu tastatura. Dacă vreţi ca tastele de deplasare
să dubleze unele dintre funcţiile barelor de derulare trebuie să furnizaţi explicit o metodă de
realizare a acestui lucru (aşa cum vom face în Capitolul 5, când vom discuta despre tastatură şi vom
relua acest program).

Domeniul şi poziţia unei bare de derulare

Fiecare bară de derulare are asociate un „domeniu" (definit printr-o pereche de numere întregi care
reprezintă valorile maximă şi minimă) şi o „poziţie" (punctul în care se află caseta de derulare în
domeniul asociat barei de derulare). Atunci când caseta de derulare se află la capătul de sus (sau la
capătul din partea stângă) al barei de derulare, poziţia corespunde valorii minime. Capătul de jos
(sau capătul din partea dreaptă) al barei de derulare reprezintă valoarea maximă.
 

Figura 3-8. Bare de derulare cu cinci poziţii ale casetei de derulare.

În mod prestabilit, domeniul unei bare de derulare este de la 0 (sus sau în stânga) la 100 (jos sau în
dreapta) dar poate fi uşor modificat astfel încât să aibă o formă mai convenabilă pentru program:

SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;

Parametrul iBar poate avea una dintre valorile SB_VERT şi SB_HORZ, iar iMin şi iMax sunt
poziţiile minimă şi maximă din domeniu, în timp ce bRedraw trebuie să aibă valoarea TRUE dacă
vreţi ca Windows să redeseneze bara de derulare pe baza noului domeniu stabilit.

Poziţia casetei de derulare este reprezentată întotdeauna printr-o valoare întreagă. De exemplu, o
bară de derulare cu domeniul cuprins între 0 şi 4 are cinci poziţii ale casetei de derulare, aşa cum se
poate vedea în Figura 3-8. Puteţi să folosiţi funcţia SetScrollPos ca să stabiliţi o nouă poziţie a
casetei de derulare pe bara de derulare:

      SetScrollPos (hwnd, iBar, iPos, bRedraw) ;

Parametrul iPos reprezintă noua poziţie, care trebuie să fie cuprinsă în domeniul delimitat de iMin şi
iMax. Windows conţine funcţii asemănătoare (GetScrollRange şi GefScrollPos) pentru obţinerea
domeniului şi a poziţiei unei bare de derulare.
Atunci când folosiţi bare de derulare într-un program Windows, răspunderea pentru întreţinerea şi
actualizarea acestora este împărţită între dumneavoastră şi sistemul de operare. Sistemul de operare
Windows are următoarele sarcini:

·        Tratează operaţiile executate cu mouse-ul asupra barei de derulare. .Afişează în video invers
zona pe care utilizatorul execută clic.

·        Mută caseta de derulare atunci când utilizatorul o trage cu ajutorul mouse-ului.

·        Trimite mesaje din partea barei de derulare către procedura de fereastră care o conţine.

Funcţiile programului în această privinţă sunt:

·        Iniţializarea domeniului barei de derulare.

·        Prelucrarea mesajului primit de la bara de derulare.

·        Actualizarea poziţiei casetei de derulare de pe bară.

Mesaje de la barele de derulare


Windows trimite procedurii de fereastră mesajul WM_VSCROLL sau mesajul WM_HSCROLL
atunci când utilizatorul execută clic pe bara de derulare sau trage caseta de derulare cu ajutorul
mouse-ului. Fiecare acţiune cu mouse-ul asupra barei de derulare generează cel puţin două mesaje -
unul la apăsarea butonului şi al doilea la eliberarea acestuia.

Cuvântul mai puţin semnificativ al parametrului wParam care însoţeşte mesajul WM_VSCROLL
sau mesajul WM_HSCROLL este un număr ce indica acţiunea efectuată cu ajutorul mouse-ului
asupra barei de derulare. Aceste numere corespund unor identificatori care încep cu literele SB_ (de
la „scroll bar"). Deşi unii dintre identificatori conţin cuvintele „UP" şi „DOWN" („sus" şi „jos"),
identificatorii se aplică atât barelor de derulare verticale, cât şi celor orizontale, aşa cum se poate
vedea în Figura 3-9. Procedura de fereastră poate să primească mai multe mesaje SB_LINEUP,
SB_PAGEUP, SB_PAGEDOWN, SB_LINEDOWN dacă butonul mouse-ului este ţinut apăsat în
timp ce indicatorul lui este poziţionat pe bara de derulare. Mesajul SB_ENDSCROLL semnalează
eliberarea butonului mouse-ului. În general puteţi ignora mesajele SB_ENDSCROLL.

Atunci când cuvântul mai puţin semnificativ al parametrului wParam are una dintre valorile
SB_THUMBTRACK sau SB_THUMBPOSITION, cuvântul mai semnificativ al parametrului
wParam conţine poziţia curentă pe bara de derulare. Această poziţie se încadrează în domeniul barei
de derulare. Pentru alte acţiuni executate asupra barei de derulare, cuvântul mai semnificativ al
parametrului wParam poate fi ignorat. De asemenea, puteţi să ignoraţi parametrul lParam care, de
obicei, este folosit pentru barele de derulare create în casetele de dialog.

Documentaţia Windows indică faptul că în cuvântul mai puţin semnificativ al parametrului wParam
pot fi transmise şi valorile SB_TOP sau SB_BOTTOM, corespunzătoare deplasării casetei de
derulare în poziţia minimă, respectiv maximă. Totuşi, aceste mesaje nu sunt trimise niciodată în
cazul barelor de derulare create ca parte a ferestrei unei aplicaţii.
Figura 3-9. Identificatorii conţinuţi de parametrul wParam al mesajelor trimise de barele de
derulare.

Tratarea mesajelor SB_THUMBTRACK şi SB_THUMBPOSITION este destul de dificilă. Dacă


stabiliţi un domeniu mare pentru bara de derulare şi utilizatorul mută rapid caseta de derulare.
Windows trimite procedurii de fereastră un număr mare de mesaje SB_THUMBTRACK. Programul
s-ar putea să aibă probleme la tratarea unui număr foarte mare de astfel de mesaje. Din acest motiv,
multe aplicaţii Windows ignoră mesajul SB_THUMBTRACK şi intră în acţiune numai la primirea
mesajelor SB_THUMBPOSITION, care indică oprirea casetei de derulare.

Totuşi, dacă puteţi să actualizaţi rapid conţinutul ferestrei, puteţi să prelucraţi şi mesajele
SB_THUMBTRACK, dar trebuie să ţineţi seama de faptul că utilizatorii care vor descoperi că
fereastra se actualizează imediat la deplasarea casetei de derulare vor încerca să o mute cât se poate
de repede, ca să vadă dacă programul poate ţine pasul. Trebuie să ştiţi că satisfacţia le va fi foarte
mare dacă programul nu va reuşi să se descurce.

Programul SYSMETS cu posibilităţi de derulare


Destul cu explicaţiile. Este timpul să punem în practică ce am aflat. Să începem cu ceva simplu. Mai
întâi, vom include în program o bară de derulare verticală, deoarece avem nevoie de ea. Derularea
orizontală poate să mai aştepte.

Programul SYSMETS2 este prezentat în Figura 3-10.

Funcţia CreateWindow adaugă o bară de derulare la fereastră, datorită includerii stilului de fereastră
WS_VSCROLL la apelare:,

      MS OVERLAPPEDWINDOW | WS VSCROLL

#------------------------

# SYSMETS2.MAK make file

#------------------------

sysmets2.exe : sysmets2.obj

     $(LINKER) $(GUIFLAGS) -OUT:sysmets2.exe sysmets2.obj $(GUILIBS)

sysmets2.obj : sysmets2.c sysmets.h

     $(CC) $(CFLAGS) sysmets2.c

/*----------------------------------------------------

   SYSMETS2.C -- System Metrics Display Program No. 2

                 (c) Charles Petzold, 1996

  ----------------------------------------------------*/

#include <windows.h>

#include <string.h>

#include "sysmets.h"

 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SysMets2" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;


 

     hwnd = CreateWindow (szAppName, "Get System Metrics No. 2",

                          WS_OVERLAPPEDWINDOW | WS_VSCROLL,

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

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxChar, cxCaps, cyChar, cyClient, iVscrollPos ;

     char        szBuffer[10] ;

     HDC         hdc ;

     int         i, y ;
     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;

               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ;

               SetScrollPos   (hwnd, SB_VERT, iVscrollPos, TRUE) ;

               return 0 ;

          case WM_SIZE :

               cyClient = HIWORD (lParam) ;

               return 0 ;

          case WM_VSCROLL :


               switch (LOWORD (wParam))

                    {

                    case SB_LINEUP :

                         iVscrollPos -= 1 ;

                         break ;

                    case SB_LINEDOWN :

                         iVscrollPos += 1 ;

                         break ;

                    case SB_PAGEUP :

                         iVscrollPos -= cyClient / cyChar ;

                         break ;

                    case SB_PAGEDOWN :

                         iVscrollPos += cyClient / cyChar ;

                         break ;

                    case SB_THUMBPOSITION :

                         iVscrollPos = HIWORD (wParam) ;

                         break ;

                    default :

                         break ;
                    }

               iVscrollPos = max (0, min (iVscrollPos, NUMLINES)) ;

               if (iVscrollPos != GetScrollPos (hwnd, SB_VERT))

                    {

                    SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;

                    InvalidateRect (hwnd, NULL, TRUE) ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               for (i = 0 ; i < NUMLINES ; i++)

                    {

                    y = cyChar * (1 - iVscrollPos + i) ;

                    TextOut (hdc, cxChar, y,

                             sysmetrics[i].szLabel,

                             strlen (sysmetrics[i].szLabel)) ;

                    TextOut (hdc, cxChar + 22 * cxCaps, y,

                             sysmetrics[i].szDesc,

                             strlen (sysmetrics[i].szDesc)) ;


 

                    SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

                    TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, y,

                             szBuffer,

                             wsprintf (szBuffer, "%5d",

                                                              GetSystemMetrics (sysmetrics[i].iIndex))) ;

                    SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

                    }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 3-10. Programul SYSMETS2.

Procedura de fereastră WndProc are două linii suplimentare, care stabilesc domeniul şi poziţia
casetei pe bara de derulare verticală în timpul prelucrării mesajului WM_CREATE:

      SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ;


      SetScrollPos   (hwnd, SB_VERT,  iVscrollPos, TRUE) ;

Structura sysmetrics are un număr NUMLINES de linii de text, aşa că domeniul barei de derulare
este stabilit de la 0 la acea valoare NUMLINES. Fiecare poziţie de pe bara de derulare corespunde
unei linii de text afişate în partea de sus a zonei client. În cazul în care caseta de derulare se află în
poziţia 0, în partea superioară a ecranului este lăsată o margine de dimensiunea unei linii. Pe măsură
ce schimbaţi poziţia casetei, derulând în jos, textul ar trebui să se mute în sus. Atunci când poziţia
casetei de derulare are valoarea maximă, în partea de sus a ecranului este afişată ultima linie a
structurii sysmetrics.

Pentru prelucrarea mesajelor WM_VSCROLL în procedura de fereastră WndProc este definită o


variabilă statică numită iVscrollPos. Această variabilă reprezintă poziţia curentă a casetei de
derulare. Pentru mesajele SB_LINEUP şi SB_LINEDOWN tot ce avem de făcut este să ajustăm
poziţia, cu o unitate. Pentru mesajele SB_PAGEUP şi SB_PAGEDOWN trebuie să deplasăm textul
cu numărul de linii afişate pe ecran, deci cu valoarea cyClient împărţită la cyChar. Pentru mesajul
SB_THUMBPOSITION, noua poziţie este indicată în cuvântul mai semnificativ al parametrului
wParam. Mesajele SB_ENDSCROLL şi SB_THUMBTRACK sunt ignorate.

Valoarea iVscrollPos este apoi ajustată cu ajutorul macroinstrucţiunilor min şi max pentru
încadrarea valorii între valorile minimă şi maximă ale domeniului. Dacă poziţia casetei de derulare
s-a modificat, valoarea este actualizată prin apelarea funcţiei SetScrollPos şi întreaga fereastră este
invalidată prin apelarea funcţiei InvalidateRect.

Apelul funcţiei InvalidateRect generează un mesaj WM_PAINT. În programul original SYSMETS1,


în timpul prelucrării mesajului WM_PAINT, poziţia pe axa y a fiecărei linii era calculată astfel:

cyChar * (1 + i)

În SYSMETS2, formula folosită este:

      cyChar * (1 - iVscrollPos + i)

În ciclu este afişat tot un număr de linii NUMLINES, dar dacă iVscrollPos are valoarea 2 sau mai
mare, afişarea liniilor începe deasupra zonei client. Fiind în afara zonei client, aceste linii nu sunt
afişate de Windows.

Am spus că vom începe cu ceva simplu. Codul de mai sus este ineficient şi consumă inutil resurse. Îl
vom corecta în curând, dar mai întâi vom discuta despre modul de actualizare a zonei client după
primirea unui mesaj WM_VSCROLL.

Structurarea programului pentru desenare

Procedura de fereastră din programul SYSMETS2 nu redesenează fereastra după primirea unui
mesaj de la bara de derulare, ci apelează funcţia InvalidateRect pentru invalidarea zonei client.
Această funcţie determină sistemul de operare să plaseze un mesaj WM_PAINT în coada de
aşteptare a programului.

Cel mai bine este să structuraţi programul astfel încât toate operaţiile de desenare în zona client să se
facă în timpul prelucrării mesajelor WM_PAINT. Deoarece programul trebuie să aibă posibilitatea
de redesenare a întregii zone client în momentul primirii unui mesaj WM_PAINT, probabil veţi
duplica unele secvenţe de cod dacă veţi face tipărirea şi în alte puncte din program.

La început s-ar putea să vă revoltaţi, deoarece acest mod de lucru este foarte diferit de modul normal
de programare pentru PC. Nu voi nega faptul că uneori este mai convenabil să desenaţi numai ca
răspuns la alte mesaje decât WM_PAINT. (Programul KEYLOCK din Capitolul 5 este un astfel de
exemplu.) În majoritatea cazurilor, însă, o asemenea abordare este inutilă şi după ce veţi stăpâni
disciplina acumulării tuturor informaţiilor pentru tipărirea ca răspuns la mesajele WM_PAINT, veţi
fi mulţumit de rezultate. Totuşi, de multe ori, programul va stabili că este necesară redesenarea unor
anumite zone din ecran în timpul prelucrării altor mesaje decât WM_PAINT. în astfel de situaţii este
utilă funcţia InvalidateRect. Puteţi să o folosiţi ca să invalidaţi anumite porţiuni sau întreaga zonă
client.

În unele aplicaţii s-ar putea să nu fie suficientă simpla transmitere a unor mesaje WM_PAINT prin
marcarea unor porţiuni din fereastră ca invalide. După ce apelaţi funcţia InvalidateRect, Windows
plasează în coada de aşteptare un mesaj WM_PAINT, iar procedura de fereastră va prelucra la un
moment dat acest mesaj. Totuşi, Windows tratează mesajele WM_PAINT ca mesaje de prioritate
scăzută şi, dacă în sistem activitatea este intensă, poate să treacă un timp destul de lung pană la
prelucrarea acestora. Toată lumea a văzut „găuri" albe în ferestre după închiderea unor casete de
dialog.

Dacă doriţi ca actualizarea porţiunii invalide să se facă imediat, după funcţia InvalidateRect apelaţi
imediat funcţia UpdateWindow:

      UpdateWindow (hwnd) ;

Funcţia UpdateWindow determină sistemul de operare să apeleze procedura de fereastră cu un mesaj


WM_PAINT dacă vreo porţiune a zonei client este invalidă. (Dacă întreaga zonă client este validă,
procedura de fereastră nu mai este apelată.) Acest mesaj WM_PAINT sare peste coada de aşteptare,
procedura de fereastră fiind apelată direct de Windows. După ce procedura de fereastră încheie
operaţia de redesenare, sistemul de operare cedează din nou controlul programului şi execuţia
continuă de la instrucţiunea de după apelul funcţiei UpdateWindow.

Veţi observa că funcţia UpdateWindow este folosită şi de WinMain pentru generarea primului mesaj
WM_PAINT. Atunci când este creată o fereastră, întreaga zonă client a acesteia este invalidă.
Funcţia UpdateWindow cere procedurii de fereastră să deseneze toată zona client.

Construirea unui program cu posibilităţi mai bune de derulare

Deoarece SYSMETS2 nu este un model destul de eficient pentru a fi imitat de alte programe, îl vom
face mai bun. SYSMETS3 - versiunea finală a programului SYSMETS din acest capitol, este
prezentat în Figura 3-11. În această versiune a fost adăugată o bară de derulare orizontală pentru
derularea de la stânga la dreapta şi a fost îmbunătăţit codul de redesenare a zonei client.

#------------------------

# SYSMETS3.MAK make file

#------------------------
 

sysmets3.exe : sysmets3.obj

     $(LINKER) $(GUIFLAGS) -OUT:sysmets3.exe sysmets3.obj $(GUILIBS)

sysmets3.obj : sysmets3.c sysmets.h

     $(CC) $(CFLAGS) sysmets3.c

/*----------------------------------------------------

   SYSMETS3.C -- System Metrics Display Program No. 3

                 (c) Charles Petzold, 1996

  ----------------------------------------------------*/

#include <windows.h>

#include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SysMets3" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

 
     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Get System Metrics No. 3",

                          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))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth,

                 iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ;

     char        szBuffer[10] ;

     HDC         hdc ;

     int         i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               GetTextMetrics (hdc, &tm) ;


               cxChar = tm.tmAveCharWidth ;

               cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;

               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               iMaxWidth = 40 * cxChar + 22 * cxCaps ;

               return 0 ;

          case WM_SIZE :

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

               iVscrollMax = max (0, NUMLINES + 2 - cyClient / cyChar) ;

               iVscrollPos = min (iVscrollPos, iVscrollMax) ;

               SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, FALSE) ;

               SetScrollPos   (hwnd, SB_VERT, iVscrollPos, TRUE) ;

               iHscrollMax = max (0, 2 + (iMaxWidth - cxClient) / cxChar) ;

               iHscrollPos = min (iHscrollPos, iHscrollMax) ;

               SetScrollRange (hwnd, SB_HORZ, 0, iHscrollMax, FALSE) ;

               SetScrollPos   (hwnd, SB_HORZ, iHscrollPos, TRUE) ;


               return 0 ;

          case WM_VSCROLL :

               switch (LOWORD (wParam))

                    {

                    case SB_TOP :

                         iVscrollInc = -iVscrollPos ;

                         break ;

                    case SB_BOTTOM :

                         iVscrollInc = iVscrollMax - iVscrollPos ;

                         break ;

                    case SB_LINEUP :

                         iVscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iVscrollInc = 1 ;

                         break ;

                    case SB_PAGEUP :

                         iVscrollInc = min (-1, -cyClient / cyChar) ;

                         break ;
 

                    case SB_PAGEDOWN :

                         iVscrollInc = max (1, cyClient / cyChar) ;

                         break ;

                    case SB_THUMBTRACK :

                         iVscrollInc = HIWORD (wParam) - iVscrollPos ;

                         break ;

                    default :

                         iVscrollInc = 0 ;

                    }

               iVscrollInc = max (-iVscrollPos,

                             min (iVscrollInc, iVscrollMax - iVscrollPos)) ;

               if (iVscrollInc != 0)

                    {

                    iVscrollPos += iVscrollInc ;

                    ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL) ;

                    SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;

                    UpdateWindow (hwnd) ;

                    }

               return 0 ;

 
          case WM_HSCROLL :

               switch (LOWORD (wParam))

                    {

                    case SB_LINEUP :

                         iHscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iHscrollInc = 1 ;

                         break ;

                    case SB_PAGEUP :

                         iHscrollInc = -8 ;

                         break ;

                    case SB_PAGEDOWN :

                         iHscrollInc = 8 ;

                         break ;

                    case SB_THUMBPOSITION :

                         iHscrollInc = HIWORD (wParam) - iHscrollPos ;

                         break ;

                    default :
                         iHscrollInc = 0 ;

                    }

               iHscrollInc = max (-iHscrollPos,

                             min (iHscrollInc, iHscrollMax - iHscrollPos)) ;

               if (iHscrollInc != 0)

                    {

                    iHscrollPos += iHscrollInc ;

                    ScrollWindow (hwnd, -cxChar * iHscrollInc, 0, NULL, NULL) ;

                    SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               iPaintBeg = max (0, iVscrollPos + ps.rcPaint.top / cyChar - 1) ;

               iPaintEnd = min (NUMLINES,

                                iVscrollPos + ps.rcPaint.bottom / cyChar) ;

               for (i = iPaintBeg ; i < iPaintEnd ; i++)

                    {

                    x = cxChar * (1 - iHscrollPos) ;

                    y = cyChar * (1 - iVscrollPos + i) ;


 

                    TextOut (hdc, x, y,

                             sysmetrics[i].szLabel,

                             strlen (sysmetrics[i].szLabel)) ;

                    TextOut (hdc, x + 22 * cxCaps, y,

                             sysmetrics[i].szDesc,

                             strlen (sysmetrics[i].szDesc)) ;

                    SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

                    TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y,

                             szBuffer,

                             wsprintf (szBuffer, "%5d",

                                                                             GetSystemMetrics (sysmetrics[i].iIndex))) ;

                    SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

                    }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }
Figura 3-11. Programul SYSMETS3.

Iată ce îmbunătăţiri au fost făcute în programul SYSMETS3 şi cum au fost implementate acestea:

·        Nu mai puteţi să derulaţi ecranul pană când ultima linie de text este afişată în partea de sus a
zonei client - puteţi să derulaţi numai până când devine vizibilă ultima linie. Spre a realiza acest
lucru, este nevoie ca programul să calculeze un nou domeniu pentru bara de derulare (şi probabil şi
o nouă poziţie a casetei de derulare) în timpul prelucrării mesajului WM_SIZE. Domeniul barei de
derulare este calculat în funcţie de numărul de linii de text, de lăţimea textului şi de dimensiunea
zonei client. Ca rezultat se obţine un domeniu mai mic - numai atât cât este nevoie pentru afişarea
textului care iese în afara zonei client.

Această abordare prezintă şi un alt avantaj interesant. Să presupunem că zona client a ferestrei este
suficient de mare pentru afişarea întregului text, inclusiv a marginilor de sus şi de jos. În acest caz,
poziţiile minimă şi maximă ale barei de derulare vor avea valoarea zero. Cum va folosi Windows
această informaţie? Va elimina bara de defilare din fereastră! Aceasta nu mai este necesară. La fel,
dacă zona client este suficient de mare ca să afişeze toate cele 60 de coloane de text, nici bara de
derulare orizontală nu mai este afişată.

·        Mesajele WM_VSCROLL şi WM_HSCROLL sunt prelucrate prin calcularea unei valori de
incrementare a poziţiei casetei de derulare pentru fiecare acţiune efectuată asupra barei de derulare.
Această valoare este apoi folosită pentru derularea conţinutului ferestrei, folosind funcţia Windows
ScrollWindow. Această funcţie are următorul format:

               ScrollWindow (hwnd, xInc, yInc, pRect, pClipRect) ;

Valorile xlnc şi ylnc specifică numărul de pixeli cu care se face derularea. În programul
SYSMETS3, parametrii pRect şi pClipRect au valoarea NULL, specificând faptul că trebuie derulată
întreaga zonă client. Windows invalidează dreptunghiul din zona client „descoperit" de operaţia de
derulare. Aceasta generează un mesaj WM_PAINT. Nu este necesară apelarea funcţiei
InvalidateRect. (Reţineţi că funcţia ScrollWindow nu este o procedură GDI, deoarece nu are nevoie
de o variabilă handle. ScrollWindow este una dintre puţinele funcţii non-GDI din Windows care
modifică aspectul zonei client a ferestrei.)

·        În timpul prelucrării mesajului WM_PAINT programul determină care sunt liniile cuprinse în
dreptunghiul invalid şi rescrie numai liniile respective. Acest lucru se face prin analizarea
coordonatelor de sus şi de jos ale dreptunghiului invalid, stocate în structura PAINTSTRUCT.
Programul redesenează numai liniile invalide. Codul este mai complex în acest caz, dar mai rapid.

·        Deoarece prelucrarea mesajului WM_PAINT este mai rapidă, am decis să las programul
SYSMETS3 să prelucreze şi operaţiile SB_THUMBTRACK din mesajele WM_VSCROLL.
Versiunile anterioare ale programului SYSMETS ignorau mesajele SB_THUMBTRACK (trimise
atunci când utilizatorul trage cu mouse-ul caseta de derulare) şi acţionau numai la mesajele
SB_THUMBPOSITION (trimise atunci când utilizatorul eliberează caseta de derulare). De
asemenea, mesajele WM_VSCROLL apelează funcţia UpdateWindow, care actualizează imediat
zona client. Atunci când utilizatorul trage cu mouse-ul caseta de pe bara de derulare verticală,
SYSMETS3 derulează continuu ecranul şi actualizează zona client. Vă las pe dumneavoastră să
decideţi dacă viteza de lucru a programului (şi a sistemului de operare) este destul de mare pentru a
justifica această modificare.
Dar mie nu îmi place să folosesc mouse-ul

La început, utilizatorii sistemului de operare Windows nu se oboseau să folosească mouse-ul. Într-


adevăr, în cazul sistemului Windows (ca şi în cazul unor aplicaţii scrise pentru Windows) nu este
absolut necesar să aveţi un mouse. Deşi calculatoarele personale fără mouse au dispărut pe acelaşi
drum cu monitoarele de tip monocrom şi cu alte dispozitive şi componente, uneori este recomandat
să dublaţi funcţiile mouse-ului cu tastatura. Acest lucru este valabil în special pentru operaţii simple,
cum ar fi derularea ecranului, deoarece pe tastatura există o mulţime de taste de deplasare, care ar
trebui să permită aceleaşi operaţii.

În Capitolul 5 veţi învăţa cum să folosiţi tastatura şi cum să adăugaţi o interfaţă cu tastatura la acest
program. Veţi observa că programul SYSMETS3 prelucrează mesajele WM_VSCROLL atunci când
cuvântul mai puţin semnificativ al parametrului wParam are valoarea SB_TOP sau SB_BOTTOM.
Am menţionat mai devreme că o procedură de fereastră nu primeşte aceste mesaje de la barele de
derulare, aşa că pentru moment acest cod pare inutil. Atunci când vom ajunge la acelaşi program în
Capitolul 5, veţi vedea de ce am inclus aceste operaţii.

Elemente de grafică
Filozofia GDI

În versiunile pe 32 de biţi ale sistemului de operare Windows elementele grafice sunt manipulate, în
principal, prin funcţii exportate din biblioteca cu legături dinamice GDI32.DLL, care, la rândul ei,
foloseşte biblioteca cu legături dinamice pe 16 biţi GDI.EXE. (În versiunile Windows anterioare,
bibliotecile cu legături dinamice erau fişiere cu extensia .EXE, nu .DLL.) Aceste module apelează
proceduri din diferite fişiere driver pentru afişare - un fişier .DRV pentru afişarea pe ecran şi,
probabil, unul sau mai multe fişiere .DRV care controlează imprimantele şi plotterele. Driverele
execută operaţiile de acces la componentele hardware ale monitorului video sau convertesc
comenzile GDI în coduri sau comenzi pe care le pot interpreta diferite tipuri de imprimante. Pentru
diferite tipuri de plăci video sau de imprimante este nevoie, desigur, de diferite fişiere driver.

Deoarece la calculatoarele compatibile PC pot fi ataşate diferite dispozitive de afişare, unul dintre
scopurile principale ale interfeţei GDI este să permită manipularea elementelor grafice independent
de dispozitiv. Programele scrise pentru Windows ar trebui să ruleze fără probleme, indiferent de
dispozitivul grafic de afişare folosit, dacă acesta este acceptat de sistemul de operare. Interfaţa GDI
realizează acest lucru prin furnizarea unor componente care izolează programele de caracteristicile
particulare ale diferitelor dispozitive de ieşire.

Lumea dispozitivelor grafice de ieşire este împărţită în două categorii: dispozitive rastru şi
dispozitive vectoriale. Majoritatea dispozitivelor de ieşire pentru PC sunt dispozitive rastru, ceea ce
înseamnă că reprezentarea imaginilor se face prin matrice de puncte. Această categorie include
plăcile video, imprimantele matriceale şi imprimantele laser. Dispozitivele vectoriale, care
desenează imaginile prin linii, sunt, în general, limitate la plottere.

Majoritatea limbajelor de programare cu posibilităţi grafice tradiţionale se bazează în exclusivitate


pe vectori. Aceasta înseamnă că un program care foloseşte unul dintre aceste limbaje grafice este
despărţit de componentele hardware printr-un nivel de abstractizare. Dispozitivul de ieşire foloseşte
pixeli pentru reprezentarea elementelor grafice, dar programul nu comunică deloc cu interfaţa în
limbajul pixelilor. Deşi puteţi să folosiţi interfaţa Windows GDI ca sistem de desenare vectorială la
nivel înalt, puteţi să folosiţi aceeaşi interfaţa şi pentru manipularea la nivel scăzut a pixelilor.

Din acest punct de vedere, interfaţa Windows GDI este pentru limbajele grafice de interfaţa
tradiţionale ceea ce este C pentru alte limbaje de programare. Limbajul C e bine cunoscut pentru
gradul înalt de portabilitate între diferite medii şi sisteme de operare. Dar, în acelaşi timp, limbajul C
e cunoscut şi pentru faptul că permite efectuarea unor operaţii de nivel scăzut, care în alte limbaje de
nivel înalt sunt, deseori, imposibile. Aşa cum C este numit uneori „limbaj de asamblare de nivel
înalt", puteţi să consideraţi că GDI este o interfaţă de nivel înalt către componentele hardware ale
dispozitivelor grafice.

Aşa cum aţi văzut, în mod prestabilit, Windows foloseşte un sistem de coordonate bazat pe pixeli.
Majoritatea limbajelor grafice tradiţionale folosesc un sistem de coordonate „virtual", cu o axă
verticală şi una orizontală care merg, de exemplu, de la 0 la 32 767. Deşi unele limbaje grafice nu vă
permit să folosiţi coordonate în pixeli, interfaţă GDI vă lasă să lucraţi în oricare dintre cele două
sisteme de coordonate (şi chiar într-un alt sistem, bazat pe dimensiunile fizice). Puteţi să folosiţi un
sistem de coordonate virtual şi să izolaţi astfel programul de componentele hardware, sau să folosiţi
sistemul de coordonate al dispozitivului şi să scrieţi programul direct pentru componentele hardware
utilizate.

Unii programatori consideră că în momentul în care începeţi să gândiţi programul în pixeli aţi
renunţat la independenţa de dispozitiv. Aţi văzut deja în Capitolul 3 că acest lucru nu este
întotdeauna adevărat. Secretul constă în utilizarea pixelilor într-o manieră independentă de
dispozitiv. Pentru aceasta este necesar ca limbajul grafic să furnizeze programului posibilitatea de
determinare a caracteristicilor hardware ale dispozitivului de ieşire, astfel încât să poată face
modificările corespunzătoare. De exemplu, în programele SYSMETS am folosit dimensiunile în
pixeli ale unui caracter din fontul sistem standard, ca să stabilim spaţiile pentru textul de pe ecran.
Această metodă permite programului să se adapteze la plăci video cu diferite rezoluţii, dimensiuni
ale textului şi rate de afişare, în acest capitol veţi vedea şi alte metode de determinare a
dimensiunilor ecranului.

La început, mulţi utilizatori foloseau sistemul de operare Windows pe un monitor monocrom. Pană
nu demult, utilizatorii calculatoarelor portabile (laptop) nu aveau la dispoziţie decât diferite tonuri
de gri. Chiar şi astăzi monitoarele video folosite pentru Windows 95 au posibilităţi diferite de afişare
a culorilor (16 culori, 256 de culori sau „full-color") şi mulţi utilizatori folosesc imprimante alb-
negru. Este posibil să folosiţi aceste dispozitive „orbeşte", dar programul are şi posibilitatea să
determine numărul culorilor disponibile pentru un anumit dispozitiv de afişare şi să folosească toate
avantajele oferite de componentele hardware.

Desigur, aşa cum este posibil să scrieţi programe C care au probleme ascunse de portabilitate atunci
când sunt rulate pe alte calculatoare, este posibil să se strecoare în programele Windows şi anumite
probleme neprevăzute, legate de dependenţa de dispozitiv. Acesta e preţul plătit pentru izolarea
incompletă de componentele hardware. Vom studia în acest capitol capcanele dependenţei de
dispozitiv.

De asemenea, trebuie să ştiţi că interfaţă Windows GDI îşi are limitele ei. Cel puţin în acest
moment, interfaţă GDI nu poate să facă tot ce v-aţi putea dori de la o interfaţă grafică. Deşi puteţi să
mutaţi pe ecran obiecte grafice, GDI este, în general, un sistem de afişare static, ce permite numai
animaţii limitate. Aşa cum este implementată în Windows 95, interfaţă GDI nu asigură un suport
direct pentru afişarea tridimensională sau pentru rotirea obiectelor. De exemplu, atunci când desenaţi
o elipsă, axele acesteia trebuie să fie paralele cu axele sistemului de coordonate. Deşi unele limbaje
grafice folosesc numere în virgulă mobilă pentru coordonatele virtuale, Windows 95 - din motive
legate de performanţă - foloseşte numai numere întregi pe 16 biţi aceasta este una dintre deficienţele
sistemului de operare Windows 95. Windows NT permite folosirea coordonatelor pe 32 de biţi.

Structura interfeţei GDI

Din punctul de vedere al programatorului, interfaţa GDI este formată din câteva sute de apeluri de
funcţii şi unele tipuri de date, macroinstrucţiuni şi structuri asociate acestor funcţii. Înainte de a
studia în detaliu câteva dintre aceste funcţii, haideţi să vedem care este structura generală a interfeţei
GDI.

Tipuri de apeluri de funcţii

În general, apelurile de funcţii GDI pot fi clasificate în mai multe categorii. Chiar dacă nu sunt
foarte stricte şi există unele suprapuneri, aceste categorii pot fi enunţate astfel:

·        Funcţii care obţin (sau creează) şi eliberează (sau distrug) un context de dispozitiv. Aşa cum
am văzut în Capitolul 3, pentru a desena aveţi nevoie de un context de dispozitiv. Funcţiile GetDC şi
ReleaseDC vă permit să faceţi aceste lucruri în timpul prelucrării altor mesaje decât WM_PAINT,
pe când funcţiile BeginPaint şi EndPaint (deşi din punct de vedere tehnic fac parte din subsistemul
USER din Windows) sunt folosite în timpul prelucrării mesajului WM_PAINT. Vom discuta în
curând despre alte funcţii legate de contextul de dispozitiv.

·        Funcţii care obţin informaţii despre contextul de dispozitiv. În programele SYSMETS, cu care
aţi făcut cunoştinţă în Capitolul 3, am folosit funcţia GetTextMetrics ca să obţinem informaţii despre
dimensiunile fontului selectat în contextul de dispozitiv. Mai târziu în acest capitol vom prezenta
programul DEVCAPS1, pentru a obţine informaţii generale despre contextul de dispozitiv.

·        Funcţii care desenează ceva. Evident, după rezolvarea problemelor preliminare, acestea sunt
funcţiile cu adevărat importante. În Capitolul 3 am folosit funcţia TextOut pentru afişarea textului în
zona client a ferestrei. Aşa cum vom vedea, alte funcţii GDI sunt folosite pentru desenarea liniilor, a
zonelor colorate şi a imaginilor de tip bitmap.

·        Funcţii care stabilesc sau obţin atribute ale contextului de dispozitiv. Un „atribut" al
contextului de dispozitiv specifică modul de lucru al funcţiilor de desenare. De exemplu, folosiţi
funcţia SetTextColor ca să precizaţi culoarea textului afişat cu funcţia TextOut (sau cu o altă funcţie
de afişare a textului). În programele SYSMETS din Capitolul 3 am folosit funcţia SetTextAlign ca să
arătăm interfeţei GDI faptul că poziţia de început a şirului de caractere este în partea dreaptă a
şirului de caractere, nu în partea stângă, aşa cum se întâmplă de obicei. Toate atributele contextului
de dispozitiv au valori prestabilite, care devin active la obţinerea contextului de dispozitiv. Pentru
fiecare funcţie de tip Set există şi o funcţie Get corespondentă, folosită pentru obţinerea valorilor
curente ale atributelor contextului de dispozitiv.

·        Funcţii care lucrează cu obiecte GDI. Aici este punctul în care lucrurile se încurcă puţin în
interfaţa GDI. Mai întâi vom da un exemplu: în mod prestabilit, toate liniile pe care le desenaţi
folosind interfaţa GDI sunt continue şi au o grosime standard. Ce se întâmplă, însă, dacă doriţi să
desenaţi o linie mai groasă sau o linie punctată ori întreruptă? Grosimea şi stilul liniei nu sunt
atribute ale contextului de dispozitiv. Acestea sunt caracteristici ale „peniţei logice" („logical pen").
Puteţi să indicaţi o peniţă logică prin specificarea acestor caracteristici în funcţiile CreatePen,
CreatePenIndirect şi ExtCreatePen. Funcţiile de mai sus returnează o variabilă handle a peniţei
logice create. (Deşi se consideră că aceste funcţii fac parte din interfaţa GDI, spre deosebire de
majoritatea celorlalte funcţii acestea nu au nevoie, ca parametru, de o variabilă handle a contextului
de dispozitiv.) Pentru folosirea peniţei selectaţi variabila handle a acesteia în contextul de dispozitiv.
Din acest moment, orice linie va fi desenată cu peniţa selectată. Ulterior, deselectaţi obiectul peniţă
din contextul de dispozitiv şi distrugeţi-l. În afara peniţelor, puteţi să folosiţi obiecte GDI pentru
crearea pensulelor care colorează o suprafaţă închisă, pentru fonturi, pentru imagini bitmap şi pentru
alte aspecte ale interfeţei GDI, despre care vom discuta în acest capitol.

Primitive GDI

Elementele grafice pe care le afişaţi pe ecran sau le tipăriţi la imprimantă pot fi împărţite în mai
multe categorii, numite „primitive". Iată care sunt aceste categorii:

·        Linii şi curbe. Liniile reprezintă baza oricărui sistem de desenare vectorial. GDI permite
folosirea liniilor drepte, a dreptunghiurilor, a elipselor (inclusiv subsetul de elipse cunoscute sub
numele de cercuri), a arcelor - care sunt curbe reprezentând porţiuni din circumferinţa unei elipse
sau a curbelor Bezier. Despre toate aceste clemente vom mai discuta în capitolul de faţă. Orice curbă
mai complexă poate fi desenată ca o linie poligonală, adică o serie de linii foarte scurte care definesc
o curbă. Liniile sunt desenate folosind peniţa curentă selectată în contextul de dispozitiv.

·        Suprafeţe pline. Dacă o serie de linii sau de curbe închid o suprafaţă, aceasta poate fi
„umplută" folosind pensula GDI curentă. Această pensulă poate fi o culoare compactă, un model
(haşuri orizontale, verticale sau pe diagonală) sau o imagine bitmap repetată pe verticală sau pe
orizontală.

·        Imagini bitmap. Imaginile bitmap sunt matrice dreptunghiulare de biţi, care corespund
pixelilor unui dispozitiv de afişare. Imaginile bitmap sunt instrumente de bază pentru sistemele
grafice de tip rastru. În general, acestea sunt folosite pentru afişarea imaginilor complexe (deseori
preluate din lumea reală) pe ecran sau pentru tipărirea acestora la imprimantă. De asemenea,
imaginile bitmap sunt folosite pentru afişarea unor mici imagini (cum ar fi pictograme, indicatoare
de mouse şi butoane de pe barele cu instrumente de lucru ale aplicaţiilor) care trebuie afişate foarte
rapid. Interfaţa GDI acceptă două tipuri de imagini bitmap: un tip mai vechi (dar util) de imagini
bitmap dependente de dispozitiv şi un tip mai nou (precum cele din Windows 3.0) de imagini bitmap
independente de dispozitiv (DIB - Device Independent Bitmap) care pot fi stocate în fişiere.

·        Text. Textul este mai puţin „matematic" decât alte aspecte ale graficii pe calculator. Textul, aşa
cum îl ştim, este legat de sute de ani de tipografia tradiţională, apreciată adesea ca adevărată artă.
Din acest motiv, textul este de multe ori nu doar cea mai complexă parte a sistemului grafic, ci şi cea
mai importantă. Structurile create pentru definirea fonturilor şi pentru obţinerea informaţiilor despre
fonturi sunt printre cele mai mari din Windows. Începând cu versiunea Windows 3.1, interfaţa GDI
acceptă fonturile TrueType, bazate pe contururi umplute, care pot fi manipulate de alte funcţii GDI.
Windows 95 acceptă în continuare şi fonturile mai vechi, de tip bitmap (cum este fontul sistem
prestabilit) pentru compatibilitate şi pentru economisirea spaţiului de memorie.
Alte aspecte

Alte aspecte ale interfeţei GDI nu sunt la fel de uşor de clasificat. Printre acestea se numără:

·        Moduri de mapare şi transformări. Deşi, în mod prestabilit, desenarea se face folosind ca
unităţi de măsură pixelii, nu sunteţi limitat la acest sistem de măsură. Modurile de mapare GDI vă
permit să desenaţi folosind ca unitate de măsură inci (sau fracţiuni de inci), milimetri sau orice altă
unitate de măsură. De asemenea, Windows 95 asigură suportul pentru o „transformare reală"
exprimată printr-o matrice 3x3. Această transformare permite deformarea şi rotirea obiectelor
grafice. Din păcate, această transformare nu este acceptată sub Windows 95.

·        Metafişiere (metafiles). Un metafişier este o colecţie de comenzi GDI stocate într-o formă
binară. Metafişierele sunt folosite, în primul rând, pentru transferarea reprezentărilor unor elemente
grafice vectoriale prin intermediul memoriei temporare (clipboard).

·        Regiuni (regions). O regiune este o suprafaţă complexă de orice formă, definită ca o
combinaţie booleană de regiuni mai simple. În general, regiunile sunt stocate intern de GDI ca o
serie de linii de scanare, diferite de combinaţia de linii folosită iniţial pentru definirea regiunii. Puteţi
să folosiţi regiunile pentru contururi, pentru umplere sau pentru decupare.

·        Căi (paths). O cale este o colecţie de linii drepte şi curbe stocate intern de GDI. Căile pot fi
folosite pentru desenare, pentru umplere sau pentru decupare. De asemenea, căile pot fi transformate
în regiuni.

·        Decupare (clipping). Desenarea poate fi limitată la o anumită secţiune a zonei client, numită
zonă de decupare, care poate fi dreptunghiulară sau poate avea o altă formă, definită printr-o serie de
linii. Zona de decupare este definită, în general, de o cale sau de o regiune.

·        Palete (palettes). Folosirea paletelor proprii este limitată, în general, numai la monitoarele care
pot reda 256 de culori. Windows rezervă 20 dintre aceste culori pentru sistemul de operare. Celelalte
236 de culori pot fi modificate pentru afişarea corespunzătoare a imaginilor din lumea reală, stocate
ca imagini bitmap.

·         Tipărire (printing). Deşi discuţiile din acest capitol sunt limitate doar la afişarea pe ecran, tot
ceea ce învăţaţi în acest capitol poate fi aplicat şi operaţiilor de tipărire. (Vezi Capitolul 15 pentru
alte informaţii despre tipărire.)

CONTEXTUL DE DISPOZITIV

Înainte de a începe să desenăm, haideţi să mai aflăm câte ceva despre contextele de dispozitiv.

Atunci când vreţi să desenaţi la un dispozitiv de ieşire grafic (cum ar fi ecranul sau imprimanta)
trebuie să obţineţi mai întâi o variabilă handle a contextului de dispozitiv (DC - device context). Prin
obţinerea acestei variabile handle primiţi permisiunea de folosire a dispozitivului. Variabila handle
este apoi inclusă ca parametru în apelul unei funcţii GDI, identificând dispozitivul la care vreţi să
desenaţi.

Contextul de dispozitiv conţine mai multe atribute curente, care specifică modul de lucru al
funcţiilor GDI pentru dispozitivul respectiv. Aceste atribute permit ca la apelarea funcţiilor GDI să
fie specificate numai coordonatele de început sau dimensiunea, nu şi toate celelalte informaţii de
care sistemul de operare are nevoie pentru desenarea obiectelor pe dispozitivul folosit. De exemplu,
atunci când apelaţi funcţia TextOut trebuie să specificaţi numai variabila handle a contextului de
dispozitiv, coordonatele de început, textul şi lungimea acestuia. Nu trebuie să precizaţi fontul,
culoarea textului, culoarea fondului din spatele textului şi spaţiul dintre caractere, deoarece aceste
atribute fac parte din contextul de dispozitiv. Atunci când doriţi să modificaţi unul dintre aceste
atribute ale contextului de dispozitiv, apelaţi o funcţie specializată. Următoarele apeluri ale funcţiei
TextOut pentru acelaşi context de dispozitiv vor folosi atributul modificat.

Obţinerea variabilei handle a contextului de dispozitiv

Sistemul de operare Windows vă pune la dispoziţie mai multe metode pentru obţinerea variabilei
handle a contextului de dispozitiv. Dacă obţineţi o variabilă handle a contextului de dispozitiv în
timpul prelucrării unui mesaj, ar trebui să ştergeţi această variabilă înainte de ieşirea din procedura
de fereastră. După ce este ştearsă, variabila handle nu mai poate fi folosită (nu mai este validă).

Cea mai cunoscută metodă de obţinere şi de ştergere a variabilei handle a contextului de dispozitiv
implică folosirea funcţiilor BeginPaint şi EndPaint în timpul prelucrării mesajului WM_PAINT:

hdc = BeginPaint (hwnd, &ps);

[alte Unii de program]

EndPaint (hwnd, &ps);

Variabila ps este o structură de tip PAINTSTRUCT. Câmpul hdc al acestei structuri conţine
variabila handle a contextului de dispozitiv. Structura PAINTSTRUCT conţine şi o structură de tip
RECT numită rcPaint, care defineşte dreptunghiul ce cuprinde regiunea invalidă a zonei client a
ferestrei. Folosind variabila handle a contextului de dispozitiv, obţinută prin apelarea funcţiei
BeginPaint, nu puteţi să desenaţi decât în regiunea invalidă a ferestrei. Funcţia BeginPaint validează
regiunea invalidă.

Programele Windows pot să obţină variabila handle a contextului de dispozitiv şi în timpul


prelucrării altor mesaje decât WM_PAINT:

hdc = GetDC (hwnd);

(alte linii de program]

ReleaseDC (hwnd, hdc);

Acest context de dispozitiv se aplică zonei client a ferestrei care are variabila handle hwnd.
Principala diferenţă între apelul de mai sus şi metoda folosirii funcţiilor BeginPaint şi EndPaint este
că variabila handle returnată de funcţia GetDC vă permite să desenaţi în toată zona client a ferestrei.
În plus, funcţiile GetDC şi ReleaseDC nu validează eventualele regiuni invalide ale zonei client.

Un program Windows poate să obţină şi o variabilă handle a unui context de dispozitiv care se
aplică întregii ferestre, nu numai zonei client a ferestrei:
hdc = GetWindowDC (hwnd);

[alte linii de program]

ReleaseDC (hwnd, hdc);

Contextul de dispozitiv include, în afară de zona client, bara de titlu a ferestrei, barele de derulare şi
chenarul. Funcţia GetWindowDC este rareori folosită de aplicaţii. Dacă vreţi să experimentaţi
folosirea acestei funcţii, trebuie să interceptaţi mesajele WM_NCPAINT („nonclient paint"),
împiedicând sistemul de operare să redeseneze porţiunile din fereastră care nu fac parte din zona
client.

Funcţiile BeginPaint, GetDC şi GetWindowDC obţin variabila handle a contextului de dispozitiv


asociat unei anumite ferestre de pe ecran. O funcţie mai generală pentru obţinerea variabilei handle a
unui context de dispozitiv este CreateDC:

hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData);

[alte linii de program]

DeleteDC (hdc);

De exemplu, puteţi să obţineţi variabila handle a contextului de dispozitiv pentru tot spaţiul de
afişare, cu următorul apel:

hdc = CreateDC ("DISPLAY", NULL, NULL, NULL);

Scrierea în afara ferestrei proprii nu este în general recomandată, dar poate fi convenabilă pentru
unele aplicaţii speciale. (Deşi această metodă nu e documentată, se poate obţine o variabila handle a
contextului de dispozitiv pentru întregul ecran şi prin apelarea funcţiei GetDC, cu parametrul
NULL). În Capitolul 15 vom folosi funcţia CreateDC pentru a obţine o variabilă handle a
contextului de dispozitiv pentru o imprimantă.

Uneori aveţi nevoie de unele informaţii despre un context de dispozitiv fără să desenaţi nimic. În
această situaţie puteţi să obţineţi o variabila handle a contextului de informaţii („information
context") folosind funcţia CreateIC. Parametrii sunt aceiaşi ca şi pentru funcţia CreateDC:

hdclnfo = CreatelC ("DISPLAY", NULL, NULL, NULL);

[alte linii de program]

DeleteDC (hdclnfo);

Nu puteţi să executaţi operaţii de scriere la un dispozitiv folosind această variabilă handle.

Atunci când lucraţi cu imagini bitmap, poate fi uneori utilă obţinerea unui „context de dispozitiv în
memorie":
hdcMem = CreateCompatibleDC (hdc);

[alte linii de program]

DeleteDC (hdcHem)

Acesta este un concept destul de abstract. În esenţă, puteţi să selectaţi o imagine bitmap într-un
context de dispozitiv în memorie şi apoi să desenaţi peste folosind funcţiile GDI. Vom discuta mai
târziu despre această tehnică şi o vom folosi în programul GRAFMENU din Capitolul 10.

Aşa cum am menţionat mai devreme, un metafişier este o colecţie de apeluri GDI codificate într-o
formă binară. Puteţi să creaţi un metafişier prin obţinerea unui context de dispozitiv pentru
metafişiere:

hdcMeta = CreateMetaFile(pszFilename);

[alte linii de program]

hmf = CloseMetaFile (hdcMeta);

Cât timp acest context este valid, nici un apel GDI pe care îl faceţi folosind parametrul hdcMeta nu
afişează nimic pe ecran, ci devine parte a metafişierului. Apelaţi apoi funcţia CloseMetaFile şi
contextul de dispozitiv este invalidat. Funcţia returnează o variabilă handle a metafişierului (hmf).

Obţinerea informaţiilor despre contextul de dispozitiv

Un context de dispozitiv se referă, de obicei, la un dispozitiv fizic de ieşire, cum ar fi un monitor


video sau o imprimantă. Dacă aveţi nevoie de anumite informaţii despre acest dispozitiv, cum ar fi
dimensiunile ecranului (dimensiunile în pixeli şi cele fizice) sau posibilităţile de folosire a culorilor,
puteţi să le obţineţi prin apelarea funcţiei GetDeviceCaps („get device capabilities"):

iValue = GetDeviceCaps (hdc, iIndex) ;

Parametrul iIndex este unul dintre cei 28 de identificatori definiţi în fişierele antet din Windows. De
exemplu, dacă iIndex are valoarea HORZRES funcţia GetDeviceCaps returnează lăţimea
dispozitivului în pixeli; VERTRES returnează înălţimea dispozitivului în pixeli. Dacă hdc este o
variabilă handle a contextului de dispozitiv pentru un monitor video, informaţiile obţinute sunt
aceleaşi cu cele returnate de funcţia GetSystemMetrics. Dacă hdc este o variabilă handle a
contextului de dispozitiv pentru o imprimantă, funcţia GetDeviceCaps returnează înălţimea şi
lăţimea zonei pe care imprimantă o poate tipări.

Puteţi să folosiţi funcţia GetDeviceCaps şi ca să obţineţi informaţii despre posibilităţile unui


dispozitiv de prelucrare a anumitor tipuri de elemente grafice. Această posibilitate nu este
importantă pentru ecran, dar poate fi folosită în cazul imprimantelor. De exemplu, majoritatea
plotterelor nu pot tipări imagini bitmap - iar funcţia GetDeviceCaps vă poate comunica acest lucru.

Programul DEVCAPS1
Programul DEVCAPS1, prezentat în Figura 4-1, afişează o parte dintre informaţiile pe care poate să
le returneze funcţia GetDeviceCaps.

DEVCAPS1.C -- Device Capabilities Display Program No. 1

(c) Charles Petzold, 1996

---------------------------------------------------------*/

#include <windows.h>

#include <string.h>

#define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0]))

struct

int  iIndex ;

char *szLabel ;

char *szDesc ;

devcaps [] =

HORZSIZE,      "HORZSIZE",     "Width in millimeters:",

VERTSIZE,      "VERTSIZE",     "Height in millimeters:",

HORZRES,       "HORZRES",      "Width in pixels:",

VERTRES,       "VERTRES",      "Height in raster lines:",

BITSPIXEL,     "BITSPIXEL",    "Color bits per pixel:",


PLANES,        "PLANES",       "Number of color planes:",

NUMBRUSHES,    "NUMBRUSHES",   "Number of device brushes:",

NUMPENS,       "NUMPENS",      "Number of device pens:",

NUMMARKERS,    "NUMMARKERS",   "Number of device markers:",

NUMFONTS,      "NUMFONTS",     "Number of device fonts:",

NUMCOLORS,     "NUMCOLORS",    "Number of device colors:",

PDEVICESIZE,   "PDEVICESIZE",  "Size of device structure:",

ASPECTX,       "ASPECTX",      "Relative width of pixel:",

ASPECTY,       "ASPECTY",      "Relative height of pixel:",

ASPECTXY,      "ASPECTXY",     "Relative diagonal of pixel:",

LOGPIXELSX,    "LOGPIXELSX",   "Horizontal dots per inch:",

LOGPIXELSY,    "LOGPIXELSY",   "Vertical dots per inch:",

SIZEPALETTE,   "SIZEPALETTE",  "Number of palette entries:",

NUMRESERVED,   "NUMRESERVED",  "Reserved palette entries:",

COLORRES,      "COLORRES",     "Actual color resolution:"

};

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine,


int iCmdShow)

static char szAppName[] = "DevCaps1" ;

HWND        hwnd ;

MSG         msg ;
WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (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 ;

wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName, "Device Capabilities", 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) ;

return msg.wParam ;

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

static int  cxChar, cxCaps, cyChar ;

char        szBuffer[10] ;

HDC         hdc ;

int         i ;

PAINTSTRUCT ps ;

TEXTMETRIC  tm ;

switch (iMsg)

case WM_CREATE:

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

cxChar = tm.tmAveCharWidth ;

cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;

cyChar = tm.tmHeight + tm.tmExternalLeading ;

ReleaseDC (hwnd, hdc) ;

return 0 ;
 

case WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

for (i = 0 ; i < NUMLINES ; i++)

TextOut (hdc, cxChar, cyChar * (1 + i),

devcaps[i].szLabel,

strlen (devcaps[i].szLabel)) ;

TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i),

devcaps[i].szDesc,

strlen (devcaps[i].szDesc)) ;

SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar,

cyChar * (1 + i), szBuffer,

wsprintf (szBuffer, "%5d",

GetDeviceCaps (hdc, devcaps[i].iIndex))) ;

SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY:

PostQuitMessage (0) ;

return 0 ;
}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

Figura 4-1. Programul DEVCAPS1.

Aşa cum se poate vedea, acest program este foarte asemănător cu programul SYSMETS din
Capitolul 3. Pentru a micşora dimensiunea codului, nu am inclus bare de derulare, deoarece ştiam că
textul afişat va încăpea pe ecran. Rezultatele afişate de acest program pe un monitor VGA cu 256 de
culori sunt prezentate în Figura 4-2.

Figura 4-2. Fereastra afişata de programul DEVCAPS1 pe un monitor VGA cu 256 de culori.

Dimensiunea dispozitivului
Cea mai importantă informaţie pe care poate să o obţină programul despre monitorul video prin
apelarea funcţiei GetDeviceCaps este dimensiunea ecranului (măsurată atât în pixeli, cat şi în
milimetri) şi proporţia dimensiunilor unui pixel. Aceste informaţii pot fi folosite pentru scalarea
imaginilor afişate pe ecran.

Valorile HORZSIZE şi VERTSIZE reprezintă lăţimea şi înălţimea suprafeţei de afişare, în


milimetri. Desigur, driverul Windows nu ştie exact ce tip de monitor este ataşat la placa video.
Dimensiunile returnate sunt bazate pe dimensiunile standard cunoscute de placa video.

Valorile HORZRES şi VERTRES reprezintă lăţimea şi înălţimea suprafeţei de afişare, în pixeli.


Pentru contextul de dispozitiv al unui ecran, aceste dimensiuni sunt aceleaşi cu cele returnate de
funcţia GetSystemMetrics. Folosind aceste valori împreună cu HORZSIZE şi VERTSIZE puteţi să
obţineţi rezoluţia dispozitivului în pixeli pe milimetru. Dacă ştiţi că un inci are 25,4 milimetri, puteţi
să calculaţi rezoluţia în puncte pe inci (dpi - dots per inch).

Valorile ASPECTX, ASPECTY şi ASPECTXY reprezintă dimensiunile aproximative pentru


lăţimea, înălţimea şi diagonală unui pixel, rotunjite la cel mai apropiat număr întreg. ASPECTXY
este rădăcina pătrată a sumei pătratelor valorilor ASPECTX şi ASPECTY, aşa cum vă amintiţi din
teorema lui Pitagora.

Valorile LOGPIXELSX şi LOGPIXELSY reprezintă numărul de pixeli pe un „inci logic", pe


orizontală şi pe verticală. În cazul monitoarelor, un inci logic nu este egal cu un inci fizic (25,4
milimetri) aşa cum puteţi să vă daţi seama imediat dacă faceţi câteva calcule cu valorile HORZSIZE,
VERTSIZE, HORZRES şi VERTRES. Probabil aţi observat că majoritatea procesoarelor de text sub
Windows afişează o riglă care însă nu este prea corectă: dacă măsuraţi rigla aşa cum este aceasta
afişată pe un ecran VGA, veţi vedea că un inci de pe aceasta are de fapt cam 1,5 inci reali. Aceste
programe folosesc pentru afişarea riglei valorile LOGPIXELSX şi LOGPIXELSY. Dacă programele
ar folosi dimensiunile fizice reale, textul de 10 sau 12 puncte ar fi atât de mic, încât nu ar putea fi
citit. Dimensiunile logice măresc elementele afişate, astfel încât textul să fie corect dimensionat.
Atunci când vom începe să lucrăm cu texte, vom relua această problemă. Această problemă priveşte
doar ecranele; toate dimensiunile returnate de funcţia GetDeviceCaps pentru imprimante sunt
consecvente.

Obţinerea informaţiilor despre culori

Pentru afişarea culorilor este nevoie de mai mulţi biţi. Cu cât se folosesc mai mulţi biţi, cu atât pot fi
afişate mai multe culori. Sau, mai precis, numărul culorilor care pot fi afişate simultan este egal cu 2
la o putere egală cu numărul de biţi folosiţi. De obicei, biţii sunt organizaţi în planuri de culori - un
plan pentru roşu, un plan pentru verde, unul pentru albastru şi unul pentru intensitatea culorii.
Adaptoarele video cu 8, 16 sau 24 de biţi pentru fiecare pixel au un singur plan de culoare, în care
un număr de biţi adiacenţi reprezintă culoarea fiecărui pixel.

Funcţia GetDeviceCaps vă permite să determinaţi modul de organizare a memoriei în adaptoarele


video şi numărul de culori care pot fi reprezentate. Apelul de mai jos returnează numărul de planuri
de culoare:

iPlanes = GetDeviceCaps (hdc, PLANES);

Apelul următor returnează numărul de biţi de culoare folosiţi pentru fiecare pixel:
iBitsPixel = GetDeviceCaps (hdc, BITSPIXEL)

Majoritatea adaptoarelor video care pot afişa culori folosesc fie mai multe planuri de culoare, fie mai
mulţi biţi de culoare pentru fiecare pixel, dar nu pe amândouă; cu alte cuvinte, unul dintre cele două
apeluri de mai sus va returna valoarea 1. Numărul de culori care pot fi redate de o placă video se
poate calcula cu formula următoare:

iColors = 1<<(iPlanes * iBitsPixel);

Această valoare poate să nu fie identică cu numărul de culori obţinut prin apelarea funcţiei
GetDeviceCaps cu parametrul NUMCOLORS:

iColors = GetDeviceCaps (hdc, NUMCOLORS);

De exemplu, cele două numere vor fi diferite pentru majoritatea plotterelor. În cazul unui plotter,
PLANES şi BITSPIXEL vor avea valoarea 1, dar NUMCOLORS va reprezenta numărul de peniţe
pe care le foloseşte plotterul. Pentru dispozitivele monocrome, funcţia GetDeviceCaps apelată cu
parametrul NUMCOLORS returnează valoarea 2.

Aceste valori pot fi diferite şi în cazul adaptoarelor video care permit încărcarea paletelor de culori.
Funcţia GetDeviceCaps apelată cu parametrul NUMCOLORS returnează numărul de culori
rezervate de Windows, adică 20. Celelalte 236 de culori pot fi stabilite de program folosind un
manager de palete.

Windows foloseşte pentru reprezentarea culorilor o valoare întreagă fără semn, pe 32 de biţi. Tipul
de date folosit pentru culori se numeşte COLORREF. Ultimii trei octeţi ai numărului (cei mai puţin
semnificativi) specifică valorile pentru culorile roşu, verde şi albastru, de la 0 la 255, aşa cum se
poate vedea în Figura 4.3. Rezultă o paletă potenţială de 224 culori (aproximativ 16 milioane de
culori).

Figura 4.3. Valoarea pe 32 de biţi folosita pentru reprezentarea culorilor.

Valoarea pe 32 de biţi de mai sus e numită deseori „culoare RGB". În fişierele antet din Windows
sunt definite mai multe macroinstrucţiuni pentru lucrul cu valorile RGB. Macroinstrucţiunea RGB
acceptă trei argumente, care reprezintă valorile pentru culorile roşu, verde şi albastru şi le combină
într-o valoarea întreagă pe 32 de biţi, fără semn:

#define RBG (r, g, b) ((COLORREF)(((BYTE)(r)|\

((WORD)(g)<<8))|\

 (((DWORD) (BYTE) (b))<<16)))


Astfel, valoarea RGB (255, 0, 255) este de fapt 0x00FF00FF, valoarea RGB pentru magenta. Dacă
toate cele trei argumente au valoarea 0, se obţine negrul, iar dacă au valoarea 255, se obţine albul.
Macroinstrucţiunile GetRValue, GetGValue şi GetBValue extrag valorile pentru culorile primare,
sub forma unor octeţi fără semn, din valoarea RGB a culorii. Aceste macroinstrucţiuni sunt utile
atunci când apelaţi funcţii Windows care returnează culori RGB.

Numărul de culori returnat de funcţia GetDeviceCaps reprezintă numărul de culori pure pe care le
poate afişa dispozitivul respectiv. Windows poate să folosească amestecarea culorilor - aceasta
implică folosirea unui model de pixeli de diferite culori - pentru reprezentarea altor culori în afara
celor pure. Nu toate combinaţiile de valori pentru roşu, verde şi albastru produc modele diferite de
amestecare a culorilor. De exemplu, în cazul unui monitor VGA pe care pot fi afişate 16 culori,
valorile pentru roşu, verde şi albastru trebuie să fie incrementate cu patru, ca să genereze un model
diferit de amestecare. Ca urmare, pentru aceste tipuri de adaptoare aveţi la dispoziţie 218 (sau 262
144) culori amestecate.

Puteţi să determinaţi cea mai apropiată culoare pură pentru o anumită valoare apelând funcţia
GetNearestColor:

rgbPureColor = GetNearestColor (hdc, rgbColor) ;

Atributele contextului de dispozitiv

Aşa cum am menţionat mai sus, Windows foloseşte contextul de dispozitiv ca să stocheze atributele
care specifică modul de lucru al funcţiilor GDI pentru afişare. De exemplu, atunci când afişaţi un
text folosind funcţia TextOut nu trebuie să specificaţi culoarea textului sau fontul folosit. Windows
foloseşte contextul de dispozitiv ca să obţină aceste informaţii.

Atunci când un program obţine o variabilă handle a unui context de dispozitiv, Windows creează un
context de dispozitiv cu valorile prestabilite pentru toate atributele. Atributele contextului de
dispozitiv sunt prezentate în tabelul următor. Un program poate să schimbe sau să obţină aceste
atribute.

Atributul Valoare Funcţia folosită pentru Funcţia folosită pentru


contextului de prestabilită modificarea atributului obţinerea atributului
dispozitiv
Mod de MM_TEXT SetMapMode GetMapMode

mapare
Originea (0,0) SetWindowOrgEx GetWindowOrgEx
ferestrei OffsetWindowOrgEx
Originea (0,0) SetViewportOrgEx GetViewportOrgEx
vizorului OffsetViewportOrgEx
Extensia (1,1) SetWindowExtEx GetWindowExtEx
ferestrei
 SetMapMode
ScaleWindowExtEx
Extensia (1,1) SetViewportExtEx GetViewportExExt
vizorului SetMapMode
ScaleViewportExtEx
Peniţă (pen) BLACK_PEN SelectObject SelectObject
Pensulă (brush) WHITE_BRUSH SelectObject SelectObject
Font SYSTEM_FONT SelectObject SelectObject
Bitmap Nu există SelectObject SelectObject
Poziţia curentă a (0,0) MoveToEx GetCurrentPositionEx
peniţei
LineTo PolylineTo
PolyBezierTo
Modul de OPAQUE SetBkMode GetBkMode
afişare a

fondului
Culoarea Alb SetBkColor GetBkColor

fondului
Culoarea Negru SetTextColor GetTextColor
textului
Mod de R2COPYPEN SetROPZ GetROP2

desenare
Mod de BLACKONWHIT SetStretchBltMode GetStretchBltMode
E
întindere
Mod de umplere ALTERNATE SetPolyFillMode GetPolyFillMode
a prigoanelor
Spaţiu între 0 SetTextCharacterExtra GetTextCharacterExtr
a
caractere
Originea (0,0) în coordonate SetBrushOrgEx GetBrushOrgEx
pensulei ecran
Regiune de Nu există SelectObject GetClipBox
decupare SelectClipRgn

Salvarea contextului de dispozitiv

În acest capitol veţi întâlni diferite funcţii folosite pentru modificarea atributelor contextului de
dispozitiv. În mod normal, Windows creează un nou context de dispozitiv cu valori prestabilite
atunci când apelaţi una dintre funcţiile GetDC sau BeginPaint. Toate modificările făcute în
atributele contextului de dispozitiv se pierd atunci când contextul de dispozitiv este şters din
memorie prin apelarea funcţiei ReleaseDC sau a funcţiei EndPaint. Dacă programul trebuie să
folosească un atribut cu o valoarea diferită de cea prestabilită va trebui să iniţializaţi contextul de
dispozitiv de fiecare dată când obţineţi o variabilă handle:
case WM_Paint:

hdc = BeginPaint (hwnd, &ps);

[iniţializează atributele contextului de dispozitiv]

[desenează zona client a ferestrei]

EndPaint (hwnd, &ps);

return 0;

Deşi această abordare este în general satisfăcătoare, s-ar putea să preferaţi să salvaţi modificările
făcute asupra contextului de dispozitiv la distrugerea acestuia, astfel încât valorile salvate să
redevină active la apelarea funcţiilor GetDC sau BeginPaint.

Puteţi realiza acest lucru incluzând indicatorul flag CS_OWNDC în clasa ferestrei, atunci când o
înregistraţi:

wndclass.style = CS_HREDRAW | CS_VREDRAH | CS_OWNDC;

Acum fiecare fereastră pe care o creaţi pe baza acestei clase va avea propriul context de dispozitiv,
care va fi păstrat până la distrugerea ferestrei. Atunci când folosiţi stilul de fereastră CS_OWNDC,
trebuie să iniţializaţi contextul de dispozitiv o singură dată, de obicei în timpul prelucrării mesajului
WM_CREATE:

case WM_CREATE:

hdc = GetDC (hwnd);

[iniţiallzează atributele contextului de dispozitiv]

ReleaseDC (hwnd, hdc);

Atributele sunt valide până când le modificaţi în mod explicit.

Stilul de fereastră CS_OWNDC afectează numai contextele de dispozitiv obţinute prin apelarea
funcţiilor GetDC sau BeginPaint, nu şi cele obţinute prin apelarea altor funcţii (cum ar fi
GetWindowDC). Stilul de fereastră CS_OWNDC are şi un dezavantaj: sistemul de operare Windows
consumă aproximativ 800 de octeţi pentru salvarea contextului de dispozitiv al fiecărei ferestre
create cu acest stil. Chiar dacă folosiţi stilul de fereastră CS_OWNDC, trebuie să distrugeţi
contextul de dispozitiv înainte de ieşirea din procedura de fereastră.

De asemenea, puteţi să folosiţi şi stilul CS_CLASSDC:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC;


Acest stil face ca fiecare clasă de fereastră să aibă un context de dispozitiv, folosit în comun de toate
ferestrele create pe baza clasei respective. În general, stilul de fereastră CS_CLASSDC este mai
greu de folosit decât stilul CS_OWNDC, deoarece orice modificare pe care o faceţi asupra
atributelor contextului de dispozitiv afectează toate ferestrele create pe baza aceleiaşi clase. Uneori
efectele pot fi destul de bizare.

Dacă s-ar putea să doriţi să modificaţi unele dintre atributele contextului de dispozitiv, să desenaţi
ceva folosind noile atribute şi apoi să reveniţi la vechile valori, salvaţi starea curentă a contextului
de dispozitiv prin următorul apel:

iSavedID = SaveDC (hdc);

Modificaţi atributele dorite. Atunci când vreţi să reveniţi la starea contextului de dispozitiv dinaintea
apelării funcţiei SaveDC, folosiţi funcţia RestoreDC:

RestoreDC (hdc, iSavedID);

Puteţi să apelaţi funcţia SaveDC de oricâte ori înainte de a apela funcţia RestoreDC. Dacă vreţi să
reveniţi la starea contextului de dispozitiv de dinaintea ultimului apel al funcţiei SaveDC, folosiţi
următorul apel:

RestoreDC (hdc, -1);

Desenarea liniilor

Teoretic, singura funcţie necesară pentru desenare este SetPixel (şi, în unele situaţii, GetPixel). Orice
altceva poate fi făcut prin apelarea unor funcţii de nivel înalt, implementate chiar în modulul GDI
sau chiar în codul aplicaţiei. De exemplu, pentru desenarea unei linii nu trebuie decât să apelaţi de
mai multe ori funcţia de „scriere" a unui pixel şi să modificaţi corespunzător coordonatele x şi y.

În realitate, puteţi să executaţi orice operaţie de desenare folosind numai funcţiile de scriere şi de
citire a unui pixel, doar dacă nu vă deranjează să aşteptaţi rezultatele. Pentru un sistem grafic este
mult mai eficientă manipularea operaţiilor de desenare a unei linii sau a altor operaţii grafice
complexe la nivelul driverului dispozitivului, care poate conţine un cod optimizat pentru executarea
acestor operaţii. Mai mult, pe măsură ce tehnologia de afişare devine tot mai complexă, plăcile
specializate conţin coprocesoare tot mai performante, care permit chiar componentelor video
hardware să facă desenarea figurilor.

Interfaţa Windows GDI conţine şi funcţiile SetPixel şi GetPixel. Deşi vom folosi funcţia SetPixel în
programul CONNECT din Capitolul 7, în programele de grafică propriu-zisă aceste funcţii sunt
rareori folosite. În majoritatea cazurilor, cea mai simplă primitivă grafică vectorială trebuie să fie
considerată linia.

Windows poate să deseneze linii drepte, linii eliptice (linii curbe, pe circumferinţa unei elipse) şi
curbe Bezier. Cele şapte funcţii acceptate de Windows 95 pentru desenarea liniilor sunt LineTo (linii
drepte), Polyline şi PolylineTo (o serie de linii drepte conectate), PolyPolyline (mai multe linii
poligonale), Arc (linii eliptice), PolyBezier şi PolyBezierTo. (Interfaţa GDI din Windows NT
acceptă încă trei funcţii pentru desenarea liniilor: ArcTo, AngleArc şi PolyDraw. Aceste funcţii nu
sunt însă acceptate în Windows 95.) Aspectul liniilor desenate cu aceste funcţii poate fi influenţat de
cinci atribute ale contextului de dispozitiv: poziţia curentă a peniţei (numai pentru funcţiile LineTo,
PolylineTo şi PolyBezierTo), peniţa selectată, modul de afişare a fondului (numai pentru peniţele
care nu desenează cu o culoare compactă), culoarea fondului (pentru modul OPAQUE de afişare a
fondului) şi modul de desenare.

Din motive pe care le voi arăta puţin mai târziu, tot în această secţiune voi prezenta şi funcţiile
Rectangle, Ellipse, RoundRect, Chord şi Pie, chiar dacă aceste funcţii sunt folosite şi pentru
desenarea suprafeţelor pline.

Funcţia LineTo este una dintre puţinele funcţii GDI care nu are nevoie de dimensiunile complete ale
obiectului ce urmează să fie desenat. Funcţia LineTo desenează o linie de la „poziţia curentă"
definită în contextul de dispozitiv pană la punctul specificat la apelarea funcţiei (exclusiv acest
punct). Poziţia curentă este folosită ca punct de plecare şi de alte funcţii GDI. În contextul de
dispozitiv prestabilit, poziţia curentă are iniţial valoarea (0,0). Dacă apelaţi funcţia LineTo fără să
stabiliţi mai întâi poziţia curentă, funcţia desenează o linie pornind din colţul din stânga-sus al zonei
client.

Dacă doriţi să desenaţi o linie din punctul (xStart, yStart) pană în punctul (xEnd, yEnd) trebuie să
apelaţi mai întâi funcţia MoveToEx ca să stabiliţi ca poziţie curentă punctul (xStart, ySfart):

MoveToEx (hdc, xStart, yStart, &pt) ;

unde pt este o structură de tip POINT, definită în fişierele antet din Windows astfel:

typedef struct tag POINT

LONG x ;

LONG y ;

POINT ;

Funcţia MoveToEx nu desenează nimic, ci doar schimbă poziţia curentă. Poziţia curentă anterioară
este stocată în structura de tip POINT. Puteţi apoi să folosiţi funcţia LineTo ca să desenaţi linia:

LineTo (hdc, xEnd, yEnd);

Apelul de mai sus desenează o linie până în punctul (xEnd, yEnd), exclusiv acest punct. După apelul
funcţiei LineTo, poziţia curentă devine (xEnd, yEnd).

O scurtă notă istorică: în versiunile pe 16 biţi ale sistemului de operare Windows, funcţia folosită
pentru modificarea poziţiei curente era MoveTo, care avea numai trei parametri - variabila handle a
contextului de dispozitiv şi coordonatele x şi y. Funcţia MoveTo returna poziţia curentă anterioară
sub forma a două valori pe 16 biţi împachetate într-un întreg fără semn pe 32 de biţi. În versiunile pe
32 de biţi ale sistemului de operare (Windows NT şi Windows 95) coordonatele sunt valori pe 32 de
biţi. Deoarece în versiunea pe 32 de biţi a limbajului C nu este definit un tip de date întreg pe 64 de
biţi, funcţia MoveTo a fost înlocuită cu funcţia MoveToEx. Această modificare a fost necesară chiar
dacă valoarea returnată de funcţia MoveTo nu a fost folosită aproape niciodată cu adevărat în
programare !

Iată o veste bună: dacă nu aveţi nevoie de poziţia curentă anterioară - ceea ce se întâmplă destul de
des - puteţi să daţi ultimului parametru al funcţiei MoveToEx valoarea NULL. De fapt, dacă doriţi să
transformaţi codul pe 16 biţi existent în cod pentru Windows 95, puteţi să definiţi o
macroinstrucţiune, astfel:

#define MoveTo (hdc, x, y) MoveToEx (hdc, x, y, NULL)

Vom folosi această macroinstrucţiune în mai multe programe din capitolele următoare.

Iată însă şi o veste proastă. Chiar dacă în Windows 95 coordonatele par a fi valori pe 32 de biţi, din
acestea sunt folosiţi numai cei 16 biţi mai puţin semnificativi. Coordonatele pot avea valori numai
între -32.768 şi 32.767.

Puteţi să obţineţi poziţia curentă a peniţei apelând funcţia GetCurrentPosition astfel:

GetCurrentPositionEx (hdc, &pt) ;

Secvenţa de cod care urmează desenează în zona client a ferestrei o grilă formată din linii aflate la
distanţa de 100 de pixeli una de alta, începând din colţul din stânga-sus. Variabila hwnd reprezintă
variabila handle a ferestrei, hdc este variabila handle a contextului de dispozitiv, iar x şi y sunt
numere întregi:

GetClientRect (hwnd, &rect);

for (x = 0 ; x < rect.right ; x +=100)

MoveToEx (hdc, x, 0, NULL) ;

LineTo (hdc, x, rect.bottom) ;

for (y = 0 ; y < rect.bottom ; y +=100)

MoveToEx (hdc, 0, y. NULL) ;

LineTo   (hdc, rect.right, y) ;


}

Deşi pare incomodă folosirea a două funcţii pentru desenarea unei singure linii, poziţia curentă
devine utilă atunci când trebuie să desenaţi o serie de linii conectate. De exemplu, să presupunem că
definiţi o matrice de cinci puncte (10 valori) care marchează un dreptunghi:

POINT pt [5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;

Remarcaţi faptul că ultimul punct este acelaşi cu primul. Acum trebuie doar să folosiţi funcţia
MoveToEx pentru primul punct şi apoi funcţia LineTo pentru punctele următoare:

MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ;

for (i = 1; i < 5; i++) LineTo (hdc, pt[i].x, pt[i].y) ;

Deoarece funcţia LineTo desenează o linie care începe la punctul curent şi se termină la punctul
specificat la apelare (exclusiv acesta), nici una dintre coordonate nu este scrisă de două ori de codul
de mai sus. Deşi suprascrierea unui punct nu este, în mod normal, o problemă în cazul monitoarelor
video, s-ar putea să nu aibă un rezultat prea plăcut în cazul unui plotter sau în alte moduri de afişare
(despre care vom discuta în curând).

Atunci când aveţi o matrice de puncte pe care vreţi să le conectaţi prin linii, puteţi să desenaţi mai
uşor liniile folosind funcţia Polyline. Instrucţiunea de mai jos desenează acelaşi dreptunghi ca şi
secvenţa de cod din exemplul anterior:

Polyline (hdc, pt, 5) ;

Ultimul parametru reprezintă numărul de puncte. Această valoare putea fi reprezentată şi prin
expresia (sizeof(pt) / sizeof(POINT)). Funcţia Polyline are acelaşi efect ca şi o funcţie MoveToEx
urmată de mai multe funcţii LineTo, exceptând faptul că funcţia Polyline nu schimbă poziţia curentă.
Funcţia PolylineTo este puţin diferită. Aceasta foloseşte ca punct de plecare poziţia curentă şi
stabileşte ca poziţie curentă sfârşitul ultimei linii desenate. Codul de mai jos desenează acelaşi
dreptunghi folosit ca exemplu şi mai sus:

MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ;

PolylineTo (hdc, pt + 1,4) ;

Deşi puteţi să folosiţi funcţiile Polyline şi PolylineTo şi pentru un număr mic de funcţii, acestea sunt
mai utile atunci când desenaţi o curbă complexă formată din sute sau chiar mii de linii. De exemplu,
să presupunem că vreţi să desenaţi o sinusoidă. Programul SINEWAVE, prezentat în Figura 4-4, vă
arată cum să faceţi acest lucru.

/*-----------------------------------------
   SINEWAVE.C -- Sine Wave Using Polyline

                 (c) Charles Petzold, 1996

  -----------------------------------------*/

#include <windows.h>

#include <math.h>

#define NUM    1000

#define TWOPI  (2 * 3.14159)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SineWave" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Sine Wave Using Polyline",

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

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxClient, cyClient ;

     HDC         hdc ;

     int         i ;

     PAINTSTRUCT ps ;
     POINT       pt [NUM] ;

     switch (iMsg)

          {

          case WM_SIZE:

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

               return 0 ;

          case WM_PAINT:

               hdc = BeginPaint (hwnd, &ps) ;

               MoveToEx (hdc, 0,        cyClient / 2, NULL) ;

               LineTo   (hdc, cxClient, cyClient / 2) ;

               for (i = 0 ; i < NUM ; i++)

                    {

                    pt[i].x = i * cxClient / NUM ;

                    pt[i].y = (int) (cyClient / 2 *

                                    (1 - sin (TWOPI * i / NUM))) ;

                    }

               Polyline (hdc, pt, NUM) ;

               return 0 ;

          case WM_DESTROY:

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;


     }

Figura 4-4. Programul SINEWAVE

Acest program conţine o matrice cu 1000 de structuri POINT. Odată cu incrementarea variabilei de
ciclare a ciclului for de la 0 la 999 este incrementată şi variabila membru x de la 0 la cxClient.
Variabila membru y a structurii primeşte valorile corespunzătoare unui ciclu al unei sinusoide lărgite
astfel încât să umple zona client. Curba este desenată cu funcţia Polyline. Deoarece funcţia Polyline
este implementată la nivelul driverului, desenarea se face mult mai repede decât dacă ar fi fost
apelată de 1000 de ori funcţia LineTo. Rezultatele programului sunt prezentate în Figura 4-5.

Figura 4-5. Fereastra afişată de programul SINEWAVE.

Dreptunghiuri de încadrare

În continuare aş vrea să discutăm despre funcţia Arc, funcţie care desenează o curbă eliptică. Dar
funcţia Arc nu prea are sens dacă nu discutăm mai întâi despre funcţia Ellipse, care, la rândul ei, nu
are sens fără o discuţie despre funcţia Rectangle. Si dacă discutăm despre funcţiile Rectangle şi
Ellipse, putem la fel de bine să discutăm şi despre funcţiile RoundRect, Chord şi Pie.
Problema este că funcţiile Rectangle, Ellipse, RoundRect, Chord şi Pie nu sunt tocmai nişte funcţii
de desenare a liniilor. Desigur, ele desenează şi linii, dar şi colorează zona închisă, cu pensula
curentă de colorare a suprafeţelor. În mod prestabilit, această pensulă are culoarea albă, aşa că s-ar
putea ca operaţia să nu fie atât de evidentă atunci când încercaţi pentru prima dată să utilizaţi aceste
funcţii. Deşi, în sens strict, funcţiile aparţin unei alte secţiuni din capitolul de faţă, „Desenarea
suprafeţelor pline", vom discuta acum despre fiecare.

Funcţiile de mai sus sunt asemănătoare prin faptul că sunt construite pe baza unui „dreptunghi de
încadrare" („bounding box"). Dumneavoastră definiţi o casetă care încadrează obiectul -
dreptunghiul de încadrare - şi Windows desenează obiectul în acest dreptunghi.

Cea mai simplă dintre aceste funcţii desenează un dreptunghi:

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

Punctul (xLeft, yTop) reprezintă colţul din stânga-sus al dreptunghiului, iar punctul (xRight,
yBottom) reprezintă colţul din dreapta-jos. O figură desenată prin apelarea funcţiei Rectangle e
prezentată în Figura 4-6. Laturile dreptunghiului sunt întotdeauna paralele cu laturile orizontală şi
verticală ale ecranului.

Figura 4-6. O figură desenată folosind funcţia Rectangle.

Programatorii care au mai lucrat cu sisteme grafice sunt deja familiarizaţi probabil cu problema
pixelului suplimentar. Unele sisteme grafice desenează figurile astfel încât să includă şi
coordonatele din dreapta şi de jos, iar alte sisteme grafice desenează figurile până la aceste
coordonate (fără să le includă). Windows foloseşte cea de-a doua metodă; voi prezenta în continuare
o comparaţie, care vă va ajuta să înţelegeţi mai bine mecanismul.

Să presupunem că avem următorul apel de funcţie:

Rectangle (hdc, 1, 1, 5, 4) ;

Am menţionat mai sus faptul că Windows desenează figura într-un dreptunghi de încadrare. Puteţi
să vă gândiţi la ecran ca la o grilă în care fiecare pixel este o celulă delimitată de liniile grilei. Caseta
de încadrare imaginară este desenată pe grilă, apoi dreptunghiul este desenat în limitele casetei. Iată
cum va fi desenată figura respectivă:
Zona care separă dreptunghiul de marginile din stânga şi de sus ale zonei client are lăţimea de un
pixel. Windows foloseşte pensula curentă pentru colorarea celor doi pixeli din interiorul
dreptunghiului.

După ce aţi aflat cum se desenează un dreptunghi, folosind aceiaşi parametri, veţi putea, la fel de
simplu, să desenaţi şi o elipsă:

Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;

În Figura 4-7 este prezentată o figură (împreună cu dreptunghiul de încadrare) desenată cu ajutorul
funcţiei Ellipse.
Funcţia pentru desenarea unui dreptunghi cu colţurile rotunjite foloseşte acelaşi dreptunghi de
încadrare ca şi funcţiile Rectangle şi Ellipse, dar are încă doi parametri:

RoundRect (hdc, xLeft, yTop, xRight, yBottom, xCornerEllipse, yCornerEllipse) ;

În Figura 4-8 este prezentată o figură desenată cu această funcţie.


Pentru desenarea colţurilor rotunjite, Windows foloseşte o mică elipsă. Lăţimea elipsei este
xCornerEllipse iar înălţimea acesteia este yCornerEllipse. Imaginaţi-vă că Windows împarte elipsa
în patru părţi egale, folosind fiecare sfert pentru câte un colţ al dreptunghiului. Colţurile
dreptunghiului sunt cu atât mai rotunjite cu cât valorile parametrilor xCornerEllipse şi
yCornerEllipse sunt mai mari. Dacă xCornerEllipse este egal cu jumătate din diferenţa dintre xLeft
şi xRight iar yCornerEllipse este egal cu jumătate din diferenţa dintre yTop şi yBottom, atunci
funcţia RoundRect va desena o elipsă.

Colţurile rotunjite ale dreptunghiului din Figura 4-8 au fost desenate folosind o elipsă ale cărei
dimensiuni au fost calculate cu următoarele formule:

xCornerEllipse = (xRight - xLeft) / 4 ;

yCornerEllipse = (yTop - yBottom) / 4 ;

Aceasta este o abordare simplistă, iar rezultatele nu arată foarte bine, deoarece rotunjirea colţurilor
este mai pronunţată pe latura mai mare a dreptunghiului. Pentru corectarea acestei probleme,
probabil veţi stabili dimensiuni reale pentru parametrii xCornerEllipse şi yCornerEllipse.

Funcţiile Arc, Chord şi Pie primesc aceiaşi parametri:

Arc (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;

Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;

Pie (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;

În Figura 4-9 este prezentată o linie desenată cu ajutorul funcţiei Arc; Figurile 4-10 şi 4-11 prezintă
desene realizate cu ajutorul funcţiilor Chord şi Pie. Windows foloseşte o linie imaginară ca să lege
punctul definit de coordonatele xStart, yStart cu centrul elipsei. Din punctul în care această linie
intersectează dreptunghiul de încadrare, Windows începe să deseneze o linie pe circumferinţa
elipsei. De asemenea, Windows foloseşte o linie imaginară ca să lege punctul (xEnd, yEnd) cu
centrul elipsei. În punctul în care această linie intersectează dreptunghiul de încadrare, Windows
încheie desenul.
În cazul funcţiei Arc, Windows şi-a terminat treaba, deoarece arcul este o linie eliptică, nu o
suprafaţă plină. În cazul funcţiei Chord, Windows uneşte capetele arcului, iar în cazul funcţiei Pie,
uneşte capetele arcului cu centrul elipsei. Interioarele suprafeţelor închise obţinute astfel sunt
colorate cu pensula curentă.

S-ar putea să vă întrebaţi de ce trebuie să folosiţi aceste poziţii de început şi de sfârşit în funcţiile
Arc, Chord şi Pie. De ce să nu specificaţi chiar punctele de început şi de sfârşit ale arcului pe
circumferinţa elipsei? Ei bine, puteţi să o faceţi, dar mai întâi trebuie să determinaţi unde se află
aceste puncte, pe când metoda folosită de Windows rezolvă problema fără să fie nevoie de o
asemenea precizie.

Programul LINEDEMO prezentat în Figura 4-12 desenează un dreptunghi, o elipsă, un dreptunghi


cu colţurile rotunjite şi două linii, dar nu în această ordine. Programul demonstrează faptul că
funcţiile care desenează suprafeţe închise colorează aceste suprafeţe, deoarece liniile desenate
anterior sunt ascunse în spatele elipsei. Rezultatele rulării programului LINEDEMO sunt prezentate
în Figura 4-13.

/*--------------------------------------------------

   LINEDEMO.C -- Line-Drawing Demonstration Program

                 (c) Charles Petzold, 1996

  --------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "LineDemo" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Line Demonstration",

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

          }
     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxClient, cyClient ;

     HDC         hdc ;

     PAINTSTRUCT ps ;

     switch (iMsg)

          {

          case WM_SIZE:

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

               return 0 ;

          case WM_PAINT:

               hdc = BeginPaint (hwnd, &ps) ;

               Rectangle (hdc,     cxClient / 8,     cyClient / 8,

                               7 * cxClient / 8, 7 * cyClient / 8) ;

               MoveToEx  (hdc,        0,        0, NULL) ;

               LineTo    (hdc, cxClient, cyClient) ;

               MoveToEx  (hdc,        0, cyClient, NULL) ;

               LineTo    (hdc, cxClient,        0) ;

               Ellipse   (hdc,     cxClient / 8,     cyClient / 8,

                               7 * cxClient / 8, 7 * cyClient / 8) ;

               RoundRect (hdc,     cxClient / 4,     cyClient / 4,


                               3 * cxClient / 4, 3 * cyClient / 4,

                                   cxClient / 4,     cyClient / 4) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY:

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 4-12. Programul LINEDEMO.

Curbe Bezier (Bezier splines)

Termenul „spline" se referea odată la o riglă flexibilă din lemn, metal sau cauciuc (florar), folosită
pentru desenarea curbelor. De exemplu, dacă aveaţi un număr de puncte aparţinând unui grafic şi
doreaţi să desenaţi o curbă între acestea (prin interpolare sau extrapolare) trebuia să marcaţi mai
întâi punctele pe hârtie. Ancorând apoi rigla în punctele respective, nu vă rămânea decât să trasaţi
curba de-a lungul riglei. (Vă rog să nu râdeţi. Metoda evocată seamănă cu practici utilizate în secolul
trecut, dar eu ştiu din proprie experienţă că riglele mecanice şi riglele de calcul erau încă folosite în
anumite instituţii acum 15 ani.)
Figura 4-13. Fereastra afişată de programul LINEDEMO.

În prezent, „spline" se referă la o funcţie matematică. Aceste funcţii au diferite forme. Curbele
Bezier sunt unele dintre cele mai cunoscute pentru programarea grafică. Această funcţie este o
achiziţie destul de recentă în arsenalul instrumentelor grafice de lucru disponibile la nivelul
sistemului de operare şi are o origine mai puţin obişnuită. În anii '60, compania de automobile
Renault trecea de la proiectarea manuală a caroseriilor (care implica folosirea argilei) la o proiectare
asistată de calculator. Erau necesare instrumente matematice, iar Pierre Bezier a pus la punct un set
de formule care s-au dovedit foarte utile pentru rezolvarea acestei probleme.

De atunci, datorită formei bidimensionale, curba Bezier s-a dovedit a fi cea mai utilă curbă pentru
grafica pe calculator. De exemplu, în limbajul PostScript funcţiile Bezier sunt folosite pentru toate
curbele - elipsele sunt aproximare prin curbe Bezier De asemenea, funcţiile Bezier sunt folosite
pentru definirea contururilor caracterelor din fonturile PostScript. (Fonturile TrueType folosesc o
formă simplificată, mai rapida, a curbelor.)

O curbă Bezier este definită prin patru puncte - două capete şi două puncte de control. Capetele
curbei sunt ancorate în cele două puncte finale. Punctele de control acţionează ca nişte „magneţi"
care deformează linia dreaptă dintre cele două puncte finale. Acest lucru este ilustrat cel mai bine de
un program interactiv, numit Bezier prezentat în Figura 4-14.

/*---------------------------------------

   BEIZER.C -- Bezier Splines Demo


               (c) Charles Petzold, 1996

  ---------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Bezier" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

     wndclass.cbSize        = sizeof (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 ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;


     hwnd = CreateWindow (szAppName, "Bezier Splines",

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

          }

     return msg.wParam ;

     }

void DrawBezier (HDC hdc, POINT apt[])

     {

     PolyBezier (hdc, apt, 4) ;

     MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;

     LineTo   (hdc, apt[1].x, apt[1].y) ;

     MoveToEx (hdc, apt[2].x, apt[2].y, NULL) ;

     LineTo   (hdc, apt[3].x, apt[3].y) ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {
     static POINT apt[4] ;

     HDC          hdc ;

     int          cxClient, cyClient ;

     PAINTSTRUCT  ps ;

     switch (iMsg)

          {

          case WM_SIZE:

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

               apt[0].x = cxClient / 4 ;

               apt[0].y = cyClient / 2 ;

               apt[1].x = cxClient / 2 ;

               apt[1].y = cyClient / 4 ;

               apt[2].x =     cxClient / 2 ;

               apt[2].y = 3 * cyClient / 4 ;

               apt[3].x = 3 * cxClient / 4 ;

               apt[3].y =     cyClient / 2 ;

               return 0 ;

          case WM_MOUSEMOVE:

               if (wParam & MK_LBUTTON || wParam & MK_RBUTTON)

                    {

                    hdc = GetDC (hwnd) ;

                    SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

                    DrawBezier (hdc, apt) ;


                    if (wParam & MK_LBUTTON)

                         {

                         apt[1].x = LOWORD (lParam) ;

                         apt[1].y = HIWORD (lParam) ;

                         }

                    if (wParam & MK_RBUTTON)

                         {

                         apt[2].x = LOWORD (lParam) ;

                         apt[2].y = HIWORD (lParam) ;

                         }

                    SelectObject (hdc, GetStockObject (BLACK_PEN)) ;

                    DrawBezier (hdc, apt) ;

                    ReleaseDC (hwnd, hdc) ;

                    }

               return 0 ;

          case WM_PAINT:

               InvalidateRect (hwnd, NULL, TRUE) ;

                          hdc = BeginPaint (hwnd, &ps) ;

               DrawBezier (hdc, apt) ;

                          EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY:

               PostQuitMessage (0) ;

               return 0 ;
          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 4-14. Programul BEZIER.

Deoarece acest program foloseşte unele tehnici de prelucrare a mesajelor de la mouse pe care le vom
studia abia în Capitolul 6, nu vom discuta modul intern de lucru al programului (deşi acesta poate să
vi se pară evident). În schimb, puteţi să folosiţi programul ca să experimentaţi modul de funcţionare
a curbelor Bezier. În acest program, cele două puncte finale ale curbei sunt stabilite, pe verticală, la
jumătatea zonei client şi, pe orizontală, la 1/4 şi 3/4 din lăţimea zonei client. Cele două puncte de
control pot fi mutate, primul - apăsând butonul din stânga al mouse-ului şi deplasând mouse-ul până
când indicatorul Iui ajunge în locul dorit, iar al doilea -apăsând butonul din dreapta al mouse-ului şi
deplasând mouse-ul. Figura 4-15 prezintă un rezultat tipic.

În afara curbei Bezier, programul desenează şi două linii drepte: una de la primul capăt al curbei (cel
din stânga) până la primul punct de control, şi a doua de la celălalt capăt al curbei (din dreapta) până
la al doilea punct de control.

Funcţiile Bezier sunt considerate utile pentru proiectarea asistată de calculator, datorită câtorva
caracteristici:

În primul rând, cu puţină practică, de obicei puteţi să manipulaţi curba până ajunge la o formă
apropiată de cea dorită.

În al doilea rând, curbele Bezier sunt foarte uşor de controlat. În unele variante ale funcţiilor spline,
curba nu trece prin nici unul din punctele care o definesc. Curbele Bezier, însă, sunt întotdeauna
ancorate în cele două puncte finale. (Aceasta este una dintre ipotezele de la care porneşte calculul
formulei Bezier.) De asemenea, unele forme ale funcţiilor spline au puncte de singularitate, din care
curba se întinde la infinit. În proiectarea asistată de calculator acest lucru este de cele mai multe ori
evitat. Ca urmare, curbele Bezier sunt întotdeauna limitate de un patrulater (numit „carcasă
convexă" - „convex hull") format prin unirea punctelor finale şi a punctelor de control.

În al treilea rând, o altă caracteristică a curbelor Bezier implică relaţiile dintre punctele finale şi
punctele de control. Curba este întotdeauna tangentă la linia trasată de la primul punct final la primul
punct de control şi are întotdeauna aceeaşi direcţie cu această linie. (Această caracteristică este
ilustrată vizual de programul BEZIER.) De asemenea, curba este întotdeauna tangentă la linia trasată
de la al doilea punct final la al doilea punct de control şi are întotdeauna aceeaşi direcţie cu această
linie. (Acestea sunt alte două ipoteze de la care este derivată formula Bezier.)

În al patrulea rând, curbele Bezier sunt satisfăcătoare şi din punct de vedere estetic. Ştiu că acesta
este un criteriu subiectiv, dar nu este numai părerea mea.
Înainte de apariţia sistemului de operare Windows 95, trebuia să creaţi propriile curbe Bezier
folosind funcţia Polyline. De asemenea, trebuia să cunoaşteţi următoarele ecuaţii parametrice ale
curbelor Bezier:

x(t) = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3

y(t) = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3

unde (x0, y0) este punctul de început al curbei, (x3, y3) este punctul de sfârşit al curbei, iar cele două
puncte de control sunt (x1, y1) şi (x2, y2). Curba este trasată pentru t având valori de la 0 la 1.

Figura 4-15. Fereastra afişată de programul BEZIER.

În Windows 95 nu mai este nevoie să ştiţi aceste formule. Pentru trasarea uneia sau a mai multor
curbe Bezier conexe, puteţi să folosiţi instrucţiunea:

PolyBezier (hdc, pt, iCount) ;

sau instrucţiunea:

PolyBezierTo (hide, pt, iCount) ;

În ambele cazuri, pt este o matrice de structuri POINT. Pentru funcţia PolyBezier primele patru
puncte specifică (în această ordine) punctul de început, primul punct de control, al doilea punct de
control şi punctul final al curbei Bezier. Următoarele curbe Bezier au nevoie doar de trei puncte,
deoarece punctul de început al următoarei curbe Bezier este punctul de sfârşit al primei curbe, şi aşa
mai departe. Parametrul iCount reprezintă numărul de puncte din matrice, adică unu plus de trei ori
numărul curbelor pe care vreţi să le desenaţi.

Funcţia PolyBezierTo foloseşte poziţia curentă ca punct de început pentru prima curbă Bezier.
Fiecare curbă desenată are nevoie doar de trei puncte. La returnarea funcţiei, poziţia curentă este
punctul final al ultimei curbe desenate.

Atenţie: atunci când trasaţi o serie de curbe Bezier conectate între ele, legătura va fi lină numai dacă
al doilea punct de control al primei curbe, punctul de sfârşit al primei curbe (care este şi punctul de
început al celei de-a doua curbe) şi primul punct de control al celei de-a doua curbe sunt coliniare,
adică se află pe aceeaşi linie dreaptă.

Folosirea peniţelor de stoc

Atunci când apelaţi oricare dintre funcţiile de desenare a liniilor despre care am discutat, Windows
foloseşte pentru desenarea liniilor „peniţa" (pen) curentă selectată în contextul de dispozitiv. Peniţa
selectată determină culoarea, lăţimea şi tipul de linie (acestea pot fi continue, punctate sau
întrerupte). Peniţa din contextul prestabilit de dispozitiv se numeşte BLACK_PEN. Aceasta
desenează o linie compactă, de culoare neagră, cu grosimea de un pixel, indiferent de modul de
mapare. BLACK_PEN este una dintre cele trei peniţe „de stoc" (stock pen) furnizate de Windows.
Celelalte două peniţe de stoc sunt WHITE_PEN şi NULL_PEN. NULL_PEN este o peniţă care nu
desenează nimic. În plus, puteţi să creaţi peniţe proprii.

În programele Windows puteţi ajunge la aceste peniţe prin variabile handle. În fişierele antet din
Windows este definit tipul de date HPEN, care reprezintă o variabilă handle a unei peniţe. Puteţi să
definiţi o variabilă (de exemplu, hPen) folosind această definiţie de tip:

HPEN hPen ;

Puteţi să obţineţi variabila handle a uneia dintre peniţele de stoc apelând funcţia GetStockObject. De
exemplu, să presupunem că vreţi să folosiţi peniţa de stoc numită WHITE_PEN. Obţineţi variabila
handle a acesteia astfel:

hPen = GetStockObject (WHITE_PEN) ;

Apoi trebuie să selectaţi în contextul de dispozitiv peniţa a cărei variabilă aţi obţinut-o, apelând
funcţia SelectObject:

SelectObject (hdc, hPen) ;

După acest apel toate liniile pe care le desenaţi vor folosi peniţa WHITE_PEN, până când selectaţi o
altă peniţă în contextul de dispozitiv sau ştergeţi contextul de dispozitiv.

În loc să definiţi explicit variabila hPen, puteţi să combinaţi funcţiile GetStockObject şi SelectObject
într-o singură instrucţiune:

SelectObject (hdc, GetStockObject (WHITE_PEN)) ;


Dacă apoi vreţi să reveniţi la peniţa BLACK_PEN, puteţi să obţineţi o variabilă handle a acesteia şi
să o selectaţi în contextul de dispozitiv tot cu o singură instrucţiune:

SelectObject (hdc, GetStockObject (WHITE_PEN));

Funcţia SelectObject returnează variabila handle a peniţei selectate anterior în contextul de


dispozitiv. Dacă deschideţi un nou context de dispozitiv şi executaţi instrucţiunea:

hPen = SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

atunci peniţa curentă selectată în contextul de dispozitiv va fi WHITE_PEN şi hPen va fi variabila


handle a peniţei BLACK_PEN. Puteţi apoi să selectaţi peniţa BLACK_PEN în contextul de
dispozitiv cu următoarea instrucţiune:

SelectObject (hdc, hPen) ;

Crearea, selectarea şi ştergerea peniţelor

Deşi folosirea peniţelor definite ca obiecte de stoc este foarte convenabilă, aveţi la dispoziţie doar un
număr limitat de opţiuni: o peniţă neagră, pentru linie continuă, una albă pentru linie continuă sau
una care nu desenează nimic. Dacă doriţi ceva diferit, trebuie să creaţi propriile dumneavoastră
peniţe. Iată procedura generală: creaţi mai întâi o „peniţă logică" (logical pen) care este doar o
descriere a unei peniţe, folosind funcţiile CreatePen sau CreatePenIndirect. (De asemenea, puteţi să
folosiţi şi funcţia ExtCreatePen, despre care vom discuta atunci când vom ajunge la căile GDI.)
Aceste funcţii returnează o variabilă handle a peniţei logice. Selectaţi peniţa în contextul de
dispozitiv apelând funcţia SelectObject. Apoi puteţi să desenaţi linii cu peniţa selectată. La un
moment dat nu poate fi selectată, într-un context de dispozitiv, decât o peniţă. După ce ştergeţi
contextul de dispozitiv (sau după ce selectaţi o altă peniţă în contextul de dispozitiv) puteţi să
ştergeţi peniţa logică pe care aţi creat-o apelând funcţia DeleteObject. După ce faceţi acest lucru,
variabila handle a peniţei nu mai este validă.

O peniţă logică este un „obiect GDI". Dumneavoastră creaţi şi folosiţi peniţa, dar aceasta nu aparţine
programului, ci modulului GDI. Peniţa este unul dintre cele şase obiecte GDI pe care puteţi să le
creaţi. Celelalte cinci sunt pensulele, imaginile bitmap, regiunile, fonturile şi paletele. Exceptând
paletele, toate celelalte obiecte pot fi selectate în contextul de dispozitiv folosind funcţia
SelectObject.

Pentru folosirea obiectelor GDI trebuie să respectaţi următoarele reguli:

Ø  La sfârşitul programului ştergeţi toate obiectele GDI pe care le-aţi creat.

Ø  Nu ştergeţi obiectele GDI în timp ce sunt selectate într-un context de dispozitiv valid.

Ø  Nu ştergeţi obiectele de stoc.

Chiar dacă sunt nişte reguli rezonabile, neplăcerile pot exista. Voi prezenta câteva exemple ca să
înţelegeţi cum funcţionează aceste reguli şi pentru a putea evita eventuale probleme.
Sintaxa generică a funcţiei CreatePen este:

hPen = CreatePen (iPenStyle, iWidth, rgbColor) ;

Parametrul iPenStyle precizează dacă se desenează o linie continuă, o linie punctată sau una
întreruptă. Parametrul iPenStyle poate fi unul dintre identificatorii definiţi în fişierele antet din
Windows. Figura 4-16 prezintă stilul de linie produs de fiecare identificator.

Figura 4-16. Cele şapte stiluri de peniţe.

Pentru stilurile PS_SOLID, PS_NULL şi PS_INSIDEFRAME, parametrul iWidth reprezintă


grosimea liniei. Dacă iWidth are valoarea 0, liniile desenate de Windows vor avea grosimea de un
pixel. Liniile desenate cu peniţele de stoc au grosimea de un pixel. Dacă parametrul iWidth are o
valoare mai mare de 1, Windows va desena o linie continuă.

Parametrul rgbColor din funcţia CreatePen este un număr întreg fără semn reprezentând culoarea
peniţei. Pentru toate stilurile de peniţe, exceptând PS_INSIDEFRAME, atunci când selectaţi peniţa
în contextul de dispozitiv, Windows converteşte acest parametru la cea mai apropiată culoare pură
pe care o poate reprezenta dispozitivul de afişare. PS_INSIDEFRAME este singurul stil care poate
folosi culori amestecate, dar numai pentru grosimi mai mari de un pixel.

Stilul PS_INSIDEFRAME are şi o altă particularitate atunci când este folosit pentru funcţii care
definesc o suprafaţă plină: pentru toate stilurile de peniţe, exceptând PS_INSIDEFRAME, dacă
grosimea liniei este mai mare de un pixel, peniţa este centrată pe linie, astfel încât o parte a liniei
poate să fie desenată în afara dreptunghiului de încadrare; în cazul folosirii stilului
PS_INSIDEFRAME, linia este desenată în întregime în interiorul dreptunghiului de încadrare.

Puteţi să creaţi o peniţă şi cu ajutorul unei structuri de tip LOGPEN („logical pen"), apelând funcţia
CreatePenIndirect. Dacă programul foloseşte un număr mai mare de peniţe diferite pe care Ie
iniţializaţi în codul sursă, această metodă este mai eficientă. Mai întâi definiţi o variabilă de tip
LOGPEN, cum ar fi logpen:

LOGPEN logpen ;
Această structură are trei membri: lopnStyle (UINT) este stilul peniţei, lopn Width (POINT) este
grosimea peniţei în unităţi logice, iar lopnColor (COLORREF) este culoarea peniţei. Membrul lopn
Width este o structură de tip POINT, dar Windows foloseşte numai membrul lopnWidth.x al acestei
structuri şi ignoră membrul lopnWidth.y. Apoi creaţi peniţa, transmiţând funcţiei CreatePenIndirect
adresa structurii logpen:

hPen = CreatePenIndirect (&logpen) ;

De asemenea, puteţi să obţineţi informaţii despre o peniţă logică existentă. Dacă aveţi deja o
variabilă handle a unei peniţe puteţi să copiaţi datele care definesc peniţa logică într-o structură de
tip LOGPEN folosind funcţia GetObject:

GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;

Remarcaţi faptul că funcţiile CreatePen şi CreatePenIndirect nu au nevoie de o variabilă handle a


contextului de dispozitiv. Aceste funcţii creează peniţe logice care nu au nici o legătură cu contextul
de dispozitiv până când nu apelaţi funcţia SelectObject. De exemplu, puteţi să folosiţi aceeaşi peniţă
logică pentru mai multe dispozitive, cum ar fi imprimanta şi ecranul.

Iată o metodă pentru crearea, selectarea şi ştergerea peniţelor. Să presupunem că programul


foloseşte trei peniţe: una neagră cu grosimea de un pixel, una roşie cu grosimea de trei pixeli şi una
neagră cu linie punctată. Mai întâi definiţi variabilele pentru stocarea variabilelor handle ale celor
trei stilouri:

static HPEN hPen1, hPen2, hPen3;

În timpul prelucrării mesajului WM_CREATE, creaţi cele trei peniţe:

hPen1 = CreatePen (PS_SOLID, 1, 0);

hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0));

hPen3 = CreatePen (PS_DOT, 0, 0);

În timpul prelucrării mesajului WM_PAINT (sau în orice alt moment în care aveţi o variabilă handle
validă a contextului de dispozitiv) puteţi să selectaţi oricare dintre aceste peniţe în contextul de
dispozitiv şi să desenaţi:

SelectObject (hdc, hPen2) ;

[funcţii de desenare a liniilor]

SelectObject (hdc, hPen1) ;

[alte funcţii de desenare a liniilor]

În timpul prelucrării mesajului WM_DESTROY puteţi să ştergeţi cele trei peniţe create:
DeleteObject (hPen1) ;

DeleteObject (hPen2) ;

DeleteObject (hPen3) ;

Aceasta este metoda cea mai directă pentru crearea, selectarea şi ştergerea peniţelor, dar are
dezavantajul că peniţele create ocupă spaţiu în memorie pe toată durata rulării programului. O altă
soluţie este să creaţi peniţele necesare în timpul prelucrării fiecărui mesaj WM_PAINT şi să le
ştergeţi după apelarea funcţiei EndPaint. (Puteţi să le ştergeţi şi înainte de apelarea funcţiei
EndPaint, dar trebuie să aveţi grijă să nu ştergeţi peniţa curentă selectată în contextul de dispozitiv.)

De asemenea, puteţi să creaţi peniţele atunci când sunt necesare, combinând funcţiile CreatePen şi
SelectObject în aceeaşi instrucţiune:

SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Din acest moment, veţi folosi o peniţă pentru linii întrerupte de culoare roşie. După ce terminaţi de
făcut desenele cu acest tip de linie, puteţi să ştergeţi peniţa. Hopa! Cum puteţi să ştergeţi peniţa dacă
nu aţi salvat variabila handle a acesteia? Vă amintiţi că funcţia SelectObject returnează variabila
handle a peniţei selectate anterior în contextul de dispozitiv. Aşa că puteţi să ştergeţi peniţa
selectând în contextul de dispozitiv peniţa de stoc BLACK_PEN şi ştergând variabila handle
returnată de funcţia SelectObject:

DeleteObject (SelectObject (hdc, GetStockObject (BLACK PEN))) ;

Există şi o altă metodă. Atunci când selectaţi în contextul de dispozitiv o peniţă nou creată, salvaţi
variabila handle returnată de funcţia SelectObject;

hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Ce este hPen? Dacă aceasta este prima apelare a funcţiei SelectObject de la obţinerea contextului de
dispozitiv, atunci hPen este variabila handle a obiectului de stoc BLACK_PEN. Puteţi acum să
selectaţi peniţa BLACK_PEN în contextul de dispozitiv şi să o ştergeţi pe cea creată (variabila
handle returnată la a doua apelare a funcţiei SelectObject) printr-o singură instrucţiune:

DeleteObject (SelectObject (hdc, hPen)) ;

Umplerea golurilor

Folosirea peniţelor pentru linii punctate sau pentru linii întrerupte ridică o întrebare interesantă: ce
se întâmplă cu pauzele dintre puncte sau dintre liniuţe? Culoarea acestor spaţii depinde de atributele
pentru culoarea fondului şi de modul de desenare a fondului, definite în contextul de dispozitiv.

Modul prestabilit de desenare a fondului este OPAQUE, ceea ce înseamnă că Windows umple
spaţiile cu culoarea fondului, care în mod prestabilit este alb. Rezultatele sunt aceleaşi în cazul
pensulei WHITE_BRUSH, folosită de majoritatea programelor în clasa ferestrei pentru ştergerea
fondului.
Puteţi să schimbaţi culoarea fondului pentru completarea spaţiilor goale dintre linii, prin apelarea
funcţiei SetBkColor:

SetBkColor (hdc, rgbColor) ;

Ca şi în cazul valorii rgbColor folosită pentru culoarea peniţei, Windows converteşte valoarea
specificată într-o culoare pură. Puteţi să obţineţi culoarea definîtă în contextul de dispozitiv prin
apelarea funcţiei GetBkColor.

De asemenea, puteţi să împiedicaţi colorarea spaţiilor stabilind modul TRANSPARENT de desenare


a fondului:

SetBkMode (hdc, TRANSPARENT) ;

Windows va ignora culoarea fondului şi nu va mai colora spaţiile goale. Modul de desenare a
fondului (TRANSPARENT sau OPAQUE) poate fi obţinut cu ajutorul funcţiei GetBkMode.

Moduri de desenare

Apectul liniilor desenate pe ecran este afectat şi de modul de desenare definit în contextul de
dispozitiv. Imaginaţi-vă că desenaţi o linie a cărei culoare este bazată nu numai pe culoarea peniţei,
ci şi pe culoarea originală a suprafeţei pe care se face desenarea. Imaginaţi-vă o cale prin care să
puteţi folosi aceeaşi peniţă ca să desenaţi o linie albă pe fond negru, dar şi o linie neagră pe fond alb,
ştiind ce culoare are fondul. Dacă vă este utilă această facilitate, puteţi să o obţineţi prin schimbarea
modului de desenare.

Atunci când foloseşte o peniţă pentru desenarea unei linii, Windows face de fapt o operaţie booleană
între pixelii peniţei şi pixelii de pe suprafaţa destinaţie. Efectuarea operaţiei booleene între pixeli se
numeşte „operaţie rastru" (ROP - raster operation).

Deoarece desenarea unei linii implică numai două modele de pixeli (peniţa şi destinaţia), operaţia
booleană se numeşte „operaţie rastru binară" sau ROP2. Windows defineşte 16 coduri ROP2 care
specifică modul de combinare între pixelii peniţei şi pixelii suprafeţei, în contextul de dispozitiv
prestabilit, modul de desenare definit este R2_COPYPEN, ceea ce înseamnă că Windows copiază
pixelii peniţei pe suprafaţa destinaţie - adică modul normal în care suntem obişnuiţi să funcţioneze o
peniţă. Există alte 15 coduri ROP2.

Care este originea celor 16 coduri ROP2 diferite? Pentru exemplificare, să presupunem că avem un
sistem monocrom. Culoarea destinaţiei (culoarea zonei client a ferestrei) poate fi negru (reprezentată
prin valorea 0) sau alb (1). La fel, culoarea peniţei poate fi alb sau negru. Există patru combinaţii
posibile în cazul folosirii unei peniţe alb/negru pe o suprafaţă alb/negru: alb pe alb, alb pe negru,
negru pe alb şi negru pe negru.

Ce se întâmplă cu destinaţia după ce desenaţi cu peniţa? O posibilitate este ca linia să fie desenată
întotdeauna cu negru, indiferent de culoarea peniţei şi a destinaţiei: acest mod de desenare este
indicat de codul R2_BLACK. O altă posibilitate este ca linia să fie desenată cu negru, exceptând
situaţia în care atât peniţa cât şi destinaţia sunt negre, caz în care linia este desenată cu alb. Deşi pare
puţin ciudat, Windows are un nume pentru acest mod de desenare: R2_NOTMERGEPEN. Windows
face o operaţie orientată pe biţi de tip SAU între pixelii destinaţie şi pixelii peniţei, şi inversează
rezultatul.

Tabelul de mai jos prezintă toate cele 16 moduri de desenare ROP2. Tabelul indică modul în care
sunt combinate culoarea peniţei (P) şi culoarea destinaţiei (D) pentru obţinerea culorii afişate. În
coloana „Operaţia booleană" sunt folosite notaţiile C pentru indicarea modului în care sunt
combinaţi pixelii peniţei cu pixelii destinaţiei.

Peniţă (P): 1 1 0 0 Operaţia Mod de desenare


Destinaţie (D): booleană
1 0 1 0
Rezultate: 0 0 0 0 0 R2_BLACK
  0 0 0 1 ~(P | D) R2_NOTMERGEPEN
  0 0 1 0 ~P & D R2_MASKNOTPEN
  0 0 1 1 ~P R2_NOTCOPYPEN
  0 1 0 0 P & ~D R2_MASKPENNOT
  0 1 0 1 ~D R2_NOT
  0 1 1 0 P^D R2_XORPEN
  0 1 1 1 ~(P & D) R2_NOTMASKPEN
  1 0 0 0 P&D R2_MASKPEN
  1 0 0 1 ~(P ^ D) R2_NOTXORPEN
  1 0 1 0 D R2_NOP
  1 0 1 1 ~P | D R2_MERGENOTPEN
  1 1 0 0 P R2_COPYPEN
(prestabilit)
  1 1 0 1 P | ~D R2_MERGEPENNOT
  1 1 1 0 P|D R2_MERGEPEN
  1 1 1 1 1 R2_WHITE

Puteţi să selectaţi un nou mod de desenare în contextul de dispozitiv astfel:

SetROP2 (hdc, iDrawMode) ;

unde parametrul iDrawMode este una dintre valorile din coloana „Mod de desenare" a tabelului de
mai sus. Puteţi să obţineţi modul curent de desenare astfel:

iDrawMode = GetROP2 (hdc) ;

Modul de desenare prestabilit al contextului de dispozitiv este R2_COPYPEN, care transferă la


destinaţie culoarea peniţei. Modul R2_NOTCOPYPEN desenează cu alb dacă peniţa este neagră şi
cu negru dacă peniţa este albă. Modul R2_BLACK desenează întotdeauna cu negru, indiferent de
culoarea peniţei şi a destinaţiei. În mod similar, modul R2_WHITE desenează întotdeauna cu alb.
Modul R2_NOP este o „operaţie nulă" - destinaţia rămâne nemodificată.

Pentru început, am folosit ca exemplu un sistem monocrom. În realitate, pe un monitor monocrom


Windows poate simula diferite tonuri de gri prin amestecarea pixelilor albi cu cei negri. Atunci când
desenează cu o peniţă pe un fond având culori amestecate, Windows execută o operaţie orientată pe
biţi pentru fiecare pixel. Modul R2_NOT inversează întotdeauna destinaţia, indiferent de culoarea
acesteia şi de culoarea peniţei. Acest mod de desenare este util atunci când nu cunoaşteţi culoarea
destinaţiei, deoarece liniile vor fi întotdeauna vizibile. (Sau aproape întotdeauna, pentru că în cazul
unui fond 50% gri, liniile vor fi virtual invizibile.) Folosirea modului R2_NOT este ilustrată în
programul BLOKOUT din Capitolul 6.

Desenarea suprafeţelor pline

Să mergem un pas mai departe şi să trecem la desenarea figurilor. Cele şapte funcţii Windows
pentru desenarea figurilor sunt prezentate în tabelul următor:

Funcţie Figura  
Rectangle Dreptunghi cu colţuri drepte

Ellipse Elipsă
 
RoundRect Dreptunghi cu colţuri rotunjite

Chord Arc pe circumferinţa unei elipse, având capetele unite printr-o coardă
Pie Suprafaţă de forma unei felii de plăcintă, reprezentând un segment de
elipsă.
Polygon
PolyPolygon Figură geometrică având mai multe laturi Mai multe figuri geometrice cu
mai multe laturi

Windows desenează conturul figurilor folosind peniţa curentă selectată în contextul de dispozitiv.
Pentru acest contur sunt folosite atributele stabilite pentru modul de desenare a fondului, culoarea
fondului şi modul de desenare, ca şi în cazul desenării unei linii simple. Tot ceea ce aţi învăţat
despre linii se aplică şi în cazul contururilor.

Figurile sunt umplute folosind pensula selectată în contextul de dispozitiv. În mod prestabilit,
aceasta este pensula de stoc WHITE_BRUSH, ceea ce înseamnă că interiorul figurilor va fi umplut
cu alb. Windows defineşte şase pensule de stoc: WHITE_BRUSH, LTGRAY_BRUSH,
GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH şi NULL_BRUSH (sau
HOLLOW_BRUSH).

Puteţi să selectaţi una dintre aceste pensule în contextul de dispozitiv la fel cum selectaţi o peniţă de
stoc. Windows defineşte tipul HBRUSH ca variabilă handle a unei pensule, aşa că puteţi să definiţi
mai întâi o variabilă de acest tip:

HBRUSH hBrush ;

Puteţi să obţineţi variabila handle a pensulei de stoc GRAY_BRUSH apelând funcţia


GetStockObject:.

hBrush = GetStoCkObject (GRAY_BRUSH) ;

Puteţi să o selectaţi apoi în contextul de dispozitiv folosind funcţia SelectObject:


SelectObject (hdc, hBrush) ;

Din acest moment, figurile desenate vor avea interiorul gri.

Dacă vreţi să desenaţi o figură fără contur, selectaţi în contextul de dispozitiv peniţa NULL_PEN:

SelectObject (hdc, GetStockObject (NULL_PEN)) ;

Dacă vreţi să desenaţi o figură fără să îi umpleţi interiorul, selectaţi în contextul de dispozitiv
pensula NULL_BRUSH:

SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

Aşa cum puteţi să creaţi peniţe proprii, puteţi să creaţi şi pensule proprii. Vom trata în curând şi
acest subiect

Funcţia Polygon şi modul de umplere a poligoanelor

Am discutat deja despre primele cinci funcţii de desenare a suprafeţelor pline. Polygon este cea de-a
şasea funcţie de desenare a unei figuri colorate în interior, cu contur. Apelul funcţiei Polygon este
asemănător cu cel al funcţiei Polyline:

Polygon (hdc, pt, iCount) ;

Parametrul pt este un pointer la o matrice de structuri POINT, iar iCount reprezintă numărul de
puncte din matrice. Dacă ultimul punct din matrice este diferit de primul punct, Windows mai
desenează o linie care conectează ultimul şi primul punct. (Acest lucru nu se întâmplă şi în cazul
funcţiei Polyline.)

Suprafaţa închisă de poligon este colorată folosind pensula curentă în două moduri, în funcţie de
modul de umplere a poligoanelor, definit în contextul de dispozitiv. Modul prestabilit de umplere a
poligoanelor este ALTERNATE, ceea ce înseamnă că Windows colorează numai suprafeţele închise
la care se poate ajunge din exteriorul poligonului prin traversarea unui număr impar de laturi (1, 3, 5
şi aşa mai departe). Celelalte suprafeţe interioare nu sunt umplute. Dacă selectaţi WINDING ca mod
de umplere, Windows colorează toate suprafeţele închise de poligon. Puteţi să selectaţi modul de
umplere cu ajutorul funcţiei SetPolyFillMode:

SetPolyFillMode (hdc, iMode) ;

Cele două moduri de umplere a poligoanelor sunt ilustrate cel mai bine pe modelul unei stele cu
cinci colţuri. În Figura 4-17 steaua din partea stângă a fost desenată cu modul de umplere
ALTERNATE, iar steaua din partea dreaptă a fost desenată cu modul de umplere WINDING.
Figura 4-17. Figuri desenate cu cele două moduri de umplere a poligoanelor ALTERNATE (stângă)
şi WINDING

Umplerea suprafeţelor interioare

Interiorul figurilor Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon şi PollyPolygon este umplut
cu pensula - numită uneori şi „model" („pattern") - selectată în contextul de dispozitiv. O pensulă
este de fapt o imagine bitmap 8x8 repetată pe verticală şi pe orizontală, pentru umplerea unei
suprafeţe.

Atunci când Windows foloseşte metoda amestecării culorilor (dithering) pentru afişarea unui număr
mai mare de culori decât ar fi în mod normal disponibile pe monitorul respectiv, de fapt foloseşte o
pensulă pentru fiecare culoare. Pe un ecran monocrom, Windows poate să afişeze 64 de tonuri de gri
prin amestecarea pixelilor pentru alb cu cei pentru negru. Pentru negru toţi biţii din matricea 8x8 au
valoarea zero. Unul dintre cei 64 de biţi are valoarea 1 (adică alb) pentru primul ton de gri, doi biţi
au valoarea 1 pentru al doilea ton de gri şi aşa mai departe, până când toţi cei 64 de biţi au valoarea
1 ca să se obţină albul pur. În cazul unui monitor color, culorile amestecate sunt tot imagini bitmap,
dar domeniul disponibil de culori este mult mai mare.

Windows conţine patru funcţii pe care puteţi să le folosiţi pentru crearea pensulelor logice. Selectaţi
o pensulă în contextul de dispozitiv folosind funcţia SelectObject. Ca şi peniţele logice, pensulele
logice sunt obiecte GDI. Trebuie să ştergeţi toate pensulele pe care le-aţi creat, dar nu trebuie să le
ştergeţi cât timp sunt selectate în contextul de dispozitiv.

Iată prima funcţie pentru crearea unei pensule logice:

hBrush = CreateSolidBrush (rgbColor) ;

Cuvântul Solid din numele acestei funcţii nu înseamnă că pensula foloseşte o culoare pură. Atunci
când selectaţi pensula în contextul de dispozitiv, Windows creează o imagine bitmap 8x8 pentru
culorile amestecate şi foloseşte imaginea respectivă atunci când este necesară pensula selectată.
Puteţi să creaţi şi o pensulă „haşurată" cu linii orizontale, verticale sau oblice. Acest stil de pensule
este folosit frecvent pentru colorarea barelor din diagrame sau grafice şi pentru desenele executate
de plottere. Funcţia care creează o pensulă haşurată este:

hBrush = CreateHatchBrush (iHatchStyle, rgbColor) ;

Parametrul iHatchStyle precizează aspectul haşurii şi poate avea una dintre următoarele valori:
HS_HORIZONTAL, HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGONAL, HS_CROSS şi
HS_DIAGCROSS. Figura 4-18 prezintă haşurile produse de fiecare dintre stilurile de mai sus.

Parametrul rgbColor din funcţia CreateHatchBrush reprezintă culoarea liniilor cu care se face
haşurarea. Atunci când selectaţi pensula în contextul de dispozitiv, Windows converteşte această
culoare în cea mai apropiată culoare pură. Spaţiul dintre liniile de haşură sunt colorate în funcţie de
modul de desenare a fondului şi de culoarea fondului, definite în contextul de dispozitiv. Dacă
modul de desenare a fondului este OPAQUE, culoarea fondului (care este convertită, la rândul ei,
într-o culoare pură) este folosită pentru umplerea spaţiilor dintre linii, în acest caz, nici liniile de
haşură, nici culoarea de umplere nu pot fi culori amestecate. Dacă modul de desenare a fondului este
TRANSPARENT, Windows desenează liniile de haşura fără să umple spaţiile dintre acestea.

Figura 4-18. Cele şase stiluri de haşura.

Deoarece pensulele sunt întotdeauna imagini bitmap 8x8, aspectul pensulelor haşurate va depinde de
rezoluţia dispozitivului pe care se face afişarea. Fiecare dintre haşurile din Figura 4-18 a fost
desenată într-un dreptunghi de 32x16 pixeli, ceea ce înseamnă că imaginea bitmap 8x8 a fost
repetată de patru ori pe orizontală şi de două ori pe verticală. Pe o imprimantă laser cu rezoluţia 300
dpi (dots per inch) acelaşi dreptunghi de 32x16 pixeli ar ocupa o suprafaţă cu înălţimea de 1 /9 inci
si lăţimea de 1/19 inci.

Cu ajutorul funcţiei CreatePatternBrush puteţi să creaţi pensule proprii, bazate pe o imagine bitmap:

hBrush = CreatePatternBrush (hBitmap) ;

Vom discuta despre această funcţie în secţiunea rezervată imaginilor bitmap din capitolul de faţă.

Windows conţine o funcţie care poate înlocui toate celelalte trei funcţii de creare a pensulelor
(CreateSolidBrush, CreateHatchBrush şi CreatePatternBrush):

hBrush = CreateBrushIndirect (&logbrush) ;


Variabila logbrush este o structură de tip LOGBRUSH („logical brush"). Cele trei câmpuri ale
structurii sunt prezentate mai jos. Valoarea câmpului lbStyle determină modul în care Windows
interpretează celelalte două câmpuri:

lbStyle (UINT) lbColor (COLORREF) lbHatch (LONG)


BS_SOLID Culoarea pensulei Ignorat
BS_HOLLOW Ignorat Ignorat
BS_HATCHED Culoarea haşurii Stilul de haşură
BS_PATTERN Ignorat Variabila handle a unei imagini bitmap

Am folosit mai devreme funcţia SelectObject ca să selectăm în contextul de dispozitiv o peniţă


logică, funcţia DeleteObject ca să ştergem o peniţă logică şi funcţia GetObject ca să obţinem
informaţii despre o peniţă logică. Puteţi să folosiţi aceleaşi funcţii şi pentru pensule. Dacă aveţi o
variabilă handle a unei pensule, puteţi să selectaţi pensula în contextul de dispozitiv folosind funcţia
SelectObject:

SelectObject (hdc, hBrush) ;

O pensulă creată poate fi ştearsă cu ajutorul funcţiei DeleteObject:

DeleteObject (hBrush) ;

Totuşi, nu ştergeţi pensula selectată în contextul de dispozitiv. Dacă vreţi să obţineţi informaţii
despre o pensulă, apelaţi funcţia GetObject:

GetObject (hBrush, sizeof (LOGBRUSH), (LPVOID) &logbrush) ;

unde variabila logbrush este o structură de tip LOGBRUSH („logical brush").

Modurile de mapare

Până acum am presupus că toate desenele se fac într-un sistem de coordonate raportate la colţul, din
stânga-sus al zonei client şi folosind ca unitate de măsură pixelul. Acesta este modul de lucru
prestabilit, dar nu este singurul mod de lucru.

Un atribut al contextului de dispozitiv care afectează aproape toate operaţiile de desenare din zona
client este „modul de mapare" („mapping mode"). Alte patru atribute ale contextului de dispozitiv -
originea ferestrei, originea vizorului (viewport), extensia ferestrei şi extensia vizorului - sunt strâns
legate de modul de mapare.

Majoritatea funcţiilor GDI primesc coordonate sau dimensiuni ca parametri. Iată, ca exemplu,
funcţia TextOut:

TextOut (hdc, x, y, szBuffer, iLength) ;


Parametrii x şi y indică poziţia în care începe textul. Parametrul x reprezintă poziţia pe axa
orizontală, iar parametrul y reprezintă poziţia pe axa verticală. Deseori, pentru indicarea acestui
punct este folosită notaţia (x, y).

În funcţia TextOut şi, de fapt, în toate funcţiile GDI, coordonatele sunt furnizate în „unităţi logice".
Windows trebuie să transforme „unităţile logice" în „unităţi de dispozitiv", adică în pixeli. Această
transformare este guvernată de modul de mapare, de originile ferestrei, ale vizorului şi de extensiile
ferestrei şi ale vizorului. De asemenea, modul de mapare stabileşte şi orientarea axelor x şi y, adică
determină sensul în care cresc valorile coordonatelor x şi y.

Windows defineşte opt moduri de mapare. Acestea sunt prezentate în tabelul următor, folosind
identificatorii definiţi în fişierele antet din Windows:

Mod de mapare Unităţi logice Creşterea valorilor axax axay


MM_TEXT Pixel Spre dreapta În jos
MM_LOMETRIC 0,1 mm Spre dreapta În sus
MM_HIMETRIC 0,01mm Spre dreapta În sus
MM_LOENGLISH 0,01 inci Spre dreapta În sus
MM_HIENGLISH 0,001 inci Spre dreapta Însus
MM_TWIPS* 1/1440 inci Spre dreapta În sus
MM_ISOTROPIC Arbitrar (x = y) Selectabil Selectabil
MM_ANISOTROPIC Arbitrar (x != y) Selectabil Selectabil

Puteţi să selectaţi modul de mapare folosind funcţia SetMapMode:

SetMapMode (hdc, iMapHode) ;

unde iMapMode este unul dintre cei opt identificatori definiţi pentru modurile de mapare. Puteţi să
obţineţi modul de mapare curent folosind funcţia GefMapMode:

iMapMode = GetMapMode (hdc) ;

Modul de mapare prestabilit este MM_TEXT. În acest mod de mapare unităţile logice sunt aceleaşi
cu unităţile fizice, ceea ce vă permite (sau, privind dintr-o altă perspectivă, vă forţează) să lucraţi în
pixeli. Într-un apel al funcţiei TextOut care arată astfel:

TextOut (hdc, 8, 16, szBuffer, iLength) ;

textul începe la o distanţă de opt pixeli faţă de marginea din stânga a zonei client şi de 16 pixeli faţă
de marginea de sus a acesteia.

Dacă modul de mapare este MM_LOENGLISH, o unitate logică este egală cu o sutime de inci:

SetMapHode (hdc, HM_LOENGLISH) ;

Acum apelul de mai sus al funcţiei TextOut trebuie să arate astfel:


TextOut (hdc, 50, -100, szBuffer, iLength) ;

Textul afişat începe la 0,5 inci faţă de marginea din stânga şi la 1 inci faţă de marginea de sus a
zonei client. (Motivul folosirii unei valori negative pentru coordonata y va deveni evident ceva mai
târziu, atunci când vom discuta în detaliu despre modurile de mapare.) Celelalte moduri de mapare
permit programului să specifice coordonatele în milimetri, în puncte pentru imprimantă sau într-un
sistem de coordonate arbitrar.

Dacă vi se pare convenabilă folosirea pixelilor ca unitate de măsură, puteţi să folosiţi numai modul
de mapare MM_TEXT. Dacă trebuie să afişaţi o imagine la dimensiunile reale în inci sau milimetri,
puteţi să obţineţi informaţiile necesare cu ajutorul funcţiei GetDeviceCaps şi să faceţi singur
scalarea. Celelalte moduri de mapare sunt doar metode convenabile de evitare a acestor operaţii de
scalare.

Indiferent de modul de mapare folosit, toate coordonatele specificate la apelarea funcţiilor Windows
trebuie să fie numere întregi cu semn având valori de la -32 768 la 32 767. În plus, unele funcţii care
folosesc coordonate pentru punctele de început şi de sfârşit ale unui dreptunghi cer ca lăţimea şi
înălţimea dreptunghiului să nu depăşească 32 767.

Coordonate de dispozitiv şi coordonate logice

S-ar putea să vă puneţi următoarea întrebare: „Dacă folosesc modul de mapare MM_LOENGLISH
voi primi mesajele WM_SIZE în sutimi de inci?" Bineînţeles că nu. Windows va folosi în
continuare coordonatele de dispozitiv pentru toate mesajele (cum ar fi WM_SIZE, WM_MOVE şi
WM_MOUSEMOVE), pentru toate funcţiile care nu fac parte din interfaţa GDI şi chiar pentru unele
funcţii GDI. Lucrurile se petrec în felul următor: modul de mapare fiind un atribut al contextului de
dispozitiv, are efect numai atunci când folosiţi funcţii GDI care primesc o variabilă handle a
contextului de dispozitiv ca parametru. GetSystemMetrics nu este o funcţie GDI, aşa că va returna în
continuare dimensiunile în unităţi de dispozitiv, adică în pixeli. Deşi GetDeviceCaps este o funcţie
GDI care primeşte ca parametru o variabilă handle a contextului de dispozitiv, Windows continuă să
returneze unităţi de dispozitiv pentru identificatorii HORZRES şi VERTRES, deoarece unul dintre
scopurile acestei funcţii este să furnizeze programului dimensiunea în pixeli a dispozitivului.

Totuşi, valorile din structura TEXTMETRIC pe care le obţineţi prin apelarea funcţiei
GetTextMetrics sunt furnizate în unităţi logice. Dacă modul de mapare este MM_LOENGLISH în
momentul apelării funcţiei, GetTextMetrics returnează lăţimea şi înălţimea caracterelor, în sutimi de
inci. Atunci când apelaţi funcţia GetTextMetrics ca să obţineţi înălţimea şi lăţimea caracterelor,
modul de mapare trebuie să fie acelaşi cu cel pe care îl veţi folosi atunci când afişaţi textul pe baza
acestor dimensiuni. Pe măsură ce voi prezenta diferite funcţii GDI în acest capitol, voi preciza dacă
acestea utilizează coordonate logice sau coordonate de dispozitiv. Toate funcţiile despre care am
discutat până acum utilizează coordonate logice, exceptând cele pentru stabilirea spaţiilor între
puncte sau liniuţe pentru stilurile de linii şi între liniile de haşură din modele. Acestea sunt
independente de modul de mapare.

Sistemele de coordonate ale dispozitivului

Windows mapează coordonatele logice specificate în funcţiile GDI la coordonatele dispozitivului.


Înainte de a discuta despre sistemele de coordonate logice folosite de diferitele moduri de mapare,
vom discuta despre diferitele sisteme de coordonate de dispozitiv definite în Windows pentru zona
de afişare. Deşi în general am lucrat doar în zona client a ferestrei, în anumite situaţii Windows
foloseşte alte două sisteme de coordonate de dispozitiv. În toate sistemele de coordonate de
dispozitiv sunt folosiţi pixelii ca unitate de măsură. Valorile de pe axa orizontală (x) cresc de la
stânga la dreapta iar valorile de pe axa verticală (y) cresc de sus în jos.

Atunci când folosim întregul ecran, spunem că lucrăm în „coordonate ecran". Colţul din stânga-sus
este punctul de coordonate (0, 0). Coordonatele ecran sunt folosite în mesajul WM_MOVE (pentru
alte ferestre decât ferestrele descendent) şi în următoarele funcţii Windows: CreateWindow şi
MoveWindow (ambele pentru alte ferestre decât ferestrele descendent), GetMessagePos,
GetCursorPos, SetCursorPos, GetWindowRect, WindowFromPoint şi SetBrushOrgEx. Acestea sunt
funcţii care fie nu au o fereastră asociată (cum ar fi cele două funcţii pentru cursor), fie trebuie să
mute (sau să găsească) o fereastră pe baza unui punct de pe ecran. Dacă folosiţi funcţia CreateDC cu
parametrul DISPLAY ca să obţineţi un context de dispozitiv pentru întregul ecran, atunci
coordonatele logice specificate la apelarea funcţiilor GDI vor fi mapate la coordonatele ecranului.

„Coordonatele de fereastră" se referă la întreaga fereastră a ecranului, inclusiv bara de titlu, meniu,
barele de derulare şi chenarul ferestrei. Pentru o fereastră normală, punctul (0, 0) este colţul din
stânga-sus al chenarului de redimensionare. Coordonatele de fereastră sunt folosite mai rar în
Windows, dar dacă obţineţi un context de dispozitiv cu ajutorul funcţiei GetWindowDC, atunci
coordonatele logice specificate la apelarea funcţiilor GDI vor fi mapate la coordonatele ferestrei.

Al treilea sistem de coordonate de dispozitiv - cu care vom lucra cel mai des -foloseşte
„coordonatele zonei client". Punctul (0,0) este colţul din stânga-sus al zonei client. Dacă obţineţi un
context de dispozitiv cu ajutorul funcţiei GetDC sau al funcţiei BeginPaint, atunci coordonatele
logice specificate Ia apelarea funcţiilor GDI vor fi mapate la coordonatele zonei client.

Puteţi să transformaţi coordonatele zonei client în coordonatele ecranului şi invers folosind funcţiile
ClientToScreen şi ScreenToClient. De asemenea, puteţi şă obţineţi poziţia şi dimensiunea întregii
ferestre în coordonate ecran folosind funcţia GetWindowRect. Aceste funcţii vă furnizează suficiente
informaţii ca să transferaţi coordonatele între oricare dintre sistemele de coordonate de dispozitiv.

Vizorul şi fereastra

Modurile de mapare definite în Windows stabilesc modul în care sunt mapate coordonatele logice
specificate în funcţiile GDI la coordonatele de dispozitiv, pe baza faptului că sistemul de coordonate
de dispozitiv folosit depinde de funcţia folosită pentru obţinerea contextului de dispozitiv. Pentru a
ne continua discuţia despre modurile de mapare, trebuie să mai facem o precizare legată de
terminologie: se spune că modul de mapare defineşte maparea coordonatelor „de fereastră"
(window) -coordonate logice - la coordonatele vizorului (viewport) - coordonate de dispozitiv.

Folosirea termenilor window (fereastră) şi viewport (vizor) nu este totuşi cea mai potrivită alegere.
În alte limbaje pentru interfeţele grafice, termenul viewport se referă la o regiune de decupare
(clipping region). Termenul window defineşte, în general, zona pe care o ocupă un program pe
ecran. În timpul discuţiilor de faţă va trebui să renunţăm la vechile definiţii ale acestor termeni.

Pentru vizor se folosesc coordonatele de dispozitiv (pixeli). De cele mai multe ori, vizorul coincide
cu zona client a ferestrei, dar poate să însemne şi coordonatele întregii ferestre sau coordonatele
întregului ecran, dacă aţi obţinut contextul de dispozitiv prin apelarea funcţiilor GetWindowDC sau
CreateDC. Punctul de coordonate (0, 0) este colţul din stânga-sus al zonei client (sau al ferestrei, ori
al ecranului). Valorile coordonatei x cresc către dreapta, iar valorile coordonatei y cresc în jos.

Pentru fereastră se utilizează coordonatele logice, care pot fi,exprimate în pixeli, milimetri, inci sau
orice altă unitate de măsură doriţi. Coordonatele logice ale ferestrei sunt specificate la apelarea
funcţiilor GDI.

Pentru toate modurile de mapare, Windows transformă coordonatele ferestrei (coordonate logice) în
coordonate ale vizorului (coordonate de dispozitiv) folosind două formule:

unde (xWindow, yWindow) este punctul în coordonate logice care trebuie translatat iar (xViewport,
yViewport) este punctul în coordonate de dispozitiv. În cazul în care coordonatele de dispozitiv sunt
coordonate ale zonei client sau coordonate ale ferestrei, Windows trebuie să le transforme în
coordonate ecran înainte de a desena un obiect.

Formulele de mai sus folosesc două puncte corespunzătoare originii ferestrei şi a vizorului:
(xWinOrg, yWinOrg) reprezintă originea ferestrei în coordonate logice, iar (xViewOrg, yViewOrg)
reprezintă originea, vizorului în coordonate de dispozitiv. În contextul de dispozitiv prestabilit,
aceste puncte au valoarea (0, 0), dar pot fi modificate. Formulele de mai sus implică faptul că
punctul (xWinOrg, yWinOrg) este întotdeauna mapat la punctul (xViewOrg, yViewOrg).

De asemenea, formulele de mai sus folosesc două puncte, pentru specificarea „extensiilor" ferestrei
şi vizorului: (xWinExt, yWinExt) reprezintă extensia, ferestrei în coordonate logice, iar (xViewExt,
yViewExt) reprezintă extensia vizorului în coordonate de dispozitiv. În majoritatea modurilor de
mapare, extensiile sunt implicate de modul de mapare şi nu pot fi modificate. Extensia în sine nu are
nici o semnificaţie, dar raportul între extensia vizorului şi extensia ferestrei reprezintă un factor de
scalare folosit pentru convertirea unităţilor logice în unităţi de dispozitiv. Extensiile pot avea valori
negative: aceasta înseamnă că nu este obligatoriu ca valorile pe axa x să crească spre dreapta şi
valorile pe axa y să crească în jos.

Windows poate să transforme şi coordonatele vizorului (coordonate de dispozitiv) în coordonate ale


ferestrei (coordonate logice):
Windows include două funcţii care vă permit să convertiţi punctele de dispozitiv în puncte logice şi
invers. Funcţia următoare converteşte punctele de dispozitiv în puncte logice:

DPtoLP (hdc, pPoints, iNumber) ;

Variabila pPoints este o matrice de structuri POINT, iar iNumber este numărul de puncte ce
urmează să fie convertite. Veţi vedea că această funcţie este foarte utilă pentru convertirea
dimensiunii zonei client obţinute prin apelarea funcţiei GetClientRect (care returnează valorile în
unităţi de dispozitiv) în coordonate logice:

GetClientRect (hwnd, &rect);

DPtoLP (hdc, (PPoint) &rect, 2);

Funcţia următoare converteşte punctele logice în puncte de dispozitiv:

LPtoDP (hdc, pPoints, iNumber) ;

Folosirea modului de mapare MM_TEXT

Pentru modul de mapare MM_TEXT, originile şi extensiile prestabilite sunt următoarele:

Originea ferestrei:               (0, 0)             Poate fi modificată

Originea vizorului:              (0, 0)             Poate fi modificată

Extensia ferestrei:              (1, 1)             Nu poate fi modificată

Extensia vizorului:             (1, 1)             Nu poate fi modificată

Raportul din extensia ferestrei şi extensia vizorului este 1, aşa că nu este necesară nici o operaţie de
scalare pentru transformarea coordonatelor logice în coordonate de dispozitiv. Formulele prezentate
anterior se reduc la acestea:

xViewport = xWindow - xWinOrg + xViewOrg

yViexport = yWindow - yWinOrg + yViewOrg

Acest mod de mapare se numeşte „mapare de tip text", nu fiindcă este cea mai potrivită pentru text,
ci datorită orientării axelor. În general, citim textul de la stânga spre dreapta şi de sus în jos, iar în
modul de mapare MM_TEXT, valorile cresc în acelaşi sens:
Windows furnizează funcţiile SetViewportOrgEx şi SetWindowOrgEx pentru modificarea originii
vizorului şi a ferestrei. Aceste funcţii au ca efect deplasarea axelor, astfel încât punctul de
coordonate (0, 0) nu se mai referă la colţul din stânga-sus al ecranului. În general, veţi folosi ori
funcţia SetViewportOrgEx, ori SetWindowOrgEx, dar nu pe amândouă.

Iată cum lucrează aceste funcţii: dacă schimbaţi originea vizorului la (xViewOrg, yViewOrg), atunci
punctul logic de coordonate (0, 0) va fi mapat la punctul de dispozitiv (xViewOrg, yViewOrg). Dacă
schimbaţi originea ferestrei la (xWinOrg, yWinOrg), atunci punctul logic (xWinOrg, yWinOrg) va fi
mapat la punctul de dispozitiv (0, 0). Indiferent de modificările pe care le faceţi asupra originii
ferestrei şi a vizorului, punctul de dispozitiv (0, 0) este întotdeauna colţul din stânga-sus al zonei
client.

De exemplu, să presupunem că zona client are lăţimea cxClient şi înălţimea cyClient. Dacă vreţi ca
punctul logic de coordonate (0, 0) să se afle în centrul zonei client, puteţi să apelaţi funcţia
următoare:

SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Argumentele funcţiei SetViewportEx sunt exprimate întotdeauna în unităţi de dispozitiv. Punctul


logic (0, 0) va fi acum mapat la punctul de dispozitiv (cxClient/2, cyClient/2). Din acest moment
folosiţi zona client ca şi cum ar avea următorul sistem de coordonate:
Valorile logice ale axei x sunt cuprinse în intervalul de la -cxClient/2 la +cxClient/2 iar valorile
logice ale axei y sunt cuprinse în intervalul de la -cyClient/2 la +cyClient/2. Colţul din dreapta-jos al
zonei client are coordonatele (cxClient/2, cyClient/2). Dacă vreţi să afişaţi text începând din colţul
din stânga-sus al zonei client, care are coordonatele de dispozitiv (0, 0), trebuie să folosiţi
coordonate logice negative:

TextOut (hdc, -cxClient/2, -cyClient/2, "Hello", 5) ;

Acelaşi rezultat poate fi obţinut cu ajutorul funcţiei SetWindowOrgEx în locul funcţiei


SetViewportOrgEx:

SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Argumentele funcţiei SetWindowOrgEx sunt exprimate întotdeauna în coordonate logice. După


apelul de mai sus, punctul logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv (0, 0),
care este colţul din stânga-sus al zonei client.

Ceea ce probabil nu doriţi să faceţi (decât dacă ştiţi ce rezultat veţi obţine) este să folosiţi cele două
funcţii împreună:

SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Aceasta înseamnă că punctul logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv
(cxClient/2, cyClient/2), rezultând următorul sistem de coordonate:
Puteţi să obţineţi originea vizorului apelând funcţia GetViewportOrgEx:

GetViewportOrgEx (hdc, &pt) ;

şi originea ferestrei apelând funcţia GetWindowOrgEx:

GetWindowOrgEx (hdc, &pt) ;

unde pt este o structură de tip POINT. Valorile returnate de funcţia GetViewportOrgEx sunt în
coordonate de dispozitiv, iar valorile returnate de funcţia GetWindowOrgEx sunt în coordonate
logice.

Uneori este de dorit să modificaţi originea ferestrei sau a vizorului ca să deplasaţi imaginea afişată
pe ecran - de exemplu, ca răspuns la acţionarea barei de derulare de către utilizator. Modificarea
originii nu determină deplasarea imediată a imaginii afişate. Mai întâi modificaţi originea, apoi
redesenaţi ecranul. De exemplu, în programul SYSMETS2 din Capitolul 2 am folosit valoarea
iVscrollPos (poziţia curentă a casetei de derulare de pe bara de derulare verticală) ca să ajustăm
coordonatele de afişare pe axa y:

case WM_PAINT :

BeginPaint (hwnd, &ps) ;

for 0 = 0; i<NUMLINES ; i++)

y = cyChar * (1 - iVscrollPos + 1) ;

[afişează textul]

}
EndPaint (hwnd, &ps) ;

return 0 ;

Putem să obţinem acelaşi rezultat cu ajutorul funcţiei SetWindowOrgEx:

case WM_PAINT :

BeginPaint (hwnd, &ps);

SetWindowOrgEx (ps.hdc, 0, cyChar * iVscrollPos) ;

for (i = 0; i < NUMLINES ; i++)

y = cyChar * (1 + i) ;

[afişează textul]

EndPaint (hwnd, &ps) ;

return 0 ;

Acum nu mai este nevoie să fie folosită valoarea iVscrollPos pentru calcularea coordonatei y pentru
funcţia TextOut. Aceasta înseamnă că puteţi să apelaţi funcţiile de afişare a textului dintr-o
subrutină, fără să transmiteţi subrutinei valoarea iVscrollPos, deoarece ajustarea poziţiei de afişare a
textului s-a făcut prin modificarea originii ferestrei.

Dacă aţi mai lucrat cu sistemele de coordonate rectangulare (carteziene), probabil deplasarea
punctului logic de coordonate (0, 0) în centrul zonei client vi se pare o operaţie logică. Totuşi,
modul de mapare MM_TEXT prezintă o mică problemă: de obicei, într-un sistem de coordonate
cartezian, valorile de pe axa y cresc în sus, pe când în modul de mapare MM_TEXT acestea cresc în
jos. In acest sens, modul de mapare MM_TEXT este un caz izolat, această problemă fiind corectată
de celelalte cinci moduri de mapare.

Modurile de mapare „metrice"

Windows include două moduri de mapare în care coordonatele logice sunt exprimate în unităţi
fizice. Deoarece coordonatele logice pe axele x şi y sunt mapate la unităţi fizice identice, aceste
moduri de mapare vă ajută să desenaţi cercuri „mai rotunde" şi pătrate „mai pătrate".
Cele cinci moduri de mapare „metrice" sunt prezentate mai jos în ordinea crescătoare a preciziei.
Cele două coloane din partea dreaptă prezintă dimensiunile unităţilor logice în inci (in.) şi în
milimetri (mm) pentru comparare:

Mod de mapare Unităţi logice Inci Milimetri


MM_LOENGLISH 0,01 in. 0,001 0,254
MM_LOMETRIC
MM_HIENGLISH 0,1 mm 0,00394 0,1
MM_TWIPS*
MM_HIMETRIC 0,001 in. 0,001 0,0254

1/1440 in. 0,000694 0,0176


0,000394
0,01 mm 0,01
* Un twip este 1/20 dintr-un punct (care este 1/72 dintr-un inci),
deci 1 /1440 dintr-un inci.

Ca să puteţi face o comparaţie între modul de mapare MM_TEXT şi aceste rezoluţii, trebuie să vă
amintesc faptul că, pe un monitor VGA standard, fiecare pixel este un pătrat cu latura de 0,325 mm,
ceea ce înseamnă că coordonatele unui dispozitiv VGA sunt mult mai grosiere decât oricare dintre
modurile de mapare metrice.

Pentru o imprimantă laser cu rezoluţia de 300 dpi (puncte pe inci) fiecare pixel are 0,0033 inci - o
rezoluţie mai mare decât cea a modurilor de mapare MM_LOENGLISH si MM_LOMETRIC, dar
mai mică decât cea a modurilor de mapare MM_HIENGLISH, MM_TWIPS şi MM_HIMETRIC.

Originile şi extensiile prestabilite sunt următoarele:

Originea ferestrei:                                (0,0)                        Poate fi modificată

Originea vizorului:                              (0,0)                        Poate fi modificată

Extensia ferestrei:                                 (?, ?)                       Nu poate fi modificată

Extensia vizorului:                              (?, ?)                       Nu poate fi modificjată

Extensiile ferestrei şi ale vizorului depind de modul de mapare şi de raportul de afişare a


dispozitivului. Aşa cum am menţionat anterior, extensiile nu sunt impor tante ca atare, având o
semnificaţie numai exprimate ca rapoarte. Iată care sunt formulele de transformare a coordonatelor:
De exemplu, pentru modul de mapare MM_LOENGLISH, Windows foloseşte extensiile pentru
următoarele calcule:

Pentru multe dispozitive de afişare (cum ar fi VGA) acest raport va fi mai mic decât 1. Deoarece
Windows lucrează numai cu numere întregi, folosirea unui raport în locul unui factor de scalare este
absolut necesară pentru o mai mare precizie în transformarea coordonatelor logice în coordonate de
dispozitiv, şi invers.

Remarcaţi semnul minus din faţa raportului extensiilor pe axa verticală. Acest semn modifică
orientarea axei y. Pentru cele cinci moduri de mapare, valorile y cresc pe măsură ce vă deplasaţi în
sus pe dispozitiv. Originile prestabilite ale ferestrei şi ale vizorului sunt (0,0). Consecinţele sunt
interesante: atunci când treceţi la unul dintre aceste cinci moduri de mapare, sistemul de coordonate
arată astfel:

Singurul mod în care puteţi să afişaţi ceva pe ecran este să folosiţi valori negative pentru coordonata
y. De exemplu, codul următor:

SetMapMode (hdc, MM_LOENGLISH) ;

TextOut (hdc, 100, -100, "Hello", 5 ) ;

afişează textul „Hello" la o distanţă de un inci de marginea din stânga şi de marginea de sus a zonei
client.

Ca să vă păstraţi judecata limpede, probabil veţi dori să schimbaţi această situaţie. O soluţie este să
mutaţi punctul de coordonate (0,0) în colţul din stânga-jos al zonei client. Presupunând că cyClient
este înălţimea zonei client în pixeli, puteţi să faceţi acest lucru apelând funcţia SetViewportOrgEx
astfel:

SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Acum sistemul de coordonate arată astfel:

O altă soluţie este să mutaţi punctul de coordonate (0,0) în centrul zonei client:

SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Acum sistemul de coordonate arată astfel:

Acum avem un sistem de coordonate cartezian, cu patru cadrane, cu aceleaşi unităţi de măsură pe
axele x şi y, în inci, milimetri sau twips.

Pentru mutarea punctului logic (0,0) puteţi să folosiţi şi funcţia SetWindowOrgEx, dar această cale
este puţin mai dificilă, deoarece parametrii funcţiei SetWindowOrgEx trebuie să fie în coordonate
logice. Mai întâi trebuie să transformaţi coordonatele de dispozitiv (cxClient, cyClient) în
coordonate logice, folosind funcţia DPtoLP. Presupunând că pt este o structură de tip POINT, codul
de mai jos mută punctul logic (0,0) în centrul zonei client:

pt.x = cxClient ;

pt.y = cyClient ;

DPtoLP (hdc, &pt, 1) ;

SetWindowOrgEx (hdc, -pt.x/2, -pt.y/2, NULL) ;

Moduri de mapare proprii

Ultimele două moduri de mapare se numesc MM_ISOTROPIC şi MM_ANISOTROPIC. Acestea


sunt singurele moduri de mapare care vă permit să modificaţi extensiile ferestrei şi ale vizorului,
ceea ce înseamnă că vă permit să modificaţi factorul de scalare pe care Windows îl foloseşte pentru
convertirea coordonatelor logice şi a coordonatelor de dispozitiv. Cuvântul „izotrop" înseamnă „egal
în toate direcţiile"; „anizotrop" este antonimul acestuia. Ca şi modurile de mapare metrice prezentate
mai înainte, MM_ISOTROPIC foloseşte axe scalate egal. Unităţile logice de pe axa x au aceeaşi
dimensiune fizică ca şi unităţile logice de pe axa y. Acest fapt vă ajută să creaţi imagini care
păstrează raportul corect de afişare, indiferent de raportul de afişare al dispozitivului.

Diferenţa dintre modurile de mapare metrice şi modul de mapare MM_ISOTROPIC constă în faptul
că puteţi să controlaţi dimensiunea fizică a unităţilor logice. Dacă doriţi, puteţi să ajustaţi
dimensiunea fizică a unităţilor logice în funcţie de dimensiunea zonei client, astfel încât imaginile pe
care le desenaţi să fie întotdeauna conţinute de zona client, mărindu-se şi micşorându-se
corespunzător. Programul ANACLOCK (ceasul analogic) din Capitolul 7 este un exemplu de
imagine izotropă. Ceasul este întotdeauna rotund. Dacă redimensionaţi fereastra, ceasul se redimen-
sionează automat. Un program Windows poate să manipuleze redimensionarea unei imagini prin
ajustarea extensiilor ferestrei şi ale vizorului. Apoi programul poate să folosească aceleaşi unităţi
logice la apelarea funcţiilor de desenare, indiferent de dimensiunea ferestrei.

Uneori, modurile de mapare metrice şi modul MM_TEXT sunt numite moduri de mapare „complet
restricţionate" („fully constrained"). Aceasta înseamnă că nu puteţi să modificaţi extensiile ferestrei
şi ale vizorului sau modul în care Windows scalează coordonatele logice faţă de coordonatele de
dispozitiv. MM_ISOTROPIC este un mod de mapare „partial restricţionat" („partly constrained").
Windows vă permite să modificaţi extensiile ferestrei şi ale vizorului, dar le ajustează astfel încât
unităţile logice x şi y să aibă aceleaşi dimensiuni fizice. Modul de mapare MM_ANISOTROPIC
este „nerestricţionat". Puteţi să modificaţi extensiile ferestrei şi ale vizorului, fără ca Windows să
mai facă vreo ajustare a valorilor.

Modul de mapare MM_ISOTROPIC

Modul de mapare MM_ISOTROPIC este ideal pentru folosirea unor axe arbitrare, cu unităţi logice
egale pe cele două axe. Dreptunghiurile cu lăţimi şi înălţimi logice egale sunt afişate ca pătrate.
Elipsele cu lăţimi şi înălţimi logice egale sunt afişate ca cercuri.

Atunci când selectaţi pentru prima dată modul de mapare MM_ISOTROPIC, Windows foloseşte
aceleaşi extensii pentru fereastră şi pentru vizor, ca şi pentru modul de mapare MM_LOMETRIC.
(Totuşi, nu vă bazaţi pe acest fapt.) Diferenţa este că acum puteţi să schimbaţi extensiile după cum
dopţi, apelând funcţiile SetWindowExtEx şi SetViewportExtEx. Windows va ajusta apoi aceste
extensii astfel încât unităţile logice de pe ambele axe să reprezinte distanţe fizice egale.

În general, veţi folosi ca parametri ai funcţiei SetWindowExtEx dimensiunile dorite pentru fereastra
logică, iar ca parametri ai funcţiei SetViewportExtEx dimensiunile reale ale zonei client. Atunci când
ajustează aceste extensii, Windows trebuie să încadreze fereastra logică în vizorul fizic, ceea ce
înseamnă că o parte a zonei client poate să rămână în afara ferestrei logice. Pentru folosirea mai
eficientă a spaţiului din zona client trebuie să apelaţi funcţia SetWindowExtEx înainte de apelarea
funcţiei SetViewportExtEx.

De exemplu, să presupunem că vreţi să folosiţi un sistem de coordonate virtual, „tradiţional", cu un


singur cadran în care punctul (0, 0) este colţul din stânga-jos al zonei client, iar lăţimea şi înălţimea
au valori de la 0 la 32.767. Dacă doriţi ca unităţile logice x şi y să aibă aceleaşi dimensiuni, iată ce
trebuie să faceţi:

SetMapMode (hdc, HH_ISOTROPIC) ;

SetWindowExtEx (hdc, 32767, 32767, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Dacă apoi obţineţi extensiile ferestrei şi ale vizorului apelând funcţiile GetWindowExtEx şi
GetViewportExtEx veţi vedea că acestea au alte valori decât cele specificate. Windows ajustează
extensiile în funcţie de raportul de afişare (aspect ratio) al dispozitivului, astfel încât unităţile logice
de pe cele două axe să reprezinte aceleaşi dimensiuni fizice.

Dacă zona client este mai mult lată decât înaltă (în dimensiuni fizice), Windows ajustează extensia x
astfel încât fereastra logică să fie mai mică decât vizorul zonei client. Fereastra logică va fi
poziţionată în partea stângă a zonei client:

Nu puteţi să afişaţi nimic în partea dreaptă a zonei client dincolo de capătul axei x, deoarece pentru
aceasta ar trebui să folosiţi o coordonată logică x mai mare de 32.767. Dacă zona client este mai
mult înaltă decât lată (în dimensiuni fizice), Windows ajustează extensia y. Fereastra logică va fi
poziţionată în partea de jos a zonei client:

Nu puteţi să afişaţi nimic în partea de sus a zonei client dincolo de capătul axei y, deoarece pentru
aceasta ar trebui să folosiţi o coordonată logică y mai mare de 32.767. Dacă preferaţi ca fereastra
logică să fie poziţionată întotdeauna în partea stângă şi în partea de sus a zonei client, puteţi să
modificaţi astfel codul precedent:

SetMapMode (hdc, HH_ISOTROPIC) ;

SetWindowExtEx (hdc, 32767, 32767, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

SetWindowOrgEx (hdc, 0, 32767, NULL) ;

În acest fel, prin apelul funcţiei SetWihdowOrgEx precizăm că dorim ca punctul logic (0,32767) să
fie mapat la punctul de dispozitiv (0,0). Dacă zona client este mai mult înaltă decât lată, sistemul de
coordonate este poziţionat astfel:
Pentru imaginile de genul celei folosite de programul ANACLOCK puteţi să folosiţi un sistem de
coordonate cartezian cu patru cadrane având axe arbitrar scalate în cele patru direcţii şi cu punctul
de coordonate (0, 0) în centrul zonei client. Dacă, de exemplu, vreţi ca fiecare axă să ia valori de la 0
la 1000, folosiţi codul următor:

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExtEx (hdc, 1000, 1000, NULL);

SetViewportExtEx (hdc, cxClient/2, -cyClient/2, NULL) ;

SetViewportOrgEx (hdc, cxClient/2, cyCllent/2, NULL) ;


Dacă zona client este mai mult lată decât înaltă, sistemul logic de coordonate arată astfel:

Sistemul logic de coordonate este centrat şi atunci când zona client este mai mult înaltă decât lată:

Reţineţi faptul că extensiile ferestrei şi ale vizorului nu implică nici o operaţie de decupare. La
apelarea funcţiilor GDI puteţi să folosiţi pentru coordonatele x şi y valori mai mici de -1000 sau mai
mari de +1000. În funcţie de forma zonei client, aceste puncte pot să fie sau să nu fie vizibile.
Folosind modul de mapare MM_ISOTROPIC puteţi să faceţi ca unităţile logice să fie mai mari
decât pixelii. De exemplu, să presupunem că doriţi să creaţi un sistem de coordonate în care punctul
de coordonate (0, 0) se află în colţul din stânga-sus al ecranului şi valorile de pe axa y cresc de sus în
jos (ca în modul de mapare MM_TEXT) dar cu coordonate logice măsurate în unităţi de 1/16 dintr-
un inci. Acest mod de mapare v-ar permite să desenaţi o riglă având un capăt în colţul din stânga-sus
al zonei client, cu diviziuni de şaisprezecimi de inci:

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExtEx(hdc, 160*GetDeviceCaps (hdc, HORZSIZE)/254,160*GetDeviceCaps(hdc,


VERTSIZE)/254, NULL);

SetViewportExtEx(hdc, GetDeviceCaps(hdc, HORZRES),GetDeviceCaps(hdc, VERTRES),


NULL);

În această secvenţă de cod extensiile vizorului sunt stabilite la dimensiunile în pixeli ale întregului
ecran. Extensiile ferestrei trebuie să fie stabilite la dimensiunile întregului ecran în unităţi de
şaisprezecimi de inci. Indicii HORZSIZE şi VERTSIZE ai funcţiei GetDeviceCaps returnează
dimensiunile dispozitivului în milimetri. Dacă lucraţi cu numere în virgulă mobilă, trebuie să
transformaţi milimetrii în inci împărţind valorile obţinute la 2,54, şi apoi să transformaţi incii în
şaisprezecimi de inci înmulţind rezultatul operaţiei anterioare cu 16. Deoarece aici lucrăm cu
numere întregi, am înmulţit rezultatul cu 160 şi l-am împărţit la 254.

Pentru majoritatea dispozitivelor, acest cod face ca unităţile logice să fie mult mai mari decât
unităţile fizice. Tot ce desenaţi pe dispozitiv va avea pentru coordonate valori mapate la multipli de
1/16 inci. Nu puteţi să desenaţi două linii orizontale aflate la distanţa de 1/32 de inci, deoarece
pentru aceasta aţi avea nevoie de o coordonată logică fracţionară.

Modul de mapare MM_ANISOTROPIC sau ajustarea imaginii

Atunci când stabiliţi extensiile ferestrei şi pe ale vizorului în modul de mapare MM_ISOTROPIC,
Windows ajustează valorile astfel încât unităţile logice de pe cele două axe să aibă aceleaşi
dimensiuni. În modul de mapare MM_ANISOTROPIC, Windows nu face nici o ajustare a valorilor
pe care le stabiliţi. Aceasta înseamnă că în modul de mapare MM_ANISOTROPIC nu este
obligatorie păstrarea raportului corect de afişare.

Pentru a folosi modul de mapare MM_ANISOTROPIC, stabiliţi un sistem arbitrar de coordonate


pentru zona client, ca şi pentru modul de mapare MM_ISOTROPIC. Codul de mai jos stabileşte
punctul de coordonate (0, 0) în colţul din stânga-jos al zonei client, iar axele de coordonate x şi y pot
lua valori de la 0 la 32.767:

SetMapMode (hdc, MM_ANISOTROPIC) ;

SetWindowExtEx (hdc, 32767, 32767, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

SetViewportOrgEx (hdc, 0, cyClient, NULL) ;


Totul despre tastatură
Tastatura – elemente de bază

Aşa cum probabil vă daţi seama, arhitectura bazată pe mesaje a sistemului de operare Windows este
ideală pentru lucrul cu tastatura. Programul „află" despre apăsarea unor taste prin intermediul
mesajelor care ajung la procedura de fereastră.

De fapt, lucrurile sunt puţin mai complicate: atunci când utilizatorul apasă şi eliberează tastele,
driverul de tastatură transmite sistemului de operare informaţiile legate de acţiunile asupra tastelor.
Windows salvează aceste acţiuni (sub formă de mesaje) în coada de aşteptare a sistemului. Mesajele
de la tastatură sunt apoi transferate, unul câte unul, în coada de mesaje a programului căruia îi
aparţine fereastra ce deţine „cursorul de intrare" (despre care vom discuta imediat). Programul
distribuie mesajele procedurii de fereastră corespunzătoare.

Motivul acestui proces în două etape - stocarea mesajelor mai întâi în coada de mesaje a sistemului
şi apoi transferarea acestora în coada de mesaje a aplicaţiilor - este legat de sincronizare. Atunci
când utilizatorul apasă pe taste într-un ritm mai rapid decât cel în care programul prelucrează
mesajele primite, Windows stochează acţiunile neprelucrate în coada de aşteptare a sistemului,
deoarece una dintre acţiunile asupra tastaturii poate avea ca efect comutarea cursorului de intrare
(input focus) către un alt program. În consecinţă, următoarele taste trebuie transmise celui de-al
doilea program.

Windows trimite programelor opt tipuri de mesaje prin care indică diferite evenimente de tastatură.
S-ar putea să pară numeroase, dar programul poate să ignore multe dintre acestea, fără probleme. De
asemenea, în majoritatea cazurilor, aceste mesaje conţin mai multe informaţii despre tastatură decât
aveţi nevoie în program. O parte a sarcinii de manipulare a tastaturii constă în a şti ce mesaje sunt
importante.

Ignorarea tastaturii
Deşi tastatura este principala sursă de intrări de la utilizatori a unui program pentru Windows,
programele nu trebuie să reacţioneze la toate mesajele pe care le primeşte de la tastatură. Multe
funcţii ale tastaturii sunt tratate chiar de Windows. De exemplu, puteţi să ignoraţi apăsările de taste
legate de funcţii sistem. Acestea sunt, în general, combinaţii cu tasta Alt.

Programul nu este obligat să monitorizeze aceste taste, deoarece Windows îi comunică efectul
acestora. (Totuşi, dacă este nevoie, programul poate face şi acest lucru.) De exemplu, dacă
utilizatorul selectează un articol dintr-un meniu cu ajutorul tastaturii, Windows trimite programului
un mesaj prin care îi comunică articolul de meniu selectat, indiferent dacă utilizatorul a folosit
mouse-ul sau tastatura.

Unele programe pentru Windows folosesc „acceleratori" (sau „taste de accelerare") pentru opţiunile
de meniu folosite mai frecvent. Acceleratorii sunt, de obicei, combinaţii de taste funcţionale - sau
alte taste corespunzătoare unor caractere - cu tasta Ctrl. Tastele de accelerare sunt definite în fişierul
de resurse al programului.
Casetele de dialog au şi ele o interfaţă cu tastatura, dar programele, în general, nu trebuie să
monitorizeze tastatura cât timp este activă o casetă de dialog. Interfaţa cu tastatura este manipulată
de Windows şi acesta trimite programului mesaje prin care îi comunică efectele tastelor apăsate.
Casetele de dialog pot conţine controale de editare pentru introducerea textului. Acestea sunt mici
casete, în care utilizatorul poate să scrie un şir de caractere. Windows asigură logica de funcţionare a
controlului de editare şi furnizează programului conţinutul final al controlului, după ce utilizatorul a
terminat. Chiar şi în fereastra principală puteţi să definiţi ferestre descendent, care să funcţioneze ca
şi controalele de editare.

Cursorul, cursorul, cine a luat cursorul de intrare?

Tastatura trebuie să fie partajată de toate aplicaţiile rulate simultan sub Windows. Unele aplicaţii pot
avea mai multe ferestre, iar tastatura trebuie să fie partajată de toate ferestrele din cadrul aceleiaşi
aplicaţii. Atunci când este apăsată o tastă, o singură fereastră trebuie să primească mesajul privind
apăsarea tastei respective. Fereastra care primeşte acest mesaj este fereastra care deţine „cursorul de
intrare" („input focus").

Conceptul cursorului de intrare este strâns legat de conceptul de „fereastră activă". Fereastra care
deţine cursorul de intrare este fie fereastra activă, fie o fereastră descendent a ferestrei active. De
obicei, fereastra activă este uşor de identificat. Dacă fereastra activă are o bară de titlu, Windows
evidenţiază bara de titlu a acesteia. Dacă fereastra activă are un cadru de dialog (o formă des
întâlnită în casetele de dialog) în locul unei bare de titlu, Windows evidenţiază acest cadru. Dacă
fereastra activă a fost redusă la o pictogramă (minimizată), Windows evidenţiază textul afişat sub
pictogramă.

Ferestrele descendent mai des întâlnite sunt controale, precum butoanele de apăsare, butoanele
radio, casetele de validare, barele de derulare, casetele de editare şi casetele listă, care, de obicei,
apar în casete de dialog. Ferestrele descendent nu sunt niciodată ferestre active. Dacă o fereastră
descendent deţine cursorul de intrare, atunci fereastra activă este fereastra părinte. Controalele de tip
fereastră descendent indică faptul că deţin cursorul de intrare prin afişarea unui cursor care clipeşte
sau a unui cursor de editare.

Dacă fereastra activă a fost redusă la o pictogramă, atunci nici o fereastră nu deţine cursorul de
intrare. Windows continuă să trimită programului mesaje de la tastatură, dar acestea sunt trimise
într-o altă formă decât mesajele trimise unei ferestre active normale.

O procedură de fereastră poate să afle când are cursorul de intrare prin interceptarea mesajelor
WM_SETFOCUS şi WM_KILLFOCUS. Mesajul WM_SETFOCUS indică faptul că fereastra
primeşte cursorul de intrare (input focus), iar mesajul WM_KILLFOCUS indică faptul că fereastra
pierde cursorul de intrare.

Acţionări de taste şi caractere


Mesajele privind tastatura pe care un program le recepţionează de la sistemul de operare fac
diferenţa între „acţionările de taste" („keystrokes") şi „caractere". Aceste noţiuni sunt legate de cele
două moduri în care puteţi să priviţi tastatura. În primul rând, tastatura este o colecţie de taste.
Tastatura are o singură tastă A; apăsarea tastei A este o acţionare de tastă, iar eliberarea tastei A este
tot o acţionare de tastă. Tastatura este, însă, în acelaşi timp, şi un dispozitiv de intrare care generează
caractere afişabile. Tasta A poate să genereze mai multe caractere, în funcţie de starea tastelor Ctrl,
Shift şi Caps Lock. În mod normal, caracterul generat este a. Dacă tasta Shift este apăsată, sau tasta
Caps Lock este activă, caracterul generat este A. Dacă tasta Ctrl este apăsată, caracterul generat este
Ctrl+A. Dacă se foloseşte un driver de tastatură pentru o limbă străină, apăsarea tastei A poate să fie
precedată de un „caracter mort" („dead-character key") sau de tastele Shift, Ctrl sau Alt în diferite
combinaţii. Combinaţiile pot să genereze un caracter a sau A cu un accent.

Pentru acţionările de taste care generează caractere afişabile, Windows trimite programului atât
mesaje pentru acţionarea de taste, cât şi mesaje pentru caractere. Unele taste nu generează caractere.
Astfel de taste sunt Shift, tastele funcţionale, tastele de deplasare şi tastele speciale, precum Insert şi
Delete. În cazul acestor taste, Windows generează numai mesaje pentru acţionari de taste.

Mesaje pentru acţionări de taste

Atunci când apăsaţi o tastă, Windows inserează în coada de aşteptare a ferestrei care deţine cursorul
de intrare un mesaj WM_KEYDOWN sau un mesaj WM_SYSKEYDOWN. Atunci când eliberaţi
fasta, Windows inserează în coada de aşteptare a ferestrei un mesaj WM_KEYUP sau un mesaj
WM_SYSKEYUP.

  Tasta a fost apăsată Tasta a fost eliberată


Tastă obişnuită WM_KEYDOWN WM_KEYUP
WM_SYSKEYDOWN
Tastă de sistem WM_SYSKEYUP

De obicei, mesajele de apăsare şi de eliberare a tastei sunt trimise în pereche. Totuşi, dacă ţineţi
apăsată o tastă pană când aceasta se autorepetă, Windows trimite procedurii de fereastră o serie de
mesaje WM_KEYDOWN (sau WM_SYSKEYDOWN) şi un singur mesaj WM_KEYUP sau
(WM_SYSKEYUP) după eliberarea tastei. Ca toate mesajele trimise prin coada de aşteptare,
mesajele pentru acţionări de taste conţin informaţii de timp. Puteţi să obţineţi momentul relativ în
care a fost apăsată sau eliberată o tastă apelând funcţia GetMessageTime.

Taste obişnuite şi taste de sistem

Particula „SYS" din mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP provin de la cuvântul


„system" şi se referă la acţionările de taste care prezintă mai multă importanţă pentru Windows
decât pentru aplicaţiile Windows. Mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP sunt
generate, de obicei, pentru taste apăsate în combinaţie cu tasta Alt. Aceste acţionări de taste apelează
opţiuni din meniul programului ori din meniul sistem, sunt folosite pentru funcţii ale sistemului,
cum ar fi comutarea ferestrei active (Alt+Tab sau Alt+Esc) sau sunt folosite pentru acceleratori de
meniu (Alt în combinaţie cu o tastă funcţională). De obicei, programul ignoră mesajele
WM_SYSKEYDOWN şi WM_SYSKEYUP şi le retransmite funcţiei DefWindowProc. Deoarece
Windows manipulează combinaţiile Alt+tastă, nu este nevoie să interceptaţi aceste mesaje.
Procedura de fereastră va primi alte mesaje, legate de rezultatul acestor acţionări de taste (cum ar fi
selectarea unei opţiuni de meniu). Chiar dacă doriţi să includeţi în program codul pentru
interceptarea acestor mesaje (aşa cum vom face în programul KEYLOCK din acest capitol),
retransmiteţi mesajele funcţiei DefWindowProc după ce le prelucraţi, astfel încât Windows să le
poată folosi în scopurile obişnuite.

Dar să ne gândim puţin la aceste lucruri. Aproape toate mesajele care afectează fereastra
programului trec mai întâi prin procedura de fereastră. Windows prelucrează aceste mesaje numai
dacă le retransmiteţi funcţiei DefWindowProc. De exemplu, dacă în procedura de fereastră adăugaţi
următoarele linii:

case WM_SYSKEYDOWN :

case WM_SYSKEYUP :

case WM_SYSCHAR :

return 0 ;

dezactivaţi toate operaţiile legate de combinaţiile Alt+tastă (comenzi de meniu, Alt+Tab, Alt+Esc şi
aşa mai departe) atunci când programul deţine cursorul de intrare.

Mesajele WM_KEYDOWN şi WM_KEYUP sunt generate, de obicei, pentru tastele apăsate şi


eliberate fără tasta Alt. Programul poate să folosească sau să ignore aceste mesaje. Sistemul de
operare le ignoră.

Variabila lParam

Pentru toate mesajele legate de acţionările de taste variabila lParam (pe 32 de biţi) transmisă
procedurii de fereastră este împărţită în şase câmpuri: contorul de repetare, codul de scanare OEM,
indicatorul flag pentru taste extinse, codul de context, starea anterioară a tastei şi starea de tranziţie
(vezi Figura 5-1).

Indicator flag pentru taste extinse

Figura 5-1. Cele sase câmpuri de biţi ale variabilei IParam din mesajele pentru acţionari de taste.

Contorul de repetare
Contorul de repetare (Repeat Count) specifică numărul de acţionari de taste reprezentat de un mesaj.
În majoritatea cazurilor, contorul de repetare are valoarea 1. Totuşi, dacă procedura de fereastră nu
reuşeşte să prelucreze mesajele de apăsare a unei taste în ritmul de autorepetare (în mod prestabilit
aproximativ 10 caractere pe secundă), Windows combină mai multe mesaje WM_KEYDOWN sau
WM_SYSKEYDOWN într-un singur mesaj şi incrementează corespunzător contorul de repetare.
Contorul de repetare are întotdeauna valoarea 1 pentru mesajele WM_KEYUP şi
WM_SYSKEYUP.

Deoarece o valoare mai mare decât 1 a contorului de repetare indică faptul că rata de autorepetare a
tastelor depăşeşte posibilităţile de prelucrare ale programului, există situaţii în care puteţi să ignoraţi
contorul de repetare atunci când prelucraţi mesajele de la tastatură. Aproape toată lumea a avut
ocazia să vadă cum un procesor de texte sau un program de calcul tabelar derulează ecranul mai
mult decât trebuie datorită comenzilor suplimentare stocate în bufferul de tastatură. Ignorarea
contorului de repetare va reduce foarte mult posibilitatea apariţiei unei asemenea situaţii. În alte
cazuri însă este bine să folosiţi contorul de repetare. Puteţi să încercaţi ambele variante şi să vedeţi
pe care o preferaţi pentru programul dumneavoastră.

Codul de scanare OEM

Codul de scanare OEM (OEM Scan Code) este codul de scanare al tastaturii, generat de
componentele hardware. (Dacă aţi scris programe în limbaj de asamblare, acest cod este identic cu
cel transmis în registrul AH, în timpul întreruperii apelului BIOS 16H.) În general, aplicaţiile
Windows ignoră acest cod, deoarece există metode mai bune de decodificare a informaţiilor de la
tastatură.

Indicatorul flag pentru taste extinse

Indicatorul flag pentru taste extinse (Extended Key Flag) are valoarea 1 dacă mesajul este generat de
una dintre tastele suplimentare de pe tastatura IBM extinsă. (Tastatura IBM extinsă are tastele
funcţionale în partea de sus şi un bloc separat sau combinat de taste pentru tastele de deplasare şi
tastele numerice.) Acest indicator are valoarea 1 pentru tastele Alt şi Ctrl din partea dreaptă a
tastaturii, pentru tastele de deplasare (inclusiv tastele Insert şi Delete) care nu fac parte din blocul de
taste numerice, pentru tastele Slash (/) şi Enter din blocul de taste numerice şi pentru tasta Num
Lock. În general, programele Windows ignoră acest indicator.

Codul de context
Codul de context (Context Code) are valoarea 1 dacă este apăsată tasta Alt. Acest bit va avea
întotdeauna valoarea 1 pentru mesajele WM_SYSKEYUP şi WM_SYSKEYDOWN şi valoarea 0
pentru mesajele WM_KEYUP si WM_KEYDOWN, cu două excepţii:

§ O fereastră activă redusă la o pictogramă nu deţine cursorul de intrare. Toate acţionările de taste
generează mesaje WM_SYSKEYUP şi WM_SYSKEYDOWN. Dacă tasta Alt nu este apăsată, bitul
pentru codul de context are valoarea 0. (Windows foloseşte aceste mesaje astfel încât fereastra
activă redusă la o pictogramă să nu prelucreze mesajele de la tastatură.)

§ În cazul folosirii unui driver de tastatură pentru alte limbi decât limba engleză, unele caractere sunt
generate prin combinarea tastelor Shift, Ctrl sau Alt cu alte taste. În această situaţie, bitul pentru
codul de context din variabila lParam care însoţeşte mesajele WM_KEYUP şi WM_KEYDOWN
are valoarea 1, dar mesajele nu reprezintă acţionări de taste de sistem.

Starea anterioară a tastei


Starea anterioară a tastei (Previous Key State) are valoarea 0 dacă tasta nu a fost anterior apăsată, şi
valoarea 1 dacă tasta a fost apăsată. Are întotdeauna valoarea 1 pentru mesajele WM_KEYUP şi
WM_SYSKEYUP, dar poate fi 0 sau 1 pentru mesajele WM_KEYDOWN şi
WM_SYSKEYDOWN. Valoarea 1 indică faptul că s-a primit un mesaj generat de autorepetarea
unei taste.

Starea de tranziţie
Starea de tranziţie (Transition State) are valoarea 0 dacă tasta este apăsată şi valoarea 1 dacă tasta
este eliberată. Acest bit are valoarea 1 pentru mesajele WM_KEYUP si WM_SYSKEYUP si
valoarea 0 pentru mesajele WM_KEYDOWN şi WM_SYSKEYDOWN.

Coduri virtuale de taste

Deşi unele informaţii din parametrul lParam pot fi utile pentru prelucrarea mesajelor WM_KEYUP,
WM_SYSKEYUP, WM_KEYDOWN şi WM_SYSKEYDOWN, parametrul wParam este mult mai
important. Acest parametru conţine codul virtual care identifică tasta apăsată sau eliberată.
Dezvoltatorii sistemului de operare Windows au încercat să definească tastele virtuale într-o manieră
independentă de dispozitiv. Din acest motiv, unele coduri virtuale de taste nu pot fi generate pe
calculatoarele IBM PC şi compatibile cu acestea, dar pot fi întâlnite la tastaturile aparţinând altor
producători.

Codurile virtuale pe care le veţi folosi cel mai des au nume definite în fişierele antet din Windows.
Tabelul de mai jos prezintă aceste nume, împreună cu codurile numerice şi tastele de pe tastatura
IBM care corespund tastelor virtuale. Deşi toate tastele generează mesaje de acţionare (keystroke
messages), tabelul nu include tastele pentru simboluri (cum ar fi / şi ?). Acestor taste le corespund
coduri virtuale de la 128 în sus şi de cele mai multe ori sunt definite diferit pentru tastaturile
internaţionale. Puteţi să determinaţi aceste coduri virtuale folosind programul KEYLOOK, prezentat
în acest capitol, dar în general nu este necesar să prelucraţi mesajele de acţionare ale acestor taste.

                                                                                                  Coduri virtuale de taste


Zecima Hexa Identificator Necesar Tastatură IBM
l WINDOWS.H
1 01 VK_LBUTTON   
2 02 VK_RBUTTON    
3 03 VK_CANCEL v Ctrl-Break
4 04 VK_MBUTTON    
8 08 VK_BACK v Backspace
9 09 VK_TAB v Tab
12 0C VK_CLEAR v Tasta numerică 5 cu tasta
        Num Lock inactivă
13 0D VK_RETURN v Enter
16 10 VK_SHIFT v Shift
17 11 VK_CONTROL v Ctrl
18 12 VK_MENU v Alt

(continuare)

Zecimal Hexa Identificator Necesar Tastatura IBM


WINDOWS.H
19 13 VK_PAUSE   Pause
20 14 VK_CAPITAL v Caps Lock
27 1B VK_ESCAPE v Esc
32 20 VK_SPACE v Bara de spaţiu
33 21 VK_PRIOR v Page Up
34 22 VK_NEXT v Page Down
35 23 VK_END   End
36 24 VK_HOME v Home
37 25 VK_LEFT v Săgeată stânga
38 26 VK_UP v Săgeată în sus
39 27 VK_RIGHT v Săgeată în dreapta
40 28 VK_DOWN v Săgeată în jos
41 29 VK_SELECT    
42 2A VK_PRINT    
43 2B VK_EXECUTE    
44 2C VK_SNAPSHOT   Print Screen
45 2D VK_INSERT Ú Insert
46 2E VK_DELETE Ú Delete
47 2F VK_HELP    
48-57 30-39   Ú De la 0 la 9 pe blocul principal de taste
65-90 41-5A   Ú De la A la Z
96 60 VK_NUMPAD0   Tasta numerică 0 cu tasta Num Lock activă
97 61 VK_NUMPAD1   Tasta numerică 1 cu tasta Num Lock activă
98 62 VK_NUMPAD2   Tasta numerică 2 cu tasta Num Lock activă
99 63 VK_NUMPAD3   Tasta numerică 3 cu tasta Num Lock activă
100 64 VK_NUMPAD4   Tasta numerică 4 cu tasta Num Lock activă
101 65 VK_NUMPAD5   Tasta numerică 5 cu tasta Num Lock activă
102 66 VK_NUMPAD6   Tasta numerică 6 cu tasta Num Lock activă
103 67 VK_NUMPAD7   Tasta numerică 7 cu tasta Num Lock activă
104 68 VK_NUMPAD8   Tasta numerică 8 cu tasta Num Lock activă
105 69 VK_NUMPAD9   Tasta numerică 9 cu tasta Num Lock activă
106 6A vk_multiply   Tasta numerică *
107 6B VK_ADD   Tasta numerică +
108 6C VK_SEPARATOR    
109 6D VK_SUBTRACT   Tasta numerică -
110 6E VK_DECIMAL   Tasta numerică .
111 6F VK_DIVIDE   Tasta numerică /
112 70 VK_F1 Ú Tasta funcţională F1
113 71 VK_F2 Ú Tasta funcţională F2
114 72 VK_F3 Ú Tasta funcţională F3
115 73 VK_P4 Ú Tasta funcţională F4
116 74 VK_F5 Ú Tasta funcţională F5
117 75 VK_F6 Ú Tasta funcţională F6
118 76 VK_F7 Ú Tasta funcţionată F7
119 77 VK_F8 Ú Tasta funcţională F8
120 78 VK_F9 Ú Tasta funcţională F9
121 79 VK_F10 Ú Tasta funcţională F10
122 7A VK_F11   Tasta funcţională F11 (tastatura extinsă)
123 7B VK_F12 .   Tasta funcţională F12 (tastatura extinsă)
124 7C VK_F13    
125 7D VK_F14    
126 7E VK_F15    
127 7F VK_F16    
144 90 VK_NUMLOCK   Num Lock
145 91 VK_SCROLL   Scroll Lock

Semnul de validare din coloana „Necesar" indică faptul că tasta respectivă este obligatorie pentru
orice implementare Windows. De asemenea, sistemul de operare cere ca tastatura şi driverul de
tastatură să permită combinarea tastelor Ctrl, Shift sau a ambelor cu orice literă, orice tastă de
deplasare şi orice tastă funcţională. VK_LBUTTON, VK_MBUTTON şi VK_RBUTTON sunt
codurile virtuale corespunzătoare buloanelor din stânga, din mijloc şi din dreapta ale mouse-ului. Cu
toate acestea, nu veţi primi niciodată mesaje pentru care parametrul wParam să conţină aceste
valori. Mouse-ul generează mesaje proprii, aşa cum vom vedea în capitolul următor.

Starea tastelor de modificare


Parametrii lParam şi wParam care însoţesc mesajele WM_KEYUP, WM_SYSKEYUP,
WM_KEYDOWN şi WM_SYSKEYDOWN nu spun nimic programului despre starea tastelor de
modificare. Puteţi să obţineţi starea oricărei taste virtuale folosind funcţia GetKeyState. Această
funcţie este folosită, de obicei, pentru obţinerea stării tastelor de modificare (Shift, Alt şi Ctrl) şi a
tastelor de comutare (Caps Lock, Num Lock şi Scroll Lock). De exemplu:

GetKeyState (VK_SHIFT) ;

returnează o valoare negativă (adică un număr în care primul bit are valoarea 1) dacă tasta Shift este
apăsată. Valoarea returnată de apelul:

GetKeyState (VK_CAPITAL) ;
are un 1 în bitul cel mai puţin semnificativ dacă tasta Caps Lock este activă. De asemenea, puteţi să
obţineţi starea butoanelor mouse-ului folosind codurile virtuale VK_LBUTTON, VK_MBUTTON
şi VK_RBUTTON. Totuşi, majoritatea programelor pentru Windows care trebuie să monitorizeze
combinaţiile între taste şi butoanele mouse-ului utilizează calea inversă - verificând starea tastelor
atunci când primesc un mesaj de la mouse. De fapt, starea tastelor de modificare este inclusă în
mesajele primite de la mouse (aşa cum veţi vedea în capitolul următor).

Aveţi grijă cum folosiţi funcţia GetKeyState. Aceasta nu verifică starea tastaturii în timp real.
GetKeyState returnează starea tastaturii până în momentul mesajului curent. Funcţia GetKeyState nu
vă permite să obţineţi informaţii despre starea tastaturii independent de mesajele obişnuite. De
exemplu, să presupunem că vreţi să întrerupeţi prelucrarea în procedura de fereastră până când
utilizatorul apasă tasta

while (GetKeyState (VK_F1) >= 0) ; // Greşit!

Nu faceţi acest lucru! Programul trebuie să preia mesajul de la tastatură din coada de aşteptare,
înainte ca funcţia GetKeyState să poată obţine starea tastelor. De fapt, această sincronizare este în
folosul dumneavoastră, deoarece puteţi să obţineţi starea corectă a tastelor de modificare în timpul
generării unui anumit mesaj de la tastatură, chiar dacă prelucraţi mesajul (şi apelaţi funcţia
GetKeyState) după ce tasta de modificare respectivă a fost eliberată. Dacă într-adevăr aveţi nevoie
de starea curentă a unei taste, puteţi să folosiţi funcţia GetAsyncKeyState.

Utilizarea mesajelor de acţionare a tastelor

Ideea unui program care să obţină informaţii despre toate acţiunile exercitate asupra tastelor este
drăguţă. Cu toate acestea, majoritatea programelor Windows ignoră cea mai mare parte a acestor
mesaje. Mesajele WM_SYSKEYUP şi WM_SYSKEYDOWN sunt folosite pentru funcţiile
sistemului, aşa că nu este nevoie să le interceptaţi. De asemenea, dacă prelucraţi mesajele
WM_KEYDOWN, de obicei puteţi să ignoraţi mesajele WM_KEYUP.

În general, programele pentru Windows folosesc mesajele WM_KEYDOWN pentru tastele care nu
generează caractere. Deşi s-ar putea să vă vină ideea să folosiţi mesajele pentru acţionări de taste în
combinaţie cu informaţiile despre tastele de modificare (furnizate de funcţia GetKeyState) ca să
transformaţi mesajele pentru acţionări de taste în mesaje pentru caractere, nu faceţi acest lucru. Veţi
avea probleme datorită diferenţelor între tastaturile internaţionale. De exemplu, dacă primiţi un
mesaj WM_KEYDOWN pentru care parametrul wParam are valoarea 33H, ştiţi că utilizatorul a
apăsat tasta 3. Până aici totul este bine. Dacă apelaţi funcţia GetKeyState veţi afla că tasta Shift este
apăsată şi s-ar putea să presupuneţi că utilizatorul a introdus un caracter diez (#), lucru care poate să
nu fie adevărat. Un utilizator britanic ar fi introdus caracterul £. Mesajele WM_KEYDOWN sunt
utile pentru tastele de deplasare, tastele funcţionale şi tastele speciale, precum Insert, Delete. Uneori
tastele Insert, Delete si tastele funcţionale sunt folosite ca acceleratori pentru comenzi de meniu.
Deoarece Windows transformă acceleratorii în comenzi de meniu, nu este nevoie să prelucraţi în
program mesajele pentru acţionarea tastelor de accelerare. Unele programe non-Windows folosesc
foarte des tastele funcţionale în combinaţie cu tastele Alt, Ctrl şi Shift. Puteţi face acest lucru şi în
programele pentru Windows, dar nu este recomandat. Dacă vreţi să folosiţi tastele funcţionale,
acestea trebuie să dubleze comenzi din meniuri. Unul dintre obiectivele sistemului de operare
Windows este să nu oblige utilizatorii să memoreze sau să folosească liste complicate de comenzi.
Am reuşit să eliminăm aproape totul, cu excepţia unui singur lucru: de cele mai multe ori veţi
prelucra mesajele WM_KEYDOWN numai pentru tastele de deplasare a cursorului. Atunci când
prelucraţi mesajele trimise de tastele de deplasare puteţi să verificaţi şi starea tastelor Shift şi Ctrl,
folosind funcţia GetKeyState. Unele funcţii Windows folosesc de multe ori tastele de deplasare în
combinaţie cu tasta Shift pentru extinderea unei selecţii - de exemplu într-un procesor de texte.
Tasta Ctrl este folosită deseori pentru a schimba semnificaţia unei taste de deplasare. De exemplu,
tasta Ctrl în combinaţie cu săgeata spre dreapta mută cursorul cu un cuvânt la dreapta.

Una dintre cele mai bune metode de a vă hotărî cum să folosiţi tastatura este să studiaţi modul de
utilizare a acesteia în programele Windows existente. Dacă nu vă place cum este folosită, puteţi să
faceţi ceva diferit. Dar nu uitaţi că orice noutate înseamnă creşterea perioadei de care are nevoie un
utilizator pentru a se obişnui cu programul dumneavoastră.

ÎMBUNĂTĂŢIREA PROGRAMULUI SYSMETS:

ADĂUGAREA INTERFEŢEI CU TASTATURA


Atunci când aţi scris cele trei versiuni ale programului SYSMETS prezentate în Capitolul 3 nu ştiaţi
încă nimic despre tastatură. Puteaţi să derulaţi textul numai folosind mouse-ul pe barele de derulare.
Acum ştim să prelucrăm mesajele de la tastatură, aşa că este timpul să adăugăm programului
SYSMETS o interfaţă cu tastatură. Aceasta este, evident, sarcina tastelor de deplasare. Cea mai mare
parte a tastelor de deplasare (Home, End, Page Up, Page Down, săgeţile în sus şi în jos) vor fi
folosite pentru derularea pe verticală. Săgeţile spre dreapta şi spre stânga vor fi folosite pentru
derularea pe orizontală, facilitate care în acest program este mai puţin importantă.

Adăugarea logicii de tratare a mesajului WM_KEYDOWN

O metodă evidentă de creare a unei interfeţe este adăugarea codului necesar pentru tratarea
mesajului WM_KEYDOWN, dublând astfel logica de tratare a mesajelor WM_VSCROLL şi
WM_HSCROLL:

case WM_KEYDOWN :

iVscrollInc = iHscrollInc = 0 ;

               switch (wParam)

                              {

                              case VK_HOME:     // la fel ca HM_VSCROLL, SB_TOP

                                             iVscrollInc = -iVscrollPos ;

                                             break ;

                              case VK_END :       // la fel ca HM_VSCROLL, SB_BOTTOM:

                                             iVscrollInc = iVscrollMax - iVscrollPos ;                                


break ;

                              case VK_UP :                        // la fel ca HM_VSCROLL, SB_LINEUP :

                                             iVscrollInc =  -1 ;

                                             break ;

                              case VK_DOWN :   // la fel ca WM_VSCROLL, SB_LINEDOWN

                                             iVscrollInc = 1 ;

                                             break ;

                              case VK_PRIOR :   // la fel ca WM_VSCROLL, SB_PAGEUP

                                             iVscrollInc = min (-1, -cyClient / cyChar) ;

                                             break ;

                              case VK_NEXT :     // la fel ca WH_VSCROLL, SB_PAGEDOWN :

                                             iVscrollInc = max (1, cyClient / cyChar) ;

                                             break ;

                              case VK_LEFT :      // la fel ca WM_HSCROLL, SB_PAGEUP

                                             iHscrollInc = -8 ;

                                             break ;

                              case VK_RIGHT :    // la fel ca WM_HSCROLL, SB_PAGEDOWN

                                             iHscrollInc = 8 ;

                                             break ;

                              default :

                                             break ;

                              }

if (iVscrollInc = max (-iVscrollPos, min(iVscrollInc, iVscrollMax-iVscrollPos)))

               {             
iVscrollPos += iVscrollInc ;

               ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL);

               SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE);

UpdateWindow (hwnd) ;

               }

if (iHscrollInc = max (-iHscrollPos,min(iHscrollInc, iHscrollMax - iHscrollPos)))     

{             

iHscrollPos += iHscrollInc;

               ScrollWindow (hwnd, -cxChar*iHscrollInc, 0, NULL, NULL) ;

               SetScrollPos (hwnd, SB_ HORZ, iHscrollPos, TRUE) ;

               }

return 0 ;

Vă displace acest cod la fel de mult ca şi mie? Dublarea codului pentru tratarea mesajelor de la
barele de derulare este o soluţie de-a dreptul proastă, deoarece dacă vom dori vreodată să modificăm
logica de funcţionare a barelor de derulare va trebui să facem aceleaşi modificări şi în codul de
tratare a mesajului WM_KEYDOWN. Trebuie să existe o soluţie mai elegantă. Şi chiar există.

Transmiterea mesajelor
Nu ar fi mai bine să transformăm mesajele WM_KEYDOWN în mesaje echivalente
WM_VSCROLL şi WM_HSCROLL şi să facem cumva funcţia WndProc să creadă că a primit
mesajele WM_VSCROLL şi WM_HSCROLL, poate chiar transmiţând procedurii de fereastră
aceste mesaje contrafăcute? Windows vă permite să faceţi acest lucru. Funcţia necesară este
SendMessage şi primeşte aceiaşi parametri ca şi procedura de fereastră:

SendMessage (hwnd, message, wParam, lParam) ;

Atunci când apelaţi funcţia SendMessage, Windows apelează procedura ferestrei identificată de
parametrul hwnd şi îi transmite cei patru parametri. După ce procedura de fereastră încheie
prelucrarea mesajului, Windows reia execuţia de la următoarea linie după apelul funcţiei
SendMessage. Procedura de fereastră căreia îi trimiteţi mesajul poate fi aceeaşi procedură de
fereastră, o altă procedură de fereastră din acelaşi program sau chiar o procedură de fereastră dintr-o
altă aplicaţie.

Iată cum putem folosi funcţia SendMessage pentru prelucrarea mesajelor WM_KEYDOWN în
programul SYSMETS:
case WH_KEYDOWN :

                              switch (wParam)

        case VK_HOME :

        SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L);

        break;

        case VK_END:

        SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L);

        break ;

       

        case VK_PRIOR :

        SendMessage (hwnd, WH_VSCROLL, SB_PAGEUP, 0L);

        break ;

          [alte linii de program]

OK, aţi prins ideea. Scopul nostru era să adăugăm o interfaţă cu tastatura pentru barele de derulare şi
exact acest lucru l-am făcut. Am permis folosirea tastelor de deplasare pentru a dubla logica barelor
de derulare trimiţând procedurii de fereastră mesajele specifice barelor de derulare. Acum vedeţi de
ce am inclus în programul SYSMETS3 din Capitolul 3 secvenţa de tratare a codurilor SB_TOP şi
SB_BOTTOM din mesajele WM_VSCROLL. În momentul respectiv acestea nu erau folosite, dar
acum permit tratarea tastelor Home şi End. Programul SYSMETS, prezentat în Figura 5-2, include
cele trei modificări discutate. Pentru compilarea programului aveţi nevoie şi de fişierul
SYSMETS.H, prezentat în Figura 3-4 din Capitolul 3.

#-----------------------

# SYSMETS.MAK make file


#-----------------------

sysmets.exe : sysmets.obj

     $(LINKER) $(GUIFLAGS) -OUT:sysmets.exe sysmets.obj $(GUILIBS)

sysmets.obj : sysmets.c sysmets.h

     $(CC) $(CFLAGS) sysmets.c

/*-----------------------------------------------------

   SYSMETS.C -- System Metrics Display Program (Final)

                (c) Charles Petzold, 1996

  -----------------------------------------------------*/

#include <windows.h>

#include <string.h>

#include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SysMets" ;

     HWND        hwnd ;


     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "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))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static int  cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth,

                 iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ;

     char        szBuffer[10] ;

     HDC         hdc ;

     int         i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :


               hdc = GetDC (hwnd) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;

               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               iMaxWidth = 40 * cxChar + 22 * cxCaps ;

               return 0 ;

          case WM_SIZE :

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

               iVscrollMax = max (0, NUMLINES + 2 - cyClient / cyChar) ;

               iVscrollPos = min (iVscrollPos, iVscrollMax) ;

               SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, FALSE) ;

               SetScrollPos   (hwnd, SB_VERT, iVscrollPos, TRUE) ;

               iHscrollMax = max (0, 2 + (iMaxWidth - cxClient) / cxChar) ;

               iHscrollPos = min (iHscrollPos, iHscrollMax) ;


 

               SetScrollRange (hwnd, SB_HORZ, 0, iHscrollMax, FALSE) ;

               SetScrollPos   (hwnd, SB_HORZ, iHscrollPos, TRUE) ;

               return 0 ;

          case WM_VSCROLL :

               switch (LOWORD (wParam))

                    {

                    case SB_TOP :

                         iVscrollInc = -iVscrollPos ;

                         break ;

                    case SB_BOTTOM :

                         iVscrollInc = iVscrollMax - iVscrollPos ;

                         break ;

                    case SB_LINEUP :

                         iVscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iVscrollInc = 1 ;

                         break ;

 
                    case SB_PAGEUP :

                         iVscrollInc = min (-1, -cyClient / cyChar) ;

                         break ;

                    case SB_PAGEDOWN :

                         iVscrollInc = max (1, cyClient / cyChar) ;

                         break ;

                    case SB_THUMBTRACK :

                         iVscrollInc = HIWORD (wParam) - iVscrollPos ;

                         break ;

                    default :

                         iVscrollInc = 0 ;

                    }

               iVscrollInc = max (-iVscrollPos,

                             min (iVscrollInc, iVscrollMax - iVscrollPos)) ;

               if (iVscrollInc != 0)

                    {

                    iVscrollPos += iVscrollInc ;

                    ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL) ;

                    SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;

                    UpdateWindow (hwnd) ;


                    }

               return 0 ;

          case WM_HSCROLL :

               switch (LOWORD (wParam))

                    {

                    case SB_LINEUP :

                         iHscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iHscrollInc = 1 ;

                         break ;

                    case SB_PAGEUP :

                         iHscrollInc = -8 ;

                         break ;

                    case SB_PAGEDOWN :

                         iHscrollInc = 8 ;

                         break ;

                    case SB_THUMBPOSITION :

                         iHscrollInc = HIWORD (wParam) - iHscrollPos ;


                         break ;

                    default :

                         iHscrollInc = 0 ;

                    }

               iHscrollInc = max (-iHscrollPos,

                             min (iHscrollInc, iHscrollMax - iHscrollPos)) ;

               if (iHscrollInc != 0)

                    {

                    iHscrollPos += iHscrollInc ;

                    ScrollWindow (hwnd, -cxChar * iHscrollInc, 0, NULL, NULL) ;

                    SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ;

                    }

               return 0 ;

          case WM_KEYDOWN :

               switch (wParam)

                    {

                    case VK_HOME :

                         SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ;

                         break ;

                    case VK_END :


                         SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ;

                         break ;

                    case VK_PRIOR :

                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ;

                         break ;

                    case VK_NEXT :

                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L) ;

                         break ;

                    case VK_UP :

                         SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0L) ;

                         break ;

                    case VK_DOWN :

                         SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0L) ;

                         break ;

                    case VK_LEFT :

                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEUP, 0L) ;

                         break ;

                    case VK_RIGHT :


                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L) ;

                         break ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               iPaintBeg = max (0, iVscrollPos + ps.rcPaint.top / cyChar - 1) ;

               iPaintEnd = min (NUMLINES,

                                iVscrollPos + ps.rcPaint.bottom / cyChar) ;

               for (i = iPaintBeg ; i < iPaintEnd ; i++)

                    {

                    x = cxChar * (1 - iHscrollPos) ;

                    y = cyChar * (1 - iVscrollPos + i) ;

                    TextOut (hdc, x, y,

                             sysmetrics[i].szLabel,

                             strlen (sysmetrics[i].szLabel)) ;

                    TextOut (hdc, x + 22 * cxCaps, y,

                             sysmetrics[i].szDesc,

                             strlen (sysmetrics[i].szDesc)) ;


 

                    SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

                    TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y,

                             szBuffer,

                             wsprintf (szBuffer, "%5d",

                              GetSystemMetrics (sysmetrics[i].iIndex))) ;

                    SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

                    }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 5-2. Programul SYSMETS (final).


mesaje CARACTER
Am discutat mai devreme despre ideea transformării mesajelor generate de acţionarea tastelor în
mesaje caracter ţinând cont de starea tastelor de modificare şi v-am avertizat că acest lucru nu este
suficient: trebuie să ţineţi seama şi de configuraţia diferită a tastaturii de la o ţară la alta. Din acest
motiv, nu trebuie să încercaţi să transformaţi dumneavoastră mesajele generate de acţionarea tastelor
în mesaje caracter. Windows o poate face în locul dumneavoastră. Aţi mai văzut această secvenţă de
cod:

while (GetMessage (&msg, NULL, 0, 0))

TranslateMessage (&msg);

DispatchMessage (&msg);

Acesta este un ciclu tipic de tratare a mesajelor din funcţia WinMain. Funcţia GetMessage preia
următorul mesaj din coada de aşteptare şi completează câmpurile structurii msg. Funcţia
DispatchMessage transmite mesajul procedurii de fereastră corespunzătoare. Între cele două funcţii
este apelată funcţia TranslateMessage, care transformă mesajele generate de acţionarea tastelor în
mesaje caracter. Dacă mesajul este WM_KEYDOWN sau WM_SYSKEYDOWN şi dacă tasta
apăsată, în funcţie de starea tastelor de modificare, generează un caracter, atunci funcţia
TranslateMessage inserează un mesaj caracter în coada de aşteptare. Acesta va fi următorul mesaj
pe care îl va prelua funcţia GetMessage după mesajul generat de acţionarea tastei. Există patru
mesaje caracter:

  Caractere Caractere „moarte"


Caractere non-sistem WM_CHAR WM_DEADCHAR
Caractere sistem WM_SYSCHAR WM_SYSDEADCHAR

Mesajele WM_CHAR şi WM_DEADCHAR sunt obţinute din mesaje WM_KEYDOWN. Mesajele


WM_SYSCHAR şi WM_SYSDEADCHAR sunt obţinute din mesaje WM_SYSKEYDOWN. În
majoritatea cazurilor programul poate să ignore toate celelalte mesaje în afară de WM_CHAR.
Parametrul lParam transmis procedurii de fereastră are acelaşi conţinut ca şi parametrul lParam al
mesajului generat de acţionarea tastei din care a fost obţinut mesajul caracter. Parametrul wParam
conţine codul ASCII al caracterului (da, chiar bătrânul ASCII!). Mesajele caracter sunt transmise
procedurii de fereastră între mesajele generate de acţionarea tastelor. De exemplu, dacă tasta Caps
Lock nu este activă şi apăsaţi şi eliberaţi tasta A, procedura de fereastră primeşte următoarele trei
mesaje:

 
Mesaj Tastă sau cod
WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a

WM_KEYUP Tasta virtuală A

Dacă introduceţi un caracter A apăsând tasta Shift, apoi tasta A, eliberând tasta A şi apoi eliberând
tasta Shift, procedura de fereastră primeşte cinci mesaje:

Mesaj Tastă sau cod


WM_KEYDOWN Tasta virtuală VK_SHIFT
WM_KEYDOWN
WM_CHAR Tasta virtuală A
WM_KEYUP
WM_KEYUP Codul ASCII al caracterului a

Tasta virtuală A

Tasta virtuală VK_SHIFT

Tasta Shift nu generează un mesaj caracter. Dacă ţineţi tasta A apăsată până când intră în acţiune
autorepetarea, veţi primi un mesaj caracter pentru fiecare caracter WM_KEYDOWN:

Mesaj Tastă sau cod


WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYUP Tasta virtuală A

Dacă unul dintre mesajele WM_KEYDOWN are contorul de repetare mai mare decât 1, mesajul
WM_CHAR va avea acelaşi contor de repetare.

Tasta Ctrl în combinaţie cu o literă generează codurile de control ASCII de la 01H (Ctrl+A) la 1AH
(Ctrl+Z). Puteţi să folosiţi şi alte taste pentru generarea acestor coduri de control. Tabelul următor
prezintă valorile parametrului wParam dintr-un mesaj WM_CHAR pentru tastele care generează
coduri de control:

Tastă Cod ASCII Dublat de


Backspace 08H Ctrl+H
Tab 09H Ctrl+I
Ctrl+Enter 0AH Ctrl+J
Enter 0DH Ctrl+M
Esc 1BH Ctrl+[

Programele Windows folosesc uneori combinaţiile tastei Ctrl cu litere ca acceleratori pentru
meniuri, caz în care literele respective nu sunt transformate în mesaje caracter.

Mesajul WM_CHAR

Atunci când trebuie să prelucreze caracterele introduse de la tastatură (de exemplu, într-un procesor
de texte sau într-un program de comunicaţii) programul prelucrează mesajele WM_CHAR. Probabil
doriţi să prelucrati într-un mod mai special tastele Backspace, Tab şi Enter (eventual si tasta de salt
la linie nouă) dar toate celelalte caractere sunt tratate la fel:

 case WH_CHAR :

switch (wParam)

case '\b' :                // backspace

[alte linii de program]

break ;

case '\f' :                 // tab

[alte linii de program]

break ;

case '\n' :                // salt la linie nouă

[alte linii de program]

break ;

case '\r' :                 // retur de car


[alte linii de program]

break ;

default :                  // cod de caractere

[alte linii de program]

break ;

return 0 ;

Acest fragment de program este asemănător cu secvenţele de cod pentru tratarea caracterelor dintr-
un program MS-DOS obişnuit.

Mesaje pentru „caractere moarte"


De obicei, programele pot să ignore mesajele WM_DEADCHAR si WM_SYSDEADCHAR. Pe
unele tastaturi, în afară de tastatura de tip american, o serie de taste sunt folosite pentru adăugarea
semnelor diacritice la o literă. Acestea se numesc „taste moarte" („dead keys") deoarece nu pot crea
singure caractere. De exemplu, pe o tastatura germană, în locul tastei +/= de pe tastatura americană
se află o tastă moartă pentru accentul ascuţit ('') dacă nu este apăsată tasta Shift, si pentru accentul
grav (`) dacă este apăsată tasta Shift.

Atunci când utilizatorul apasă această tastă moartă, procedura de fereastră primeşte un mesaj
WM_DEADCHAR pentru care parametrul wParam conţine codul ASCII al semnului diacritic. Dacă
utilizatorul apasă apoi o literă (de exemplu, tasta A), procedura de fereastră primeşte un mesaj
WM_CHAR pentru care parametrul wParam conţine codul ASCII al literei „a" cu semnul diacritic
respectiv. Ca urmare, programul nu trebuie să prelucreze mesajul WM_DEADCHAR, deoarece
mesajul WM_CHAR următor îi furnizează toate informaţiile necesare. Codul Windows se ocupă
chiar şi de tratarea erorilor: dacă tasta moartă este urmată de o literă care nu poate avea semnul
diacritic respectiv (cum ar fi litera „s"), procedura de fereastră primeşte două mesaje WM_CHAR -
pentru primul parametrul wParam conţine codul ASCII al semnului diacritic, iar pentru al doilea
parametrul wParam conţine codul ASCII al literei „s".

Examinarea mesagelor de la tastatură

Dacă doriţi să vedeţi cum trimite sistemul de operare mesajele de la tastatură către o aplicaţie,
programul KEYLOOK (prezentat în figura 5.3) vă poate ajuta. Acest program afişează în zona client
toate informaţiile pe care Windows le trimite procedurii de fereastră pentru opt mesaje diferite de la
tastatură.
#-----------------------

# KEYLOOK.MAK make file

#-----------------------

keylook.exe : keylook.obj

     $(LINKER) $(GUIFLAGS) -OUT:keylook.exe keylook.obj $(GUILIBS)

keylook.obj : keylook.c

     $(CC) $(CFLAGS) keylook.c

/*-------------------------------------------------------

   KEYLOOK.C -- Displays Keyboard and Character Messages

                (c) Charles Petzold, 1996

  -------------------------------------------------------*/

#include <windows.h>

#include <stdio.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

RECT rect ;

int  cxChar, cyChar ;

 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "KeyLook" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Keyboard Message Looker",


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

          }

     return msg.wParam ;

     }

void ShowKey (HWND hwnd, int iType, char *szMessage,

              WPARAM wParam, LPARAM lParam)

     {

     static char *szFormat[2] = { "%-14s %3d    %c %6u %4d %3s %3s %4s %4s",

                                  "%-14s    %3d %c %6u %4d %3s %3s %4s %4s" } ;

     char        szBuffer[80] ;

     HDC         hdc ;

 
     ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;

     hdc = GetDC (hwnd) ;

     SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

     TextOut (hdc, cxChar, rect.bottom - cyChar, szBuffer,

              wsprintf (szBuffer, szFormat [iType],

                        szMessage, wParam,

                        (BYTE) (iType ? wParam : ' '),

                        LOWORD (lParam),

                        HIWORD (lParam) & 0xFF,

                        (PSTR) (0x01000000 & lParam ? "Yes"  : "No"),

                        (PSTR) (0x20000000 & lParam ? "Yes"  : "No"),

                        (PSTR) (0x40000000 & lParam ? "Down" : "Up"),

                        (PSTR) (0x80000000 & lParam ? "Up"   : "Down"))) ;

     ReleaseDC (hwnd, hdc) ;

     ValidateRect (hwnd, NULL) ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static char szTop[] =

               "Message        Key Char Repeat Scan Ext ALT Prev Tran";
     static char szUnd[] =

               "_______        ___ ____ ______ ____ ___ ___ ____ ____";

     HDC         hdc ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;

               rect.top = 3 * cyChar / 2 ;

               return 0 ;

          case WM_SIZE :

               rect.right  = LOWORD (lParam) ;


               rect.bottom = HIWORD (lParam) ;

               UpdateWindow (hwnd) ;

               return 0 ;

          case WM_PAINT :

               InvalidateRect (hwnd, NULL, TRUE) ;

               hdc = BeginPaint (hwnd, &ps) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               SetBkMode (hdc, TRANSPARENT) ;

               TextOut (hdc, cxChar, cyChar / 2, szTop, (sizeof szTop) - 1) ;

               TextOut (hdc, cxChar, cyChar / 2, szUnd, (sizeof szUnd) - 1) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_KEYDOWN :

               ShowKey (hwnd, 0, "WM_KEYDOWN", wParam, lParam) ;

               return 0 ;

          case WM_KEYUP :

               ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam) ;

               return 0 ;

 
          case WM_CHAR :

               ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam) ;

               return 0 ;

          case WM_DEADCHAR :

               ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam) ;

               return 0 ;

          case WM_SYSKEYDOWN :

               ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSKEYUP :

               ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSCHAR :

               ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSDEADCHAR :

               ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

 
          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 5.3. Programul KEYLOOK

Programul KEYLOOK foloseşte ecranul după modelul teleimprimatorului. Atunci când primeşte un
mesaj generat de acţionarea unei taste, programul KEYLOOK apelează funcţia ScrollWindow, care
derulează în sus conţinutul întregii zone client cu înălţimea unui caracter. Funcţia TextOut este
folosită pentru afişarea unei noi linii de informaţii, începând afişarea într-un punct aflat la o distanţă
egală cu înălţimea unui caracter, faţă de marginea de jos a ecranului. Este la fel de simplu ca şi în
cazul unui teleimprimator. Figura 5-4 prezintă fereastra afişată de programul KEYLOOK atunci
când tastaţi cuvântul „Windows". Prima coloană conţine mesajele de la tastatură; a doua coloană
conţine codurile de taste virtuale pentru mesajele generate de acţionarea fastelor; a treia coloană
conţine codul caracterului (şi caracterul); celelalte şase coloane prezintă starea celor şase câmpuri
din parametrul lParam al mesajului.

În cea mai mare parte, programul KEYLOOK foloseşte caracteristici Windows despre care deja am
discutat în programele SYSMETS prezentate până acum, dar sunt folosite şi câteva funcţii noi.
Remarcaţi, totuşi, că formatarea coloanelor în programul KEYLOOK ar fi fost destul de dificilă
dacă s-ar fi folosit fontul prestabilit proporţional. Codul pentru afişarea fiecărei linii ar trebui
împărţit în nouă secţiuni pentru a păstra coloanele aliniate. Pentru rezolvarea unei astfel de situaţii o
soluţie mai simplă este să folosiţi un font cu dimensiune fixă. Aşa cum am arătat în capitolul
anterior, pentru aceasta este nevoie de două funcţii, pe care le-am combinat într-o singură
instrucţiune:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

Programul KEYLOOK apelează aceste funcţii de fiecare dată când obţine un context de dispozitiv.
Acest lucru se întâmplă în trei locuri din program: în funcţia ShowKey, în timpul prelucrării
mesajului WM_CREATE din funcţia WndProc şi în timpul prelucrării mesajului WM_PAINT.
Funcţia GetStockObject obţine o variabilă handle a unui obiect grafic de stoc - care în acest caz este
fontul de dimensiune fixă folosit în versiunile mai vechi Windows 3.0. Funcţia SelectObject
selectează obiectul în contextul de dispozitiv. După acest apel, orice text va fi afişat cu fontul de
dimensiune fixă selectat. Puteţi să vă întoarceţi la fontul proporţional prestabilit, cu următoarea
instrucţiune:

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;


 

Figura 5-4. Fereastra afişată de programul KEYLOOK.

Funcţia ShowKey apelează funcţia ScrollWindow, care derulează liniile anterioare înaintea afişării
unei noi linii. În mod normal, această operaţie ar duce la invalidarea parţială a ferestrei şi ar generă
un mesaj WM_PAINT. Pentru a împiedica acest lucru, înainte de terminare, funcţia ShowKey
apelează funcţia ValidateRect.

Programul KEYLOOK nu salvează mesajele pe care le primeşte, aşa că la primirea unui mesaj
WM_PAINT nu poate să re-creeze fereastra. Din acest motiv, la primirea mesajului WM_PAINT
programul KEYLOOK afişează în partea superioară a ferestrei capetele celor nouă coloane. Înaintea
apelării funcţiei BeginPaint, programul KEYLOOK invalidează întreaga fereastră. În acest fel, este
ştearsă toată suprafaţa ferestrei, nu numai dreptunghiul invalid.

(Faptul că programul KEYLOOK nu salvează mesajele pe care le primeşte şi deci la primirea unui
mesaj WM_PAINT nu poate să re-creeze fereastra este un neajuns al programului. Programul
TYPER, prezentat puţin mai târziu, corectează această eroare.)

Programul KEYLOOK afişează în partea superioară a zonei client un antet pentru identificarea celor
nouă coloane. Deşi este posibilă crearea unui font subliniat, în acest program am ales o abordare
puţin diferită. Am definit două şiruri de caractere, numite szTop (care conţine textul antetului) şi
szUnd (care conţine liniuţele de subliniere) şi le-am afişat în aceeaşi poziţie în partea de sus a
ecranului, în timpul prelucrării mesajului WM_PAINT. În mod normal, Windows afişează textul în
modul „opac" (OPAQUE), ceea ce înseamnă că şterge zona de fond din spatele caracterelor afişate.
Din această cauză, al doilea şir de caractere (szUnd) ar şterge primul şir de caractere (szTop). Pentru
a împiedica acest lucru, am schimbat modul de afişare în contextul de dispozitiv din „opac" în
„transparent":

SetBkMode (hdc, TRANSPARENT) ;

cursorul DE EDITARE (CARET)

Atunci când introduceţi text într-un program, poziţia în care va apărea următorul caracter este
indicată de o liniuţă de subliniere sau de un mic dreptunghi, în Windows pentru acest semn se
foloseşte termenul „cursor de editare".

Funcţii pentru cursorul de editare

Există cinci funcţii principale pentru cursorul de editare:

§  CreateCaret - creează un cursor de editare asociat unei ferestre.

§  SetCaretPos - stabileşte poziţia cursorului de editare în fereastră.

§  ShowCaret - afişează cursorul de editare.

§  HideCaret - maschează cursorul de editare.

§  DestroyCaret - distruge cursorul de editare creat.

Mai există şi alte funcţii pentru obţinerea poziţiei cursorului de editare (GetCaretPos) şi pentru
stabilirea şi obţinerea intervalelor de licărire a acestuia (SetCaretBlinkTime şi GetCaretBlinkTime).

Cursorul de editare este, de obicei, o linie ori un bloc orizontal de dimensiunea unui caracter, sau o
linie verticală. Linia verticală este recomandată în cazul folosirii unui font proportional, cum ar fi
fontul sistem prestabilit din Windows. Deoarece caracterele din fonturile proporţionale nu au aceeaşi
lăţime, linia sau blocul orizontal nu poate avea lăţimea exactă a unui caracter.

Cursorul de editare nu poate fi creat pur şi simplu în timpul prelucrării mesajului WM_CREATE şi
nici distrus în timpul prelucrării mesajului WM_DESTROY. El este ceea ce se numeşte o „resursă
de sistem". Aceasta înseamnă că în sistem există un singur cursor de editare. De fapt, atunci când un
program trebuie să afişeze un cursor de editare în fereastra proprie, el „împrumută" acest semn de la
sistem.

Pare prea restrictiv? Ei bine, în realitate nu este. Gândiţi-vă puţin: afişarea unui cursor de editare
într-o fereastră are sens numai dacă fereastra respectivă deţine cursorul de intrare (input focus).
Cursorul de editare indică utilizatorului faptul că poate introduce text în program. La un moment
dat, o singură fereastră poate deţine cursorul de intrare, aşa că existenţa unui singur cursor de editare
este logică.

Un program poate determina dacă deţine cursorul de intrare prin prelucrarea mesajelor
WM_SETFOCUS şi WM_KILLFOCUS. O procedură de fereastră recepţionează un mesaj
WM_SETFOCUS atunci când primeşte cursorul de intrare şi un mesaj WM_KILLFOCUS atunci
când pierde cursorul de intrare. Aceste mesaje sunt transmise în pereche. O procedură de fereastră
primeşte întotdeauna un mesaj WM_SETFOCUS înainte de a primi un mesaj WM_KILLFOCUS şi
întotdeauna va primi un număr egal de mesaje WM_SETFOCUS şi WM_KILLFOCUS pană la
distrugerea ferestrei.

Principala regulă de folosire a cursorului de editare este simplă. O procedură de fereastră apelează
funcţia CreateCaret în timpul prelucrării mesajului WM_SETFOCUS şi funcţia DestroyCaret în
timpul prelucrării mesajului WM_KILLFOCUS.

Există şi alte câteva reguli: la creare, cursorul de editare nu este afişat. După apelarea funcţiei
CreateCaret, programul trebuie să apeleze funcţia ShowCaret pentru a face vizibil cursorul de
editare. În plus, procedura de fereastră trebuie să-l mascheze, apelând funcţia HideCaret, ori de câte
ori desenează ceva pe ecran în timpul prelucrării unui alt mesaj decât WM_PAINT. După terminarea
operaţiei de desenare, programul apelează funcţia ShowCaret ca să afişeze din nou cursorul de
editare. Efectul funcţiei HideCaret este aditiv: dacă apelaţi funcţia HideCaret de mai multe ori fără
să apelaţi funcţia ShowCaret, atunci când doriţi ca acest cursor de editare să devină din nou vizibil,
trebuie să apelaţi funcţia ShowCaret de tot atâtea ori de cate ori aţi apelat şi funcţia HideCaret.

Programul TYPER

Programul TYPER, prezentat în Figura 5-5, combină cea mai mare parte a lucrurilor învăţate în
acest capitol. Puteţi să vă gândiţi la programul TYPER ca la un editor de text mai rudimentar. Puteţi
să introduceţi text în fereastră, să mutaţi cursorul de editare cu ajutorul tastelor de deplasare şi să
ştergeţi conţinutul ferestrei apăsând tasta Esc. Conţinutul ferestrei este şters, de asemenea, şi atunci
când fereastra este redmensionată. Nu există posibilitatea de derulare, de căutare sau de înlocuire, de
salvare a fişierelor sau de verificare ortografică, dar putem spune că este un început!

Pentru a face lucrurile mai uşoare pentru mine, TYPER foloseşte un font cu dimensiune fixă. Aşa
cum vă puteţi imagina, scrierea unui editor pentru un font proportional este mult mai dificilă.
Programul obţine un context de dispozitiv în timpul prelucrării mai multor mesaje: WM_CREATE,
WM_KEYDOWN, WM_CHAR si WM_PAINT. De fiecare dată, prin apelarea funcţiilor
GetStockObject şi SelectObject, este selectat în contextul de dispozitiv un font cu dimensiune fixă.

În timpul prelucrării mesajului WM_SIZE, TYPER calculează lăţimea şi înălţimea în caractere a


ferestrei şi salvează aceste valori în variabilele cxBuffer şi cyBuffer. Apoi apelează funcţia malloc
pentru alocarea unei zone de memorie suficient de mare pentru a conţine toate caracterele care pot fi
introduse într-o fereastră. Variabilele xCaret şi yCaret sunt folosite pentru stocarea poziţiei
cursorului de editare.

În timpul prelucrării mesajului WM_SETFOCUS, TYPER apelează funcţia CreateCaret pentru


crearea unui semn caret cu dimensiunile unui caracter, apoi funcţia SetCaretPos pentru stabilirea
poziţiei cursorului de editare şi funcţia ShowCaret, pentru a-l face vizibil. În timpul prelucrării
mesajului WM_KILLFOCUS, TYPER apelează funcţiile HideCaret şi DestroyCaret.
 

#---------------------

# TYPER.MAK make file

#---------------------

typer.exe : typer.obj

     $(LINKER) $(GUIFLAGS) -OUT:typer.exe typer.obj $(GUILIBS)

typer.obj : typer.c

     $(CC) $(CFLAGS) typer.c

/*--------------------------------------

   TYPER.C -- Typing Program

              (c) Charles Petzold, 1996

  --------------------------------------*/

#include <windows.h>

#include <stdlib.h>

#define BUFFER(x,y) *(pBuffer + y * cxBuffer + x)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Typer" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Typing Program",


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

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static char *pBuffer = NULL ;

     static int  cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer,

                 xCaret, yCaret ;

     HDC         hdc ;

     int         x, y, i ;

     PAINTSTRUCT ps ;
     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;

               return 0 ;

          case WM_SIZE :

                                   // obtain window size in pixels

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

                                   // calculate window size in characters

               cxBuffer = max (1, cxClient / cxChar) ;


               cyBuffer = max (1, cyClient / cyChar) ;

                                   // allocate memory for buffer and clear it

               if (pBuffer != NULL)

                    free (pBuffer) ;

               if ((pBuffer = (char *) malloc (cxBuffer * cyBuffer)) == NULL)

                    MessageBox (hwnd, "Window too large.  Cannot "

                                      "allocate enough memory.", "Typer",

                                      MB_ICONEXCLAMATION | MB_OK) ;

               else

                    for (y = 0 ; y < cyBuffer ; y++)

                         for (x = 0 ; x < cxBuffer ; x++)

                              BUFFER(x,y) = ' ' ;

                                   // set caret to upper left corner

               xCaret = 0 ;

               yCaret = 0 ;

               if (hwnd == GetFocus ())

                    SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;
 

          case WM_SETFOCUS :

                                   // create and show the caret

               CreateCaret (hwnd, NULL, cxChar, cyChar) ;

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               ShowCaret (hwnd) ;

               return 0 ;

          case WM_KILLFOCUS :

                                   // hide and destroy the caret

               HideCaret (hwnd) ;

               DestroyCaret () ;

               return 0 ;

          case WM_KEYDOWN :

               switch (wParam)

                    {

                    case VK_HOME :

                         xCaret = 0 ;

                         break ;

                    case VK_END :

                         xCaret = cxBuffer - 1 ;


                         break ;

                    case VK_PRIOR :

                         yCaret = 0 ;

                         break ;

                    case VK_NEXT :

                         yCaret = cyBuffer - 1 ;

                         break ;

                    case VK_LEFT :

                         xCaret = max (xCaret - 1, 0) ;

                         break ;

                    case VK_RIGHT :

                         xCaret = min (xCaret + 1, cxBuffer - 1) ;

                         break ;

                    case VK_UP :

                         yCaret = max (yCaret - 1, 0) ;

                         break ;

                    case VK_DOWN :

                         yCaret = min (yCaret + 1, cyBuffer - 1) ;


                         break ;

                    case VK_DELETE :

                         for (x = xCaret ; x < cxBuffer - 1 ; x++)

                              BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ;

                         BUFFER (cxBuffer - 1, yCaret) = ' ' ;

                         HideCaret (hwnd) ;

                         hdc = GetDC (hwnd) ;

                         SelectObject (hdc,

                              GetStockObject (SYSTEM_FIXED_FONT)) ;

                         TextOut (hdc, xCaret * cxChar, yCaret * cyChar,

                                  & BUFFER (xCaret, yCaret),

                                  cxBuffer - xCaret) ;

                         ShowCaret (hwnd) ;

                         ReleaseDC (hwnd, hdc) ;

                         break ;

                    }

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;


               return 0 ;

          case WM_CHAR :

               for (i = 0 ; i < (int) LOWORD (lParam) ; i++)

                    {

                    switch (wParam)

                         {

                         case '\b' :                    // backspace

                              if (xCaret > 0)

                                   {

                                   xCaret-- ;

                                   SendMessage (hwnd, WM_KEYDOWN,

                                                VK_DELETE, 1L) ;

                                   }

                              break ;

                         case '\t' :                    // tab

                              do

                                   {

                                   SendMessage (hwnd, WM_CHAR, ' ', 1L) ;

                                   }

                              while (xCaret % 8 != 0) ;

                              break ;

 
                         case '\n' :                    // line feed

                              if (++yCaret == cyBuffer)

                                   yCaret = 0 ;

                              break ;

                         case '\r' :                    // carriage return

                              xCaret = 0 ;

                              if (++yCaret == cyBuffer)

                                   yCaret = 0 ;

                              break ;

                         case '\x1B' :                  // escape

                              for (y = 0 ; y < cyBuffer ; y++)

                                   for (x = 0 ; x < cxBuffer ; x++)

                                        BUFFER (x, y) = ' ' ;

                              xCaret = 0 ;

                              yCaret = 0 ;

                              InvalidateRect (hwnd, NULL, FALSE) ;

                              break ;

                         default :                       // character codes


                              BUFFER (xCaret, yCaret) = (char) wParam ;

                              HideCaret (hwnd) ;

                              hdc = GetDC (hwnd) ;

                              SelectObject (hdc,

                                   GetStockObject (SYSTEM_FIXED_FONT)) ;

                              TextOut (hdc, xCaret * cxChar, yCaret * cyChar,

                                       & BUFFER (xCaret, yCaret), 1) ;

                              ShowCaret (hwnd) ;

                              ReleaseDC (hwnd, hdc) ;

                              if (++xCaret == cxBuffer)

                                   {

                                   xCaret = 0 ;

                                   if (++yCaret == cyBuffer)

                                        yCaret = 0 ;

                                   }

                              break ;

                         }

                    }
 

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               for (y = 0 ; y < cyBuffer ; y++)

                    TextOut (hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 5.5. Programul TYPER

 
Prelucrarea mesajelor WM_KEYDOWN şi WM_CHAR este ceva mai complicată. Prelucrarea
mesajului WM_KEYDOWN implică, în cea mai mare parte, tastele de deplasare a cursorului. Tasta
Home duce cursorul de editare la începutul liniei, tasta End îl duce la sfârşitul liniei, iar tastele Page
Up şi Page Down îl mută la începutul, respectiv la sfârşitul ferestrei. Tastele cu săgeţi funcţionează
normal, ca şi în alte programe. În cazul apăsării tastei Delete, programul TYPER trebuie să mute
spre stânga cu o poziţie tot conţinutul bufferului de la următorul caracter, până la sfârşitul liniei şi
apoi să afişeze un spaţiu la sfârşitul liniei.

În timpul prelucrării mesajului WM_CHAR sunt tratate tastele Backspace, Tab, Linefeed
(Ctrl+Enter), Enter, Esc şi caracterele. Remarcaţi că am folosit contorul de repetare (Repet Count)
din parametrul IParam pentru prelucrarea mesajelor WM_CHAR (pornind de la ideea că toate
caracterele introduse de utilizator sunt importante) dar nu şi pentru prelucrarea mesajelor
WM_KEYDOWN (pentru a evita derularea nedorită a ecranului). Tratarea tastelor Backspace şi Tab
este simplificată prin folosirea funcţiei SendMessage. Tasta Backspace este emulată prin logica de
tratare a tastei Delete iar tasta Tab este emulată prin mai multe spaţii.

Aşa cum am menţionat anterior, este recomandat să mascaţi cursorul de editare atunci când efectuaţi
operaţii de desenare în timpul prelucrării altor mesaje decât WM_PAINT. Programul face acest
lucru în timpul prelucrării mesajului WM_KEYDOWN pentru tasta Delete şi în timpul prelucrării
mesajului WM_CHAR pentru caractere. În ambele cazuri, programul TYPER schimbă conţinutul
bufferului şi apoi afişează noile caractere în fereastră.

Aşa cum se poate vedea în Figura 5.6, am folosit programul TYPER atunci când am lucrat la
discursuri.
Figura 5.6. Fereastra afişată de programul TYPER.

setul DE CARACTERE windows

Am menţionat anterior că apăsarea unei taste moarte înaintea tastei corespunzătoare unei litere
precedată de o tasta moartă generează un mesaj WM_CHAR pentru care parametrul wParam
conţine codul ASCII al caracterului cu semnul diacritic. S-ar putea ca această afirmaţie să nu fie
prea clară, deoarece setul de caractere ASCII nu conţine nici un cod pentru caractere cu semne
diacritice. Atunci, ce conţine parametrul wParam? Pentru a răspunde la această întrebare trebuie să
discutăm despre seturile de caractere, un subiect care, deşi la prima vedere pare a-şi avea mai
degrabă locul într-o discuţie despre fonturi, are importanţa lui şi pentru manipularea tastaturii.

Setul de caractere ASCII standard pe 7 biţi defineşte codurile de la 0 la 31 (0x1F) şi codul 127
(0x7F) pentru caracterele de control şi codurile de la 32 (0x20) la 126 (0x7E) pentru caracterele
afişabile. Nici unul dintre aceste caractere nu are semne diacritice. Deoarece calculatoarele
personale folosesc octeţi de câte opt biţi, producătorii de calculatoare definesc un set de caractere
care conţine 256 de coduri în loc de 128. Codurile suplimentare pot conţine caractere cu semne
diacritice. „Setul de caractere extins" rezultat include şi setul de caractere ASCII cu coduri de la 0 la
127.
Dacă Windows ar accepta un asemenea set de caractere extinse, afişarea caracterelor cu diacritice ar
fi foarte simplă, dar Windows nu acceptă un set de caractere extins, ci două. Din nefericire, însă,
prezenţa celor două seturi de caractere nu face lucrurile de două ori mai uşoare.

Setul de caractere OEM

Mai întâi, haideţi să revedem componentele hardware cu care lucrează sistemul de operare Windows
- calculatoarele IBM PC şi compatibile. La începutul anilor '80, dezvoltatorii calculatoarelor IBM
PC au decis să extindă setul de caractere ASCII, aşa cum se poate vedea în Figura 5.7. Codurile de
la 0x20 la 0x7E sunt caracterele afişabile din setul de caractere ASCII. Celelalte sunt nestandard -
sau cel puţin aşa erau în momentul respectiv.

Acest set de caractere nu poate fi ignorat. Este codificat hardware în cipurile ROM ale plăcilor
grafice, imprimantelor şi circuitelor BIOS ale plăcilor de bază IBM. A fost copiat de numeroşii
producători de calculatoare şi periferice compatibile IBM. Acest set de caractere face parte din ceea
ce se numeşte „standardul IBM". Multe programe non-Windows în mod caracter scrise pentru
calculatoarele IBM PC au nevoie de acest set de caractere, deoarece folosesc pentru afişare
caracterele de desenare a blocurilor şi a liniilor (codurile de la B0H la DFH).

Singura problemă este faptul că setul extins de caractere definit de IBM nu este potrivit pentru
Windows. În primul rând, caracterele de desenare a blocurilor şi a liniilor folosite de programele în
mod text nu sunt necesare sub Windows, deoarece Windows lucrează în mod grafic. Dacă vreţi să
desenaţi o linie orizontală, în Windows este mai uşor să apelaţi o funcţie de desenare decât să afişaţi
un şir de caractere 0xC4. În al doilea rând, alfabetul grecesc şi simbolurile matematice sunt mai
puţin importante în Windows decât literele accentuate, folosite de majoritatea limbilor europene. Un
program care trebuie să afişeze simboluri matematice o poate face mai uşor folosind funcţii grafice.

Pe scurt, sistemul de operare Windows acceptă setul de caractere IBM, dar îi acordă o importanţă
mai mică - în principal pentru aplicaţii mai vechi, rulate în ferestre. În mod normal, aplicaţiile
Windows nu folosesc setul de caractere IBM. În documentaţia Windows, setul de caractere IBM este
numit „setul de caractere OEM". Setul de caractere OEM este mai precis definit ca setul de caractere
nativ pe calculatorul pe care rulează sistemul de operare Windows.

Asigurarea suportului internaţional sub MS-DOS

Există mai multe variante ale setului de caractere IBM PC, variante numite „pagini de cod ".
Varianta folosită în Statele Unite şi în majoritatea ţărilor europene este pagina 437. Calculatoarele
vândute în Norvegia, Danemarca, Portugalia şi alte câteva ţări europene folosesc alte pagini, care
conţin multe dintre caracterele speciale folosite în ţările respective. În ultimul timp, o parte dintre
aceste ţări au început să folosescă pagina 850, care conţine mai puţine caractere grafice şi mai multe
litere cu accent şi alte caractere speciale.

Windows asigură suportul pentru paginile de coduri prin instalarea fonturilor OEM (folosite pentru
rularea aplicaţiilor MS-DOS în ferestre şi pentru afişarea memoriei clipboard) corespunzătoare
paginii de coduri a sistemului şi prin instalarea tabelelor de conversie corespunzătoare pentru
funcţiile CharToOem şi OemToChar (despre care vom discuta ceva mai târziu).

Programul de instalarea Windows Setup va selecta paginile de cod corespunzătoare, în funcţie de


atributele regionale ale configuraţiei curente a sistemului.
Setul de caractere ANSI

Setul extins de caractere folosit de obicei de Windows şi de programele Windows se numeşte „setul
de caractere ANSI" şi este, de fapt, un standard ISO. Atunci când programul dumneavoastră
primeşte un mesaj WM_CHAR, parametrul wParam conţine codul ANSI al caracterului. Setul de
caractere ANSI este prezentat în Figura 5.8. Aşa cum se poate vedea, codurile de la 0x20 la 0x7E
reprezintă aceleaşi caractere ca şi în setul de caractere OEM şi în setul de caractere ASCII.
Caracterele afişate sub forma unor blocuri nu sunt definite. Acestea pot apărea diferit pentru
Figura 5.7. Setul IBM de caractere extinse, aranjate după codul caracterelor.

Figura 5.8. Setul ANSI de caractere extinse, aranjate după codul caracterelor.

alte dispozitive de ieşire (cum ar fi imprimanta). Fonturile TrueType definesc unele caractere
suplimentare pentru codurile ANSI de la 0x80 la 0x9F.

OEM, ANSI şi fonturile

Windows conţine diferite fonturi pentru afişarea seturilor de caractere ANSI şi OEM. Atunci când
obţineţi pentru prima dată o variabilă handle a unui context de dispozitiv, unul dintre atributele
acestuia este fontul selectat, în mod prestabilit, acesta este SYSTEM_FONT („fontul sistem") care
foloseşte setul de caractere ANSI. Dacă vreţi să afişaţi caractere din setul OEM, puteţi să selectaţi
fontul OEM_FIXED_FONT (numit şi „font de terminal") în contextul de dispozitiv, folosind
următoarea instrucţiune:

SelectObject (hdc, GetStockObject (OEM_FIXED_FONT)) ;

PROBLEME LEGATE DE INTERNAŢIONALIZARE


Am văzut că atunci când un utilizator Windows cu o tastatură diferită de cea definită pentru Statele
Unite introduce un caracter cu semn diacritic, parametrul wPavam al mesajului WM_CHAR conţine
codul ANSI al caracterului respectiv.

Ca urmare, dacă trebuie să afişaţi pe ecran caracterul respectiv, este bine să folosiţi un font pentru
setul de caractere ANSI (cum ar fi SYSTEM_FONT sau SYSTEM_FIXED_FONT). Dacă folosiţi
fontul OEM_FIXED_FONT, caracterul afişat pe ecran va fi incorect şi îl va surprinde pe utilizator.
Alte câteva reguli simple vă vor ajuta să păstraţi nemodificat codul de manipulare a tastaturii atunci
când scrieţi un program pentru piaţa europeană.

Folosirea seturilor de caractere


Atunci când primiţi un mesaj WM_CHAR, ţineţi seama de faptul că parametrul wParam poate avea
şi valori mai mari de 128. Nu porniţi de la ideea că orice cod mai mare de 128 reprezintă un caracter
invalid.

S-ar putea să aveţi uneori nevoie de un caracter scris cu majusculă. Nu folosiţi un algoritm propriu,
cum ar fi:

if (ch = 'a' && ch <= 'z') ch -=32 ;                  // Greşit!!!

Aceasta este o metodă greşită în programarea sub Windows. Dar nu folosiţi nici funcţiile standard
de conversie din C:

ch = toupper (ch) ;          // Greşit!!!

Rezultatele acestor funcţii sunt corecte numai pentru prima jumătate a setului de caractere. Funcţiile
standard din C nu vor converti codul 0xE0 în 0xC0.

În schimb, puteţi să folosiţi funcţiile Windows CharUpper şi CharLower. Dacă pString este un şir
de caractere terminat cu zero, puteţi să îl convertiţi în litere mari folosind funcţia CharUpper:

CharUpper (pString) ;

Dacă şirul de caractere nu este terminat cu zero folosiţi funcţia CharUpperBuff:

CharUpperBuff (pString, nLength) ;


Puteţi să folosiţi funcţia CharUpper şi pentru un singur caracter, dar sunt necesare unele conversii
forţate de tip, deoarece cuvântul mai semnificativ al parametrului trebuie să aibă valoarea 0:

ch = CharUpper ((PSTR) (LONG) (BYTE) ch) ;

Dacă ch este definit ca un caracter fără semn (unsigned character) prima conversie forţată (BYTE)
nu este necesară. Windows include şi funcţiile CharLower şi CharLowerBuff pentru convertirea
şirurilor de caractere în litere mici.

Dacă vă gândiţi cu seriozitate la scrierea unor programe Windows care să poată fi modificate cu
uşurinţă pentru alte limbi, ar trebui să examinaţi şi funcţiile CharNext şi CharPrev. Aceste funcţii vă
ajută să manipulaţi seturile de caractere pe mai mulţi octeţi, deseori folosite în ţările orientale.
Aceste seturi conţin mai mult de 256 de caractere, dintre care unele folosesc doi octeţi. Dacă vă
bazaţi pe aritmetica de pointeri normală din C pentru parcurgerea unui şir de caractere (de exemplu
pentru căutarea unui caracter backslash într-o cale de acces la un director) s-ar putea să credeţi că aţi
găsit caracterul căutat şi, de fapt, să folosiţi al doilea caracter al unui cod. Funcţiile CharNext şi
CharPrev primesc ca parametru un pointer de tip far la un şir de caractere şi returnează un pointer de
tip far corect incrementat sau decrementat pentru codurile de caractere formate din doi octeţi.

Comunicarea cu MS-DOS

Dacă Windows ar fi singurul sistem de operare rulat pe calculator, aţi putea să uitaţi de setul de
caractere OEM şi să folosiţi numai setul de caractere ANSI. Dar utilizatorii pot să creeze fişiere în
MS-DOS şi să le folosească în Windows, sau să creeze fişiere în Windows şi să le folosească în MS-
DOS. Din păcate, sistemul de operare MS-DOS foloseşte setul de caractere OEM.

Iată o posibilă problemă de comunicare. Să presupunem că utilizatorul unui calculator personal,


vorbitor de limbă germană, creează un fişier numit UBUNGEN.TXT („exerciţii practice") într-un
program MS-DOS, cum ar fi EDIT. Pe un calculator IBM PC caracterul U face parte din setul de
caractere IBM (adică OEM) şi are codul 154 sau 0x9A. (Folosind tastatura de tip american sub MS-
DOS pe un calculator IBM PC puteţi să creaţi această literă introducând codul Alt+154 folosind
cifrele din blocul de taste numerice.) MS-DOS foloseşte acest cod de caracter pentru numele
fişierului din director.

Dacă un program Windows apelează o funcţie MS-DOS ca să obţină fişierele dintr-un director şi
afişează numele fişierelor pe ecran folosind un font pentru setul de caractere ANSi, prima literă din
numele fişierului - UBUNGEN.TXT va fi afişată ca un bloc negru, deoarece codul 154 este unul
dintre caracterele nedefinite din setul de caractere ANSI. Programul Windows trebuie să
convertească codul extins IBM 154 (0x9A) în codul ANSI 220 (0xDC), care reprezintă litera U în
setul de caractere ANSI. Acest lucru este făcut de funcţia Windows OemToChar. Funcţia
OemToChar primeşte ca parametri doi pointeri de tip far către două şiruri de caractere. Caracterele
OEM din primul şir sunt convertite în caractere ANSI şi sunt stocate în cel de-al doilea şir:

OemToChar (lpszOemStr, lpszAnsiStr) ;

Acum să vedem acelaşi exemplu, dar în sens invers. Vorbitorul de limbă germană doreşte ca
programul Windows să creeze un fişier numit UBUNGEN.TXT. Primul caracter al numelui de fişier
introdus de utilizator are codul 220 (0xDC). Dacă folosiţi o funcţie MS-DOS pentru deschiderea
fişierului, MS-DOS foloseşte caracterul respectiv în numele fişierului. Dacă ulterior utilizatorul vrea
să vadă numele fişierului sub MS-DOS, primul caracter va fi afişat sub forma unui bloc. Înaintea
apelării funcţiei MS-DOS trebuie să convertiţi numele fişierului în caractere OEM:

CharToOem (IpszAnsiStr, IpszOemStr) ;

Funcţia CharToOem converteşte caracterul 220 (0xDC) în caracterul 154 (0x9A). În Windows
există două funcţii cu acelaşi rezultat, CharToOemBuff şi OemToCharBuff, pentru care, însă, şirurile
de caractere nu trebuie să se termine cu 0.

Conversiile de mai sus sunt făcute şi de funcţia Windows OpenFile. Dacă folosiţi funcţia OpenFile
nu apelaţi şi funcţia CharToOem. Dacă folosiţi funcţii MS-DOS ca să obţineţi lista de fişiere (aşa
cum face programul Windows File Manager), atunci numele fişierelor trebuie convertite cu funcţia
OemToChar, înainte de a fi afişate.

Convertirea conţinutului fişierelor este o altă problemă care apare atunci când fişierele sunt folosite
atât sub Windows, cât şi sub MS-DOS. Dacă programul Windows foloseşte fişiere despre care ştiţi
cu siguranţă că au fost create într-un program MS-DOS, atunci conţinutul acestora ar trebui să fie
convertit cu ajutorul funcţiei OemToChar. La fel, dacă programul dumneavoastră creează fişiere
care vor fi folosite de un program MS-DOS, trebuie să folosiţi funcţia CharToOem ca să convertiţi
conţinutul acestora.

Funcţiile OemToChar şi CharToOem sunt stocate în driverul de tastatură şi conţin tabele de căutare
foarte simple. Funcţia OemToChar converteşte codurile OEM de Ia 0x80 la 0xFF într-un cod ANSI
reprezentând caracterul care seamănă cel mai mult cu caracterul OEM corespunzător, în unele cazuri
această conversie este aproximativă. De exemplu, majoritatea caracterelor de desenare din setul de
caractere IBM sunt convertite în semne plus, liniuţe de despărţire şi linii verticale. Majoritatea
codurilor OEM de la 0x00 la 0x1F nu sunt convertite în coduri ANSI.

Funcţia CharToOem converteşte codurile ANSI de Ia 0xA0 la 0xFF în coduri din setul OEM.
Literele accentuate din setul de caractere ANSI care nu apar în setul de caractere OEM sunt
convertite în caracterul ASCII corespunzător, fără semnele diacritice.

Folosirea blocului de taste numerice


După cum probabil ştiţi, tastatura IBM PC şi sistemul BIOS vă permit să introduceţi coduri pentru
caracterele din setul extins IBM; astfel, apăsaţi tasta Alt, introduceţi cu ajutorul tastelor numerice
codul din trei cifre al caracterului OEM, apoi eliberaţi tasta Alt. În Windows, această posibilitate
este dublată în două feluri.

În primul rând, atunci când introduceţi codul Alt+[cod OEM] de la tastele numerice, Windows
returnează (prin parametrul wParam al mesajului WM_CHAR) codul caracterului ANSI care
seamănă cel mai mult cu caracterul al cărui cod OEM l-aţi introdus. Aceasta înseamnă că Windows
trece codul prin funcţia OemToChar înainte de generarea mesajului WM_CHAR. Această operaţie
se face în folosul utilizatorului. Dacă nu aveţi o tastatură pentru limba străină pe care doriţi să o
folosiţi şi sunteţi obişnuit să introduceţi caracterul U folosind combinaţia de taste Alt+154, puteţi să
faceţi acelaşi lucru şi în Windows. Nu este nevoie să învăţaţi şi codurile ANSI.
În al doilea rând, dacă vreţi să generaţi un cod ANSI extins folosind tastatura de tip american,
introduceţi codul Alt+0[cod ANSI] de la blocul de taste numerice. Parametrul wParam al mesajului
WM_CHAR va conţine codul ANSI respectiv. Ca urmare, Alt+0220 este tot U. Puteţi să încercaţi
acest lucru în programele KEYLOCK sauTYPER.

Soluţia Unicode pentru Windows NT

Dezvoltatorii de programe implicaţi în crearea unor aplicaţii pentru piaţa internaţională au fost
nevoiţi să inventeze diferite soluţii pentru rezolvarea deficienţelor codului ASCII pe 7 biţi, cum ar fi
paginile de coduri sau seturile de caractere pe doi octeţi. Este nevoie de o soluţie mai bună, şi
aceasta ar putea fi Unicode.

Unicode este un standard de codificare a caracterelor, care foloseşte un cod uniform pe 16 biţi
pentru fiecare caracter. Acest cod permite reprezentarea tuturor caracterelor, din toate limbajele
scrise, care pot fi folosite în comunicaţiile prin calculator, inclusiv simbolurile chinezeşti, japoneze
şi coreene. Unicode a fost dezvoltat de un consorţiu de companii din industria calculatoarelor
(inclusiv cele mai mari dintre acestea).

Din nefericire, sistemul de operare Windows 95 asigură doar un suport rudimentar pentru standardul
Unicode şi nu poate furniza caractere Unicode prin driverul de tastatură. Aceasta este una dintre
diferenţele faţă de Windows NT, care include suportul pentru standardul Unicode.

Evident, adaptarea programelor (şi a modului de gândire al programatorilor) la ideea folosirii


codurilor pe 16 biţi este o sarcină laborioasă, dar merită dacă în acest fel vom putea afişa pe ecran şi
vom putea tipări la imprimantă texte în toate limbajele scrise existente.

Totul despre mouse


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 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.)

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.

/*--------------------------------------------------

CONNECT.C -- Connect-the-Dots Mouse Demo Program

 (c) Charles Petzold, 1996

 --------------------------------------------------*/

#include <windows.h>

#define MAXPOINTS 1000

#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL)

 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Connect" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;


 

     hwnd = CreateWindow (szAppName, "Connect-the-Points Mouse 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) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static POINT points[MAXPOINTS] ;

     static int   iCount ;

     HDC          hdc ;

     PAINTSTRUCT  ps ;
     int          i, j ;

     switch (iMsg)

          {

          case WM_LBUTTONDOWN :

               iCount = 0 ;

               InvalidateRect (hwnd, NULL, TRUE) ;

               return 0 ;

          case WM_MOUSEMOVE :

               if (wParam & MK_LBUTTON && iCount < 1000)

                    {

                    points[iCount  ].x = LOWORD (lParam) ;

                    points[iCount++].y = HIWORD (lParam) ;

           hdc = GetDC (hwnd) ;

           SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L) ;

                    ReleaseDC (hwnd, hdc) ;

                    }

               return 0 ;

          case WM_LBUTTONUP :

               InvalidateRect (hwnd, NULL, FALSE) ;

               return 0 ;

 
          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               SetCursor (LoadCursor (NULL, IDC_WAIT)) ;

               ShowCursor (TRUE) ;

               for (i = 0 ; i < iCount - 1 ; i++)

                    for (j = i + 1 ; j < iCount ; j++)

                         {

                         MoveTo (hdc, points[i].x, points[i].y) ;

                         LineTo (hdc, points[j].x, points[j].y) ;

                         }

               ShowCursor (FALSE) ;

               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;


     }

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.

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 !!!

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 DOWNWM_NCLBUTTONUP WM_NCLBUTTONDBLCLK
Mijloc WM_NCMBUTTONDOWNWM_NCMBUTTONUPWM_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).

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 ;

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 ;

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.

/*-------------------------------------------------

CHECKER1.C -- Mouse Hit-Test Demo Program No. 1

(c) Charles Petzold, 1996

-------------------------------------------------*/

#include <windows.h>

#define DIVISIONS 5

#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR  szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Checker1" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;


 

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Checker1 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))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static BOOL fState[DIVISIONS][DIVISIONS] ;

     static int  cxBlock, cyBlock ;

     HDC         hdc ;

     PAINTSTRUCT ps ;

     RECT        rect ;

     int         x, y ;

     switch (iMsg)

          {

          case WM_SIZE :

               cxBlock = LOWORD (lParam) / DIVISIONS ;

               cyBlock = HIWORD (lParam) / DIVISIONS ;

               return 0 ;
 

          case WM_LBUTTONDOWN :

               x = LOWORD (lParam) / cxBlock ;

               y = HIWORD (lParam) / 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) ;

                    }

               else

                    MessageBeep (0) ;

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               for (x = 0 ; x < DIVISIONS ; x++)


                    for (y = 0 ; y < DIVISIONS ; y++)

                         {

                         Rectangle (hdc, x * cxBlock, y * cyBlock,

                                   (x + 1) * cxBlock, (y + 1) * cyBlock) ;

                         if (fState [x][y])

                              {

                              MoveTo (hdc,  x    * cxBlock,  y    * cyBlock) ;

                              LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ;

                              MoveTo (hdc,  x    * cxBlock, (y+1) * cyBlock) ;

                              LineTo (hdc, (x+1) * cxBlock,  y    * cyBlock) ;

                              }

                         }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

 
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:

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

#define DIVISIONS 5

#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)


     {

     static char szAppName[] = "Checker2" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Checker2 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))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static BOOL fState[DIVISIONS][DIVISIONS] ;

     static int  cxBlock, cyBlock ;

     HDC         hdc ;

     PAINTSTRUCT ps ;

     POINT       point ;

     RECT        rect ;

     int         x, y ;

 
     switch (iMsg)

          {

          case WM_SIZE :

               cxBlock = LOWORD (lParam) / DIVISIONS ;

               cyBlock = HIWORD (lParam) / DIVISIONS ;

               return 0 ;

          case WM_SETFOCUS :

               ShowCursor (TRUE) ;

               return 0 ;

          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 :

                         y-- ;

                         break ;

                    case VK_DOWN :


                         y++ ;

                         break ;

                    case VK_LEFT :

                         x-- ;

                         break ;

                    case VK_RIGHT :

                         x++ ;

                         break ;

                    case VK_HOME :

                         x = y = 0 ;

                         break ;

                    case VK_END :

                         x = y = DIVISIONS - 1 ;

                         break ;

                    case VK_RETURN :

                    case VK_SPACE :

                         SendMessage (hwnd, WM_LBUTTONDOWN, MK_LBUTTON,

                                   MAKELONG (x * cxBlock, y * cyBlock)) ;

                         break ;

                    }

               x = (x + DIVISIONS) % DIVISIONS ;

               y = (y + DIVISIONS) % DIVISIONS ;


 

               point.x = x * cxBlock + cxBlock / 2 ;

               point.y = y * cyBlock + cyBlock / 2 ;

               ClientToScreen (hwnd, &point) ;

               SetCursorPos (point.x, point.y) ;

               return 0 ;

          case WM_LBUTTONDOWN :

               x = LOWORD (lParam) / cxBlock ;

               y = HIWORD (lParam) / 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) ;

                    }

               else
                    MessageBeep (0) ;

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               for (x = 0 ; x < DIVISIONS ; x++)

                    for (y = 0 ; y < DIVISIONS ; y++)

                         {

                         Rectangle (hdc, x * cxBlock, y * cyBlock,

                                   (x + 1) * cxBlock, (y + 1) * cyBlock) ;

                         if (fState [x][y])

                              {

                              MoveTo (hdc,  x    * cxBlock,  y    * cyBlock) ;

                              LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ;

                              MoveTo (hdc,  x    * cxBlock, (y+1) * cyBlock) ;

                              LineTo (hdc, (x+1) * cxBlock,  y    * cyBlock) ;

                              }

                         }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }
     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

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
lParam al mesajului generat de mouse conţine coordonatele relative la colţul din stânga-sus al ferestrei
descendent, nu al ferestrei părinte.
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.

/*-------------------------------------------------

   CHECKER3.C -- Mouse Hit-Test Demo Program No. 3

                 (c) Charles Petzold, 1996

  -------------------------------------------------*/

#include <windows.h>

#define DIVISIONS 5

#define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL)

LRESULT CALLBACK WndProc   (HWND, UINT, WPARAM, LPARAM) ;

LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;

char      szChildClass[] = "Checker3_Child" ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,


                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Checker3" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     wndclass.lpfnWndProc   = ChildWndProc ;

     wndclass.cbWndExtra    = sizeof (WORD) ;


     wndclass.hIcon         = NULL ;

     wndclass.lpszClassName = szChildClass ;

                wndclass.hIconSm       = NULL ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "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))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
     {

     static HWND hwndChild[DIVISIONS][DIVISIONS] ;

     int         cxBlock, cyBlock, x, y ;

     switch (iMsg)

          {

          case WM_CREATE :

               for (x = 0 ; x < DIVISIONS ; x++)

                    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_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 :

               MessageBeep (0) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT iMsg, WPARAM wParam,


LPARAM lParam)

     {

     HDC         hdc ;

     PAINTSTRUCT ps ;

     RECT        rect ;

     switch (iMsg)


          {

          case WM_CREATE :

               SetWindowWord (hwnd, 0, 0) ;       // on/off flag

               return 0 ;

          case WM_LBUTTONDOWN :

               SetWindowWord (hwnd, 0, 1 ^ GetWindowWord (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 (GetWindowWord (hwnd, 0))

                    {

                    MoveTo (hdc, 0,          0) ;

                    LineTo (hdc, rect.right, rect.bottom) ;

                    MoveTo (hdc, 0,          rect.bottom) ;

                    LineTo (hdc, rect.right, 0) ;

                    }

 
               EndPaint (hwnd, &ps) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 6.8. Programul CHECKER3. 

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- WS_CHILDWINDOW|
WINDOW 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 NULL hwnd
părinte
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 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.

#------------------------

# BLOKOUT1.MAK make file

#------------------------

blokout1.exe : blokout1.obj

     $(LINKER) $(GUIFLAGS) -OUT:blokout1.exe blokout1.obj $(GUILIBS)

blokout1.obj : blokout1.c

     $(CC) $(CFLAGS) blokout1.c

/*-----------------------------------------

   BLOKOUT1.C -- Mouse Button Demo Program

                 (c) Charles Petzold, 1996

  -----------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


 

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "BlokOut1" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

 
     hwnd = CreateWindow (szAppName, "Mouse Button 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) ;

          }

     return msg.wParam ;

     }

void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd)

     {

     HDC hdc ;

     hdc = GetDC (hwnd) ;

     SetROP2 (hdc, R2_NOT) ;


     SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

     Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ;

     ReleaseDC (hwnd, hdc) ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)

     {

     static BOOL  fBlocking, fValidBox ;

     static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

     HDC          hdc ;

     PAINTSTRUCT  ps ;

     switch (iMsg)

          {

          case WM_LBUTTONDOWN :

               ptBeg.x = ptEnd.x = LOWORD (lParam) ;

               ptBeg.y = ptEnd.y = HIWORD (lParam) ;

               DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

               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 ;

          case WM_LBUTTONUP :

               if (fBlocking)

                    {

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

                    }

               return 0 ;

          case WM_CHAR :

               if (fBlocking & wParam == '\x1B')       // ie, Escape

                    {

                    DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

                    SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

                    fBlocking = FALSE ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               if (fValidBox)
                    {

                    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 :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

Figura 6.9. Programul BLOKOUT1.


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.

Î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.
#------------------------

# BLOKOUT2.MAK make file

#------------------------

blokout2.exe : blokout2.obj

     $(LINKER) $(GUIFLAGS) -OUT:blokout2.exe blokout2.obj $(GUILIBS)

blokout2.obj : blokout2.c

     $(CC) $(CFLAGS) blokout2.c

/*---------------------------------------------------

   BLOKOUT2.C -- Mouse Button & Capture Demo Program

                 (c) Charles Petzold, 1996

  ---------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "BlokOut2" ;

     HWND        hwnd ;

     MSG         msg ;


     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (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 ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "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 (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd)

     {

     HDC hdc ;

     hdc = GetDC (hwnd) ;

     SetROP2 (hdc, R2_NOT) ;

     SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

     Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ;

     ReleaseDC (hwnd, hdc) ;

     }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
     {

     static BOOL  fBlocking, fValidBox ;

     static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

     HDC          hdc ;

     PAINTSTRUCT  ps ;

     switch (iMsg)

          {

          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 ;

               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 ;

                    fValidBox  = TRUE ;


 

                    InvalidateRect (hwnd, NULL, TRUE) ;

                    }

               return 0 ;

          case WM_CHAR :

               if (fBlocking & wParam == '\x1B')       // i.e., Escape

                    {

                    DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

                    ReleaseCapture () ;

                    SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

                    fBlocking = FALSE ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               if (fValidBox)

                    {

                    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 :

               PostQuitMessage (0) ;

               return 0 ;

          }

     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

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.

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