Documente Academic
Documente Profesional
Documente Cultură
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.
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.
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ă.
public class MyClass
{
}
Aici, EventHandler este un delegat care definește tipul procedurii de tratare a evenimentului.
Abonarea la eveniment este realizată astfel:
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
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ă).
.
.
.
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;
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".
Î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.
{
}
Î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.
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.
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.
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!
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.
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ă:
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.
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.
/*******************************************************************
*******************************************************************/
LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam);
/*----------------------------------------------------------------
*/
{
WNDCLASSEX wnd; // instance of the Window class ('wnd' object)
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(szCmdLine);
wnd.cbSize = sizeof(wnd);
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
wnd.hbrBackground = GetStockObject(WHITE_BRUSH);
wnd.lpszMenuName = NULL;
wnd.hIconSm = NULL;
wnd.lpszClassName = "HelloWin";
RegisterClassEx(&wnd);
hwnd = CreateWindow(
wnd.lpszClassName, // window class name
ShowWindow(hwnd, iShowCmd);
UpdateWindow(hwnd);
while (1) {
status = msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/*----------------------------------------------------------------
* of this application
*/
break;
}
case WM_DESTROY: { // windows is about to be destroyed
PostQuitMessage(0);
break;
}
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
char cBuffer[100];
(wm_keydown_ctr % 1000),
(wm_mousemove_ctr % 1000),
led_text);
GetClientRect(me, &rect);
EndPaint(me, &ps);
break;
}
break;
}
++wm_mousemove_ctr;
break;
}
break;
}
}
Î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.
/*******************************************************************
*******************************************************************/
LRESULT CALLBACK WndProc(HWND me, UINT sig, WPARAM wParam, LPARAM lParam);
/*----------------------------------------------------------------
*/
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.
wnd.cbSize = sizeof(wnd);
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
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
hwnd = CreateWindow(
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).
while (1) {
status = GetMessage(&msg, NULL, 0, 0); // generically WAIT for any message to arrive in
the queue
status = msg.wParam;
break; // terminate the event loop
}
TranslateMessage(&msg);
}
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.
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).
/*----------------------------------------------------------------
* of this application
*/
break;
}
PostQuitMessage(0);
break;
}
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
char cBuffer[100];
EndPaint(me, &ps);
break;
}
++wm_keydown_ctr;
break;
}
++wm_mousemove_ctr;
break;
}
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.
Î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ă.
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
char cBuffer[100];
led_text);
EndPaint(me, &ps);
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.
Î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. ).
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.
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:
++wm_keydown_ctr;
break;
}
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:
++wm_keydown_ctr;
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.
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:
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
char cBuffer[100];
GetClientRect(me, &rect);
EndPaint(me, &ps);
break;
}
case WM_TIMER: {
break;
}
++wm_keydown_ctr;
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.
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.
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.
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ă.
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.
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.
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.
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.
include <stdio.h>
main ()
Î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.
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.
#------------------------
#------------------------
hellowin.exe : hellowin.obj
hellowin.obj : hellowin.c
/*------------------------------------------------------------
------------------------------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
{
//return 0 ;
return 0 ;
case WM_DESTROY :
return 0 ;
}
}
Î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.
NMAKE HELLOWIN.MAK
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.
· 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).
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:
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
WS Stil de fereastră
WM Mesaj de fereastră
Î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.
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
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
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.
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:
c char
n short
i int
fn funcţie
p pointer
#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.
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:
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.
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ă.
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.
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").
WNDCLASSEX wndclass ;
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.
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".
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
Următorul câmp este variabila handle a instanţei (care este chiar unul dintre parametrii funcţiei
WinMain):
wndclass.hInstance = hinstance ;
Instrucţiunea:
şi instrucţiunea:
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:
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:
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:
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:
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.
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"):
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
Variabila msg este o structură de tip MSG, definită în fişierele antet din Windows astfel:
HWND hwnd ;
UINT message ;
WPARAM wParam ;
LPARAM lParam ;
DWORD time ;
POINT pt ;
MSG ,
LONG x ;
LONG y ;
POINT ;
Funcţia GetMessage apelată la începutul ciclului de mesaje preia un mesaj din coada de aşteptare:
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.
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) ;
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.
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ă.
switch (iMsg)
case WM_CREATE :
case WM_PAINT :
return 0 ;
case WM_DESTROY :
return 0 ;
Este esenţial să apelaţi funcţia DefWindowProc pentru prelucrarea tuturor mesajelor ignorate de
procedura de fereastră a programului.
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ă.
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:
Î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ă.
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:
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) ;
Î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.
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.
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.
Î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ă.
* Î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
· 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.
Windows salvează zona de ecran pe care a scris şi apoi o restaurează, în cazurile în care:
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.
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.
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:
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.
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:
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.
case WM_PAINT :
return 0 ;
case WM_PAINT:
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:
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.
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:
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.
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.
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) ;
ReleaseDC(hwnd, hdc) ;
Î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.
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:
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.
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.
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.
Pentru folosirea funcţiei GetTextMetrics trebuie să definiţi mai întâi o variabilă de tipul
TEXTMETRIC (numită, de obicei, tm):
TEXTMETRIC tm;
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.
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):
case WH_CREATE:
cxChar = tm.tmAveCharWidth;
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;
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;
iLenght = sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB);
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));
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.
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.
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.
/*-----------------------------------------------
-----------------------------------------------*/
struct
int iIndex ;
char *szLabel ;
char *szDesc ;
sysmetrics [ ] =
SM_CXSIZE, "SM_CXSIZE",
"Minimize/Maximize icon width",
SM_CYSIZE, "SM_CYSIZE",
"Minimize/Maximize icon height",
};
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"
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
int i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
{
TextOut (hdc, cxChar, cyChar * (1 + i), sysmetrics[i].szLabel, strlen
(sysmetrics[i].szLabel)) ;
TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar * (1 + i), szBuffer, wsprintf
(szBuffer, "%5d",
}
return 0 ;
return 0 ;
}
}
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ă 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:
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:
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:
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.
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:
return 0 ;
Mesajul WM_SIZE este urmat de un mesaj WM_PAINT, deoarece la definirea clasei am specificat
următorul stil de fereastră:
Puteţi să calculaţi numărul de linii de text care pot fi afişate în zona client folosind formula:
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:
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.
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).
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ă.
Î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:
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:
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.
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.
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.
Funcţia CreateWindow adaugă o bară de derulare la fereastră, datorită includerii stilului de fereastră
WS_VSCROLL la apelare:,
#------------------------
#------------------------
sysmets2.exe : sysmets2.obj
/*----------------------------------------------------
----------------------------------------------------*/
#include <windows.h>
#include <string.h>
#include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
int i, y ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
return 0 ;
{
iVscrollPos -= 1 ;
break ;
iVscrollPos += 1 ;
break ;
break ;
break ;
break ;
default :
break ;
}
{
}
return 0 ;
{
sysmetrics[i].szLabel,
sysmetrics[i].szDesc,
szBuffer,
}
return 0 ;
return 0 ;
}
}
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:
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.
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.
cyChar * (1 + 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.
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:
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.
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.exe : sysmets3.obj
/*----------------------------------------------------
----------------------------------------------------*/
#include <windows.h>
#include "sysmets.h"
{
wndclass.cbSize = sizeof (wndclass) ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
while (GetMessage (&msg, NULL, 0, 0))
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
char szBuffer[10] ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
{
break ;
break ;
iVscrollInc = -1 ;
break ;
iVscrollInc = 1 ;
break ;
break ;
break ;
break ;
default :
iVscrollInc = 0 ;
}
if (iVscrollInc != 0)
{
}
return 0 ;
case WM_HSCROLL :
{
iHscrollInc = -1 ;
break ;
iHscrollInc = 1 ;
break ;
iHscrollInc = -8 ;
break ;
iHscrollInc = 8 ;
break ;
break ;
default :
iHscrollInc = 0 ;
}
if (iHscrollInc != 0)
{
}
return 0 ;
{
sysmetrics[i].szLabel,
sysmetrics[i].szDesc,
szBuffer,
}
return 0 ;
return 0 ;
}
}
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:
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
Î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.
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.
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.
Î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.
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:
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ă.
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);
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.
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:
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:
DeleteDC (hdclnfo);
Atunci când lucraţi cu imagini bitmap, poate fi uneori utilă obţinerea unui „context de dispozitiv în
memorie":
hdcMem = CreateCompatibleDC (hdc);
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);
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).
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.
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.
---------------------------------------------------------*/
#include <windows.h>
#include <string.h>
struct
int iIndex ;
char *szLabel ;
char *szDesc ;
devcaps [] =
};
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClassEx (&wndclass) ;
UpdateWindow (hwnd) ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
char szBuffer[10] ;
HDC hdc ;
int i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (iMsg)
case WM_CREATE:
cxChar = tm.tmAveCharWidth ;
return 0 ;
case WM_PAINT:
devcaps[i].szLabel,
strlen (devcaps[i].szLabel)) ;
devcaps[i].szDesc,
strlen (devcaps[i].szDesc)) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
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.
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.
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:
Această valoare poate să nu fie identică cu numărul de culori obţinut prin apelarea funcţiei
GetDeviceCaps cu parametrul 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).
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:
((WORD)(g)<<8))|\
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:
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.
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
Î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:
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:
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:
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ă.
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:
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:
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:
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):
unde pt este o structură de tip POINT, definită în fişierele antet din Windows astfel:
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:
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:
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.
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:
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:
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:
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:
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
-----------------------------------------*/
#include <windows.h>
#include <math.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
int i ;
PAINTSTRUCT ps ;
POINT pt [NUM] ;
{
return 0 ;
{
}
return 0 ;
return 0 ;
}
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.
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.
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.
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.
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ă:
Î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:
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:
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.
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.
/*--------------------------------------------------
--------------------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
{
return 0 ;
return 0 ;
return 0 ;
}
}
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.
/*---------------------------------------
---------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
{
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
static POINT apt[4] ;
PAINTSTRUCT ps ;
{
return 0 ;
{
{
}
{
}
}
return 0 ;
return 0 ;
return 0 ;
}
}
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:
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.
Î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:
sau instrucţiunea:
Î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ă.
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:
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:
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:
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.
Ø Nu ştergeţi obiectele GDI în timp ce sunt selectate într-un context de dispozitiv valid.
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:
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.
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:
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:
Î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:
Î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:
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:
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;
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:
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:
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.
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.
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:
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 ;
Dacă vreţi să desenaţi o figură fără contur, selectaţi în contextul de dispozitiv peniţa 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:
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
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:
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:
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
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.
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:
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.
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:
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):
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:
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:
Î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:
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:
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:
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:
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.
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.
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.
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:
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:
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:
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ă:
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:
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 :
y = cyChar * (1 - iVscrollPos + 1) ;
[afişează textul]
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_PAINT :
y = cyChar * (1 + i) ;
[afişează textul]
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.
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:
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.
Extensia ferestrei: (?, ?) Nu poate fi modificată
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:
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:
O altă soluţie este să mutaţi punctul de coordonate (0,0) în centrul zonei client:
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 ;
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 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.
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:
Î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:
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:
Î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ă.
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.
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.
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.
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.
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.
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.
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.
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).
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 (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 (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 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.
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.
(continuare)
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.
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
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.
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ă.
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 ;
{
break ;
iVscrollInc = -1 ;
break ;
iVscrollInc = 1 ;
break ;
break ;
break ;
iHscrollInc = -8 ;
break ;
iHscrollInc = 8 ;
break ;
default :
break ;
}
{
iVscrollPos += iVscrollInc ;
UpdateWindow (hwnd) ;
}
{
iHscrollPos += iHscrollInc;
}
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ă:
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 :
break;
break ;
break ;
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.exe : sysmets.obj
/*-----------------------------------------------------
-----------------------------------------------------*/
#include <windows.h>
#include <string.h>
#include "sysmets.h"
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
ShowWindow (hwnd, iCmdShow) ;
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
return 0 ;
{
break ;
break ;
iVscrollInc = -1 ;
break ;
iVscrollInc = 1 ;
break ;
case SB_PAGEUP :
break ;
break ;
break ;
default :
iVscrollInc = 0 ;
}
if (iVscrollInc != 0)
{
return 0 ;
{
iHscrollInc = -1 ;
break ;
iHscrollInc = 1 ;
break ;
iHscrollInc = -8 ;
break ;
iHscrollInc = 8 ;
break ;
default :
iHscrollInc = 0 ;
}
if (iHscrollInc != 0)
{
}
return 0 ;
{
break ;
break ;
break ;
break ;
break ;
break ;
break ;
break ;
}
return 0 ;
{
sysmetrics[i].szLabel,
sysmetrics[i].szDesc,
szBuffer,
}
return 0 ;
return 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:
Mesaj Tastă sau cod
WM_KEYDOWN 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:
Tasta virtuală A
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:
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:
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)
break ;
break ;
break ;
break ;
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.
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".
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.exe : keylook.obj
keylook.obj : keylook.c
/*-------------------------------------------------------
-------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
RECT rect ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
{
}
}
{
static char *szFormat[2] = { "%-14s %3d %c %6u %4d %3s %3s %4s %4s",
ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
"Message Key Char Repeat Scan Ext ALT Prev Tran";
static char szUnd[] =
"_______ ___ ____ ______ ____ ___ ___ ____ ____";
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
return 0 ;
return 0 ;
return 0 ;
return 0 ;
case WM_CHAR :
return 0 ;
return 0 ;
case WM_DESTROY :
return 0 ;
}
}
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:
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:
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":
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".
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ă.
#---------------------
#---------------------
typer.exe : typer.obj
typer.obj : typer.c
/*--------------------------------------
--------------------------------------*/
#include <windows.h>
#include <stdlib.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
int x, y, i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
{
return 0 ;
else
xCaret = 0 ;
yCaret = 0 ;
return 0 ;
return 0 ;
DestroyCaret () ;
return 0 ;
{
xCaret = 0 ;
break ;
yCaret = 0 ;
break ;
break ;
break ;
break ;
break ;
break ;
}
{
{
{
xCaret-- ;
}
break ;
do
{
}
break ;
case '\n' : // line feed
yCaret = 0 ;
break ;
xCaret = 0 ;
yCaret = 0 ;
break ;
xCaret = 0 ;
yCaret = 0 ;
break ;
{
xCaret = 0 ;
yCaret = 0 ;
}
break ;
}
}
return 0 ;
return 0 ;
return 0 ;
}
}
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.
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.
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.
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).
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.
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:
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ă.
S-ar putea să aveţi uneori nevoie de un caracter scris cu majusculă. Nu folosiţi un algoritm propriu,
cum ar fi:
Aceasta este o metodă greşită în programarea sub Windows. Dar nu folosiţi nici funcţiile standard
de conversie din C:
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ă 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.
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:
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:
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.
Î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.
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.
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:
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:
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:
¨ Dublu clic - apăsarea şi eliberarea unui buton al mouse-ului de două ori, într-o succesiune
rapidă.
Î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.
Î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)
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").
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ă.
¨ 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.)
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.
/*--------------------------------------------------
--------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
int i, j ;
{
iCount = 0 ;
return 0 ;
{
}
return 0 ;
return 0 ;
case WM_PAINT :
{
}
return 0 ;
return 0 ;
}
¨ 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.
else
else if (MK_CONTROL&wParam)
{
else
{
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:
return 0 ;
case WM_RBUTTONDOMN
return 0 ;
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.
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.
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:
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:
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ă.
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:
case WM_NCHITTEST
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.
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.
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
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 ;
iColumn = cxMouse/cxColWidth ;
iFromTop = cyMouse/cyChar ;
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.
/*-------------------------------------------------
-------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.lpszMenuName = NULL ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
int x, y ;
{
return 0 ;
{
}
else
return 0 ;
{
{
}
}
return 0 ;
return 0 ;
}
}
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.
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) ;
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.
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.
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
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
int x, y ;
switch (iMsg)
{
return 0 ;
return 0 ;
return 0 ;
{
y-- ;
break ;
break ;
x-- ;
break ;
x++ ;
break ;
x = y = 0 ;
break ;
x = y = DIVISIONS - 1 ;
break ;
break ;
}
return 0 ;
{
fState[x][y] ^= 1 ;
}
else
MessageBeep (0) ;
return 0 ;
{
{
}
}
return 0 ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
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.
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".
/*-------------------------------------------------
-------------------------------------------------*/
#include <windows.h>
#define DIVISIONS 5
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
{
{
0, 0, 0, 0,
NULL) ;
}
return 0 ;
return 0 ;
return 0 ;
return 0 ;
}
}
{
PAINTSTRUCT ps ;
return 0 ;
return 0 ;
{
}
EndPaint (hwnd, &ps) ;
return 0 ;
}
}
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:
Î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ă.
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.
Pentru a vedea de ce este uneori necesară capturarea mouse-ului, haideţi să studiem programul
BLOKOUT1, prezentat în Figura 6.9.
#------------------------
#------------------------
blokout1.exe : blokout1.obj
blokout1.obj : blokout1.c
/*-----------------------------------------
-----------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
hwnd = CreateWindow (szAppName, "Mouse Button Demo",
WS_OVERLAPPEDWINDOW,
{
}
}
{
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
{
if (fBlocking)
{
}
return 0 ;
if (fBlocking)
{
}
return 0 ;
{
}
return 0 ;
if (fValidBox)
{
}
if (fBlocking)
{
}
return 0 ;
return 0 ;
}
}
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.
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 () ;
Î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.exe : blokout2.obj
blokout2.obj : blokout2.c
/*---------------------------------------------------
---------------------------------------------------*/
#include <windows.h>
{
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
WS_OVERLAPPEDWINDOW,
{
}
}
{
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
PAINTSTRUCT ps ;
{
return 0 ;
if (fBlocking)
{
}
return 0 ;
if (fBlocking)
{
ReleaseCapture () ;
}
return 0 ;
{
ReleaseCapture () ;
}
return 0 ;
if (fValidBox)
{
if (fBlocking)
{
}
return 0 ;
return 0 ;
}
}
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.