Sunteți pe pagina 1din 143

www.cartiaz.

ro Carti si articole online gratuite de la A la Z

Programare in Microsoft Visual C++ Capitolul 1


Aceast carte se adreseaz utilizatorilor experimentai ai limbajului de programare C care doresc s nvee s scrie aplicaii pentru sistemul de operare Windows 95. Experiena n folosirea limbajului C este prima condiie necesar pentru utilizarea acestei cri. A doua cerin este s avei instalat pe calculator mediul de dezvoltare Microsoft Visual C++ versiunea 4.0 pentru programarea pe 32 de bii. n al treilea rnd - dar nu ultimul - este important s cunoatei sistemul de operare Windows 95 i s nelegei elementele fundamentale de proiectare a interfeei acestuia. Aa cum probabil tii Windows 95 este cea mai recent versiune a unui sistem de operare grafic prezentat pentru prima dat n noiembrie 1985 pentru calculatoarele IBM PC sau compatibile cu acestea. Din punctul de vedere al competiiei pe pia, Windows 95 a eliminat practic orice concurent din ultimul deceniu i a devenit standardul de facto pentru mediile de lucru ale calculatoarelor personale, n prezent, dac scriei un program pentru calculatoarele compatibile PC, atunci scriei un program pentru Windows. Considerai acest capitol prima dumneavoastr zi la coal. Chiar dac unii profesori mai ciudai ncep prin predarea celor mai dificile elemente ale unui curs, majoritatea prefer o introducere mai lejer. Ca urmare, n acest capitol vom prezenta cteva informaii istorice despre Windows, principiile pe care s-a bazat elaborarea acestei cri i chiar (dac mi permitei) cteva lucruri despre mine nsumi i despre modul n care am ajuns s scriu cartea de fa. Totui, nu pot garanta c acest capitol nu v va solicita vreun efort intelectual. Suntei programator, suntei inginer software i, la fel ca n cazul altor ingineri, rolul dumneavoastr principal este acela de a face mai uoar viaa utilizatorilor - sarcin grea i plin de provocri, dar deseori, din fericire, i de satisfacii. Dumneavoastr construii podurile i drumurile care i aduc pe oameni mai aproape de destinaii i de scopuri - iar aceste structuri trebuie s fie solide, fiabile i eficiente. PARTEA I NCEPUTUL PROVOCAREA PROGRAMATORULUI n filmul Marele canion un tat i nva fiul de 15 ani s conduc maina i i spune:O curb la stnga n Los Angeles este unul dintre cele mai dificile lucruri pe care trebuie s le nvei n via". Acelai lucru l-ar fi putut spune i despre programarea sub Windows. Nu m refer aici anume la tehnicile de programare sub Windows acest subiect neplcut va fi abordat abia ncepnd din capitolul urmtor; m gndesc mai mult la filozofia de proiectare a programelor, care uneori v oblig s v dai peste cap" ca s facei lucrurile mai uoare pentru utilizatori. Acesta este un concept relativ nou. n primele zile ale calculatoarelor, utilizatorii erau i programatori. Primele programe lucrau cam n felul urmtor: nghieau nite date, le mcinau puin, apoi scuipau ceva pe hrtie. Utilizatorii naivi nici nu intrau n calcule. Chiar i atunci cnd programele au devenit puin mai interactive i au permis folosirea unei linii de comand pe un monitor video, utilizatorii erau nevoii s rein o mulime de comenzi i de opiuni criptice. Probabil adevrata revoluie n proiectarea programelor pentru calculator au constituit-o primele procesoare de texte (WordStar, de exemplu) i primele programe de calcul tabelar (VisiCalc) interactive, ce includeau forme primitive ale elementului fundamental de proiectare a interfeelor moderne. Desigur, este vorba despre meniuri. Meniurile din aceste prime aplicaii interactive nu erau prea bine implementate, dar ideea apruse i ncet-ncet a luat forme tot mai complexe. Privind retrospectiv, meniul pare acum un lucru elementar: un mijloc prin care utilizatorului i sunt prezentate toate posibilitile programului. Desigur, pe atunci, lipsa memoriei re-strngea posibilitile programatorilor de a crea o interfa cu utilizatorul prea sofisticat. Programele neprietenoase sunt mai scurte i mai uor de scris; programele prietenoase cu utilizatorul sunt mari i dificil de conceput. Principiul NNEMBDOMG (Nimic Nu Este Mai Bun Dect O Mas Gratuit") este aplicabil i aici, dar cineva trebuie s fac i lucrurile grele. Ca programator, dumneavoastr ai ales s fii acel cineva". Din fericire, o mare parte din lucrurile grele au fost deja fcute de proiectanii i programatorii sistemului de operare Microsoft Windows 95. Aceti eroi necunoscui au implementat o parte din codul necesar pentru crearea obiectelor care formeaz interfaa modern cu utilizatorul i pentru afiarea rezultatelor programelor, folosind text formatat i elemente grafice. Fcnd aceste lucruri, ei au creat i o interfa pentru programe de aplicaii (API - application programming interface) care i poate intimida la nceput pe programatorii care folosesc pentru prima dat mediul Windows. Nu este ceva specific sistemului de operare Windows - acest lucru este valabil pentru orice interfa grafic modern. Mai demult se spunea c unui programator i trebuie aproximativ 6 luni ca s poat scrie un program sub Windows. (Eu glumesc uneori spunnd c, folosind cartea mea, aceast perioad ar putea fi redus la 26 de sptmni sau chiar la 180 de zile.) n ultimii ani Windows a devenit un sistem de operare foarte complex, dar n

www.cartiaz.ro Carti si articole online gratuite de la A la Z


acelai timp au aprut i instrumente de lucru care v ajut n prile mai grele, aa c regula celor 6 luni probabil este nc aplicabil. V spun aceste lucruri pentru c nu vreau s v simii incompetent dac nu prindei" totul de la nceput. Dac nu suntei obinuit cu programarea n medii grafice, Windows poate prea foarte ciudat. Cu timpul ns v vei obinui.

REGULI DE BAZ
n aceast carte voi ncerca s v nv ceea ce eu numesc programare clasic" sub Windows. n exemplele de programe din aceast carte folosesc btrnul" limbaj de programare C (nu C++) i interfaa brut pentru programele de aplicaii (API), fr s apelez la funcii de mpachetare (wrappers") care mascheaz interfaa API sub alte interfee mai simple. n ciuda avantajelor pe care Windows 95 le ofer fa de versiunile anterioare ale sistemului de operare, majoritatea programelor din aceast carte nu sunt cu mult diferite de programele scrise pentru versiunea Microsoft Windows 1.0 acum 10 ani. Dintr-un anumit punct de vedere, aceast carte prezint un mod mai dificil de programare sub Windows - dar, n acelai timp, cel mai puternic i cel mai flexibil. Nu putei s cerei mai mult sistemului de operare Windows pe nici o alt cale. Mai mult, dac nvai programarea clasic sub Windows, folosind limbajul C i interfaa API brut, putei s nelegei mai bine modul n care interacioneaz aplicaia dumneavoastr cu sistemul de operare, iar aceast informaie se dovedete util n multe cazuri. Dei voi vorbi aici despre programarea clasic sub Windows, nu v recomand s v limitai la aceasta. n prezent avei la dispoziie diferite opiuni care pot face programarea sub Windows mai uoar. O variant popular este limbajul C++, folosit, n general, mpreun cu biblioteci de clase precum Microsoft Foundation Classes (MFC) sau Borland Object Windows Library (OWL). Alte variante sunt Microsoft Visual Basic sau Borland Delphi. De asemenea, exist produse de generare a codului care fac mai uoar munca de programare. Putei chiar s creai aplicaii Windows folosind limbaje script simple, precum limbajul ToolBox al firmei Asymetrix. Nu v pot spune care dintre aceste variante este cea mai bun; depinde de aplicaia pe care o scriei i de ct de mult vrei s sacrificai izolndu-v de mediul n care lucrai. Un alt subiect pe care nu l voi aborda n aceast carte este folosirea unor medii de dezvoltare interactiv (IDE - interactive development environment), precum Microsoft Visual C++ versiunea 4.0. Aceste medii de lucru v ajut deseori n operaiile de creare a resurselor (cum ar fi meniurile i casetele de dialog), genernd fiiere de construcie (make files) i oferindu-v un mediu integrat pentru scrierea, compilarea, rularea i depanarea programelor. Nu am nimic mpotriva mediilor de dezvoltare interactiv. Ba chiar cred c sunt foarte bune. Totui, folosirea acestora mai degrab v mpiedic dect v ajut s nvai programarea clasic sub Windows. Pe scurt, nu vreau s v nv cum s completai casete de dialog IDE, ci vreau s v nv cum s proiectai propriile dumneavoastr casete de dialog i cum s prelucrai intrrile acestora. Din acest motiv, codul de exemplificare din aceast carte este prezentat ca i cum ar fi creat ntr-un editor de texte obinuit i compilat din demodata" linie de comand. Acest lucru va deveni evident atunci cnd vei ajunge la prezentarea fiierelor script de resurse (resource script), care conine descrierea sub form de text a meniurilor, casetelor de dialog i a altor resurse. Fiierele script de resurse din aceast carte sunt scrise astfel nct s poat fi citite cu uurin, lucru care nu este de obicei valabil i pentru fiierele de resurse generate de un mediu de dezvoltare. Am fcut acest lucru deoarece vreau s nelegei ce nseamn fiecare instruciune din fiierul de resurse. Despre fiierele de construcie (make files) - acele fiiere care conin instruciunile pentru compilarea codului surs i editarea legturilor din program pentru crearea fiierului executabil - se tie c sunt lungi i complexe atunci cnd sunt generate de un mediu de dezvoltare. Ale mele sunt scurte i simple, i nu-mi cer scuze pentru acest lucru! Singurele resurse din aceast carte care nu sunt de tip text sunt pictogramele, indicatoarele de mouse i imaginile bitmap care prin natura lor trebuie create cu instrumente de lucru specializate. Avantajul acestui mod de abordare este faptul c putei s copiai fiierele surs pe hard-disc, s deschidei o fereastr MS-DOS ca s ajungei n linia de comand, s rulai fiierul de construcie ca s creai programul executabil i s rulai programul direct din linia de comand. Majoritatea programelor din aceast carte sunt foarte scurte, scopul lor fiind unul de ilustrare a ctorva concepte importante. Unele dintre ele sunt chiar reduse la elementele de baz. Programele-exemplu sunt numai nite instrumente cu rol didactic, nu sunt modele reale pentru programe complete sub Windows. De exemplu, orice program sub Windows ar trebui s aib o pictogram unic; foarte puine dintre programele din aceast carte au o pictogram, deoarece aceasta nu ar face dect s aglomereze programul. De asemenea, ar trebui ca toate irurile de text folosite de un program s fie stocate ntr-un fiier script de resurse, pentru a facilita traducerea programului n alte limbi. i aceast abordare ar aglomera ns programele i ar face mai greu de neles ideea ilustrat de program. Din experien v pot spune c, cu ct programul este mai scurt, cu att ansele lui la cititor sunt mai mari.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


Aceast carte nu poate s acopere toate aspectele programrii sub Windows 95. De asemenea, ea nu reprezint un nlocuitor pentru documentaia oficial. Chiar i dup ce vei parcurge aceast carte, va fi nevoie s mai studiai documentaia Windows privind caracteristicile i apelurile de funcii ale sistemului de operare. Ultima caracteristic a acestei cri pe care vreau s o subliniez este faptul c nu am insistat asupra aspectelor nedocumentate" sau neautorizate" ale programrii sub Windows 95. Dei uneori astfel de informaii pot fi interesante, cred c ele nu sunt semnificative dect pentru cele mai ciudate aplicaii sub Windows. Pe msur ce am cptat mai mult experien n programare, am nvat s nu m bazez pe detaliile de implementare, deoarece acestea se pot schimba n versiunile ulterioare ale sistemului de operare. n schimb, am ncercat s folosesc interfaa API ca i cum aceasta ar fi descris complet de documentaia tehnic. Acest lucru nu este ntotdeauna adevrat, desigur, i exist deseori aspecte despre care nu se pomenete nimic n documentaie. De asemenea, mai exist i erori de programare. Nu voi ignora astfel de probleme. UN SCURT ISTORIC AL SISTEMULUI DE OPERARE WINDOWS Curnd dup apariia pe pia a calculatorului IBM PC n toamna anului 1981, a devenit evident c sistemul de operare predominant pentru calculatoarele PC i compatibile va fi MS-DOS (prescurtarea de la Microsoft Disk Operating System). Primele versiuni ale sistemului de operare MS-DOS ofereau utilizatorului o interfa n linie de comand i comenzi precum DIR i TYPE. Sistemul de operare putea s ncarce programe n memorie i oferea acestor programe servicii pentru accesul la fiiere, citirea tastaturii, scrierea pe ecran (numai n mod caracter) i la portul de imprimant. Datorit memoriei insuficiente i posibilitilor reduse ale componentelor hardware, mediile grafice sofisticate au ntrziat s apar. Firma Apple Computers a prezentat o soluie lansnd n ianuarie 1983 sistemul de operare Lisa, care ns nu a avut o soart prea fericit, i apoi a stabilit n ianuarie 1984 un standard pentru mediile grafice, prezentnd sistemul de operare Macintosh, care (n ciuda faptului c ocup un segment de pia n descretere) este considerat a fi etalonul n domeniul mediilor grafice. Crearea sistemului de operare Windows a fost anunat de Microsoft Corporation n noiembrie 1983 (dup Lisa, dar nainte de Macintosh) i doi ani mai trziu, n noiembrie 1985, a fost lansat versiunea Windows 1.0. Versiunile lansate n urmtorii doi ani au fost actualizate i adaptate pentru piaa internaional, furniznd noi drivere pentru alte plci video i inprimante. Versiunea Windows 2.0 a fost lansat n noiembrie 1987. Aceast versiune include cteva modificri asupra interfeei cu utilizatorul. Cea mai semnificativ dintre acestea este folosirea ferestrelor suprapuse n locul ferestrelor alturate din versiunile Windows 1.x. De asemenea. Windows 2.0 include unele mbuntiri ale interfeei cu mouse-ul i cu tastatura, n special legate de meniuri i de casetele de dialog. Pan n acest moment, Windows folosea procesorul Intel 8086 sau Intel 8088 n mod real" pentru a obine accesul la 1 megaoctet de memorie. Windows/386 (lansat la puin timp dup Windows 2.0) folosea modul virtual 86" al microprocesorului Intel 80386 ca s ruleze n ferestre, simultan, mai multe programe MS-DOS care aveau acces direct la hardware. Pentru simetrie, versiunea Windows 2.1 a fost redenumit Windows/286. Versiunea Windows 3.0 a fost prezentat pe data de 22 mai 1990. Aceast versiune le ngloba pe cele anterioare - Windows/286 i Windows/386 - ntr-un singur produs. Cea mai important modificare adus de Windows 3.0 a fost acceptarea modului de lucru protejat al microprocesoarelor Intel 80286, 80386 i 80486. Acest mod de lucru a permis sistemului de operare i aplicaiilor Windows accesul la 16 megaoctei de memorie. Programele de tip shell pentru rularea programelor i ntreinerea fiierelor au fost complet revizuite. Windows 3.0 a fost prima versiune care s-a bucurat de un succes deplin, devenind ceva obinuit pe calculatoarele tuturor utilizatorilor, att n birouri, ct i acas. Versiunea Windows 3.1 a fost lansat n aprilie 1992. Printre modificrile importante ale caracteristicilor se numr includerea tehnologiei TrueType pentru fonturi (ceea ce a permis folosirea sub Windows a fonturilor scalabile), a tehnologiei multimedia (sunet i muzic), a tehnologiei OLE i a casetelor de dialog comune. De asemenea. Windows 3.1 ruleaz numai n mod protejat i are nevoie de un procesor 80286 sau 80386 cu cel puin l megaoctet de memorie. n orice istoric Windows ar trebui s fie menionat i sistemul de operare OS/2, o alternativ la MS-DOS i Windows, dezvoltat iniial de Microsoft n colaborare cu IBM. Versiunea OS/2 1.0 (numai n mod caracter) a fost lansat la sfritul anului 1987 i rula pe microprocesoarele Intel 80286 sau mai noi. Mediul grafic Presentation Manager (PM) a aprut odat cu versiunea OS/2 1.1 n octombrie 1988. PM a fost proiectat iniial ca o variant n mod protejat a mediului Windows, dar interfaa API s-a modificat att de mult nct productorii software nu au putut s susin ambele platforme. n septembrie 1990 conflictele dintre Microsoft i IBM s-au acutizat i fiecare a pornit pe propriul drum. IBM a preluat sistemul de operare OS/2, iar Microsoft a declarat c n centrul strategiei proprii de dezvoltare a sistemelor de operare se va situa Windows. Dei OS/2 are nc admiratori nfocai, acest sistem de operare nu a ajuns nici pe departe la popularitatea atins de Windows. Windows NT, lansat n iulie 1993, a fost prima versiune Windows care accept modelul de programare pe 32 de bii al microprocesoarelor Intel 80386, 80486 i Pentium. Windows NT folosete un spaiu de adrese simplu pe

www.cartiaz.ro Carti si articole online gratuite de la A la Z


32 de bii i numere ntregi pe 32 de bii. De asemenea Windows NT este portabil i poate rula pe mai multe staii de lucru de tip RISC. Windows 95 (care a purtat anterior numele de cod Chicago i este numit uneori Windows 4.0) a fost lansat n august 1995. Ca i Windows NT, Windows 95 accept modelul de programare pe 32 de bii (i, ca urmare, are nevoie de un microprocesor 80386 sau mai nou). Dei Windows 95 nu egaleaz performanele sistemului de operare Windows NT n anumite aspecte cum ar fi securitatea sau portabilitatea pe calculatoarele RISC, Windows 95 are avantajul c poate fi rulat pe calculatoarele cu cel puin 4 megaoctei de memorie. Evident, programele scrise pentru versiunile pe 16 bii ale sistemului de operare Windows (aprute nainte de Windows 95 i Windows NT) nu sunt direct portabile ctre versiunile mai noi, pe 32 de bii; n capitolele urmtoare vom discuta unele dintre modificrile care sunt necesare pentru a realiza acest lucru. Microsoft a ncercat s diferenieze platformele prin definirea unor seturi de interfee API. Interfaa Win16 API este acceptat de versiunea Windows 3.1. Interfaa Win32 API este acceptat de versiunile Windows 95 i Windows NT. Dar ateptai - mai este ceva. Microsoft permite programatorilor s scrie programe pe 32 de bii pentru Windows 3.1; apelurile de funcii pe 32 de bii au fost convertite n apeluri pe 16 bii de o bibliotec cu legturi dinamice (DLL). Aceast interfaa API a fost numit Win32s (s" de la subset", deoarece aceast interfaa accept numai funciile din Win16). Pentru o perioad, interfaa API din Windows 95 a fost numit Win32c (c" de la compatibil") dar acest nume a fost abandonat mai trziu. n acest moment se consider c att Windows NT ct i Windows 95 accept interfaa Win32 API. Cu toate acestea, fiecare dintre cele dou sisteme de operare are unele caracteristici pe care cellalt sistem de operare nu le are. Partea comun fiind considerabil, scrierea programelor care ruleaz sub ambele sisteme este totui posibil. De asemenea, o prere larg rspndit susine c cele dou produse vor fi nglobate ntr-unul singur, n viitor. ESTE TIMPUL S NCEPEM OK, prima zi de coal se apropie de sfrit. n capitolul urmtor vom ncepe studierea unor exemple de cod. Pentru aceasta, trebuie s avei instalat pe calculator programul Microsoft Visual C++ versiunea 4.0. n fiierul AUTOEXEC.BAT trebuie s includei instruciunea:CALL \HSDEV\BIN\VCVARS32.BAT Acest fiier este inclus n Visual C++ i stabilete o serie de variabile de mediu folosite pentru compilarea programelor C din linia de comand. Aceste variabile precizeaz cile de acces la fiierele antet, la fiierele bibliotec i la fiierele binare. De asemenea, tot n fiierul AUTOEXEC.BAT trebuie s includei o a doua instruciune CALL ca s rulai fiierul MSC.BAT, prezentat n Figura 1-1. Acest fiier definete variabilele de mediu folosite de fiierele de construcie, din capitolele care urmeaz. REM ------------------------------------------------------------REM MSC.BAT -- Set up environment for Microsoft C/C++ 7.0 NMAKE REM ------------------------------------------------------------SET CC=cl SET CFLAGS=-c -DSTRICT -G3 -Ow -W3 -Zp -Tp SET CFLAGSMT=-c -DSTRICT -G3 -MT -Ow -W3 -Zp -Tp SET LINKER=link SET GUIFLAGS=-SUBSYSTEM:windows SET DLLFLAGS=-SUBSYSTEM:windows -DLL SET GUILIBS=-DEFAULTLIB:user32.lib gdi32.lib winmm.lib comdlg32.lib comctl32.lib SET RC=rc SET RCVARS=-r -DWIN32
Figura 1.1. Rulai fiierul de mai sus ca s compilai programele din aceast carte.

Fiierul MSC.BAT poate fi gsit n directorul CHAP01 de pe discheta care nsoete aceast carte. Voi descrie rolul instruciunilor de mai sus n capitolele care urmeaz.

Capitolul 2

Hello, Windows 95!


Dac programai pentru prima dat sub un mediu grafic, aa cum este Microsoft Windows 95, probabil vei considera c avei de-a face cu ceva cu totul nou. Windows are reputaia de a fi un program simplu pentru utilizator, dar dificil pentru programatori. Este normal ca noii-venii s se simt neajutorai n faa arhitecturii Windows i a structurii aplicaiilor care ruleaz sub acest sistem de operare. Chiar dac vi se ntmpl i dumneavoastr, nu trebuie s credei c v lipsete acea parte a creierului care v-ar permite s devenii un

www.cartiaz.ro Carti si articole online gratuite de la A la Z


programator Windows de succes. Confuzia iniial este ceva normal i s nu credei pe nimeni care v spune altceva. Programarea sub Windows este destul de ciudat, greoaie, ncurcat i uneori i tortureaz creierii. Lucrurile nu sunt simple i s-ar putea s fie nevoie de ceva timp pn vei putea s strigai Evrika!". O estimare obinuit spune c un programator, n general, are nevoie de o perioad de nvare de aproximativ 6 luni nainte de a scrie un program sub Windows i, chiar i dup aceea, mai sunt lucruri de nvat. Sperana mea este c aceast carte v va ajuta s reducei cu cteva sptmni (poate chiar luni) perioada iniial de nvare. S-ar putea s v ntrebai Dac programarea sub Windows este att de dificil, de ce s ne mai batem capul?" Rspunsul cel mai simplu este tot o ntrebare: Avei de ales?". Sistemul de operare Windows a deschis noi posibiliti, aa nct programarea pentru MS-DOS (n mod caracter sau n mod grafic) nu mai este posibil. Dac scriei aplicaii comerciale, s-ar putea ca produsul dumneavoastr s fie ignorat dac nu este scris pentru Windows. Dac scriei programe speciale pentru o firm, utilizatorilor (sau poate chiar angajailor) dumneavoastr nu le va conveni faptul c programul nu se integreaz printre celelalte aplicaii pentru Windows pe care le folosesc. Dar exist i alte motive ca s alegei ca platform sistemul de operare Windows.

CE ADUCE NOU SISTEMUL DE OPERARE WINDOWS


Sistemul de operare Windows ofer avantaje semnificative fa de mediul MS-DOS, att pentru utilizatori, ct i pentru programatori. Multe dintre aceste avantaje sunt comune pentru utilizatori i pentru programatori, deoarece sarcina dezvoltatorilor unui program este s ofere utilizatorilor lucruri de care acetia au nevoie. Windows 95 face posibil acest lucru.

INTERFAA GRAFIC CU UTILIZATORUL (GUI)


Windows este o interfa grafic cu utilizatorul (GUI - graphical user interface), numit uneori i interfa vizual" sau mediu grafic cu ferestre" (graphical windowing environment"). Conceptele care stau la baza acestui tip de interfa cu utilizatorul dateaz de la mijlocul anilor '70, avnd la origine munca de pionierat a celor de la Centrul de cercetare al firmei Xerox de la Palo Alto (PARC - Palo Alto Research Center) pentru calculatoare precum Alto i Star i medii ca Smalltalk. Ideile au fost readuse n prim-plan i popularizate de Apple Computer, Inc., mai nti cu ghinionistul sistem de operare Lisa i un an mai trziu cu Macintosh, un sistem de operare de succes, prezentat n ianuarie 1984. De la apariia sistemului de operare Macintosh, interfeele grafice au cunoscut o rspndire extraordinar n industria calculatoarelor. Acum este evident faptul c interfa grafic cu utilizatorul este (citndu-l pe Charles Simonyi de la Microsoft) singurul consens general" important din industria calculatoarelor personale.

CONCEPTELE I FUNDAMENTELE GUI


Toate tipurile de interfee grafice cu utilizatorul folosesc elemente grafice afiate ntr-o imagine de tip bitmap. Elementele grafice asigur o utilizare mai eficient a spaiului de afiare, un mediu bogat din punct de vedere vizual pentru transferul de informaii i posibilitatea de afiare a rezultatelor aa cum vor arta n realitate pe hrtie (WYSIWYG - what you see is what you get). La nceput, ecranul era folosit numai pentru afiarea informaiilor pe care utilizatorul le introducea de la tastatur. ntr-o interfa grafic, ecranul devine chiar o surs pentru celelalte intrri ale utilizatorului. Pe ecran sunt afiate 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 apsate i barele de derulare pot fi derulate. Interaciunea dintre program i utilizator devine mult mai direct. n locul unui ciclu unidirectional al informaiilor de la tastatur la program i de la program la ecran, utilizatorul interacioneaz direct cu obiectele de pe ecran.

CONSECVENA PRIVIND INTERFA CU UTILIZATORUL


Utilizatorii nu mai sunt dispui s piard prea mult timp pentru a nva cum se folosete calculatorul sau cum s stpneasc un anumit program. Windows i ajut n acest sens, deoarece toate programele pentru Windows au un aspect asemntor i se comport fundamental la fel. Programele ocup o fereastr - o suprafa dreptunghiular de pe ecran. Fereastra poate fi identificat datorit unei bare de titlu. Majoritatea funciilor oferite de program sunt apelate cu ajutorul unui meniu. Informaiile afiate care nu ncap pe un singur ecran pot fi vizualizate cu ajutorul barelor de derulare. Unele articole de meniu apeleaz casete de dialog n care utilizatorul introduce informaii suplimentare. n majoritatea programelor mai mari pentru Windows exist o caset de dialog care deschide un fiier. Aceast caset de dialog arat la fel (sau foarte asemntor) n mai multe programe Windows i este apelat aproape ntotdeauna cu aceeai opiune de meniu. Dup ce nvai s folosii un program Windows, vei fi capabil deja s utilizai orice alt program pentru Windows. Meniurile i casetele de dialog permit utilizatorului s experimenteze diferite funcii ale programului i

www.cartiaz.ro Carti si articole online gratuite de la A la Z


s i exploreze posibilitile. Majoritatea programelor Windows au interfa att cu mouse-ul, ct i cu tastatura. Dei majoritatea funciilor unui program pentru Windows pot fi controlate i cu ajutorul tastaturii, mouse-ul este deseori mai convenabil pentru diferite sarcini. Din punctul de vedere al programatorului, consecvena n realizarea interfeei cu utilizatorul este rezultatul folosirii procedurilor integrate n Windows pentru construirea meniurilor i a casetelor de dialog. Toate meniurile au aceeai interfa cu tastatura i cu mouse-ul deoarece aceste operaii sunt manipulate mai degrab de Windows, dect de programul respectiv.

Avantajul oferit de multitasking


Dei unii continu nc s se ntrebe dac multitaskingul este cu adevrat necesar pe un calculator folosit de un singur utilizator, majoritatea utilizatorilor se bucur de existena acestei faciliti i pot s-i foloseasc avantajele. Acest lucru a fost dovedit de popularitatea unor programe rezidente n memoria RAM sub MS-DOS, precum Sidekick. Dei programele derulante, n sens strict, nu sunt acelai lucru cu multitaskingul, acestea permit comutarea rapid de la un context la altul, proces care implic multe dintre conceptele multitaskingului. Sub Windows, toate programele sunt, de fapt, programe rezidente n memoria RAM. Mai multe programe Windows pot fi afiate i rulate n acelai timp. Fiecare program ocup o fereastr dreptunghiular de pe ecran. Utilizatorul poate s mute ferestrele pe ecran, poate s le schimbe dimensiunile, s treac de la un program la altul i s transfere date de la un program la altul. Deoarece aceasta creeaz uneori aspectul spaiului de lucru de pe un birou (desktop) - pe vremea cnd birourile nu erau dominate nc de calculator - se spune uneori despre Windows c folosete pentru afiarea mai multor programe metafora suprafeei de lucru (desktop)". Versiunile anterioare ale sistemului de operare Windows foloseau un tip de multitasking numit necontrolat". Aceasta nseamn c Windows nu folosea ceasul sistemului ca s aloce timpi de procesare diferitelor programe care ruleaz n sistem. Programele trebuie s cedeze voluntar controlul, astfel nct alte programe s-i poat continua execuia. n Windows 95 multitaskingul este controlat i programele se pot mpri n mai multe fire de execuie, care par s ruleze n acelai timp.

Gestionarea memoriei
Un sistem de operare nu poate s implementeze multitaskingul fr o gestionare adecvat a memoriei. Pe msur ce noi programe sunt ncrcate n memorie i altele vechi i ncheie execuia, 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. innd 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 depeasc memoria existent; un program poate conine mai mult cod dect ncape n memorie la un moment dat. Windows poate s elimine din memorie o parte din cod i ulterior s rencarce codul respectiv din fiierul executabil de pe hard-disc. Un utilizator poate rula simultan mai multe copii ale aceluiai program, numite instane"; toate aceste instane folosesc acelai cod din memorie. Programele rulate sub Windows pot s partajeze rutine stocate n alte fiiere numite biblioteci cu legturi dinamice" (dynamic link libraries). Windows conine un mecanism de editare a legturilor dintre program i rutinele din bibliotecile cu legturi dinamice, n timpul execuiei. Chiar sistemul de operare este, n esen, o colecie de biblioteci cu legturi dinamice. n acest fel, chiar i n Windows 1.0, limita de 640 kilooctei impus de arhitectura calculatoarelor personale a fost extins fr s fie nevoie de memorie suplimentar. Dar Microsoft nu s-a oprit aici: Windows 2 a permis aplicaiilor Windows accesul la memoria expandat (EMS) iar Windows 3 ruleaz n mod protejat, ceea ce permite aplicaiilor Windows accesul la 16 megaoctei de memorie. Windows 95 a depit toate aceste restricii, fiind un sistem de operare pe 32 de bii, cu un spaiu de memorie plat.

Interfaa grafic independent de dispozitiv


Windows este o interfa grafic i programele pentru Windows pot folosi toate avantajele oferite de elementele grafice i de textul formatat att pentru ecran, ct i pentru imprimant. O interfa grafic nu are numai avantajul de a fi mai atractiv, ci i pe acela de a furniza utilizatorului o cantitate mai mare de informaii. Programele scrise pentru Windows nu au acces direct la componentele hardware ale dispozitivelor de afiare, cum ar fi ecranul sau imprimanta. n schimb, Windows folosete un limbaj de programare pentru grafic (numit GDI Graphics Device Interface) care simplific afiarea textului formatat i a elementelor grafice. Windows transform componentele hardware de afiare n dispozitive virtuale. Un program scris pentru Windows va rula cu orice plac video i cu orice imprimanta pentru care Windows are un driver de dispozitiv. Programul nu trebuie s determine ce tip de dispozitiv fizic este ataat la calculator. Crearea unei interfee grafice independente de dispozitiv pentru calculatoarele IBM PC nu a fost o sarcin uoar pentru dezvoltatorii sistemului de operare Windows. Proiectarea calculatoarelor personale s-a fcut pe baza

Acest tip de multitasking se mai numete i multitasking cooperativ", (n.t)

www.cartiaz.ro Carti si articole online gratuite de la A la Z


principiului arhitecturilor deschise. Productorii teri de componente hardware au fost ncurajai s dezvolte dispozitive periferice, i datorit acestui fapt au aprut o mulime de astfel de dispozitive. Dei au fost create anumite standarde, programele convenionale pentru MS-DOS trebuie s asigure suportul individual pentru diferite configuraii standard. De exemplu, este un lucru destul de obinuit ca un program de procesare de texte pentru MS-DOS s fie livrat mpreun cu cteva dischete care conin mai multe fiiere mici, pentru adaptarea la diferite tipuri de imprimante. Programele Windows 95 nu au nevoie de drivere proprii, deoarece acestea sunt asigurate chiar de sistemul de operare.

Dac optai pentru Windows...


Programarea sub Windows 95 este o propunere de tip totul sau nimic". De exemplu, nu putei s scriei aplicaii pentru MS-DOS i s utilizai facultile oferite de Windows numai pentru anumite grafice. Dac vrei s avei acces la toate posibilitile pe care le are mediul Windows, trebuie s v hotri i s scriei programe complete pentru Windows. Logica acestei opiuni va deveni evident dup ce vei afla mai multe despre structura unui program pentru Windows. Toate componentele sistemului Windows sunt interconectate. Dac vrei s desenai ceva pe ecran avei nevoie de un element numit variabil handle a unui context de dispozitiv". Pentru aceasta avei nevoie de o variabil handle a unei ferestre". Pentru a o obine, trebuie s creai o fereastr i s fii pregtit s recepionai mesajele" trimise ctre fereastr. Pentru recepionarea i prelucrarea mesajelor avei nevoie de o procedur de fereastr". A face toate aceste operaii nseamn a scrie un program pentru Windows. Nu putei s zburai dect dac v dezlipii de pmnt.

Apelurile de funcii
Windows 95 asigur suportul pentru mai mult de o mie de apeluri de funcii pe care le pot folosi aplicaiile. Este foarte puin probabil ca cineva s memoreze sintaxa tuturor acestor apeluri. Majoritatea programatorilor Windows petrec cea mai mare parte din timpul de lucru cutnd diferite apeluri de funcii, n documentaia scris sau n surse. Fiecare funcie Windows are un nume sugestiv, scris att cu majuscule, ct i cu minuscule, cum ar fi CreateWindow. Aceast funcie, aa cum probabil v-ai dat seama, creeaz o fereastr. Un alt exemplu: funcia IsClipboardFormatAvailable determin dac n memoria temporar (clipboard) sunt stocate date ntr-un anumit format. Toate funciile Windows importante sunt declarate n fiiere antet. Principalul fiier antet se numete WINDOWS.H i include multe alte fiiere antet. Aceste fiiere antet sunt furnizate de orice mediu de programare pentru Windows 95, aa cum este, de pild, C. Fiierele antet sunt o parte important a documentaiei Windows. Putei s tiprii o copie a fiierelor antet sau s folosii un browser pentru o cutare rapid. n programele pentru Windows folosii apelurile de funcii la fel cum folosii funciile de bibiotec C, cum ar fi strlen. Principala diferen este faptul c n cazul funciilor C codul funciilor este legat direct de codul programului, pe cnd codul funciilor Windows este stocat n fiiere din afara programului, numite biblioteci cu legturi dinamice (DLL - dynamic link libraries). Atunci cnd rulai un program pentru Windows, interconectarea acestuia cu sistemul de operare se face printrun proces numit legare dinamic" (dynamic linking). Un fiier executabil al unui program Windows conine referine la diferite biblioteci cu legturi dinamice, pe care le folosete i la funciile coninute de acestea. Majoritatea bibliotecilor DLL se afl n subdirectorul SYSTEM din directorul Windows. Atunci cnd un program pentru Windows este ncrcat n memorie, apelurile de funcii din program sunt rezolvate astfel nct s indice intrrile funciilor din bibliotecile cu legturi dinamice, care sunt i ele ncrcate n memorie, dac este nevoie. Atunci cnd editai legturile unui program ca s creai un fiier executabil, trebuie s editai i legturile cu bibliotecile speciale de import" furnizate de mediul de programare folosit. Aceste biblioteci de import conin numele funciilor Windows folosite n bibliotecile cu legturi dinamice i informaii de referin pentru acestea. Programul de editare a legturilor folosete aceste informaii ca s construiasc n fiierul executabil tabelul folosit de Windows pentru rezolvarea apelurilor de funcii Windows la ncrcarea programului.

Programarea orientat pe obiecte


Programarea sub Windows este un tip de programare orientat pe obiecte (OOP -object-oriented programming). Acest lucru devine evident dac v gndii la obiectul pe care l vei folosi cel mai mult n Windows - obiectul care d numele sistemului de operare, obsedanta fereastr". Aa cum am menionat mai devreme, ferestrele sunt nite suprafee dreptunghiulare de pe ecran. O fereastr primete intrri de la tastatur sau de la mouse i afieaz rezultate n zona proprie de pe suprafaa ecranului. Fereastra unei aplicaii conine, de obicei, bara de titlu a programului, meniul, chenarul de dimensionare i, eventual, barele de derulare. Casetele de dialog sunt ferestre adiionale. Mai mult, suprafaa unei casete de dialog conine ntotdeauna cteva ferestre suplimentare, numite ferestre descendent" (child windows"). Ferestrele descendent apar sub forma butoanelor de apsare, a butoanelor radio, a casetelor de validare, a cmpurilor pentru introducerea textului, casetelor-list i a barelor de derulare.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


Utilizatorul vede ferestrele ca obiecte pe ecran i poate aciona direct asupra lor, apsnd un buton sau manipulnd o bar de derulare. Interesant este faptul c perspectiva programatorului este foarte asemntoare cu cea a utilizatorului. Fereastra recepioneaz inteniile utilizatorului sub forma unor mesaje". De asemenea, ferestrele folosesc mesaje pentru comunicarea cu alte ferestre. nelegerea acestor mesaje este unul dintre pragurile pe care va trebui s le depii ca s ajungei programator Windows.

Arhitectura bazat pe mesaje


Atunci cnd am vzut pentru prima dat n aciune o interfa grafic cu utilizatorul, am fost uimit. Demonstraia includea rularea unui procesor de texte rudimentar ntr-o fereastr. Procesorul de texte rearanja textul atunci cnd dimensiunile ferestrei erau modificate. Era evident c sistemul de operare manipula detaliile logicii" de redimensionare a ferestrei i c programul putea s rspund la aceast funcie a sistemului. Dar de unde tia programul c fereastra a fost redimensionat? Ce mecanism folosea sistemul de operare ca s transmit aceast informaie ctre fereastra programului? Experiena pe care o aveam atunci nu m ajuta s neleg acest mod de funcionare. S-a dovedit c rspunsul la aceste ntrebri este esenial pentru nelegerea arhitecturii folosite la construirea interfeelor grafice cu utilizatorul. n Windows, atunci cnd utilizatorul redimensioneaz o fereastr, sistemul de operare trimite programului un mesaj prin care i comunic noile dimensiuni ale ferestrei. Programul poate apoi s modifice coninutul ferestrei, astfel nct acesta s se adapteze la noile dimensiuni. Sistemul de operare trimite programului un mesaj." Sper c nu ai luat ad-litteram sensul acestei propoziii. Ce poate s nsemne acest lucru? Pentru c nu este vorba despre un sistem de pot electronic, ci despre cod, cum poate un sistem de operare s trimit" mesaje ctre un program? Atunci cnd am spus c sistemul de operare trimite programului un mesaj" m-am referit la faptul c Windows apeleaz o funcie din program. Parametrii acestei funcii descriu mesajul respectiv. Aceast funcie din programul Windows este cunoscut sub numele de procedur de fereastr" (window procedure").

Procedura de fereastr
Desigur, suntei obinuit cu ideea c un program apeleaz funcii ale sistemului de operare. Acest lucru este fcut de un program atunci cnd, de exemplu, deschide un fiier de pe hard-disc. S-ar putea s nu fii la fel de obinuit, ns, i cu ideea c sistemul de operare apeleaz o funcie a programului. Totui, 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 funcie care se poate afla chiar n program sau ntr-o bibliotec cu legturi dinamice (DLL). Windows trimite un mesaj ctre o fereastr prin apelarea procedurii de fereastr. Procedura de fereastr prelucreaz anumite informaii 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 ctre fereastr. Folosirea unei clase de fereastr permite ca pe baza aceleiai clase s se creeze mai multe ferestre care, ca urmare, folosesc aceeai procedur de fereastr. De exemplu, toate butoanele din toate programele pentru Windows sunt create pe baza aceleiai clase de fereastr. Aceast clas are o procedur de fereastr (aflat ntr-una dintre bibliotecile cu legturi dinamice ale sistemului de operare) care prelucreaz mesajele trimise ctre ferestrele butoanelor. n programarea orientat pe obiecte, un obiect" este o combinaie de cod i date. O fereastr este un obiect. Codul obiectului este procedura de fereastr. Datele obiectului sunt informaiile pstrate de fereastr i informaiile pstrate 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 aciunile 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 apsat. Alte mesaje comunic ferestrei c a fost redimensionat sau c trebuie s fie refcut. Atunci cnd un program este lansat n execuie, Windows creeaz o coad de mesaje" (message queue") pentru programul respectiv, n aceast coad de mesaje sunt pstrate mesajele trimise ctre toate ferestrele pe care le creeaz programul. Programul conine o mic secven de cod numit ciclu de mesaje" (message loop") care preia mesajele din coada de ateptare i le distribuie procedurilor de fereastr corespunztoare. Alte mesaje sunt trimise direct procedurii de fereastr, fr s mai fie plasate n coada de mesaje. Dac simii c aceast descriere excesiv de abstract a arhitecturii Windows v depete, s-ar putea s fi sosit momentul s vedei concret cum fereastra, clasa de fereastr, procedura de fereastr, coada de mesaje i mesajele trimise ferestrei se integreaz n contextul real al unui program. S-i dm drumul!

PRIMUL DUMNEAVOASTR PROGRAM

WINDOWS

www.cartiaz.ro Carti si articole online gratuite de la A la Z


n cartea devenit clasic Limbajul de programare C, Brian Kernighan i Dennis Ritchie ncep descrierea limbajului C cu de-acum faimosul program Hello, world!": include <stdio.h> main () { printf ("Hello, world\n"); } n acest capitol v voi prezenta un program asemntor, scris pentru Microsoft Windows 95. Programul se numete HELLOWIN, afieaz pe ecran irul de caractere Hello, Windows 95!" i red un fiier de sunet cu vocea mea rostind aceleai cuvinte. Dac nu v-ai prbuit atunci cnd ai vzut pentru prima dat codul programului HELLOWIN, v avertizez c are mai mult de 80 de linii. Majoritatea acestor linii reprezint cod de ntreinere (overhead code) i vei include un asemenea cod n toate programele Windows pe care le vei scrie. n loc s ne ntrebm de ce programul Hello, Windows 95!" este att de lung i de complex, haidei s vedem de ce programul Hello, world!" tradiional este att de scurt i de simplu.

Ce este greit n acest program?


Modelul de afiare pentru programul Hello, world!" i pentru alte programe C tradiionale este o component hardware antic numit teleimprimator. Teleimprimatorul seamn cu o main de scris cu o rol de hrtie continu. ntr-un trecut nu prea ndeprtat, programatorii stteau n faa unui astfel de teleimprimator i scriau comenzi al cror ecou" aprea pe hrtie. Calculatorul rspundea la comenzi prin tiprirea rspunsurilor tot pe hrtie. Odat cu apariia terminalelor sistemelor mainframe i a calculatoarelor personale, ideea teleimprimatorului a fost extins i pentru monitoarele video. Monitoarele video erau folosite ca un teleimprimator de sticl" care, pur i simplu, derula imaginea n momentul cnd textul scris ajungea n partea de jos a ecranului. Cum poate programul tradiional Hello, world!" s afieze text fr s i comunice sistemului de operare la ce dispozitiv de ieire s fie afiat textul? Simplu, deoarece exist un singur dispozitiv de ieire - ecranul, folosit ca teleimprimator. Dac utilizatorul dorete ca rezultatele s fie trimise n alt parte, trebuie s le redirecioneze folosind linia de comand. Cum poate programul s afieze text fr s i comunice sistemului de operare n ce poziie urmeaz s fie afiat acesta? Textul este afiat ntotdeauna acolo unde se afl cursorul - de obicei pe urmtoarea linie dup comanda de executare a programului. Dac vrei s afiai mesajul Hello, world!" n centrul ecranului, trebuie s folosii o serie de coduri dependente de dispozitiv ca s poziionai mai nti cursorul n poziia dorit. S presupunem c vrei s rulai mai multe programe Hello, world!" n acelai timp. Ce mizerie! Copiile programului se vor amesteca unele cu altele. Teleimprimatorul nu furnizeaz nici o metod de separare a ieirilor de la mai multe programe rulate simultan. Un alt lucru interesant este faptul c textul Hello, world!" rmne pe ecran chiar i dup terminarea programului, n loc s curee ecranul dup terminare, programul las urme" ale existenei sale. Programul Hello, world!" este att de simplu pentru c este proiectat pentru calculatoare mai simple i pentru dispozitive de ieire mai simple. Nu numai c nu este ceea ce numim astzi un produs software modern, dar nici nu se afl mcar pe acelai teren.

Fiierele programului HELLOWIN


Dou dintre cele trei fiiere necesare pentru crearea programului HELLOWIN sunt prezentate n Figura 2-1. Acestea sunt fiierul de construcie " HELLOWIN.MAK i fiierul surs HELLOWIN.C. Cel de-al treilea fiier este stocat pe discheta ataat crii, cu numele HELLOWIN.WAV i conine varianta sonor a textului afiat de program. #-----------------------# HELLOWIN.MAK make file #-----------------------hellowin.exe : hellowin.obj $(LINKER) $(GUIFLAGS) -OUT:hellowin.exe hellowin.obj $(GUILIBS) hellowin.obj : hellowin.c $(CC) $(CFLAGS) hellowin.c /*-----------------------------------------------------------HELLOWIN.C -- Displays "Hello, Windows 95!" in client area (c) Charles Petzold, 1996

www.cartiaz.ro Carti si articole online gratuite de la A la Z


------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "HelloWin" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, // window class name "The Hello Program", // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { // case WM_CREATE : //PlaySound ("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC) ;// //return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, "Hello, Windows 95! ", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

www.cartiaz.ro Carti si articole online gratuite de la A la Z


EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; } Figura 2-1. Programul HELLOWIN. n Capitolul 9 vei ntlni un alt tip de fiier frecvent folosit pentru programele Windows, numit fiier script" (resource script") i avnd extensia .RC. Pn acolo, majoritatea programelor, fiind mai simple, vor folosi un fiier de construcie, un fiier surs i, eventual, un fiier antet. Aa cum am menionat anterior, cea mai mare parte din codul programului HELLOWIN.C este cod de ntreinere (overhead code) inclus n aproape toate programele Windows. Nimeni nu se strduiete s in minte sintaxa acestui cod; n general, programatorii Windows creeaz un nou program copiind un program existent i fcnd modificrile necesare n acesta. Suntei liber s folosii n acest mod oricare dintre programele de pe discheta care nsoete cartea. Dac ai instalat pe calculatorul dumneavoastr sistemul de operare Windows 95 si mediul Microsoft Visual C++ 4.0 i ai rulat fiierul de comenzi VCVARS32.BAT inclus n Visual C++ i fiierul de comenzi MSC.BAT prezentat n acest capitol, ar trebui s putei crea fiierul executabil HELLOWIN.EXE cu comanda: Hello, Windows 95! NMAKE HELLOWIN.MAK din linia de comand MS-DOS. Dac totul merge bine, putei s rulai 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 afiat textul Hello, Windows 95!". Dac avei pe calculator o plac de sunet, vei auzi i mesajul vorbit. (Iar dac nu avei nc, ce mai ateptai?)

Figura 2-2. Rularea programului HELLOWIN sub Windows 95. De fapt, aceast fereastr are o mulime de funcii pentru cele numai 80 de linii de cod. Putei s agai" bara de titlu cu mouse-ul i s mutai fereastra pe ecran sau, trgnd chenarul ferestrei, putei redimensiona fereastra. Atunci cnd fereastra i schimb dimensiunea, programul repoziioneaz automat textul n noul centru al zonei client. Putei s executai clic pe butonul de mrire a ferestrei (maximize) si fereastra va umple ecranul. Putei s executai clic pe butonul de micorare a ferestrei (minimize) i fereastra va elibera ecranul. Toate aceste opiuni pot fi apelate i din meniul sistem. De asemenea, putei s nchidei programul selectnd opiunea Close din meniul sistem, executnd clic pe butonul de nchidere a ferestrei din partea dreapt a barei de titlu sau executnd dublu clic pe pictograma din partea stng a barei de titlu. Dei s-ar putea s v plac faptul c programul HELLOWIN are toate funciile unui program Windows normal, probabil nu vei fi la fel de fericit atunci cnd vei examina codul surs necesar pentru crearea programului. Dar haidei s ne lum inima n dini i s disecm" acest program bucat cu bucat.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


FIIERUL DE CONSTRUCIE
Pentru simplificarea compilrii programelor Windows putei s folosii utilitarul NMAKE, inclus n setul de programe Microsoft Visual C++ 4.0. De fiecare dat cnd vrei s modificai ceva n codul surs al programului HELLOWIN.C trebuie s rulai programul NMAKE, aa cum am artat mai sus, ca s creai un fiier executabil actualizat. Un fiier de construcie este format din una sau mai multe seciuni, fiecare ncepnd cu un rnd de text aliniat la stnga, care conine un fiier destinaie, urmat de dou puncte i de unul sau mai multe fiiere dependente. Aceast linie este urmat de una sau mai multe linii de comand aliniate mai n interior. Aceste comenzi creeaz fiierul destinaie din fiierele dependente. Dac data sau ora de modificare ale oricruia dintre fiierele dependente este ulterioar datei sau orei de creare a fiierului destinaie, programul NMAKE execut liniile de comand. n mod normal, programul NMAKE actualizeaz numai fiierul destinaie din prima seciune a fiierului de construcie. Totui, dac unul sau mai multe dintre fiierele dependente sunt la rndul lor fiiere destinaie n alte seciuni ale fiierului de construcie, NMAKE le va actualiza nti pe acestea. Fiierul HELLOWIN.MAK conine dou seciuni. Prima seciune ruleaz programul de editare a legturilor dac HELLOWIN.OBJ a fost modificat mai recent dect HELLOWIN.EXE. A doua seciune ruleaz compilatorul C dac HELLOWIN.C a fost modificat mai recent dect HELLOWIN.OBJ. Deoarece fiierul HELLOWIN.OBJ este fiier dependent n prima seciune i fiier destinaie n a doua seciune, programul NMAKE va verifica mai nti dac HELLOWIN.OBJ trebuie s fie actualizat nainte ca fiierul HELLOWIN.EXE s fie re-creat. n aceast situaie, fiierul de construcie este de fapt executat de la un capt la cellalt. Rularea compilatorului C creeaz modulul obiect HELLOWIN.OBJ din fiierul surs HELLOWIN.C. Rularea programului de editare a legturilor creeaz fiierul executabil HELLOWIN.EXE din HELLOWIN.OBJ. n Capitolul 1 am artat cum sunt furnizai macro-identificatorii din fiierul de construcie de ctre variabilele de mediu stabilite de fiierele 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 legturilor. Dac dorii amnunte, revenii la seciunea respectiv din Capitolul 1.

Fiierul surs C
Al doilea fiier prezentat n Figura 2-1 este HELLOWIN.C, fiierul care conine codul surs C. Va trece un timp pan cnd v vei da seama c acest program este ntr-adevr scris n limbaj C! nainte de a intra n detalii, haidei s aruncm o privire general asupra fiierului HELLOWIN.C. Fiierul conine numai dou funcii: WinMain i WndProc. Funcia WinMain reprezint punctul de intrare n program. Aceasta este echivalentul funciei main din programele scrise n limbajul C. Orice program pentru Windows trebuie s aib o funcie WinMain. WndProc este procedura de fereastr" a ferestrei create de programul HELLOWIN. Orice fereastr indiferent dac este fereastra principal a aplicaiei sau fereastra unui mic buton de apsare - are o procedur de fereastr. Procedura de fereastr este un mod de ncapsulare a codului care rspunde intrrilor (n general de la tastatur i de la mouse) i afieaz elementele grafice pe ecran. Aa cum vei vedea, procedura de fereastr face acest lucru prin prelucrarea mesajelor" trimise ctre fereastr. Pentru moment nu v batei capul cu modul de funcionare a acestui mecanism. Vei avea destul timp s v luptai cu acest concept. Nici o instruciune din fiierul HELLOWIN.C nu apeleaz direct funcia WndProc: WndProc este apelat de sistemul de operare Windows. Totui, n funcia WinMain apare o referire la funcia WndProc, acesta fiind motivul pentru care WndProc este declarat n partea de nceput a programului, nainte de WinMain.

Apelurile de funcii Windows


HELLOWIN apeleaz nu mai puin de 17 funcii Windows. Aceste funcii, mpreun cu o scurt descriere, sunt prezentate n continuare, n ordinea apariiei n programul HELLOWIN: LoadIcon - ncarc o pictogram care urmeaz s fie folosit de un program. LoadCursor - ncarc un indicator pentru mouse, care urmeaz s fie folosit de un program. GetStockObject - obine un obiect grafic (n acest caz o pensul folosit pentru desenarea fondului ferestrei). RegisterClassEx - nregistreaz o clas de fereastr pentru fereastra programului. CreateWindow - creeaz o fereastr pe baza unei clase de fereastr. ShowWindow - afieaz o fereastr pe ecran. UpdateWindow - cere unei ferestre s se redeseneze. GetMessage - preia un mesaj din coada de mesaje. TranslateMessage - convertete unele dintre mesajele de la tastatur. DispatchMessage - trimite un mesaj ctre o procedur de fereastr. PlaySound - red un fiier de sunet. BeginPaint - iniiaz o operaie de desenare a ferestrei.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


GetClientRect - obine dimensiunile zonei client a ferestrei. DrawText - afieaz un text. EndPaint - ncheie o operaie de desenare. PostQuitMessage - insereaz un mesaj de ncheiere n coada de ateptare. DefWindowProc - execut operaiile prestabilite de prelucrare a mesajelor. Aceste funcii sunt documentate n cri i n sursele incluse de compilator i sunt declarate n diferite fiiere antet incluse n fiierul WINDOWS.H.

Identificatori cu majuscule
Vei observa c n fiierul HELLOWIN.C sunt folosii civa identificatori scrii cu majuscule. Aceti identificatori sunt definii n fiierele antet din Windows. Civa dintre ei conin un prefix de dou sau trei litere, urmat de o liniu de subliniere: CS_HREDRAW DT_VCENTER WM_CREATE CS_VREDRAW IDC_ARROW WM_DESTROY CW_USEDEFAULT IDI_APPLICATION WM_PAINT DT_CENTER SND_ASYNC WS_OVERLAPPEDWINDOW DT_SINGLELINE SND_FILENAME Acestea sunt simple constante numerice. Prefixul indic o categorie general creia i aparine constanta respectiv, aa cum se arat n tabelul urmtor: Prefix CS IDI IDC WS CW WM SND DT Categorie Opiune pentru stilul clasei Numr de identificare pentru o pictogram Numr de identificare pentru un cursor Stil de fereastr Opiune de creare a unei ferestre Mesaj de fereastr Opiune pentru sunete Opiune de desenare a textului

n mod normal nu este nevoie s reinei nici o constant numeric pentru scrierea unui program pentru Windows. De fapt, toate constantele numerice folosite n Windows au un identificator definit n fiierele antet.

Noi tipuri de date


Ali identificatori folosii n fiierul HELLOWIN.C sunt noi tipuri de date, definite n fiierele antet cu ajutorul instruciunii typedef sau #define. Aceste definiii au fost stabilite iniial pentru a simplifica tranziia programelor Windows originale de la sistemele pe 16 bii originale la viitoarele sisteme pe 32 de bii, sau la cele bazate pe o alt tehnologie. Aceast tranziie nu s-a fcut chiar att de uor i de transparent cum ar fi crezut unii la momentul respectiv, dar conceptul este destul de bun. Uneori aceste noi tipuri de date sunt doar abrevieri mai uor de folosit. De exemplu, tipul de date UINT folosit pentru al doilea parametru al funciei WndProc este prescurtarea de ia unsigned int, ceea ce n Windows 95 nseamn o valoare ntreag pe 32 de bii. Tipul de date PSTR folosit pentru al treilea parametru al funciei WinMain este un pointer la un ir de caractere, adic nlocuiete tipul char*. Alte tipuri de date nu sunt la fel de evidente. De exemplu, al treilea i al patrulea parametri ai funciei WndProc sunt definii cu tipurile de date WPARAM si LPARAM. Originea acestor nume ine oarecum de istoria sistemului Windows: pe cnd Windows era un sistem pe 16 bii, al treilea parametru al funciei WndProc era definit ca WORD, adic un numr ntreg scurt, fr semn, pe 16 bii (unsigned short) iar al patrulea parametru era definit ca LONG, adic un numr ntreg pe 32 de bii (long) - de aici provenind prefixele W" i L" ale cuvntului PARAM". n Windows 95, WPARAM este definit ca UINT iar LPARAM este definit ca LONG (care este chiar tipul long din C), aa c ambii parametri ai procedurii de fereastr sunt valori pe 32 de bii. Deoarece tipul de date WORD este definit n Windows 95 ca un numr ntreg fr semn pe 16 bii (unsigned short), prefixul W" din WPARAM este oarecum impropriu i poate crea confuzii. Funcia WndProc returneaz o valoare LRESULT, definit ca un numr de tip LONG. Funcia WinMain este de tipul WINAPI, iar funcia WndProc este de tipul CALLBACK. Ambii identificatori sunt definii ca __stdcall, care definete o secven special de apelare pentru apelurile de funcii dintre Windows i aplicaii. Programul HELLOWIN folosete patru structuri de date (despre care vom discuta mai trziu n acest capitol) definite n fiierele antet din Windows. Aceste structuri de date sunt:

www.cartiaz.ro Carti si articole online gratuite de la A la Z


Structura MSG WNDCLASSEX PAINTSTRUCT RECT Semnificaie Structura mesajului Structura clasei de fereastr Structur pentru desenare Dreptunghi

Primele dou structuri sunt folosite n funcia WinMain pentru definirea a dou structuri numite msg i wndclass. Celelalte dou sunt folosite n funcia WndProc pentru definirea altor dou structuri, numite ps i rect.

Cteva cuvinte despre variabilele handle


n sfrit, n program sunt folosii trei identificatori cu majuscule pentru diferite tipuri de variabile handle: Identificator HINSTANCE HWND HDC Semnificaie Variabil handle a unei instane" - programul nsui Variabil handle a unei ferestre Variabil handle a unui context de dispozitiv

Variabilele handle sunt folosite destul de des n Windows. n acest capitol vei face cunotin 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 numr (de obicei pe 32 de bii) care face trimitere la un obiect. Variabilele handle din Windows sunt asemntoare cu cele folosite pentru fiiere (file handles) n programele convenionale C sau MS-DOS. Un program obine aproape ntotdeauna o variabil apelnd o funcie Windows. Programul folosete apoi variabila handle obinut pentru trimiterea la obiect n alte funcii. 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.

Notaia 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 funciei WinMain. Muli programatori Windows folosesc notaia ungar", o convenie de denumire a variabilelor intitulat astfel n onoarea legendarului programator de la Microsoft, Charles Simonyi. Convenia este foarte simpl - fiecare nume de variabil ncepe cu una sau mai multe litere mici care specific tipul de date al variabilei. De exemplu, prefixul sz al variabilei szCmdLine semnific ir de caractere terminat cu zero". Prefixul h al variabilelor hInstance i hPrevInstance nseamn variabil handle"; prefixul i al variabilei iCmdShow nseamn ntreg". i ultimii doi parametri ai funciei WndProc respect notaia ungar, dei, aa cum am artat mai devreme, wParam ar fi trebuit s se numeasc uiParam (de la unsigned integer"). Totui, deoarece aceti doi parametri se definesc folosind tipurile de date WPARAM i LPARAM, am pstrat denumirile originale. Atunci cnd denumii variabilele de tip structur, putei s folosii numele de tip al structurii (sau o abreviere a acestuia) ca prefix al numelui variabilei sau chiar ca nume al variabilei. De exemplu, n funcia WinMain din programul HELLOWIN.C, variabila msg este o structur de tip MSG iar wndclass este o variabil de tip WNDCLASSEX. n funcia WndProc, ps este o structur de tip PAINTSTRUCT iar rect este o structur de tip RECT. Notaia ungar v ajut s descoperii erorile nainte ca acestea s ajung n programele finale. Deoarece numele unei variabile descrie att modul de folosire a acesteia, ct i tipul de date, este mai puin probabil s facei erori de programare care implic amestecarea unor tipuri de date incompatibile. Prefixele folosite pentru variabilele din aceast carte sunt prezentate n tabelul urmtor: Prefix Tip de date c char by BYTE (unsigned char) n short i int x, y int (folosit pentru coordonate) cx, cy int (folosit pentru dimensiuni pe axele x si y, c vine de la contor") b sau f BOOL (int); f vine de la flag" (indicator) w WORD (unsigned short) l LONG (long) dw DWORD (unsigned long) fn funcie s ir de caractere

www.cartiaz.ro Carti si articole online gratuite de la A la Z


sz h p sir de caractere terminat cu zero variabil handle pointer

Punctul de intrare n program


Dup ce v-ai format o idee general despre programul HELLOWIN.C, putem s disecm programul linie cu linie. Codul ncepe cu o instruciune #include pentru includerea fiierului antet WINDOWS.H:
#include <windows.h>

Fiierul WINDOWS.H include la rndul lui mai multe fiiere antet care conin declaraiile funciilor, structurilor, noilor tipuri de date i constantelor numerice din Windows. Instruciunea #include este urmat de declaraia avansat a funciei WndProc:
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

Declararea n avans a acestei funcii este necesar deoarece WndProc este referit n cadrul funciei WinMain. Punctul de intrare al unui program C scris pentru un mediu convenional este o funcie numit main. De la aceast funcie ncepe execuia programului. (De fapt, funcia main este punctul de intrare la acea parte a programului scris de programator. De obicei, compilatorul C insereaz n fiierul executabil unele secvene de cod pentru lansarea n execuie. Funcia main este apoi apelat de acest cod de lansare). Punctul de intrare ntr-un program Windows este o funcie numit WinMain. Funcia WinMain este ntotdeauna definit astfel:
int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

Aceast funcie folosete secvena de apelare WINAPI i la terminare returneaz sistemului de operare o valoare ntreag. Numele funciei trebuie s fie WinMain. Aceast funcie are patru parametri: Parametrul hlnstance este numit variabil handle a instanei" (instance handle"). Acesta este un numr care identific n mod unic toate programele rulate n Windows. Utilizatorul poate rula simultan mai multe copii ale aceluiai program. Aceste copii se numesc instane" i fiecare are o valoare diferit pentru parametrul hlnstance. Variabila handle a instanei este asemntoare cu identificatorul de operaie" (task ID") sau identificatorul de proces" (process ID") din alte sisteme de operare multitasking. hPrevInstance (previous instance" - instana anterioar) este un parametru nvechit. n versiunile Windows anterioare acest parametru coninea variabila handle a celei mai recente instane nc activ a aceluiai program. Dac nu erau ncrcate alte instane ale programului, hPrevInstance avea valoarea 0 sau NULL. n Windows 95, parametrul hPrevInstance are ntotdeauna valoarea NULL. Parametrul szCmdLine este un pointer la un ir de caractere terminat cu zero care conine eventualii parametri transmii programului n linia de comand. Putei s rulai un program Windows cu parametri incluznd parametrii respectivi dup numele programului n linia de comand MS-DOS sau specificndu-i n caseta de dialog Run apelat din meniul Start. Parametrul iCmdShow este un numr care indic modul iniial de afiare a ferestrei n Windows. Acest numr este atribuit de programul care lanseaz n execuie programul aflat n discuie. 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 gndii 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" (afiare fereastr). Acest parametru specific dac fereastra programului este afiat normal sau dac este afiat iniial doar ca o pictogram.

nregistrarea clasei de fereastr


O fereastr este ntotdeauna creat pe baza unei clase de fereastr. Aceasta identific procedura de fereastr care prelucreaz toate mesajele trimise ctre fereastr. Pe baza aceleiai clase pot fi create mai multe ferestre. De exemplu, toate butoanele din Windows sunt create pe baza unei singure clase de fereastr. Aceasta definete procedura de fereastr i alte caracteristici ale ferestrei create pe baza clasei respective. Atunci cnd creai o fereastr, definii i atributele suplimentare ale acesteia, care sunt unice pentru fereastra respectiv.

ntr-un program C funciile pot fi declarate nainte de a fi definite. Declaraia unei funcii precizeaz tipul parametrilor primii de funcie i tipul valorii returnate de aceasta, fr s defineasc i corpul acesteia. Acest lucru este necesar atunci cnd funcia este indicat nainte de a fi definit, dar uureaz i citirea programului, (n.t.)

www.cartiaz.ro Carti si articole online gratuite de la A la Z


nainte de a crea fereastra programului trebuie s nregistrai o clas de fereastr, apelnd funcia RegisterClassEx. Aceasta este o versiune extins (de aici sufixul Ex") a funciei RegisterClass din versiunile anterioare ale sistemului de operare Windows. Totui, funcia RegisterClass poate fi nc folosit n Windows 95. Funcia RegisterClassEx accept un singur parametru: un pointer la o structur de tipul WNDCLASSEX. Structura WNDCLASSEX este definit n fiierele antet din Windows astfel: typedef struct tagWNDCLASSEX { UINT cbSize ; UINT style ; WNDPROC lpfnWndProc ; int cbClsExtra ; int cbWnExtra ; HINSTANCE hinstance ; HICON hicon ; HCURSOR hCursor ; HBRUSH hbrBackground ; LPCSTR lpszMenuName ; LPCSTR lpszClassName ; HICON hIconSm ; } WNDCLASSEX ; Sunt necesare cteva observaii privind tipurile de date i notaia ungar folosit n aceast structur: prefixele LP i lp sunt prescurtri pentru long pointer" i sunt rmie" din versiunile Windows pe 16 bii, n cazul crora programatorii trebuie s fac diferena ntre pointerii de tip short (sau near) pe 16 bii i pointerii de tip long (sau far) pe 32 de bii. n Windows 95 toi pointerii au valori pe 32 de bii. 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 vei mai ntlni n alte programe. Remarcai i alte moduri de folosire a notaiei ungare: lpfn vine de la long pointer to a function" (pointer de tip long la o funcie"). Prefixul cb provine de la count of bytes" (contor de octei"). Prefixul hbr vine de la handle to a brush" (variabil handle a unei pensule"). n funcia WinMain trebuie s definii o structur de tipul WNDCLASSEX, cum ar fi: WNDCLASSEX wndclass ; Apoi definii cele 12 cmpuri ale structurii i apelai funcia RegisterClassEx: RegisterClassEx (&wndclass) ; Cele mai importante cmpuri ale structurii sunt al treilea i penultimul. Penultimul cmp conine numele clasei de fereastr (i n programele care creeaz o singur fereastr are, de obicei, acelai nume ca i programul). Al treilea cmp (lpfnWndProc) este adresa procedurii de fereastr folosit pentru toate ferestrele create pe baza acestei clase (care este funcia WndProc din programul HELLOWIN.C). Celelalte cmpuri descriu caracteristicile tuturor ferestrelor create pe baza acestei clase. Cmpul cbSize reprezint dimensiunea structurii. Instruciunea: wndclass.style = CS_HREDRAW | CS_VREDRAW ; combin doi identificatori pentru stilul de clas" (class style") folosind operatorul SAU orientat pe bii din limbajul C. n fiierele antet din Windows sunt definii mai muli identificatori cu prefixul CS_. Acestea sunt constante pe 32 de bii 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 definii n acest fel sunt numii uneori identificatori pe bii". Acetia pot fi combinai cu ajutorul operatorului SAU orientat pe bii din limbajul C. Cei doi identificatori pentru stilul clasei indic faptul c toate ferestrele create pe baza acestei clase sunt redesenate complet, ori de cte ori se modific dimensiunea pe orizontal (CS_HREDRAW) sau cea pe vertical (CS_VREDRAW) a ferestrei. Dac redimensionai fereastra programului HELLOWIN vei vedea c textul este redesenat, astfel nct s apar n centrul noii ferestre. Acest lucru este asigurat de cei doi identificatori de stil. Vom vedea imediat cum este informat procedura de fereastr privind modificarea dimensiunii ferestrei. Al treilea cmp al structurii WNDCLASSEX este iniializat prin instruciunea: wndclass.lpfnWndProc = WndProc ; Aceast instruciune stabilete ca procedur de fereastr funcia WndProc, adic a doua funcie definit n fiierul HELLOWIN.C. Aceast procedur va prelucra toate mesajele trimise ctre toate ferestrele create pe baza

www.cartiaz.ro Carti si articole online gratuite de la A la Z


acestei clase de fereastr. Aa cum am artat mai sus, prefixul lpfn nseamn, n notaia ungar, pointer de tip long la o funcie". Urmtoarele dou instruciuni: wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; rezerv un spaiu suplimentar n structura clasei i n structura ferestrei, pstrat n interiorul sistemului de Windows. Un program poate s utilizeze spaiul suplimentar n scopuri proprii. Programul HELLOWIN nu folosete aceast posibilitate, aa c nu se rezerv nici un spaiu suplimentar. n alte situaii, aa cum indic i notaia ungar, cmpurile vor avea rolul de contor de octei". Urmtorul cmp este variabila handle a instanei (care este chiar unul dintre parametrii funciei WinMain): wndclass.hInstance = hinstance ; Instruciunea: wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; i instruciunea: wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; definesc o pictogram pentru ferestrele create pe baza acestei clase. Pictograma este o mic imagine de tip bitmap care apare n bara de taskuri a sistemului de operare i n bara de titlu a ferestrei. Mai trziu vei nva cum s creai pictograme proprii pentru programele Windows. Pentru moment, vom aborda o metod mai simpl i vom folosi o pictogram predefinit. Pentru obinerea unei variabile handle a unei pictograme predefinite apelai funcia LoadIcon cu primul parametru avnd valoarea NULL. (Atunci cnd ncrcai o pictogram proprie, acest parametru va conine variabila handle a instanei programului.) Al doilea parametru este un identificator cu prefixul IDI_ definit n fiierele antet din Windows. Pictograma IDI_APPLICATION este o mic imagine a unei ferestre. Funcia LoadIcon returneaz o variabil handle a acestei pictograme. Nu ne intereseaz valoarea real a acestei variabile, ci doar o stocm n cmpurile hIcon i hIconSm. Aceste cmpuri sunt definite n structura WNDCLASSEX de tipul HICON (handle to an icon"). Instruciunea:
wndclass.hCursor = LoadCursor (NULL, IDC_ ARROW) ;

este foarte asemntoare cu cele dou instruciuni anterioare. Funcia LoadCursor ncarc un cursor predefinit pentru mouse, numit IDC_ARROW, i returneaz o variabil handle a acestui cursor. Atunci cnd este deplasat deasupra zonei client a ferestrei create pe baza acestei clase, indicatorul mouse-ului este afiat sub forma unei sgei. Urmtorul cmp 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 suprafee. Windows are mai multe pensule standard (sau pensule de stoc"). Funcia GetStockObject returneaz o variabil handle a unei pensule albe:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

Aceasta nseamn c fondul zonei client a ferestrei va fi colorat cu alb, ceea ce este un lucru obinuit. Urmtorul cmp specific meniul ferestrei. Programul HELLOWIN nu are nici un meniu, aa c acest cmp are valoarea NULL:
wndclass.lpszMenuName = NULL ;

n sfrit, 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 iniializarea celor 12 cmpuri ale structurii, HELLOWIN nregistreaz clasa de ferestre prin apelarea funciei RegisterClassEx. Singurul parametru al funciei este un pointer ctre structura WNDCLASSEX: RegisterClassEx (&wndclass) ;

Crearea terestrei
Clasa de fereastr definete caracteristicile generale ale unei ferestre, permind astfel folosirea aceleiai clase pentru crearea mai multor ferestre. Atunci cnd creai o fereastr apelnd funcia Create Window, putei s specificai mai multe detalii despre fereastra respectiv. Programatorii Windows mai noi sunt uneori derutai de diferenele ntre o clas de fereastr i o fereastr, i nu neleg de ce nu pot s specifice toate caracteristicile unei ferestre printr-o singur operaie. De fapt, mprirea informaiilor n acest fel este foarte convenabil. De exemplu, toate butoanele de apsare 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 ctre butonul de

www.cartiaz.ro Carti si articole online gratuite de la A la Z


apsare i definirea aspectului vizual al butonului pe ecran. Din acest punct de vedere, toate butoanele de apsare funcioneaz n acelai mod. Acestea ns pot avea diferite dimensiuni, diferite poziii pe ecran i diferite etichete. Aceste caracteristici fac parte din definiia ferestrei. n loc s foloseasc o structur de date, aa cum face funcia RegisterClassEx, funcia CreateWindow cere ca toate informaiile s fie transmise ca parametri. Iat cum este apelat funcia CreateWindow n programul HELLOWIN.C: hwnd =CreateWindow (szAppName, "The Hello Program", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USE DEFAULT, CW_USEDEFAULT, NULL, NULL, hlnstance, NULL) ; // numele clasei de fereastra // titlul ferestrei // stilul ferestrei // poziia iniiala pe axa x // poziia iniiala pe axa y // dimensiunea iniiala pe axa x // dimensiunea iniiala pe axa y // variabila handle a ferestrei printe // variabila handle a meniului // variabila handle a instanei programului // parametri de creare

Pentru o citire mai uoar, am folosit simbolul // pentru notarea comentariilor pe o singur linie care descriu parametrii funciei Create Window. Parametrul notat cu numele clasei de fereastr" este szAppName, care conine 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 stng a barei de titlu se afl caseta cu meniul sistem; n partea dreapt se afl butoanele de mrire, de micorare i de nchidere; fereastra are un chenar ngroat, care permite redimensionarea. Acesta este stilul standard al ferestre lor, numit WS_OVERLAPPEDWINDOW; n funcia CreateWindow i corespunde comentariul stilul ferestrei". Titlul ferestrei" este textul afiat n bara de titlu. Parametrii notai cu poziia iniial pe axa x" i poziia iniial pe axa y" specific poziia iniial a colului din stnga-sus al ferestrei, relativ la colul din stnga-sus al ecranului. Prin folosirea identificatorului CW_USEDEFAULT pentru aceti para metri indicm sistemului de operare s foloseasc valorile prestabilite pentru o fereastr suprapus. (CW_USEDEFAULT este definit ca 0x80000000.) n mod prestabilit, Windows poziioneaz mai multe ferestre suprapuse succesive la o distan cresctoare pe vertical i pe orizontal fa de colul din stnga-sus al ecranului. La fel, parametrii dimensiunea iniial pe axa x" i dimensiunea iniial pe axa y" specific dimensiunile iniiale ale ferestrei. Identificatorul CW_USEDEFAULT indic sistemului de operare s foloseasc valorile prestabilite. Parametrul indicat ca variabil handle a ferestrei printe" are valoarea NULL, deoarece aceast fereastr nu are nici o fereastr printe. Atunci cnd ntre dou ferestre exist o relaie printe-descendent, fereastra descendent este afiat ntotdeauna pe suprafaa ferestrei printe. Parametrul indicat ca variabil handle a meniului" are tot valoarea NULL, deoarece fereastra nu are meniu. Parametrul indicat ca variabil handle a instanei programului" are ca valoare variabila handle transmis programului ca parametru la apelarea funciei WinMain. n sfrit, parametrul de creare" are valoarea NULL. Putei s folosii acest parametru pentru adresarea unor date folosite ulterior n program. Funcia 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 folosete variabila handle pentru indicarea ferestrei. Multe funcii Windows au un parametru hwnd, care specific fereastra la care se refer funcia 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.

Afiarea terestrei
Dup executarea funciei CreateWindow, fereastra a fost creat de Windows, dar nc nu este afiat pe ecran. Pentru aceasta mai sunt necesare nc dou apeluri de funcii. Primul este: ShowWindow (hwnd, iCmdShow) ; Primul parametru este o variabil handle a ferestrei create de funcia CreateWindow. Al doilea parametru este variabila iCmdShow, transmis funciei WinMain. Dac iCmdShow este SW_SHOWNORMAL (egal cu 1), fereastra este afiat normal. Dac iCmdShow este SW_SHOWMINNOACTIVE (egal cu 7), atunci fereastra nu este afiat, dar numele i pictograma acesteia apar pe bara de taskuri.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


n programul HELLOWIN funcia ShowWindow afieaz fereastra pe ecran. Dac al doilea parametru al funciei este SW_SHOWNORMAL, Windows terge zona client a ferestrei folosind pensula specificat n clasa ferestrei. Apelul: UpdateWindow (hwnd) ; determin redesenarea zonei client. Acest lucru se face prin trimiterea ctre procedura de fereastr (funcia WndProc din HELLOWIN.C) a unui mesaj WM_PAINT. Vom vedea imediat cum trateaz funcia WndProc aceste mesaje.

Ciclul de mesaje
Dup apelarea funciei UpdateWindow, fereastra devine vizibil pe ecran. Programul trebuie s fie acum pregtit s citeasc intrrile de la mouse i de la tastatur. Windows formeaz o coad de mesaje" pentru fiecare program rulat concurenial. Atunci cnd apare un eveniment exterior, Windows convertete acest eveniment ntrun mesaj pe care l plaseaz n coada de ateptare. Un program preia mesajele din coada de ateptare prin executarea unei secvene de cod numit ciclu de mesaje" (message loop"): while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; Variabila msg este o structur de tip MSG, definit n fiierele antet din Windows astfel: typedef struct tagMSG { HWND hwnd ; UINT message ; WPARAM wParam ; LPARAM lParam ; DWORD time ; POINT pt ; } MSG , Tipul de date POINT este tot o structur, definit astfel: typedef struct tagPOINT { LONG x ; LONG y ; } POINT ; Funcia GetMessage apelat la nceputul ciclului de mesaje preia un mesaj din coada de ateptare: GetMessage (&msg, NULL, 0, 0) Acest apel transmite sistemului de operare un pointer, numit msg, la o structur de tip MSG. Al doilea, al treilea i al patrulea parametru au valoarea NULL sau 0, ceea ce indic faptul c programul vrea s preia toate mesajele, pentru toate ferestrele create de program. Windows completeaz cmpurile structurii de mesaje cu urm torul mesaj din coada de ateptare. Cmpurile acestei structuri sunt: hwnd - variabila handle a ferestrei creia i este destinat mesajul. n pro gramul HELLOWIN, aceasta este aceeai cu valoarea hwnd returnat de funcia CreateWindow, deoarece aceasta este singura fereastr a programului. message - identificatorul mesajului. Acesta este un numr folosit pentru identificarea mesajului. Pentru fiecare mesaj n fiierele antet din Windows este definit un identificator care ncepe cu prefixul WM_ (window message"). De exemplu, dac poziionai indicatorul mouse-ului n zona client a programului HELLOWIN i apsai butonul din stnga, Windows va insera n coada de ateptare un mesaj pentru care cmpul message conine identificatorul WM_LBUTTONDOWN, adic valoarea 0x0201. wParam - un parametru pe 32 de bii a crui valoare depinde de mesajul trimis. lParam - un alt parametru pe 32 de bii dependent de mesaj. time - momentul inserrii mesajului n coada de mesaje.

www.cartiaz.ro Carti si articole online gratuite de la A la Z


pt - coordonatele poziiei mouse-ului n momentul inserrii mesajului n coada de mesaje. Dac n cmpul message este transmis orice alt valoare dect WM_QUIT (egal cu 0x0012), funcia GetMessage returneaz o valoare diferit de zero. Mesajul WM_QUIT determin ieirea din ciclul de mesaje. Programul se ncheie, returnnd valoarea parametrului wParam al structurii msg. Instruciunea: TranslateMessage (&msg) ; retransmite structura msg sistemului de operare, pentru convertirea unor mesaje de la tastatur. (Vom discuta mai multe despre aceasta n Capitolul 5.) Instruciunea: DispatchMessage (&msg) ; ca i funcia TranslateMessage, retransmite structura msg sistemului de operare. Windows trimite apoi mesajul ctre procedura de fereastr corespunztoare, n vederea prelucrrii - cu alte cuvinte, Windows apeleaz procedura de fereastr. n programul HELLOWIN, procedura de fereastr este WndProc. Dup ce prelucreaz mesajul, funcia WndProc pred controlul sistemului de operare, care nc elaboreaz rspunsul la apelul DispatchMessage. Atunci cnd Windows returneaz controlul programului HELLOWIN, dup executarea apelului DispatchMessage, ciclul de tratare a mesajelor continu cu urmtorul apel al funciei GetMessage.

Procedura de fereastr
Tot codul descris pn n acest moment este cod de ntreinere: a fost nregistrat clasa de fereastr, a fost creat fereastra, care apoi a fost afiat pe ecran i programul a intrat n ciclul de tratare a mesajelor n vederea prelurii mesajelor din coada de ateptare. Operaiile reale au loc ns n procedura de fereastr. Procedura de fereastr arat ce afieaz fereastra n zona client i cum rspunde fereastra la intrrile utilizatorului. n programul HELLOWIN procedura de fereastr este funcia WndProc. O procedur de fereastr poate avea orice nume (cu condiia ca numele respectiv s nu existe deja). Un program pentru Windows poate conine mai multe proceduri de fereastr. O procedur de fereastr este ntotdeauna asociat unei clase de fereastr, nregistrat cu ajutorul funciei RegisterClassEx. Funcia CreateWindow creeaz o fereastr pe baza unei anumite clase. Pe baza aceleiai clase pot fi create mai multe ferestre. Procedura de fereastr este definit ntotdeauna astfel: LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam. LPARAM lParam) Remarcai faptul c cei patru parametri ai procedurii de fereastr sunt identici cu primele patru cmpuri ale structurii MSG. Primul parametru este hwnd, variabila handle a ferestrei care recepioneaz mesajul. Aceasta este aceeai variabil cu cea returnat de funcia 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 aceleiai clase (i deci folosete aceeai procedur de fereastr), atunci hwnd identific fereastra care primete mesajul. Al doilea parametru este un numr (mai precis, un ntreg fr semn - UINT - pe 32 de bii) care identific mesajul. Ultimii doi parametri (wParam, de tipul WPARAM si lParam, de tipul LPARAM) furnizeaz mai multe informaii despre mesaj. Acetia sunt numii parametri de mesaj". Coninutul acestor parametri este specific fiecrui tip de mesaj.

Prelucrarea mesajelor
Fiecare mesaj recepionat de o procedur de fereastr este identificat printr-un numr, acesta fiind parametrul iMsg al procedurii de fereastr. n fiierele antet din Windows sunt definii identificatori, cu prefixul WM (window message"), pentru fiecare parametru de mesaje. n general, programatorii Windows folosesc o construcie switch sau case ca s determine ce mesaje a primit fereastra i s stabileasc modul de prelucrare a fiecrui mesaj. Atunci cnd prelucreaz un mesaj, procedura de fereastr trebuie s returneze valoarea 0. Orice mesaj pe care procedura de fereastr nu-l prelucreaz este transmis unei funcii Windows numit DefWindowProc. Valoarea returnat de funcia DefWindowProc trebuie s fie returnat i de procedura de fereastr. n programul HELLOWIN, procedura de fereastr (WndProc) prelucreaz numai trei mesaje: WM_CREATE, WM_PAINT i WM_DESTROY. Procedura de fereastr este structurat astfel: switch (iMsg) { case WM_CREATE : [prelucreaz mesajul WM_CREATE]

www.cartiaz.ro Carti si articole online gratuite de la A la Z


return 0 ; case WM_PAINT : [prelucreaz mesajul WM_PAINT] return 0 ; case WM_DESTROY : [prelucreaz mesajul WM_DESTROY] return 0 ; } return DefWindowProc(hwnd, iMsg, wParam, lParam) ; Este esenial s apelai funcia DefWindowProc pentru prelucrarea tuturor mesajelor ignorate de procedura de fereastr a programului.

Redarea unui fiier de sunet


Primul mesaj pe care l primete o procedur de fereastr - i primul mesaj prelucrat de funcia WndProc este WM_CREATE. WndProc recepioneaz acest mesaj n timp ce Windows execut funcia Create Window din WinMain. Aceasta nseamn c atunci cnd HELLOWIN apeleaz funcia CreateWindow, Windows face ce are de fcut i apeleaz funcia WndProc, transmindu-i variabila handle a ferestrei i mesajul WM_CREATE. WndProc prelucreaz mesajul WM_CREATE i returneaz controlul sistemului de operare. Windows poate apoi s ncheie execuia funciei CreateWindow i s se ntoarc la programul HELLOWIN pentru alte operaii din funcia WinMain. Deseori, procedurile de fereastr fac toate operaiile de iniializare a ferestrei n timpul prelucrrii mesajului WM_CREATE. n timpul prelucrrii acestui mesaj, programul HELLOWIN red un fiier de sunet, numit HELLOWIN.WAV. Acest lucru se face prin apelarea funciei PlaySound. Primul parametru al funciei este numele fiierului. Acesta ar putea s fie i un alias (nume de nlocuire) definit n seciunea Sounds a panoului de control (Control Panel) sau o resurs de program. Al doilea parametru este folosit numai dac fiierul de sunet este o resurs. Al treilea parametru specific un set de opiuni. n acest caz am indicat faptul c primul parametru este un nume de fiier i c sunetul trebuie s fie redat asincron, adic funcia PlaySound trebuie s returneze controlul imediat dup nceperea operaiei de redare, fr s atepte terminarea acesteia. WndProc ncheie prelucrarea mesajului WM_CREATE cu returnarea valorii zero din procedura de fereastr.

Mesajul WM_PAINT
Al doilea mesaj prelucrat de funcia 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 cnd 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 cnd programul apeleaz funcia UpdateWindow din WinMain) cere procedurii de fereastr s deseneze ceva n zona client. Zona client devine invalid i atunci cnd redimensionai fereastra programului HELLOWIN. V amintii c parametrul de stil al structurii wndclass din HELLOWIN conine identificatorii CS_VREDRAW i CS_HREDRAW. Aceti identificatori determin sistemul de operare Windows s invalideze ntreaga fereastr atunci cnd aceasta i schimb dimensiunea i apoi s trimit un mesaj WM_PAINT ctre procedura de fereastr. Dac micorai la o pictogram fereastra programului HELLOWIN i apoi i refacei dimensiunea iniial. Windows nu salveaz coninutul zonei client. ntr-un mediu grafic ar fi mult prea multe date de salvat. De aceea. Windows invalideaz fereastra. Procedura de fereastr primete mesajul WM_PAINT i reface coninutul acesteia. Atunci cnd mutai ferestrele astfel nct s se suprapun. Windows nu salveaz poriunea unei ferestre care a fost acoperit de o alt fereastr. n schimb, atunci cnd fereastra respectiv este readus la suprafa, poriunea anterior acoperit este invalidat. Procedura de fereastr primete un mesaj WM_PAINT pentru redesenarea poriunii respective. Aproape ntotdeauna, prelucrarea mesajului WM_PAINT ncepe prin apelarea funciei BeginPaint: hdc = BeginPaint (hwnd, &ps) ; i se termin cu apelarea funciei EndPaint: EndPaint (hwnd, &ps) ;

www.cartiaz.ro Carti si articole online gratuite de la A la Z


n ambele situaii, primul parametru este variabila handle a ferestrei programului, iar al doilea parametru este un pointer la o structur de tip PAINTSTRUCT. Structura PAINTSTRUCT conine unele informaii pe care programul le poate folosi pentru redesenarea zonei client. (Vom discuta despre acest cmp al structurii n urmtorul capitol.) n timpul apelrii funciei BeginPaint, Windows terge fondul zonei client, dac acesta nu a fost deja ters. Fondul este ters folosindu-se pensula specificat n cmpul 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 colorndu-l, n acelai timp, cu alb. Funcia 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 ieire (cum ar fi un monitor video) mpreun cu driverul care controleaz acest dispozitiv. Avei nevoie de o variabil handle a contextului de dispozitiv ca s afiai text i elemente grafice n zona client a ferestrei. Folosind variabila handle a con textului de dispozitiv returnat de funcia BeginPaint nu putei s desenai n afara zonei client a ferestrei, chiar dac ncercai. Funcia EndPaint elibereaz variabila handle a contextului de dispozitiv, astfel nct aceasta nu mai este valid. Dac procedura de fereastr nu prelucreaz mesajele WM_PAINT - ceea ce se ntmpl foarte rar - acestea trebuie s fie transmise funciei DefWindowProc. Funcia DefWindowProc apeleaz funciile BeginPaint i EndPaint, astfel nct zona client a ferestrei s fie din nou validat. Dup funcia BeginPaint, WndProc apeleaz funcia GetClientRect: GetClientRect (hwnd. &rect) ; Primul parametru al funciei GetClientRect este variabila handle a ferestrei programului. Al doilea parametru al funciei este un pointer la o variabil numit rect, de tipul RECT, definit n WndProc. RECT este o structur pentru un dreptunghi" definit n fiierele antet din Windows. Structura conine patru cmpuri de tip LONG, numite left, top, right i bottom. Funcia GetClientRect atribuie acestor cmpuri valorile corespunztoare dimensiunilor zonei client a ferestrei. Cmpurile left i top au ntotdeauna valoarea 0. Cmpurile right i bottom conin limea i nlimea zonei client, n pixeli. WndProc transmite funciei DrawText un pointer ctre structura RECT, prin cel de-al patrulea parametru al funciei: DrawText (hdc, "Hello, Windows 95!", -1, &rect, DT_SINGLELINE : DT_CENTER ! DT_VCENTER) ; Funcia DrawText, aa cum arat i numele, este folosit pentru desenarea" unui text. Deoarece funcia deseneaz ceva, primul parametru este o variabil handle a contextului de dispozitiv, returnat de funcia BeginPaint. Al doilea parametru este textul care urmeaz s fie afiat, iar al treilea parametru are valoarea -1, ceea ce indic faptul c textul de afiat se termin cu un octet 0. Ultimul parametru al funciei este o combinaie de indicatori flag pe bii, definii n fiierele antet din Windows. Indicatorii flag folosii determin afiarea textului pe o singur linie, centrat orizontal i vertical relativ la dreptunghiul specificat prin al patrulea parametru. Ca urmare, funcia DrawText afieaz textul Hello, Windows 95!" n centrul zonei client a ferestrei. De fiecare dat cnd zona client devine invalid (aa cum se ntmpl atunci cnd modificai dimensiunea ferestrei), funcia WndProc recepioneaz un mesaj WM_PAINT. WndProc obine noua dimensiune a ferestrei i afieaz din nou textul n centrul acesteia.

Mesajul WM_DESTROY
Un alt mesaj important este WM_DESTROY. Acest mesaj indic faptul c sistemul de operare desfoar un proces de distrugere a ferestrei pe baza unei comenzi de la utilizator. Mesajul este trimis atunci cnd utilizatorul execut clic pe butonul Close, selecteaz opiunea Close din meniul sistem sau apas fastele Alt+F4. Programul HELLOWIN rspunde la acest mesaj printr-o metod standard, apelnd funcia PostQuitMessage: PostQuitMessage (0) ; Aceast funcie insereaz n coada de ateptare a programului un mesaj WM_QUIT. Am spus anterior c funcia GetMessage returneaz o valoare diferit de zero n cazul prelurii oricrui mesaj n afar de WM_QUIT. Atunci cnd preia din coada de ateptare un mesaj WM_QUIT, funcia GetMessage returneaz valoarea 0, ceea ce determin ieirea din ciclul de tratare a mesajelor din funcia WinMain i nchiderea programului.

PROBLEME LEGATE DE PROGRAMAREA SUB WINDOWS


Chiar i dup explicaiile legate de programul HELLOWIN, structura i modul de funcionare ale programelor Windows s-ar putea s fi rmas ceva misterios. ntr-un scurt program C scris pentru un mediu convenional, ntregul program poate fi coninut de funcia main. n programul HELLOWIN, funcia WinMain conine numai codul de ntreinere (overhead) necesar pentru nregistrarea clasei, crearea ferestrei i obinerea i livrarea mesajelor din coada de mesaje. Toat aciunea" programului se desfoar n procedura de fereastr. n pro gramul HELLOWIN, aceasta nu face prea multe - red un fiier de sunet i afieaz un text n fereastr. n urmtoarele capitole vei vedea cam tot

www.cartiaz.ro Carti si articole online gratuite de la A la Z


ce poate face un program Windows ca rspuns la un mesaj. Prelucrarea mesajelor este unul dintre principalele obstacole pe care trebuie s le depii atunci cnd scriei un program pentru Windows.

Nu ne apela, te apelm noi!


Aa cum am menionat mai devreme, programatorii sunt obinuii cu ideea apelrii unor funcii ale sistemului de operare, pentru ndeplinirea unei anumite operaii. De exemplu, programatorii C folosesc funcia fopen ca s deschid un fiier. Funciile de bibliotec livrate odat cu compilatorul conin secvene de cod care apeleaz funcii ale sistemului de operare pentru deschiderea unui fiier. Nici o problem. n Windows, ns, lucrurile se petrec altfel. Dei sistemul de operare are mai mult de o mie de funcii pe care programele le pot apela, i Windows poate s apeleze funcii 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 funciei RegisterClassEx. O fereastr creat pe baza acestei clase folosete procedura de fereastr specificat de clas pentru prelucrarea tuturor mesajelor recepionate. Windows trimite un mesaj ctre o fereastr prin apelarea procedurii de fereastr a acesteia. Windows apeleaz funcia WndProc atunci cnd fereastra este creat. Windows apeleaz funcia WndProc atunci cnd fereastra este distrus. Windows apeleaz funcia WndProc atunci cnd fereastra este redimensionat, mutat sau redus la o pictogram. Windows apeleaz funcia WndProc atunci cnd utilizatorul selecteaz o opiune dintr-un meniu. Windows apeleaz funcia WndProc atunci cnd o bar de derulare este manipulat sau cnd utilizatorul execut clic pe aceasta. Windows apeleaz funcia WndProc pentru a-i comunica ferestrei c trebuie s redeseneze zona client. Toate aceste apeluri sunt fcute 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 fiierele antet din Windows. De fapt, ideea includerii ntr-un program a unei proceduri care s poat fi apelat din exterior exist i n programarea convenional. Funcia signal din limbajul C intercepteaz codul combinaiei de taste Ctrl+Break. Probabil ai folosit metode de interceptare a ntreruperilor hardware n limbaj de asamblare sau una dintre construciile ON din Microsoft BASIC. Driverul Microsoft Mouse conine 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 operaie. Toate evenimentele legate de o fereastr sunt transmise procedurii de fereastr sub forma unor mesaje. Procedura de fereastr rspunde la aceste mesaje sau le retransmite ctre procedura DefWindowProc, pentru operaii de prelucrare prestabilite. Parametrii wParam i lParam ai procedurii de fereastr nu sunt folosii n programul HELLOWIN dect pentru a fi retransmii funciei DefWindowProc. Aceti parametri furnizeaz procedurii de fereastr informaii suplimentare despre mesajul primit. Semnificaia acestor parametri este dependent de mesaj. Haidei s vedem un exemplu. Ori de cte 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 conine unul dintre identificatorii SIZENORMAL, SIZEICONIC, SIZEFULLSCREEN, SIZEZOOMSHOW i SIZEZOOMHIDE (definii n fiierele antet din Windows cu valori de la 0 la 4). Parametrul wParam arat dac fereastra a fost redus la o pictogram, a fost mrit la dimensiunea ecranului sau a fost mascat (n urma acoperirii de ctre o alt fereastr). Parametrul lParam conine noua dimensiune a ferestrei. Noua lime (o valoare pe 16 bii) i noua nlime (tot o valoare pe 16 bii) sunt mpachetate n parametrul lParam, care este o valoare pe 32 de bii. n fiierele antet din Windows este definit o macroinstruciune care v ajut s extragei cele dou valori din parametrul lParam. Vom face acest lucru n capitolul urmtor. Uneori mesajele genereaz alte mesaje ca rezultat al prelucrrii n funcia DefWindowProc. De exemplu, s presupunem c rulai programul HELLOWIN i selectai opiunea Close din meniul sistem, folosind tastatura sau mouse-ul. Funcia DefWindowProc prelucreaz intrrile de la tastatur sau de la mouse. Atunci cnd detecteaz selectarea opiunii Close, trimite un mesaj WM_SYSCOMMAND ctre procedura de fereastr. WndProc retransmite acest mesaj funciei DefWindowProc. Funcia DefWindowProc rspunde prin trimiterea unui mesaj WM_CLOSE ctre procedura de fereastr. WndProc retransmite i acest mesaj ctre funcia DefWindowProc. Funcia DefWindowProc rspunde la acest mesaj prin apelarea funciei Destroy Window. Funcia Destroy Window determin sistemul de operare s trimit un mesaj WM_DESTROY ctre procedura de fereastr. WndProc rspunde la acest mesaj apelnd funcia PostQuitMessage, care insereaz un mesaj WM_QUIT n coada de ateptare. Acest mesaj determin ieirea din ciclul de tratare a mesajelor i nchiderea programului.

Mesaje n coada de ateptare i n afara acesteia


Am vorbit despre faptul c Windows trimite mesaje ctre o fereastr, ceea ce nseamn c Windows apeleaz procedura de fereastr. Dar un program Windows are si un ciclu de tratare a mesajelor care preia mesajele din

www.cartiaz.ro Carti si articole online gratuite de la A la Z


coada de ateptare apelnd funcia GetMessage i le distribuie procedurii de fereastr apelnd funcia DispatchMessage. Aadar, un program Windows interogheaz coada de ateptare (aa cum un program convenional interogheaz bufferul de intrare de la tastatur) i apoi retransmite mesajele n anumite locuri? Sau primete mesajele direct din exterior? Ei bine, sunt folosite ambele metode. Mesajele pot fi trimise prin coada de ateptare sau direct. Mesajele trimise prin coada de ateptare sunt preluate de program i sunt distribuite n ciclul de tratare a mesajelor. Mesajele care nu trec prin coada de ateptare sunt trimise ferestrei direct, atunci cnd Windows apeleaz procedura de fereastr. Rezultatul este faptul c procedura de fereastr primete toate mesajele, indiferent dac sunt trimise prin coada de ateptare sau direct. Din punct de vedere structural, programele Windows sunt foarte clare, deoarece prelucrarea mesajelor se face centralizat, ntr-un singur punct. Aadar, mesajele preluate de funcia GetMessage sunt inserate n coada de ateptare, iar mesajele care nu trec prin coada de ateptare sunt trimise procedurii de fereastr. Mesajele din coada de ateptare sunt, n principal, rezultatul acionrii tastelor (aa sunt, de pild, WM_KEYDOWN i WM_KEYUP) sau caractere trimise de la tastatur (WM_CHAR), rezultatul deplasrii mouse-ului (WM_MOUSEMOVE) i al apsrii butoanelor mouse-ului (WM_LBUTTONDOWN). Tot n categoria mesajelor inserate n coada de ateptare 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 ateptare. Atunci cnd transmitei un mesaj din procedura de fereastr ctre funcia DefWindowProc, de multe ori Windows prelucreaz mesajul prin trimiterea altor mesaje ctre procedura de fereastr. Evident, acest proces este foarte complex dar, din fericire, de cea mai mare parte rspunde sistemul de operare, nu programul. Din perspectiva procedurii de fereastr, aceste mesaje sunt recepionate 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 ateptare rezult din apelarea unor funcii Windows sau din expedierea n mod explicit a unui mesaj, prin apelarea funciei SendMessage. (Mesajele din cealalt categorie sunt inserate n coada de ateptare prin apelarea funciei PostMessage.) De exemplu, atunci cnd WinMain apeleaz funcia CreateWindow, Windows creeaz fereastra i trimite procedurii de fereastr un mesaj WM_CREATE. Atunci cnd WinMain apeleaz funcia ShowWindow, Windows trimite procedurii de fereastr mesajele WM_SIZE i WM_SHOWWINDOW. Atunci cnd WinMain apeleaz funcia UpdateWindow, Windows trimite procedurii de fereastr un mesaj WM_PAINT. Mesajele nu seamn cu ntreruperile hardware. n timpul prelucrrii 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 funcie care genereaz un nou mesaj. Ciclul de mesaje i procedura de fereastr nu sunt executate simultan. Atunci cnd procedura de fereastr prelucreaz un mesaj extras din coada de ateptare, acest mesaj este rezultat n urma apelrii funciei DispatchMessage n WinMain. Funcia DispatchMessage nu returneaz controlul dect dup ce procedura de fereastr termin prelucrarea mesajului. Remarcai faptul c procedura de fereastr trebuie s fie reentrant. Windows trimite deseori funciei WndProc un nou mesaj atunci cnd aceasta apeleaz funcia DefWindowProc pentru mesajul anterior. n majoritatea cazurilor reentrana procedurii de fereastr nu este o problem, dar trebuie s fii contient de aceast posibilitate. De exemplu, s presupunem c n procedura de fereastr stabilii valoarea unei variabile n timpul prelucrrii unui mesaj, apoi apelai o funcie Windows. Dup returnarea din funcia apelat, putei fi sigur c valoarea variabilei a rmas aceeai? Nu este obligatoriu - de exemplu, este posibil ca funcia Windows apelat s genereze un alt mesaj, iar procedura de fereastr s modifice valoarea aceleiai variabile n timpul prelucrrii acestui mesaj. Acesta este unul dintre motivele pentru care unele forme de optimizare ale compilatorului trebuie s fie dezactivate atunci cnd compilai programe Windows. n multe situaii, procedura de fereastr poate s rein anumite informaii n timpul prelucrrii unor mesaje i s le foloseasc pentru prelucrarea altor mesaje. Aceste informaii trebuie s fie stocate fie n variabile statice definite n procedura de fereastr, fie n variabile globale. Desigur, vei nelege mai bine toate aceste lucruri n urmtoarele capitole, pe msur ce vom extinde procedurile de fereastr astfel nct s prelucreze i alte mesaje.

Nu fii egoist!
Windows 95 este un mediu care asigur multitaskingul controlat. Aceasta nseamn c atunci cnd un program execut o operaie de durat, sistemul de operare permite utilizatorului s treac n alt program. Acesta

www.cartiaz.ro Carti si articole online gratuite de la A la Z


este un lucru bun i este unul dintre avantajele oferite de Windows 95 fa de versiunile Windows bazate pe MSDOS. Totui, datorit structurii Windows, multitaskingul controlat nu funcioneaz ntotdeauna aa cum ar trebui. De exemplu, s presupunem c programul are nevoie de mai multe minute pentru prelucrarea unui mesaj. Este adevrat 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 operaii trebuie s fie executate de procedura de fereastr, iar procedura de fereastr este ocupat cu o operaie de durat, chiar dac nu ea pare a fi cea care execut operaiile de mutare i de redimensionare. Aceste operaii sunt executate de funcia DefWindowProc, care trebuie considerat o parte a procedurii de fereastr. Dac programul trebuie s execute o operaie de durat n timpul prelucrrii unui anumit mesaj, putei s facei acest lucru i ntr-un mod mai politicos, aa cum vom vedea n Capitolul 14. Chiar i ntr-un mediu cu multitasking controlat, nu este o idee prea bun s blocai o fereastr pe ecran. Acest lucru deranjeaz utilizatorii i i face s gndeasc lucruri urate despre programul dumneavoastr.

Ciclul de nvare
Aa cum v-ai dat seama din acest capitol, programarea sub Windows este foarte diferit de programarea pentru un mediu convenional, cum ar fi MS-DOS, i nu pretinde nimeni c programarea sub Windows este uoar. Atunci cnd am nceput s nv programarea sub Windows, am vrut s fac ceea ce fceam de obicei pentru a nva un nou sistem de operare sau un nou limbaj de programare - s scriu un program simplu, care s afieze coninutul hexazecimal al unui fiier. Sub MS-DOS, un astfel de program implic lucrul de la linia de comand, operaii rudimentare de intrare/ieire cu fiiere i formatarea rezultatelor pe ecran. Programul de afiare sub Windows s-a dovedit a fi ceva monstruos. A trebuit s nv o mulime de lucruri despre meniuri, casete de dialog, bare de derulare i altele. Desigur, acest prim efort de nvare care se impunea nu era un punct n favoarea programrii sub Windows. Cu toate acestea, odat terminat, programul de afiare hexazecimal nu semna cu nici unul dintre programele pe care le scrisesem pn atunci n acelai scop. n loc s obin numele fiierului din linia de comand, WINDUMP (acesta era numele pe care l-am dat programului) folosea o caset de dialog n care erau prezentate toate fiierele 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 fiierului. Ca un avantaj suplimentar, puteam s rulez simultan dou copii ale programului WINDUMP, ceea ce mi permitea s compar dou fiiere afiate n dou ferestre alturate. Pe scurt, WINDUMP a fost primul program de afiare hexazecimal de care am fost cu adevrat mndru. Trebuie s v punei urmtoarea ntrebare: doresc s folosesc o interfa modern i eficient, care s includ meniuri, casete de dialog, bare de derulare i elemente grafice? Dac rspunsul este afirmativ, trebuie s v punei 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 uor - s nvai s folosii 1000 de funcii, sau s le scriei chiar dumneavoastr? Este mai uor s v orientai gndirea ctre arhitectura bazat pe mesaje a sistemului de operare Windows, sau preferai s luptai cu aceasta folosind diverse surse de intrare, dup modelul tradiional? Dac dorii s scriei codul unei interfee proprii, mai bine nchidei aceast carte i apucai-v de treab. n acest timp, noi ceilali vom nva cum s afim i s derulm textul ntr-o fereastr.

Capitolul 3
Afiarea textului
n capitolul anterior ai vzut un program simplu pentru Windows 95, care afia o singur linie de text n centrul unei ferestre sau - ca s fiu mai precis - n centrul zonei client. Diferena dintre fereastra aplicaiei i zona client este destul de important: zona client este partea din fereastra aplicaiei care nu e ocupat de bara de titlu, chenarul pentru dimensionarea ferestrei, de bara de meniu (dac exist) i de barele de derulare (dac exist). Pe scurt, zona client este poriunea din fereastr n care programul poate s deseneze ceva, oferind astfel utilizatorilor diverse informaii vizuale. n zona client a programului putei s facei aproape orice, evitnd ns s plecai de la presupunerea c ea are o anumit dimensiune i c aceast dimensiune nu se va modifica n timpul rulrii programului. Dac suntei obinuit cu scrierea programelor sub MS-DOS, s-ar putea ca aceast situaie s vi se par puin surprinztoare. Nu v mai putei baza pe un ecran de 25 (sau 43 sau 50) de linii de text a cte 80 de caractere. Programul pe care l scriei trebuie s partajeze ecranul cu celelalte programe rulate sub Windows. Modul n care ferestrele programelor sunt aranjate pe ecran este controlat de utilizator. Dei este posibil crearea unei ferestre cu dimensiuni fixe (situaie potrivit pentru calculator sau alte utilitare), n majoritatea cazurilor ferestrele pot fi redimensionate. Programul pe care l scriei trebuie s accepte dimensiunile pe care le stabilii i s se descurce" n aceste condiii. Acest lucru este valabil n ambele sensuri. Un program poate avea o zon client n care abia ncape mesajul Hello!" sau poate fi rulat pe un calculator cu un monitor mare, de nalt rezoluie, n care s aib la dispoziie o zon client suficient de mare pentru dou pagini de text i destul spaiu suplimentar liber. Abordarea inteligent, eficient a ambelor situaii este o parte important a programrii sub Windows. Dei sistemul de operare Windows pune la dispoziie un numr de funcii GDI (Graphics Device Interface) pentru afiarea imaginilor, n acest capitol ne vom rezuma la afiarea liniilor de text. De asemenea, vom ignora numeroasele fonturi (corpuri de liter) pe care le avem Ia dispoziie n Windows i vom folosi numai fontul prestabilit - fontul sistem". Aceasta poate prea o limitare, dar n realitate nu este. Problemele pe care le vom ntlni n acest capitol - i pe care le vom rezolva - sunt comune pentru toate programele Windows. Atunci cnd afiai o combinaie de text i de imagini (aa cum face, de exemplu, programul Calculator) dimensiunea caracterelor din fontul sistem determin, de cele mai multe ori, dimensiunea imaginilor. Vom discuta mai puin despre posibilitile de desenare, dar, n schimb, vei ntlni o serie de informaii privind programarea independent de dispozitiv. Programele Windows pot face foarte puine presupuneri privind dimensiunea zonei client sau dimensiunea caracterelor; de asemenea, ele trebuie s foloseasc funciile Windows ca s obin informaii despre mediul n care ruleaz.

Desenarea i redesenarea
Sub MS-DOS, un program care folosete tot ecranul (full-screen mode) poate s scrie n orice parte a acestuia. Elementele afiate rmn pe ecran i nu dispar n mod misterios. Programul poate s renune apoi la informaiile necesare pentru redesenarea ecranului. Dac un alt program (cum ar fi programele rezidente n memorie) afieaz ceva peste o parte a ecranului, atunci programul respectiv trebuie s-i refac ecranul. n Windows nu putei s afiai dect n zona client a ferestrei i nu putei s presupunei c ceea ce afiai n zona client va rmne acolo pan cnd programul afieaz explicit altceva. De exemplu, o caset de dialog a unei alte aplicaii poate s se suprapun peste o parte a zonei client a programului dumneavoastr. Dei 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 poriunea afectat din zona client. Windows este un sistem de operare bazat pe mesaje. El informeaz aplicaiile privind apariia evenimentelor, prin trimiterea unor mesaje n coada de mesaje a aplicaiei, sau prin trimiterea unor mesaje ctre procedurile de fereastr corespunztoare. De pild, Windows informeaz o procedur de fereastr c o parte a zonei client trebuie s fie reactualizat prin trimiterea unui mesaj WM_PAINT.

Mesajul WM_PAINT
Majoritatea programelor Windows apeleaz funcia UpdateWindow n timpul procesului de iniializare din procedura WinMain, puin dup intrarea n ciclul de tratare a mesajelor. Windows profit de aceast ocazie ca s trimit ctre procedura ferestrei primul mesaj WM_PAINT. Acest mesaj informeaz procedura ferestrei c zona client este pregtit pentru desen. Din acest moment, procedura ferestrei ar trebui s fie pregtit s prelucreze orice mesaj WM_PAINT n urmtoarele situaii: O zon anterior acoperit a ferestrei este adus la suprafa atunci cnd utilizatorul mut o fereastr. Utilizatorul redimensioneaz fereastra (dac stilul clasei ferestrei include seturile de bii CS_HREDRAW si CS_VREDRAW). Programul folosete funciile ScrollWindow sau ScrollDC ca s deruleze o parte din zona client a ferestrei. Programul folosete funciile InvalidateRect sau InvalidateRgn pentru a genera n mod explicit un mesaj WM_PAINT.

n anumite cazuri, cnd zona client este acoperit parial cu text, Windows ncearc s salveze o zon a ecranului, pe care o va restaura mai trziu. Aceast metod, ns, nu d ntotdeauna rezultate bune. Windows poate trimite, de aceea, un mesaj WM_PAINT, n situaiile n care: Windows a ters o caset de dialog sau caset de mesaje care acoperea o parte a ferestrei. Un meniu este tras n jos i apoi eliberat. Windows salveaz zona de ecran pe care a scris i apoi o restaureaz, n cazurile n care: Indicatorul mouse-ului este micat n zona client. O pictogram este tras n zona client. Tratarea mesajelor WM_PAINT implic revizuirea modului de scriere pe ecran. Programul trebuie structurat astfel nct s acumuleze toate informaiile necesare pentru redesenarea zonei client, dar s fac aceast operaie numai la cerere" - atunci cnd Windows i trimite un mesaj WM_PAINT. Dac programul trebuie s actualizeze zona client, poate fora sistemul de operare s i trimit un mesaj WM_PAINT. Dei aceast metod de afiare pare ocolitoare, ea contribuie la structurarea programului. Dreptunghiuri valide i invalide Dei procedura unei ferestre trebuie s fie pregtit s actualizeze ntreaga zon client a ferestrei atunci cnd primete mesajul WM_PAINT, deseori este necesar numai reactualizarea unei poriuni mai mici (de cele mai multe ori o suprafa dreptunghiular din zona client). O astfel de situaie apare atunci cnd o parte a zonei client este acoperit de o caset de dialog. Redesenarea este necesar numai pentru zona dreptunghiular adus la suprafa dup nchiderea casetei de dialog. Aceast zon este numit regiune invalid" (invalid region") sau regiune de actualizare" (update region"). Prezena unei regiuni invalide n cadrul zonei client determin sistemul de operare s plaseze un mesaj WM_PAINT n coada de ateptare a aplicaiei. Procedura de fereastr a unui program recepioneaz un mesaj WM_PAINT numai dac o parte a zonei client a ferestrei este invalid. Windows pstreaz n interior o structur cu informaii pentru desenare" (paint information structure) pentru fiecare fereastr. Aceast structur conine (printre alte informaii) 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 informaiile actualizate n structura de informaii pentru desenare, fr s plaseze un nou mesaj WM_PAINT n coada de ateptare a aplicaiei. Procedura unei ferestre poate s invalideze un dreptunghi din zona client proprie prin apelarea funciei InvalidateRect. Dac n coada de ateptare exist deja un mesaj WM_PAINT, Windows calculeaz un nou dreptunghi invalid. n caz contrar, plaseaz n coada de ateptare un nou mesaj WM_PAINT. La recepionarea mesajului WM_PAINT, procedura ferestrei poate obine coordonatele dreptunghiului invalid (aa cum vom vedea ceva mai trziu n acest capitol). De asemenea, poate obine aceste coordonate n orice alt moment, apelnd funcia GetUpdateRect. Dup ce procedura de fereastr apeleaz funcia BeginPaint n timpul prelucrrii mesajului WM_PAINT, ntreaga zon client este validat. De asemenea, programul poate s valideze orice poriune dreptunghiular din zona client, apelnd funcia ValidateRect. Dac n urma acestui apel ntreaga zon invalid este validat, toate mesajele WM_PAINT aflate n coada de ateptare sunt terse.

Interfaa GDI
Pentru desenarea zonei client a ferestrei folosii funciile din interfaa Windows pentru dispozitivele grafice (GDI - Graphics Device Interface). (Vom face o prezentare general a interfeei GDI n capitolul urmtor.) Ai ntlnit deja funcia DrawText n Capitolul 2, dar cea mai cunoscut funcie este TextOut. Aceast funcie are urmtorul format:
TextOut (hdc, x, y, psString, iLength) ;

Funcia TextOut afieaz 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 poziia de nceput a irului de caractere. (Vom prezenta n curnd mai multe detalii.) Parametrul hdc este o variabil handle a contextului de dispozitiv i reprezint o parte important a interfeei GDI. De fapt, toate funciile GDI au nevoie de acest parametru la apelare. Contextul de dispozitiv O variabil handle, aa cum am mai spus, este pur i simplu un numr pe care Windows l folosete pentru indicarea unui obiect. Putei s obinei aceast variabil din Windows i apoi s o folosii n alte funcii. Variabila handle a contextului de dispozitiv este calea de acces a ferestrei dumneavoastr la funciile GDI. Folosind aceast variabil suntei liber s desenai zona client a ferestrei i s o facei aa cum dorii.

Contextul de dispozitiv (prescurtat DC - device context) este o structur de date ntreinut intern de interfaa GDI. Fiecare context de dispozitiv este asociat unui anumit dispozitiv de afiare, 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 particulariti privind modul de lucru al unor funcii de desenare din interfaa GDI. n cazul funciei 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 afiarea textului. Atunci cnd vrea s deseneze, programul trebuie s obin mai nti o variabil handle a unui context de dispozitiv. Dup terminarea operaiilor 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 obin i s elibereze variabila handle n timpul prelucrrii unui singur mesaj. Cu excepia contextelor de dispozitiv create cu funcia CreateDC (despre care nu vom discuta n acest capitol) este recomandat s nu pstrai variabilele handle ale contextelor de dispozitiv de la un mesaj la altul. n general, aplicaiile Windows folosesc dou metode pentru obinerea variabilelor handle ale contextelor de dispozitiv, atunci cnd se pregtesc pentru desenarea ecranului.

Obinerea unei variabile handle a contextului de dispozitiv: prima metod


Aceast metod este folosit n timpul prelucrrii mesajelor WM_PAINT. Sunt implicate dou funcii: BeginPaint i EndPaint. Aceste funcii au nevoie de variabila handle a ferestrei (transmis procedurii de fereastr ca parametru) i de adresa unei variabile de tipul PAINTSTRUCT. De obicei, programatorii Windows numesc aceast variabil ps i o definesc n procedura de fereastr astfel: PAINTSTRUCT ps; n timpul prelucrrii mesajului WM_PAINT, procedura de fereastr apeleaz mai nti funcia BeginPaint ca s completeze cmpurile structurii ps. Valoarea returnat de funcia BeginPaint este variabila handle a contextului de dispozitiv. n general, aceasta este salvat ntr-o variabil numit hdc. n prcedura de fereastr definii aceast variabil astfel: HDC hdc; Tipul de date HDC este definit ca un ntreg fr semn, pe 32 de bii. Programul poate apoi s foloseasc funcii GDI, cum ar fi TextOut. Apelul funciei EndPaint elibereaz variabila handle a contextului de dispozitiv. n general, prelucrarea mesajului WM_PAINT se face astfel: case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; [apelarea unor funcii GDI] EndPaint (hwnd, &ps) ; return 0 ; n timpul prelucrrii mesajului WM_PAINT, procedura de fereastr trebuie s apeleze funciile BeginPaint i EndPaint. Dac o procedur de fereastr nu prelucreaz mesajele WM_PAINT, trebuie s le retransmit procedurii DefWindowProc (procedura de fereastr prestabilit). DefWindowProc prelucreaz mesajele WM_PAINT n urmtoarea secven de cod: case WM_PAINT: BeginPaint (hwnd, &ps) ; EndPaint (hwnd, &ps) ; return 0 ; Apelarea n secven a funciilor BeginPaint i EndPaint fr nici o alt instruciune intermediar nu face dect s valideze regiunea invalidat anterior. Aadar, nu procedai astfel: case WM_PAINT: return 0 ; // GREIT !!! Windows plaseaz un mesaj WM_PAINT n coada de ateptare, deoarece o parte a zonei client este invalid. Dac nu apelai funciile BeginPaint i EndPaint (sau ValidateRect) Windows nu valideaz zona de fereastr respectiv, ci v trimite n continuare mesaje WM_PAINT.

STRUCTURA DE INFORMAII PENTRU DESENARE


Am vorbit anterior despre structura de informaii pentru desenare" (paint information structure") pstrat de Windows pentru fiecare fereastr. Aceast structur este definit astfel: typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase;

RECT BOOL BOOL BYTE

rcPaint; fRestore; fIncUpdate; rgbReserved[32];

} PAINTSTRCUT; Windows completeaz cmpurile acestei structuri atunci cnd programul dumneavoastr apeleaz funcia BeginPaint. Programul poate s foloseasc numai primele trei cmpuri, celelalte fiind folosite intern de sistemul de operare. Cmpul hdc reprezint variabila handle a contextului de dispozitiv. Deoarece redundana este tipic pentru sistemul de operare Windows, valoarea returnat de funcia BeginPaint este aceeai variabil handle. n majoritatea cazurilor, cmpul fErase va avea valoarea TRUE (diferit de zero), ceea ce nseamn c Windows a ters fondul dreptunghiului invalid. Pentru tergerea fondului, Windows folosete pensula specificat n cmpul hbrBackground al structurii WNDCLASSEX, pe care ai folosit-o la nregistrarea clasei n timpul iniializrilor, din funcia WinMain. Multe programe Windows folosesc o pensul de culoare alb: wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); Totui, dac programul invalideaz un dreptunghi din zona client apelnd funcia InvalidateRect, ultimul parametru al funciei specific dac vrei ca fondul s fie ters. Dac acest parametru este FALSE (0), Windows nu va terge fondul i cmpul fErase va avea valoarea FALSE. Cmpul rcPaint al structurii PAINTSTRUCT este o structur de tip RECT. Aa cum ai aflat din Capitolul 2, structura RECT definete un dreptunghi. Cele patru cmpuri ale structurii sunt left, top, right i bottom. Cmpul rcPaint al structurii PAINTSTRUCT definete limitele unui dreptunghi invalid, aa cum se poate vedea n figura 3.1. Valorile sunt date n pixeli, i se raporteaz la colul din stnga-sus al zonei client a ferestrei. Dreptunghiul invalid este suprafaa pe care ar trebui s o redesenai. Dei un program Windows ar putea s redeseneze ntreaga zon client a ferestrei de fiecare dat cnd primete un mesaj WM_PAINT, redesennd numai poriunea ferestrei definit de dreptunghi programul economisete timp.

Figura 3-1. Limitele unui dreptunghi invalid. Dreptunghiul rcPaint din structura PAINTSTRUCT nu este un simplu dreptunghi invalid, ci un dreptunghi de decupare" (clipping rectangle). Aceasta nseamn c Windows restricioneaz desenarea n interiorul dreptunghiului. (Mai precis, dac regiunea invalid nu este dreptunghiular, Windows restricioneaz desenarea numai la regiunea respectiv.) Atunci cnd folosii variabila handle a contextului de dispozitiv din structura PAINTSTRUCT, Windows nu deseneaz n afara dreptunghiului rcPaint. Pentru desenarea n afara dreptunghiului rcPaint n timpul prelucrrii mesajului WM_PAINT, facei urmtorul apel: InvalidateRect (hWnd, NULL, TRUE); naintea apelrii funciei 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 fcut peste ceea ce exist deja. n programul HELLOWIN prezentat n Capitolul 2 nu am inut seama de dreptunghiurile invalide sau de dreptunghiurile de decupare n timpul prelucrrii mesajului WM_PAINT. Dac zona n care era afiat textul se afla n dreptunghiul invalid, funcia DrawText o refcea. Dac nu, n timpul prelucrrii apelului DrawText,

Windows determina faptul c pe ecran nu trebuie s afieze nimic. Dar aceste operaii de determinare dureaz. Un programator interesat de performan i vitez va dori s foloseasc dimensiunile dreptunghiului invalid n timpul prelucrrii mesajului WM_PAINT, astfel nct s evite apelarea inutil a unor funcii GDI.

Obinerea unei variabile handle a contextului de dispozitiv: a doua metod


Putei s obinei o variabil handle a unui context de dispozitiv i n timpul prelucrrii altor mesaje dect WM_PAINT, sau atunci cnd avei nevoie de variabila handle a contextului n alte scopuri, cum ar fi obinerea unor informaii despre contextul de dispozitiv. Apelai funcia GetDC pentru a obine variabila handle i apoi apelai funcia ReleaseDC atunci cnd nu mai avei nevoie de aceasta: hdc = GetDC(hwnd) ; [apelarea unor funcii GDI] ReleaseDC(hwnd, hdc) ; La fel ca funciile BeginPaint i EndPaint, i funciile GetDC i ReleaseDC ar trebui apelate n pereche. Atunci cnd apelai funcia GetDC n timpul prelucrrii unui mesaj, este recomandat s apelai funcia ReleaseDC nainte de a iei din procedura de fereastr. Nu apelai funcia GetDC ca rspuns la un mesaj i funcia ReleaseDC ca rspuns la un alt mesaj. Spre deosebire de variabila handle a contextului de dispozitiv obinut din structura PAINTSTRUCT, variabila handle returnat de funcia GetDC se refer la un dreptunghi cu ntreaga zon client a ferestrei. Putei s desenai n orice parte a zonei client, nu doar n dreptunghiul invalid (dac exist un dreptunghi invalid). Spre deosebire de funcia BeginPaint, GetDC nu valideaz nici o regiune invalid. n general, folosii funciile GetDC i ReleaseDC ca rspuns 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 reacie la informaiile introduse de utilizator de la tastatur sau cu ajutorul mouse-ului, fr s invalideze n mod deliberat o zon a ferestrei pentru generarea unui mesaj WM_PAINT. Totui, programul trebuie s acumuleze suficiente informaii pentru a putea s reactualizeze ecranul ori de cte ori primete un mesaj WM_PAINT.

Funcia TextOut: detalii


Atunci cnd obinei o variabil handle a contextului de dispozitiv, Windows completeaz structura contextului de dispozitiv, pstrat intern, cu valorile prestabilite. Aa cum vei vedea n capitolele urmtoare, putei s modificai valorile prestabilite cu ajutorul funciilor GDI. Funcia GDI care ne intereseaz n acest moment este TextOut: TextOut (hdc, x, y, psString, iLength) ; Haidei s examinm n detaliu aceast funcie. Primul parametru este o variabil handle a contextului de dispozitiv - valoarea hdc returnat de funcia GetDC sau valoarea hdc returnat de funcia BeginPaint n timpul prelucrrii mesajului WM_PAINT. Atributele contextului de dispozitiv controleaz caracteristicile textului afiat. De exemplu, unul dintre atributele contextului de dispozitiv stabilete culoarea textului. Culoarea prestabilit este negru. De asemenea, contextul prestabilit de dispozitiv stabilete ca fondul s fie alb. Atunci cnd un program afieaz un text pe ecran, Windows folosete aceast culoare de fond ca s umple spaiul dreptunghiular care nconjoar fiecare caracter, spaiu numit caset caracter" (character box"). Fondul textului nu este acelai lucru cu fondul pe care l stabilii atunci cnd definii 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 folosete ca s tearg zona client. La definirea structurii de clas a ferestrei, multe aplicaii Windows folosesc identificatorul WHITE_BRUSH, astfel nct culoarea de fond din contextul de dispozitiv prestabilit s fie aceeai cu culoarea pensulei pe care Windows o folosete pentru tergerea zonei client. Parametrul psString este un pointer la un ir de caractere, iar iLength este lungimea acestui ir de caractere, adic numrul de caractere coninut de ir. irul de caractere nu poate conine caractere ASCII de control, cum ar fi CR (carriage return - retur de car), LF (line feed - salt la linie nou), tab sau backspace. Windows afieaz aceste caractere ca blocuri negre sau mici casete. Funcia TextOut nu recunoate caracterul 0 ca terminator de ir si are nevoie de parametrul iLength pentru precizarea lungimii irului de caractere. Valorile x i y din apelul funciei TextOut definesc nceputul irului de caractere n zona client a ferestrei. Valoarea x indic poziia pe orizontal, iar valoarea y indic poziia pe vertical. Colul din stnga-sus al primului caracter se afl n poziia 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 colul din stnga-sus al zonei client. Dac n funcia TextOut folosii valoarea 0 pentru parametrii x i y, irul de caractere este afiat ncepnd din colul stngasus al zonei client. n documentaie, coordonatele GDI sunt numite coordonate logice". Vom vorbi mai mult despre semnificaia acestui termen n capitolul urmtor. Pentru moment, reinei c n Windows exist diferite moduri de mapare care

controleaz transformarea coordonatelor logice transmise funciilor GDI n coordonate fizice ale pixelilor afiai pe ecran. Modul de mapare este definit n contextul de dispozitiv. Modul de mapare prestabilit este MM_TEXT (folosind identificatorul definit n fiierele antet Windows). n modul de mapare MM_TEXT, unitile logice sunt aceleai cu unitile fizice, adic pixelii; ele se raporteaz la colul din stnga-sus al zonei client, iar valorile coordonatei y cresc pe msur ce cobori n zona client a ferestrei (vezi Figura 3.2). Sistemul de coordonate MM_TEXT este acelai cu sistemul de coordonate folosit de Windows pentru definirea dreptunghiului invalid din structura PAINTSTRUCT. Foarte convenabil. (Nu la fel se ntmpl ns dac folosii alte moduri de mapare.)

Figura 3.2. Coordonatele x i y n modul de mapare MM_TEXT. Contextul de dispozitiv definete i o regiune de decupare (clipping region). Aa cum ai vzut, regiunea prestabilit de decupare este ntreaga zon client, pentru o variabil handle a contextului de dispozitiv obinut prin apelarea funciei GetDC, sau numai regiunea invalid, pentru o variabil handle a contextului de dispozitiv obinut prin apelarea funciei BeginPaint. Windows nu afieaz partea care se afl n afara regiunii de decupare i care aparine irului de caractere, ci numai poriunile care sunt cuprinse n regiunea de decupare. Scrierea n afara zonei client a unei ferestre este o operaiune dificil, aa c nu v speriai - nu este posibil s facei acest lucru din greeal.

Fontul sistem
Tot n contextul de dispozitiv este definit i fontul pe care sistemul de operare Windows l folosete pentru scrierea textului n zona client. Fontul prestabilit este numit font sistem" sau (folosind identificatorul definit n fiierele antet Windows) SYSTEM_FONT. Fontul sistem este fontul pe care Windows l folosete 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 aceeai lime, ca la mainile de scris. ncepnd cu versiunea Windows 3.0 i continund 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 dect i". Este foarte clar c un text scris cu un font avnd dimensiune variabil este mai uor de citit dect un font cu dimensiune fix. Aa cum v putei imagina, ns, aceast schimbare a dat peste cap multe programe Windows scrise pentru primele versiuni i programatorii au fost nevoii s nvee 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 plci video. O firm care produce un nou driver de afiare trebuie s creeze i fontul sistem potrivit cu rezoluia de afiare respectiv. O alt soluie este ca productorul s specifice unul dintre fonturile sistem furnizate mpreun cu sistemul de operare Windows. Fontul sistem trebuie proiectat astfel nct pe ecran s ncap cel puin 25 de linii cu cte 80 de caractere. Aceasta este singura garanie privind compatibilitatea ntre dimensiunea ecranului i dimensiunea fontului. Dimensiunea unui caracter Pentru afiarea mai multor linii de text cu ajutorul funciei TextOut trebuie s determinai dimensiunile caracterelor din fontul folosit. Putei s stabilii spaiul dintre liniile succesive de text pe baza nlimii unui caracter i spaiul dintre coloane pe baza limii medii a caracterelor din font. Dimensiunile caracterelor sunt obinute prin apelarea funciei GetTextMetrics. Funcia GetTextMetrics are nevoie de o variabil handle a contextului de dispozitiv, deoarece returneaz informaii despre fontul selectat n contextul de dispozitiv. Windows copiaz valorile referitoare la dimensiunile caracterelor ntr-o structur de tip TEXTMETRIC. Valorile sunt exprimate n uniti de msur care depind de modul de mapare selectat n contextul de dispozitiv. n contextul prestabilit de dispozitiv, modul de mapare este MM_TEXT, aa c dimensiunile sunt date n pixeli.

Pentru folosirea funciei GetTextMetrics trebuie s definii mai nti o variabil de tipul TEXTMETRIC (numit, de obicei, tm): TEXTMETRIC tm; n continuare obinei o variabil handle a contextului de dispozitiv i apelai funcia GetTextMetrics: hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm) ; ReleaseDC(hwnd, hdc); Apoi putei s examinai valorile din structura de dimensiuni a textului i, dac dorii, s salvai unele dintre aceste dimensiuni pentru utilizarea n viitor. Dimensiunile textului: detalii Structura TEXTMETRIC furnizeaz o mulime de informaii despre fontul curent selectat n contextul de dispozitiv. Totui, aa cum putei vedea n Figura 3-3, dimensiunea vertical a unui font este definit de numai cinci valori.

Figura 3-3. Cele cinci valori care definesc dimensiunea verticala a unui caracter.

Acestea sunt uor de neles. Valoarea tmInternalLeading se refer la spaiul pstrat deasupra unui caracter pentru semnele de accentuare. Dac aceast valoare este zero, literele mari cu accent sunt afiate ceva mai mici, astfel nct accentul s ncap n partea de sus a caracterului. Valoarea tmExternalLeading se refer la spaiul recomandat de ctre proiectantul fontului a fi lsat ntre rndurile de text. Putei s acceptai sau nu aceast recomandare atunci cnd stabilii distana dintre rndurile de text. Structura TEXTMETRIC conine dou cmpuri care descriu limea unui caracter: tmAveCharWidth (limea medie a literelor mici) i tmMaxCharWidth (limea 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 - limea medie a majusculelor. O valoare destul de precis poate fi obinut calculnd 150% din valoarea tmAveCharWidth. Este important s reinei faptul c dimensiunile fontului sistem depind de rezoluia 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 scriei programe Windows care se bazeaz pe ghicirea dimensiunilor unui caracter. Nu introducei n cod valori fixe. Folosii funcia GetTextMetrics ca s obinei valorile de care avei nevoie. Fomatarea textului Deoarece dimensiunile fontului sistem nu se modific n timpul unei sesiuni Windows, trebuie s apelai funcia GetTextMetrics o singur dat dup lansarea n execuie 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 primete procedura de fereastr. Windows trimite un mesaj WM_CREATE procedurii de fereastr atunci cnd apelai funcia CreateWindow din funcia WinMain. S presupunem c scriei un program Windows care afieaz mai multe linii de text una sub alta, n zona client a ferestrei. Trebuie s obinei valorile pentru nlimea i limea caracterelor. n procedura de fereastr putei s definii dou variabile n care s salvai limea medie (cxChar) i nlimea total a caracterelor (cyChar): static int cxChar, cyChar ; Prefixul c adugat la numele variabilelor provine de la contor" i n combinaie cu x sau y se refer la dimensiunea pe orizontal sau pe vertical a caracterelor. Aceste variabile sunt declarate ca statice deoarece trebuie s fie valide atunci cnd procedura de fereastr prelucreaz alte mesaje (cum ar fi WM_PAINT). Dac variabilele sunt declarate ca globale n alte funcii nu mai este necesar s fie declarate statice. Iat codul de prelucrare a mesajului WM_CREATE: case WH_CREATE:

hdc = GetDC (hwnd); GetTextMetrics (hdc, &tm); cxChar = tm.tmAveCharWidth; cyChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC (hwnd, hdc); return 0; Dac nu vrei s folosii spaiul suplimentar extern pentru spaierea liniilor de text, putei s utilizai instruciunea: cyChar = tm.tmHeight; Dumneavoastr stabilii modul n care folosii dimensiunile caracterelor pentru calcularea coordonatelor de afiare. O metod simpl este s lsai o margine de dimensiunea cyChar n partea de sus a zonei client i una de mrimea cxChar n partea stng. Pentru afiarea mai multor linii de text aliniate la stnga, folosii valoarea cxChar pentru parametrul care reprezint coordonata pe axa x n apelul funciei TextOut. Valoarea coordonatei pe axa y la apelarea funciei TextOut este: cyChar * (1 + i); unde i reprezint numrul liniei, ncepnd de la 0. Deseori este necesar s afiai att numere formatate, cat i iruri simple de caractere. Dac ai scris programe DOS folosind funciile standard C de bibliotec, probabil ai folosit pentru formatarea numerelor funcia printf. n Windows nu putei s folosii funcia printf, deoarece ea determin afiarea la dispozitivul standard de ieire, acesta fiind un concept care n Windows nu are nici un sens. n schimb, putei s folosii funcia sprintf. Funcia sprintf lucreaz la fel ca i funcia printf, cu excepia faptului c irul de caractere formatat este stocat ntr-o matrice de caractere. Apoi putei utiliza funcia TextOut pentru a scrie irul ce trebuie afiat. Funcia sprintf returneaz lungimea irului de caractere, ceea ce este foarte convenabil - putei s transmitei valoarea returnat n locul parametrului iLength din apelul funciei TextOut. Secvena de cod de mai jos prezint o combinaie tipic a funciilor TextOut i sprintf: int iLenght; char szBuffer [40]; [alte Iinii de program] iLenght = sprintf (szBuffer, "The sum of %d and %d is %d"), nA, nB, nA + nB); TextOut (hdc, x, y, szBuffer, iLength); Pentru operaiile simple de afiare precum cea de mai sus putei s renunai la definirea variabilei iLength i s combinai cele dou instruciuni rmase ntr-una singur: TextOut (hdc, x, y, szBuffer, sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB)); Nu arat prea frumos, dar funcioneaz. Dac nu trebuie s afiai numere n virgul mobil, putei folosi funcia wsprintf n locul funciei sprintf. Funcia wsprintf are aceeai sintax ca i funcia sprintf, dar este inclus n Windows, aa c nu va mri dimensiunea fiierului executabil. S punem totul la un loc Acum se pare c avem toate elementele necesare pentru scrierea unui program simplu, care afieaz mai multe linii de text. tim cum s obinem o variabil handle a contextului de dispozitiv, cum s folosim funcia TextOut i cum s spaiem textul n funcie de dimensiunea caracterelor. Singurul lucru care a mai rmas este s gsim ceva interesant de afiat. Informaiile returnate de funcia GetSystemMetrics par destul de interesante. Funcia GetSystemMetrics returneaz informaii despre dimensiunea unor elemente grafice din Windows, cum ar fi pictograme, cursoare, bare de titlu i bare de derulare. Aceste dimensiuni depind de placa video i de driverul de afiare. Funcia GetSystemMetrics accept un singur parametru, numit index". Indexul este unul dintre cei 73 de identificatori de tip ntreg definii n fiierele antet din Windows. GetSystemMetrics returneaz o valoare ntreag care reprezint, de obicei, dimensiunea elementului transmis ca parametru. Haidei s scriem un program care afieaz o parte dintre informaiile returnate de funcia GetSystemMetrics ntr-un format simplu, cu o linie de text pentru fiecare element. Folosirea acestor informaii este simplificat prin crearea unui fiier antet care definete o structur coninnd att identificatorii din fiierele antet Windows, folosii pentru indexul transmis funciei GetSystemMetrics, ct i textul pe care vrem s l afieze programul pentru fiecare valoare returnat de funcie. Acest fiier antet este numit SYSMETS.H i este prezentat n Figura 3-4.
/*----------------------------------------------SYSMETS.H -- System metrics display structure -----------------------------------------------*/ #define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0])) struct {

int iIndex ; char *szLabel ; char *szDesc ; } sysmetrics [ ] = { SM_CXSCREEN, SM_CYSCREEN, SM_CXVSCROLL, SM_CYHSCROLL, SM_CYCAPTION, SM_CXBORDER, SM_CYBORDER, SM_CXDLGFRAME, SM_CYDLGFRAME, SM_CYVTHUMB, SM_CXHTHUMB, SM_CXICON, SM_CYICON, SM_CXCURSOR, SM_CYCURSOR, SM_CYMENU, SM_CXFULLSCREEN, SM_CYFULLSCREEN, SM_CYKANJIWINDOW, SM_MOUSEPRESENT, SM_CYVSCROLL, SM_CXHSCROLL, SM_DEBUG, SM_SWAPBUTTON, SM_RESERVED1, SM_RESERVED2, SM_RESERVED3, SM_RESERVED4, SM_CXMIN, SM_CYMIN, SM_CXSIZE, SM_CYSIZE, SM_CXFRAME, SM_CYFRAME, SM_CXMINTRACK, SM_CYMINTRACK, SM_CXDOUBLECLK, SM_CYDOUBLECLK, SM_CXICONSPACING, SM_CYICONSPACING, SM_MENUDROPALIGNMENT, SM_PENWINDOWS, SM_DBCSENABLED, SM_CMOUSEBUTTONS, SM_SHOWSOUNDS, };

"SM_CXSCREEN", "SM_CYSCREEN", "SM_CXVSCROLL", "SM_CYHSCROLL", "SM_CYCAPTION", "SM_CXBORDER", "SM_CYBORDER", "SM_CXDLGFRAME", "SM_CYDLGFRAME", "SM_CYVTHUMB", "SM_CXHTHUMB", "SM_CXICON", "SM_CYICON", "SM_CXCURSOR", "SM_CYCURSOR", "SM_CYMENU", "SM_CXFULLSCREEN", "SM_CYFULLSCREEN", "SM_CYKANJIWINDOW", "SM_MOUSEPRESENT", "SM_CYVSCROLL", "SM_CXHSCROLL", "SM_DEBUG", "SM_SWAPBUTTON", "SM_RESERVED1", "SM_RESERVED2", "SM_RESERVED3", "SM_RESERVED4", "SM_CXMIN", "SM_CYMIN", "SM_CXSIZE", "SM_CYSIZE", "SM_CXFRAME", "SM_CYFRAME", "SM_CXMINTRACK", "SM_CYMINTRACK", "SM_CXDOUBLECLK", "SM_CYDOUBLECLK", "SM_CXICONSPACING", "SM_CYICONSPACING", "SM_MENUDROPALIGNMENT", "SM_PENWINDOWS", "SM_DBCSENABLED", "SM_CMOUSEBUTTONS", "SM_SHOWSOUNDS",

"Screen width in pixels", "Screen height in pixels", "Vertical scroll arrow width", "Horizontal scroll arrow height", "Caption bar height", "Window border width", "Window border height", "Dialog window frame width", "Dialog window frame height", "Vertical scroll thumb height", "Horizontal scroll thumb width", "Icon width", "Icon height", "Cursor width", "Cursor height", "Menu bar height", "Full screen client area width", "Full screen client area height", "Kanji window height", "Mouse present flag", "Vertical scroll arrow height", "Horizontal scroll arrow width", "Debug version flag", "Mouse buttons swapped flag", "Reserved", "Reserved", "Reserved", "Reserved", "Minimum window width", "Minimum window height", "Minimize/Maximize icon width", "Minimize/Maximize icon height", "Window frame width", "Window frame height", "Minimum window tracking width", "Minimum window tracking height", "Double click x tolerance", "Double click y tolerance", "Horizontal icon spacing", "Vertical icon spacing", "Left or right menu drop", "Pen extensions installed", "Double-Byte Char Set enabled", "Number of mouse buttons", "Present sounds visually"

Figura 3.4. Fiierul SYSMETS.H Programul care afieaz aceste informaii se numete SYSMETS1. Fiierele necesare pentru crearea fiierului executabil SYSMETS1.EXE (fiierul de construcie 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 excepia numelui programului, fiierul de construcie este identic cu cel al programului HELLOWIN. Funcia WinMain din fiierul SYSMETS1.C este foarte asemntoare cu cea din programul HELLOWIN.
SYSMTES1.MAK
#-----------------------# Fiierul de construcie SYSMTES1.C #-----------------------sysmets1.exe : sysmets1.obj $(LINKER) $(GUIFLAGS) -OUT: sysmets1.exe sysmets1.obj $(GUILIBS) sysmets1.obj : sysmets1.c sysmets1.h $(CC) $(CFLAGS) sysmets1.c /*---------------------------------------------------SYSMETS1.C -- System Metrics Display Program No. 1 (c) Charles Petzold, 1996 ----------------------------------------------------*/ #include <windows.h> #include <string.h> #include "sysmets.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "SysMets1" ;

SYSMTES1.C

HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Get System Metrics No. 1", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar ; char szBuffer[10] ; HDC hdc ; int i; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (i = 0 ; i < NUMLINES ; i++) { TextOut (hdc, cxChar, cyChar * (1 + i), sysmetrics[i].szLabel, strlen (sysmetrics[i].szLabel)) ; TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i), sysmetrics[i].szDesc, strlen (sysmetrics[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar * (1 + i), szBuffer, wsprintf (szBuffer, "%5d", GetSystemMetrics (sysmetrics[i].iIndex))) ; SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 3.5. Programul SYSMTES1 Figura 3-6 prezint fereastra afiat de programul SYSMETSl pe un monitor VGA. Aa cum se poate vedea din textul afiat n fereastra programului, ecranul are limea de 640 de pixeli i nlimea de 480 de pixeli. Cele dou valori, ca i multe altele afiate de program, pot fi diferite pentru alte tipuri de monitoare video.

Procedura de fereastr a programului SYSMETS1.C

Procedura de fereastr WndProc din fiierul 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. WM_CREATE este primul mesaj pe care l primete procedura de fereastr. Acest mesaj este generat de Windows atunci cnd funcia Create Window creeaz fereastra. n timpul prelucrrii mesajului WM_CREATE, SYSMETS1 obine un context de dispozitiv pentru fereastr, apelnd funcia GetDC, i dimensiunile fontului sistem prestabilit, apelnd funcia GetTextMetrics. SYSMETS1 salveaz limea medie a caracterelor n variabila cxChar i nlimea total (inclusiv spaiul suplimentar extern) a caracterelor n variabila cyChar.

Figura 3-6. Fereastra afiat de programul SYSMETS1.

De asemenea, SYSMETS1 salveaz limea 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 puin semnificativ al cmpului tmPitchAndFamily din structura TEXTMETRIC are valoarea 1 pentru fonturile cu dimensiune variabil i valoarea 0 pentru fonturile cu dimensiune fix. Programul SYSMETS1 folosete acest bit ca s calculeze valoarea cxCaps din cxChar: cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; Toate operaiile de desenare n fereastr sunt fcute n timpul prelucrrii mesajului WM_PAINT. Aa cum este normal, procedura de fereastr obine mai nti o variabil handle a contextului de dispozitiv prin apelarea funciei BeginPaint. Un ciclu for parcurge toate liniile structurii sysmetrics definit n fiierul antet SYSMETS.H. Cele trei coloane de text sunt afiate prin apelarea de trei ori a funciei TextOut. La fiecare apel, cel de-al treilea parametru al funciei TextOut are valoarea: cyChar * (1 + i) Acest parametru indic n pixeli poziia prii 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 mrimea cyChar. Prima linie (pentru care i are valoarea 0) ncepe cu cyChar pixeli mai jos de marginea de sus a zonei client. Prima instruciune TextOut afieaz cu litere mari un identificator n prima dintre cele trei coloane. Al doilea parametru al funciei are o valoare egal cu cxChar. n acest fel, n partea stng a zonei client este lsat liber un spaiu egal cu cxChar, deci de mrimea unui caracter. Textul este obinut din cmpul szLabel al structurii sysmetrics. Lungimea irului de caractere, necesar pentru ultimul parametru al funciei TextOut, este obinut prin apelarea funciei strlen. A doua instruciune TextOut afieaz descrierea sistemului de valori metrice. Aceste descrieri sunt stocate n cmpul szDesc al structurii sysmetrics. n acest caz, al doilea parametru al funciei TextOut are valoarea: cxChar + 22 * cxCaps Cel mai lung identificator afiat n prima coloan are 20 de caractere, aa c a doua coloan trebuie s nceap la o distan cel puin egal cu valoarea de 20*cxCaps fa de nceputul primei coloane. A treia instruciune TextOut afieaz valoarea numeric obinut prin apelarea funciei 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 aceeai lime, dar mai mare dect cea a unui spaiu. Numerele pot fi formate din una sau mai multe cifre, aa c dou numere diferite pot ncepe din poziii diferite pe orizontal.

Nu ar fi mai simplu dac am putea s afim o coloan de numere aliniate la dreapta prin specificarea poziiei n care se termin numerele, n locul poziiei la care ncep acestea? Dup ce programul SYSMETS apeleaz funcia: SetTextAlign (hdc. TA_RIGHT | TA_TOP) ; coordonatele transmise funciilor TextOut care urmeaz specific localizarea colului din dreapta-sus al irului de caractere, n locul colului din stnga-sus. Funcia TextOut care afieaz coloana de numere are ca al doilea parametru urmtoarea expresie: cxChar + 22 * cxCaps + 40 * cxChar Valoarea 40 x cxChar reprezint limea nsumat a coloanelor doi i trei. Dup apelarea funciei TextOut este apelat din nou funcia SetTextAlign, pentru a readuce la normal modul de aliniere a textului. Nu exist destul spaiu! Programul SYSMETS are un mic neajuns: dac nu avei un ecran gigantic i o plac video de nalt rezoluie, nu putei vedea ultima parte a listei de valori metrice. Dac ngustai fereastra, nu putei vedea nici valorile din partea dreapt. Programul SYSMETS1 nu tie ct de mare este zona client a ferestrei. De aceea, el ncepe afiarea valorilor din partea de sus a ferestrei i las n seama sistemului de operare decuparea textului care depete marginile zonei client. Pentru c nu este cel mai convenabil mod de afiare, prima recomandare este aceea de a determina ct de mult din ceea ce afieaz programul poate ncpea n zona client.

Dimensiunea zonei client


Dac facei cteva experimente cu aplicaiile Windows existente, vei vedea c dimensiunea ferestrelor poate s varieze foarte mult. Dac nu are meniu i bare de derulare, fereastra poate fi mrit la maximum i zona client ocup ntregul ecran, cu excepia barei de titlu a programului. Dimensiunile totale ale ferestrei mrite pot fi obinute prin apelarea funciei GetSystemMetrics cu parametrii SM_CXFULLSCREEN i SM_CYFULLSCREEN. Pentru un monitor VGA valorile returnate sunt 640 i 461 de pixeli. Dimensiunile minime ale ferestrei pot fi destul de mici, uneori apropiate de zero, eliminnd de fapt zona client. O metod obinuit de determinare a dimensiunilor zonei client a unei ferestre este prelucrarea mesajului WM_SIZE n procedura de fereastr. Windows trimite un mesaj WM_SIZE ctre procedura de fereastr, de fiecare dat cnd se modific dimensiunile ferestrei. Parametrul lParam transmis procedurii de fereastr conine limea zonei client n cuvntul mai puin semnificativ (LOWORD) i nlimea zonei client n cuvntul mai semnificativ (HIWORD). Codul pentru prelucrarea acestui mesaj arat astfel: static int cxClient, cyClient ; [alte linii de program] case WM_SIZE : cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; Macroinstruciunile LOWORD i HIWORD sunt definite n fiierele antet din Windows. Ca i variabilele cxChar i cyChar, variabilele cxClient i cyClient sunt definite ca statice n cadrul procedurii de fereastr, deoarece vor fi folosite ulterior pentru prelucrarea altor mesaje. Mesajul WM_SIZE este urmat de un mesaj WM_PAINT, deoarece la definirea clasei am specificat urmtorul stil de fereastr: CS_HREDRAW | CS_VREDRAW Acest stil cere sistemului de operare s foreze redesenarea ferestrei de fiecare dat cnd se modific dimensiunea vertical sau orizontal a acesteia. Putei s calculai numrul de linii de text care pot fi afiate n zona client folosind formula: cyClient / cyChar Aceast valoare poate fi zero dac zona client este prea mic pentru afiarea unui caracter. La fel, putei s calculai numrul aproximativ de caractere care pot fi afiate pe orizontal n zona client, folosind formula: cxClient / cxChar Dac determinai valorile cxChar i cyChar n timpul prelucrrii mesajului WM_CREATE, nu v facei griji privind eventualitatea unei mpriri la zero n formulele de mai sus. Procedura de fereastr primete mesajul WM_CREATE atunci cnd funcia WinMain apeleaz funcia CreateWindow. Primul mesaj WM_SIZE este primit puin mai trziu, atunci cnd funcia WinMain apeleaz funcia ShowWindow, moment n care variabilelor cxChar i cyChar le-au fost deja atribuite valori pozitive diferite de zero. Aflarea dimensiunilor zonei client a ferestrei este primul pas pentru furnizarea unei metode de deplasare a textului n zona client atunci cnd aceasta nu este suficient de mare pentru a cuprinde tot textul afiat. Dac ai mai lucrat cu alte aplicaii Windows i ai ntmpinat aceleai probleme, probabil tii deja de ce avei nevoie -minunat invenie cunoscut sub numele de bar de derulare.

Barele de derulare Barele de derulare se numr printre cele mai reuite componente ale unei interfee grafice pentru mouse; sunt uor de folosit i determin o reacie vizual foarte rapid. Putei s folosii bare de derulare oriunde afiai ceva text, imagini, foi de calcul tabelar, nregistrri din baze de date - pentru care avei nevoie de mai mult spaiu dect este disponibil n zona client a ferestrei. Barele de derulare sunt poziionale vertical (pentru deplasri n sus i n jos) sau orizontal (pentru deplasri la stnga i la dreapta). Putei s executai clic pe sgeile de Ia capetele barei de derulare sau pe zona dintre acestea. Bara de derulare este parcurs longitudinal de o caset de derulare" care indic poziia aproximativ a prii afiate pe ecran fa de ntregul document. De asemenea, putei s tragei caseta de derulare cu ajutorul mouseului, ca s o mutai ntr-o anumit poziie. Figura 3-7 prezint modul recomandat de folosire a unei bare de derulare verticale pentru text. Programatorii au uneori probleme cu terminologia legat de barele de derulare, deoarece perspectiva lor este diferit de cea a utilizatorilor: un utilizator care deruleaz un document n jos vrea s aduc pe ecran o parte a documentului aflat mai jos; pentru aceasta, programul mut de fapt documentul mai sus n raport cu fereastra de pe ecran. Documentaia Windows i identificatorii definii n fiierele antet se bazeaz, ns, pe perspectiva utilizatorului: derularea n sus nseamn parcurgerea documentului ctre nceput; derularea n jos nseamn parcurgerea documentului ctre sfrit.

Tragei caseta de derulare cu mouse-ul, pentru a fixa o poziie aproximativ n cadrul documentului.

Vizualizai urmtoarea linie n jos executnd clic aici (coninutul ferestrei se deplaseaz n sus) documentului.

Figura 3-7. Bara de derulare vertical.

Este foarte uor s includei n fereastra aplicaiei o bar de derulare orizontal sau vertical. Tot ce trebuie s facei este s includei identificatorul WS_VSCROLL (derulare vertical) i/sau WS_HSCROLL (derulare orizontal) n stilul de fereastr din apelul funciei CreateWindow. Barele de derulare sunt plasate ntotdeauna la marginea de jos sau la cea din dreapta a ferestrei i se ntind pe toat limea, respectiv nlimea zonei client. Zona client nu include spaiul ocupat de barele de derulare. Limea unei bare de derulare verticale i nlimea uneia orizontale sunt constante pentru un driver de afiare dat. Dac avei nevoie de aceste valori, putei s le obinei (aa cum ai vzut) prin apelarea funciei 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 vrei ca tastele de deplasare s dubleze unele dintre funciile barelor de derulare trebuie s furnizai explicit o metod de realizare a acestui lucru (aa cum vom face n Capitolul 5, cnd vom discuta despre tastatur i vom relua acest program).

Domeniul i poziia unei bare de derulare


Fiecare bar de derulare are asociate un domeniu" (definit printr-o pereche de numere ntregi care reprezint valorile maxim i minim) i o poziie" (punctul n care se afl caseta de derulare n domeniul asociat barei de derulare). Atunci cnd caseta de derulare se afl la captul de sus (sau la captul din partea stng) al barei de derulare, poziia corespunde valorii minime. Captul de jos (sau captul 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 stnga) la 100 (jos sau n dreapta) dar poate fi uor modificat astfel nct s aib o form mai convenabil pentru program: SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ; Parametrul iBar poate avea una dintre valorile SB_VERT i SB_HORZ, iar iMin i iMax sunt poziiile minim i maxim din domeniu, n timp ce bRedraw trebuie s aib valoarea TRUE dac vrei ca Windows s redeseneze bara de derulare pe baza noului domeniu stabilit.

Vizualizai urmtoarea linie n sus executnd clic aici (coninutul ferestrei se deplaseaz n jos)

Poziia 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 poziii ale casetei de derulare, aa cum se poate vedea n Figura 3-8. Putei s folosii funcia SetScrollPos ca s stabilii o nou poziie a casetei de derulare pe bara de derulare: SetScrollPos (hwnd, iBar, iPos, bRedraw) ;

Poziia Poziia Poziia Poziia Poziia 4 Poziia 0 Poziia 1 Poziia 2 Poziia 3 Poziia 4

Figura 3-8. Bare de derulare cu cinci poziii ale casetei de derulare. Parametrul iPos reprezint noua poziie, care trebuie s fie cuprins n domeniul delimitat de iMin i iMax. Windows conine funcii asemntoare (GetScrollRange i GefScrollPos) pentru obinerea domeniului i a poziiei unei bare de derulare. Atunci cnd folosii bare de derulare ntr-un program Windows, rspunderea pentru ntreinerea i actualizarea acestora este mprit ntre dumneavoastr i sistemul de operare. Sistemul de operare Windows are urmtoarele sarcini: Trateaz operaiile executate cu mouse-ul asupra barei de derulare. .Afieaz n video invers zona pe care utilizatorul execut clic. Mut caseta de derulare atunci cnd utilizatorul o trage cu ajutorul mouse-ului. Trimite mesaje din partea barei de derulare ctre procedura de fereastr care o conine. Funciile programului n aceast privin sunt: Iniializarea domeniului barei de derulare. Prelucrarea mesajului primit de la bara de derulare. Actualizarea poziiei casetei de derulare de pe bar. Mesaje de la barele de derulare Windows trimite procedurii de fereastr mesajul WM_VSCROLL sau mesajul WM_HSCROLL atunci cnd utilizatorul execut clic pe bara de derulare sau trage caseta de derulare cu ajutorul mouse-ului. Fiecare aciune cu mouse-ul asupra barei de derulare genereaz cel puin dou mesaje - unul la apsarea butonului i al doilea la eliberarea acestuia. Cuvntul mai puin semnificativ al parametrului wParam care nsoete mesajul WM_VSCROLL sau mesajul WM_HSCROLL este un numr ce indica aciunea efectuat cu ajutorul mouse-ului asupra barei de derulare. Aceste numere corespund unor identificatori care ncep cu literele SB_ (de la scroll bar"). Dei unii dintre identificatori conin cuvintele UP" i DOWN" (sus" i jos"), identificatorii se aplic att barelor de derulare verticale, ct i celor orizontale, aa 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 apsat n timp ce indicatorul lui este poziionat pe bara de derulare. Mesajul SB_ENDSCROLL semnaleaz eliberarea butonului mouse-ului. n general putei ignora mesajele SB_ENDSCROLL. Atunci cnd cuvntul mai puin semnificativ al parametrului wParam are una dintre valorile SB_THUMBTRACK sau SB_THUMBPOSITION, cuvntul mai semnificativ al parametrului wParam conine poziia curent pe bara de derulare. Aceast poziie se ncadreaz n domeniul barei de derulare. Pentru alte aciuni executate asupra barei de derulare, cuvntul mai semnificativ al parametrului wParam poate fi ignorat. De

asemenea, putei s ignorai parametrul lParam care, de obicei, este folosit pentru barele de derulare create n casetele de dialog. Documentaia Windows indic faptul c n cuvntul mai puin semnificativ al parametrului wParam pot fi transmise i valorile SB_TOP sau SB_BOTTOM, corespunztoare deplasrii casetei de derulare n poziia minim, respectiv maxim. Totui, aceste mesaje nu sunt trimise niciodat n cazul barelor de derulare create ca parte a ferestrei unei aplicaii.

Figura 3-9. Identificatorii coninui de parametrul wParam al mesajelor trimise de barele de derulare. Tratarea mesajelor SB_THUMBTRACK i SB_THUMBPOSITION este destul de dificil. Dac stabilii un domeniu mare pentru bara de derulare i utilizatorul mut rapid caseta de derulare. Windows trimite procedurii de fereastr un numr mare de mesaje SB_THUMBTRACK. Programul s-ar putea s aib probleme la tratarea unui numr foarte mare de astfel de mesaje. Din acest motiv, multe aplicaii Windows ignor mesajul SB_THUMBTRACK i intr n aciune numai la primirea mesajelor SB_THUMBPOSITION, care indic oprirea casetei de derulare. Totui, dac putei s actualizai rapid coninutul ferestrei, putei s prelucrai i mesajele SB_THUMBTRACK, dar trebuie s inei seama de faptul c utilizatorii care vor descoperi c fereastra se actualizeaz imediat la deplasarea casetei de derulare vor ncerca s o mute ct se poate de repede, ca s vad dac programul poate ine pasul. Trebuie s tii c satisfacia le va fi foarte mare dac programul nu va reui s se descurce. Programul SYSMETS cu posibiliti de derulare Destul cu explicaiile. Este timpul s punem n practic ce am aflat. S ncepem cu ceva simplu. Mai nti, vom include n program o bar de derulare vertical, deoarece avem nevoie de ea. Derularea orizontal poate s mai atepte. Programul SYSMETS2 este prezentat n Figura 3-10. Funcia CreateWindow adaug o bar de derulare la fereastr, datorit includerii stilului de fereastr WS_VSCROLL la apelare:, MS OVERLAPPEDWINDOW | WS VSCROLL
#-----------------------# SYSMETS2.MAK make file #-----------------------sysmets2.exe : sysmets2.obj $(LINKER) $(GUIFLAGS) -OUT:sysmets2.exe sysmets2.obj $(GUILIBS) sysmets2.obj : sysmets2.c sysmets.h $(CC) $(CFLAGS) sysmets2.c /*---------------------------------------------------SYSMETS2.C -- System Metrics Display Program No. 2 (c) Charles Petzold, 1996 ----------------------------------------------------*/ #include <windows.h> #include <string.h> #include "sysmets.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "SysMets2" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Get System Metrics No. 2", WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos ; char szBuffer[10] ; HDC hdc ; int i, y ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; return 0 ; case WM_SIZE : cyClient = HIWORD (lParam) ; return 0 ; case WM_VSCROLL : switch (LOWORD (wParam)) { case SB_LINEUP : iVscrollPos -= 1 ; break ; case SB_LINEDOWN : iVscrollPos += 1 ; break ; case SB_PAGEUP : iVscrollPos -= cyClient / cyChar ; break ; case SB_PAGEDOWN :

iVscrollPos += cyClient / cyChar ; break ; case SB_THUMBPOSITION : iVscrollPos = HIWORD (wParam) ; break ; default : break ; } iVscrollPos = max (0, min (iVscrollPos, NUMLINES)) ; if (iVscrollPos != GetScrollPos (hwnd, SB_VERT)) { SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; InvalidateRect (hwnd, NULL, TRUE) ; } return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (i = 0 ; i < NUMLINES ; i++) { y = cyChar * (1 - iVscrollPos + i) ; TextOut (hdc, cxChar, y, sysmetrics[i].szLabel, strlen (sysmetrics[i].szLabel)) ; TextOut (hdc, cxChar + 22 * cxCaps, y, sysmetrics[i].szDesc, strlen (sysmetrics[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf (szBuffer, "%5d", GetSystemMetrics (sysmetrics[i].iIndex))) ; SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 3-10. Programul SYSMETS2. Procedura de fereastr WndProc are dou linii suplimentare, care stabilesc domeniul i poziia casetei pe bara de derulare vertical n timpul prelucrrii mesajului WM_CREATE: SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; Structura sysmetrics are un numr NUMLINES de linii de text, aa c domeniul barei de derulare este stabilit de la 0 la acea valoare NUMLINES. Fiecare poziie de pe bara de derulare corespunde unei linii de text afiate n partea de sus a zonei client. n cazul n care caseta de derulare se afl n poziia 0, n partea superioar a ecranului este lsat o margine de dimensiunea unei linii. Pe msur ce schimbai poziia casetei, derulnd n jos, textul ar trebui s se mute n sus. Atunci cnd poziia casetei de derulare are valoarea maxim, n partea de sus a ecranului este afiat ultima linie a structurii sysmetrics. Pentru prelucrarea mesajelor WM_VSCROLL n procedura de fereastr WndProc este definit o variabil static numit iVscrollPos. Aceast variabil reprezint poziia curent a casetei de derulare. Pentru mesajele SB_LINEUP i SB_LINEDOWN tot ce avem de fcut este s ajustm poziia, cu o unitate. Pentru mesajele SB_PAGEUP i SB_PAGEDOWN trebuie s deplasm textul cu numrul de linii afiate pe ecran, deci cu valoarea cyClient mprit la cyChar. Pentru mesajul SB_THUMBPOSITION, noua poziie este indicat n cuvntul mai semnificativ al parametrului wParam. Mesajele SB_ENDSCROLL i SB_THUMBTRACK sunt ignorate. Valoarea iVscrollPos este apoi ajustat cu ajutorul macroinstruciunilor min i max pentru ncadrarea valorii ntre valorile minim i maxim ale domeniului. Dac poziia casetei de derulare s-a modificat, valoarea este

actualizat prin apelarea funciei SetScrollPos i ntreaga fereastr este invalidat prin apelarea funciei InvalidateRect. Apelul funciei InvalidateRect genereaz un mesaj WM_PAINT. n programul original SYSMETS1, n timpul prelucrrii mesajului WM_PAINT, poziia pe axa y a fiecrei linii era calculat astfel: cyChar * (1 + i) n SYSMETS2, formula folosit este: cyChar * (1 - iVscrollPos + i) n ciclu este afiat tot un numr de linii NUMLINES, dar dac iVscrollPos are valoarea 2 sau mai mare, afiarea liniilor ncepe deasupra zonei client. Fiind n afara zonei client, aceste linii nu sunt afiate de Windows. Am spus c vom ncepe cu ceva simplu. Codul de mai sus este ineficient i consum inutil resurse. l vom corecta n curnd, dar mai nti vom discuta despre modul de actualizare a zonei client dup primirea unui mesaj WM_VSCROLL. Structurarea programului pentru desenare Procedura de fereastr din programul SYSMETS2 nu redeseneaz fereastra dup primirea unui mesaj de la bara de derulare, ci apeleaz funcia InvalidateRect pentru invalidarea zonei client. Aceast funcie determin sistemul de operare s plaseze un mesaj WM_PAINT n coada de ateptare a programului. Cel mai bine este s structurai programul astfel nct toate operaiile de desenare n zona client s se fac n timpul prelucrrii mesajelor WM_PAINT. Deoarece programul trebuie s aib posibilitatea de redesenare a ntregii zone client n momentul primirii unui mesaj WM_PAINT, probabil vei duplica unele secvene de cod dac vei face tiprirea i n alte puncte din program. La nceput s-ar putea s v revoltai, 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 desenai numai ca rspuns la alte mesaje dect WM_PAINT. (Programul KEYLOCK din Capitolul 5 este un astfel de exemplu.) n majoritatea cazurilor, ns, o asemenea abordare este inutil i dup ce vei stpni disciplina acumulrii tuturor informaiilor pentru tiprirea ca rspuns la mesajele WM_PAINT, vei fi mulumit de rezultate. Totui, de multe ori, programul va stabili c este necesar redesenarea unor anumite zone din ecran n timpul prelucrrii altor mesaje dect WM_PAINT. n astfel de situaii este util funcia InvalidateRect. Putei s o folosii ca s invalidai anumite poriuni sau ntreaga zon client. n unele aplicaii s-ar putea s nu fie suficient simpla transmitere a unor mesaje WM_PAINT prin marcarea unor poriuni din fereastr ca invalide. Dup ce apelai funcia InvalidateRect, Windows plaseaz n coada de ateptare un mesaj WM_PAINT, iar procedura de fereastr va prelucra la un moment dat acest mesaj. Totui, Windows trateaz mesajele WM_PAINT ca mesaje de prioritate sczut i, dac n sistem activitatea este intens, poate s treac un timp destul de lung pan la prelucrarea acestora. Toat lumea a vzut guri" albe n ferestre dup nchiderea unor casete de dialog. Dac dorii ca actualizarea poriunii invalide s se fac imediat, dup funcia InvalidateRect apelai imediat funcia UpdateWindow: UpdateWindow (hwnd) ; Funcia UpdateWindow determin sistemul de operare s apeleze procedura de fereastr cu un mesaj WM_PAINT dac vreo poriune a zonei client este invalid. (Dac ntreaga zon client este valid, procedura de fereastr nu mai este apelat.) Acest mesaj WM_PAINT sare peste coada de ateptare, procedura de fereastr fiind apelat direct de Windows. Dup ce procedura de fereastr ncheie operaia de redesenare, sistemul de operare cedeaz din nou controlul programului i execuia continu de la instruciunea de dup apelul funciei UpdateWindow. Vei observa c funcia UpdateWindow este folosit i de WinMain pentru generarea primului mesaj WM_PAINT. Atunci cnd este creat o fereastr, ntreaga zon client a acesteia este invalid. Funcia UpdateWindow cere procedurii de fereastr s deseneze toat zona client. Construirea unui program cu posibiliti mai bune de derulare Deoarece SYSMETS2 nu este un model destul de eficient pentru a fi imitat de alte programe, l vom face mai bun. SYSMETS3 - versiunea final a programului SYSMETS din acest capitol, este prezentat n Figura 3-11. n aceast versiune a fost adugat o bar de derulare orizontal pentru derularea de la stnga la dreapta i a fost mbuntit codul de redesenare a zonei client.
#-----------------------# SYSMETS3.MAK make file #------------------------

sysmets3.exe : sysmets3.obj $(LINKER) $(GUIFLAGS) -OUT:sysmets3.exe sysmets3.obj $(GUILIBS) sysmets3.obj : sysmets3.c sysmets.h $(CC) $(CFLAGS) sysmets3.c /*----------------------------------------------------

SYSMETS3.C -- System Metrics Display Program No. 3 (c) Charles Petzold, 1996 ----------------------------------------------------*/ #include <windows.h> #include "sysmets.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "SysMets3" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Get System Metrics No. 3", WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth, iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ; char szBuffer[10] ; HDC hdc ; int i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; iMaxWidth = 40 * cxChar + 22 * cxCaps ; return 0 ; case WM_SIZE : cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; iVscrollMax = max (0, NUMLINES + 2 - cyClient / cyChar) ; iVscrollPos = min (iVscrollPos, iVscrollMax) ; SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, FALSE) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;

iHscrollMax = max (0, 2 + (iMaxWidth - cxClient) / cxChar) ; iHscrollPos = min (iHscrollPos, iHscrollMax) ; SetScrollRange (hwnd, SB_HORZ, 0, iHscrollMax, FALSE) ; SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ; return 0 ; case WM_VSCROLL : switch (LOWORD (wParam)) { case SB_TOP : iVscrollInc = -iVscrollPos ; break ; case SB_BOTTOM : iVscrollInc = iVscrollMax - iVscrollPos ; break ; case SB_LINEUP : iVscrollInc = -1 ; break ; case SB_LINEDOWN : iVscrollInc = 1 ; break ; case SB_PAGEUP : iVscrollInc = min (-1, -cyClient / cyChar) ; break ; case SB_PAGEDOWN : iVscrollInc = max (1, cyClient / cyChar) ; break ; case SB_THUMBTRACK : iVscrollInc = HIWORD (wParam) - iVscrollPos ; break ; default : iVscrollInc = 0 ; } iVscrollInc = max (-iVscrollPos, min (iVscrollInc, iVscrollMax - iVscrollPos)) ; if (iVscrollInc != 0) { iVscrollPos += iVscrollInc ; ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; UpdateWindow (hwnd) ; } return 0 ; case WM_HSCROLL : switch (LOWORD (wParam)) { case SB_LINEUP : iHscrollInc = -1 ; break ; case SB_LINEDOWN : iHscrollInc = 1 ; break ; case SB_PAGEUP : iHscrollInc = -8 ; break ; case SB_PAGEDOWN : iHscrollInc = 8 ; break ; case SB_THUMBPOSITION : iHscrollInc = HIWORD (wParam) - iHscrollPos ; break ; default : iHscrollInc = 0 ; } iHscrollInc = max (-iHscrollPos, min (iHscrollInc, iHscrollMax - iHscrollPos)) ; if (iHscrollInc != 0) {

iHscrollPos += iHscrollInc ; ScrollWindow (hwnd, -cxChar * iHscrollInc, 0, NULL, NULL) ; SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ; } return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; iPaintBeg = max (0, iVscrollPos + ps.rcPaint.top / cyChar - 1) ; iPaintEnd = min (NUMLINES, iVscrollPos + ps.rcPaint.bottom / cyChar) ; for (i = iPaintBeg ; i < iPaintEnd ; i++) { x = cxChar * (1 - iHscrollPos) ; y = cyChar * (1 - iVscrollPos + i) ; TextOut (hdc, x, y, sysmetrics[i].szLabel, strlen (sysmetrics[i].szLabel)) ; TextOut (hdc, x + 22 * cxCaps, y, sysmetrics[i].szDesc, strlen (sysmetrics[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf (szBuffer, "%5d", GetSystemMetrics (sysmetrics[i].iIndex))) ; SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 3-11. Programul SYSMETS3. Iat ce mbuntiri au fost fcute n programul SYSMETS3 i cum au fost implementate acestea: Nu mai putei s derulai ecranul pan cnd ultima linie de text este afiat n partea de sus a zonei client putei s derulai numai pn cnd 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 poziie a casetei de derulare) n timpul prelucrrii mesajului WM_SIZE. Domeniul barei de derulare este calculat n funcie de numrul de linii de text, de limea textului i de dimensiunea zonei client. Ca rezultat se obine un domeniu mai mic - numai att ct este nevoie pentru afiarea 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 afiarea ntregului text, inclusiv a marginilor de sus i de jos. n acest caz, poziiile minim i maxim ale barei de derulare vor avea valoarea zero. Cum va folosi Windows aceast informaie? Va elimina bara de defilare din fereastr! Aceasta nu mai este necesar. La fel, dac zona client este suficient de mare ca s afieze toate cele 60 de coloane de text, nici bara de derulare orizontal nu mai este afiat. Mesajele WM_VSCROLL i WM_HSCROLL sunt prelucrate prin calcularea unei valori de incrementare a poziiei casetei de derulare pentru fiecare aciune efectuat asupra barei de derulare. Aceast valoare este apoi folosit pentru derularea coninutului ferestrei, folosind funcia Windows ScrollWindow. Aceast funcie are urmtorul format: ScrollWindow (hwnd, xInc, yInc, pRect, pClipRect) ; Valorile xlnc i ylnc specific numrul de pixeli cu care se face derularea. n programul SYSMETS3, parametrii pRect i pClipRect au valoarea NULL, specificnd faptul c trebuie derulat ntreaga zon client. Windows invalideaz dreptunghiul din zona client descoperit" de operaia de derulare. Aceasta genereaz un mesaj WM_PAINT. Nu este necesar apelarea funciei InvalidateRect. (Reinei c funcia ScrollWindow nu este o procedur GDI, deoarece nu are nevoie de o variabil handle. ScrollWindow este una dintre puinele funcii non-GDI din Windows care modific aspectul zonei client a ferestrei.) n timpul prelucrrii 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 operaiile SB_THUMBTRACK din mesajele WM_VSCROLL. Versiunile anterioare ale programului SYSMETS ignorau mesajele SB_THUMBTRACK (trimise atunci cnd utilizatorul trage cu mouse-ul caseta de derulare) i acionau numai la mesajele SB_THUMBPOSITION (trimise atunci cnd utilizatorul elibereaz caseta de derulare). De asemenea, mesajele WM_VSCROLL apeleaz funcia UpdateWindow, care actualizeaz imediat zona client. Atunci cnd 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 decidei dac viteza de lucru a programului (i a sistemului de operare) este destul de mare pentru a justifica aceast modificare.

Dar mie nu mi place s folosesc mouse-ul


La nceput, utilizatorii sistemului de operare Windows nu se oboseau s foloseasc mouse-ul. ntr-adevr, n cazul sistemului Windows (ca i n cazul unor aplicaii scrise pentru Windows) nu este absolut necesar s avei un mouse. Dei calculatoarele personale fr mouse au disprut pe acelai drum cu monitoarele de tip monocrom i cu alte dispozitive i componente, uneori este recomandat s dublai funciile mouse-ului cu tastatura. Acest lucru este valabil n special pentru operaii simple, cum ar fi derularea ecranului, deoarece pe tastatura exist o mulime de taste de deplasare, care ar trebui s permit aceleai operaii. n Capitolul 5 vei nva cum s folosii tastatura i cum s adugai o interfa cu tastatura la acest program. Vei observa c programul SYSMETS3 prelucreaz mesajele WM_VSCROLL atunci cnd cuvntul mai puin semnificativ al parametrului wParam are valoarea SB_TOP sau SB_BOTTOM. Am menionat mai devreme c o procedur de fereastr nu primete aceste mesaje de la barele de derulare, aa c pentru moment acest cod pare inutil. Atunci cnd vom ajunge la acelai program n Capitolul 5, vei vedea de ce am inclus aceste operaii.

Capitolul 4. Noiuni elementare de grafic Interfaa pentru dispozitive grafice (GDI - Graphics Device Interface) este o component a sistemului de operare Windows 95 i are ca sarcin afiarea elementelor grafice (inclusiv a textului) pe ecran i la imprimant. Aa cum probabil v imaginai, GDI este o parte foarte important a sistemului de operare, nu numai pentru c aplicaiile pe care le scriei pentru Window 95 folosesc exclusiv interfaa GDI pentru afiarea informaiilor vizuale, dar i datorit faptului c sistemul de operare folosete pe scar larg interfaa GDI pentru afiarea unor elemente din interfaa cu utilizatorul, cum ar fi meniuri, bare de derulare, pictograme i indicatoare de mouse. Probabil datorit modului de programare cu MS-DOS, deseori programatorii nceptori care folosesc Windows (dar uneori chiar i programatorii cu o oarecare experien) sunt tentai s ocoleasc" interfaa GDI i s scrie direct pe ecranul video. V rog - nici s nu v gndii la aa ceva. Nu vei avea dect dureri de cap, cauzate, de pild, de interferarea cu alte programe Windows sau de incompatibilitatea cu versiunile ulterioare ale sistemului de operare. Exceptnd cteva caracteristici introduse abia n Windows 95, interfaa GDI exist de la primele versiuni ale sistemului de operare. Sistemul de operare Windows 1.0 era format, n esen, din trei biblioteci cu legturi dinamice: KERNEL (manipularea proceselor, gestionarea memoriei i operaii de intrare/ieire cu fiiere), USER (interfaa cu utilizatorul) i GDI. Versiunile ulterioare ale sistemului de operare au adugat interfeei GDI noi funcii, dei, n cea mai mare parte, compatibilitatea cu programele existente a fost meninut. Elementele de baz ale interfeei GDI - indiferent dac acest lucru e bun sau ru - au rmas aproape neschimbate. Pentru o discuie exhaustiv despre interfaa GDI ar fi nevoie s dedicm un ntreg volum acestui subiect; de aceea nu vom ncerca acest lucru aici. Scopul pe care ni l-am propus n capitolul de fa e s v oferim suficiente informaii despre interfaa GDI pentru a nelege modul de lucru al procedurilor de desenare prezentate n restul crii, plus alte cteva informaii care s-ar putea dovedi utile.

Filozofia GDI
n versiunile pe 32 de bii ale sistemului de operare Windows elementele grafice sunt manipulate, n principal, prin funcii exportate din biblioteca cu legturi dinamice GDI32.DLL, care, la rndul ei, folosete biblioteca cu legturi dinamice pe 16 bii GDI.EXE. (n versiunile Windows anterioare, bibliotecile cu legturi dinamice erau fiiere cu extensia .EXE, nu .DLL.) Aceste module apeleaz proceduri din diferite fiiere driver pentru afiare - un fiier .DRV pentru afiarea pe ecran i, probabil, unul sau mai multe fiiere .DRV care controleaz imprimantele i plotterele. Driverele execut operaiile 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 plci video sau de imprimante este nevoie, desigur, de diferite fiiere driver. Deoarece la calculatoarele compatibile PC pot fi ataate diferite dispozitive de afiare, unul dintre scopurile principale ale interfeei GDI este s permit manipularea elementelor grafice independent de dispozitiv. Programele scrise pentru Windows ar trebui s ruleze fr probleme, indiferent de dispozitivul grafic de afiare folosit, dac acesta este acceptat de sistemul de operare. Interfaa GDI realizeaz acest lucru prin furnizarea unor componente care izoleaz programele de caracteristicile particulare ale diferitelor dispozitive de ieire. Lumea dispozitivelor grafice de ieire este mprit n dou categorii: dispozitive rastru i dispozitive vectoriale. Majoritatea dispozitivelor de ieire pentru PC sunt dispozitive rastru, ceea ce nseamn c reprezentarea imaginilor se face prin matrice de puncte. Aceast categorie include plcile video, imprimantele matriceale i imprimantele laser. Dispozitivele vectoriale, care deseneaz imaginile prin linii, sunt, n general, limitate la plottere. Majoritatea limbajelor de programare cu posibiliti grafice tradiionale se bazeaz n exclusivitate pe vectori. Aceasta nseamn c un program care folosete unul dintre aceste limbaje grafice este desprit de componentele hardware printr-un nivel de abstractizare. Dispozitivul de ieire folosete pixeli pentru reprezentarea elementelor grafice, dar programul nu comunic deloc cu interfaa n limbajul pixelilor. Dei putei s folosii interfaa Windows GDI ca sistem de desenare vectorial la nivel nalt, putei s folosii aceeai interfaa i pentru manipularea la nivel sczut a pixelilor. Din acest punct de vedere, interfaa Windows GDI este pentru limbajele grafice de interfaa tradiionale 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 acelai timp, limbajul C e cunoscut i pentru faptul c permite efectuarea unor operaii de nivel sczut, care n alte limbaje de nivel nalt sunt, deseori, imposibile. Aa cum C este numit uneori limbaj de asamblare de

48

nivel nalt", putei s considerai c GDI este o interfa de nivel nalt ctre componentele hardware ale dispozitivelor grafice. Aa cum ai vzut, n mod prestabilit. Windows folosete un sistem de coordonate bazat pe pixeli. Majoritatea limbajelor grafice tradiionale folosesc un sistem de coordonate virtual", cu o ax vertical i una orizontal care merg, de exemplu, de la 0 la 32.767. Dei unele limbaje grafice nu v permit s folosii coordonate n pixeli, interfa GDI v las s lucrai n oricare dintre cele dou sisteme de coordonate (i chiar ntr-un alt sistem, bazat pe dimensiunile fizice). Putei s folosii un sistem de coordonate virtual i s izolai astfel programul de componentele hardware, sau s folosii sistemul de coordonate al dispozitivului i s scriei programul direct pentru componentele hardware utilizate. Unii programatori consider c n momentul n care ncepei s gndii programul n pixeli ai renunat la independena de dispozitiv. Ai vzut deja n Capitolul 3 c acest lucru nu este ntotdeauna adevrat. 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 ieire, astfel nct s poat face modificrile corespunztoare. De exemplu, n programele SYSMETS am folosit dimensiunile n pixeli ale unui caracter din fontul sistem standard, ca s stabilim spaiile pentru textul de pe ecran. Aceast metod permite programului s se adapteze la plci video cu diferite rezoluii, dimensiuni ale textului i rate de afiare, fn acest capitol vei vedea i alte metode de determinare a dimensiunilor ecranului. La nceput, muli utilizatori foloseau sistemul de operare Windows pe un monitor monocrom. Pan nu demult, utilizatorii calculatoarelor portabile (laptop) nu aveau la dispoziie dect diferite tonuri de gri. Chiar i astzi monitoarele video folosite pentru Windows 95 au posibiliti diferite de afiare a culorilor (16 culori, 256 de culori sau full-color") i muli utilizatori folosesc imprimante alb-negru. Este posibil s folosii aceste dispozitive orbete", dar programul are i posibilitatea s determine numrul culorilor disponibile pentru un anumit dispozitiv de afiare i s foloseasc toate avantajele oferite de componentele hardware. Desigur, aa cum este posibil s scriei programe C care au probleme ascunse de portabilitate atunci cnd sunt rulate pe alte calculatoare, este posibil s se strecoare n programele Windows i anumite probleme neprevzute, legate de dependena de dispozitiv. Acesta e preul pltit pentru izolarea incomplet de componentele hardware. Vom studia n acest capitol capcanele dependenei de dispozitiv. De asemenea, trebuie s tii c interfa Windows GDI i are limitele ei. Cel puin n acest moment, interfa GDI nu poate s fac tot ce v-ai putea dori de la o interfa grafic. Dei putei s mutai pe ecran obiecte grafice, GDI este, n general, un sistem de afiare static, ce permite numai animaii limitate. Aa cum este implementat n Windows 95, interfa GDI nu asigur un suport direct pentru afiarea tridimensional sau pentru rotirea obiectelor. De exemplu, atunci cnd desenai o elips, axele acesteia trebuie s fie paralele cu axele sistemului de coordonate. Dei unele limbaje grafice folosesc numere n virgul mobil pentru coordonatele virtuale. Windows 95 - din motive legate de performan - folosete numai numere ntregi pe 16 bii aceasta este una dintre deficienele sistemului de operare Windows 95. Windows NT permite folosirea coordonatelor pe 32 de bii.

STRUCTURA INTERFEEI GDI


Din punctul de vedere al programatorului, interfaa GDI este format din cteva sute de apeluri de funcii i unele tipuri de date, macroinstruciuni i structuri asociate acestor funcii, nainte de a studia n detaliu cteva dintre aceste funcii, haidei s vedem care este structura general a interfeei GDI. Tipuri de apeluri de funcii n general, apelurile de funcii GDI pot fi clasificate n mai multe categorii. Chiar dac nu sunt foarte stricte i exist unele suprapuneri, aceste categorii pot fi enunate astfel: Funcii care obin (sau creeaz) i elibereaz (sau distrug) un context de dispozitiv. Aa cum am vzut n Capitolul 3, pentru a desena avei nevoie de un context de dispozitiv. Funciile GetDC i ReleaseDC v permit s facei aceste lucruri n timpul prelucrrii altor mesaje dect WM_PAINT, pe cnd funciile BeginPaint i EndPaint (dei din punct de vedere tehnic fac parte din subsistemul USER din Windows) sunt folosite n timpul prelucrrii mesajului WM_PAINT. Vom discuta n curnd despre alte funcii legate de contextul de dispozitiv. Funcii care obin informaii despre contextul de dispozitiv. n programele SYSMETS, cu care ai fcut cunotin n Capitolul 3, am folosit funcia GetTextMetrics ca s obinem informaii despre dimensiunile fontului selectat n contextul de dispozitiv. Mai trziu n acest capitol vom prezenta programul DEVCAPS1, pentru a obine informaii generale despre contextul de dispozitiv.

Funcii care deseneaz ceva. Evident, dup rezolvarea problemelor preliminare, acestea sunt funciile cu adevrat importante. n Capitolul 3 am folosit funcia TextOut pentru afiarea textului n zona client a ferestrei. Aa cum vom vedea, alte funcii GDI sunt folosite pentru desenarea liniilor, a zonelor colorate i a imaginilor de tip bitmap. Funcii care stabilesc sau obin atribute ale contextului de dispozitiv. Un atribut" al contextului de dispozitiv specific modul de lucru al funciilor de desenare. De exemplu, folosii funcia SetTextColor ca s precizai culoarea textului afiat cu funcia TextOut (sau cu o alt funcie de afiare a textului). n programele SYSMETS din Capitolul 3 am folosit funcia SetTextAlign ca s artm interfeei GDI faptul c poziia de nceput a irului de caractere este n partea dreapt a irului de caractere, nu n partea stng, aa cum se ntmpl de obicei. Toate atributele contextului de dispozitiv au valori prestabilite, care devin active la obinerea contextului de dispozitiv. Pentru fiecare funcie de tip Set exist i o funcie Get corespondent, folosit pentru obinerea valorilor curente ale atributelor contextului de dispozitiv. Funcii care lucreaz cu obiecte GDI. Aici este punctul n care lucrurile se ncurc puin n interfaa GDI. Mai nti vom da un exemplu: n mod prestabilit, toate liniile pe care le desenai folosind interfaa GDI sunt continue i au o grosime standard. Ce se ntmpl, ns, dac dorii s desenai 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 peniei logice" (logical pen"). Putei s indicai o peni logic prin specificarea acestor caracteristici n funciile CreatePen, CreatePenIndirect i ExtCreatePen. Funciile de mai sus returneaz o variabil handle a peniei logice create. (Dei se consider c aceste funcii fac parte din interfaa GDI, spre deosebire de majoritatea celorlalte funcii acestea nu au nevoie, ca parametru, de o variabil handle a contextului de dispozitiv.) Pentru folosirea peniei selectai variabila handle a acesteia n contextul de dispozitiv. Din acest moment, orice linie va fi desenat eu penia selectat. Ulterior, deselectai obiectul peni din contextul de dispozitiv i distrugei-l. n afara penielor, putei s folosii obiecte GDI pentru crearea pensulelor care coloreaz o suprafa nchis, pentru fonturi, pentru imagini bitmap i pentru alte aspecte ale interfeei GDI, despre care vom discuta n acest capitol.

Primitive GDI Elementele grafice pe care le afiai pe ecran sau le tiprii la imprimant pot fi mprite n mai multe categorii, numite primitive". Iat care sunt aceste categorii: Linii i curbe. Liniile reprezint baza oricrui 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 reprezentnd poriuni din circumferina unei elipse sau a curbelor Bezier. Despre toate aceste clemente vom mai discuta n capitolul de fa. Orice curb mai complex poate n desenat ea o linie poligonal, adic o serie de linii foarte scurte care definesc o curb. Liniile sunt desenate folosind penia curent selectat n contextul de dispozitiv. Suprafee 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 (hauri orizontale, verticale sau pe diagonal) sau o imagine bitmap repetat pe vertical sau pe orizontal. Imagini bitmap. Imaginile bitmap sunt matrice dreptunghiulare de bii, care corespund pixelilor unui dispozitiv de afiare. Imaginile bitmap sunt instrumente de baz pentru sistemele grafice de tip rastru. n general, acestea sunt folosite pentru afiarea imaginilor complexe (deseori preluate din lumea real) pe ecran sau pentru tiprirea acestora la imprimant. De asemenea, imaginile bitmap sunt folosite pentru afiarea unor mici imagini (cum ar fi pictograme, indicatoare de mouse i butoane de pe barele cu instrumente de lucru ale aplicaiilor) care trebuie afiate foarte rapid. Interfaa 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 fiiere. Text. Textul este mai puin matematic" dect alte aspecte ale graficii pe calculator. Textul, aa cum l tim, este legat de sute de ani de tipografia tradiional, apreciat adesea ca adevrat 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 obinerea informaiilor despre fonturi sunt printre cele mai mari din Windows. ncepnd cu versiunea Windows 3.1, interfaa GDI accept fonturile TrueType, bazate pe contururi umplute, care pot fi manipulate de alte funcii GDI. Windows 95 accept n continuare i fonturile mai vechi,

de tip bitmap (cum este fontul sistem prestabilit) pentru compatibilitate i pentru economisirea spaiului de memorie. Alte aspecte Alte aspecte ale interfeei GDI nu sunt la fel de uor de clasificat. Printre acestea se numr: Moduri de mapare i transformri. Dei, n mod prestabilit, desenarea se face folosind ca uniti de msur pixelii, nu suntei limitat la acest sistem de msur. Modurile de mapare GDI v permit s desenai folosind ca unitate de msur inci (sau fraciuni de inci), milimetri sau orice alt unitate de msur. De asemenea. Windows 95 asigur suportul pentru o transformare real" exprimat printr-o matrice 3x3. Aceast transformare permite deformarea i rotirea obiectelor grafice. Din pcate, aceast transformare nu este acceptat sub Windows 95. Metafiiere (metafiles). Un metafiier este o colecie de comenzi GDI stocate ntr-o form binar. Metafiierele sunt folosite, n primul rnd, pentru transferarea reprezentrilor unor elemente grafice vectoriale prin intermediul memoriei temporare (clipboard). Regiuni (regions). O regiune este o suprafa complex de orice form, definit ca o combinaie boolean de regiuni mai simple. n general, regiunile sunt stocate intern de GDI ca o serie de linii de scanare, diferite de combinaia de linii folosit iniial pentru definirea regiunii. Putei s folosii regiunile pentru contururi, pentru umplere sau pentru decupare. Ci (paths). O cale este o colecie de linii drepte i curbe stocate intern de GDI. Cile pot fi folosite pentru desenare, pentru umplere sau pentru decupare. De asemenea, cile pot fi transformate n regiuni. Decupare (clipping). Desenarea poate fi limitat la o anumit seciune 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 afiarea corespunztoare a imaginilor din lumea real, stocate ca imagini bitmap. Tiprire (printing). Dei discuiile din acest capitol sunt limitate doar la afiarea pe ecran, tot ceea ce nvai n acest capitol poate fi aplicat i operaiilor de tiprire. (Vezi Capitolul 15 pentru alte informaii despre tiprire.) CONTEXTUL DE DISPOZITIV nainte de a ncepe s desenm, haidei s mai aflm cte ceva despre contextele de dispozitiv. Atunci cnd vrei s desenai la un dispozitiv de ieire grafic (cum ar fi ecranul sau imprimanta) trebuie s obinei mai nti o variabil handle a contextului de dispozitiv (DC - device context). Prin obinerea acestei variabile handle primii permisiunea de folosire a dispozitivului. Variabila handle este apoi inclus ca parametru n apelul unei funcii GDI, identificnd dispozitivul Ia care vrei s desenai. Contextul de dispozitiv conine mai multe atribute curente, care specific modul de lucru al funciilor GDI pentru dispozitivul respectiv. Aceste atribute permit ca la apelarea funciilor GDI s fie specificate numai coordonatele de nceput sau dimensiunea, nu i toate celelalte informaii de care sistemul de operare are nevoie pentru desenarea obiectelor pe dispozitivul folosit. De exemplu, atunci cnd apelai funcia TextOut trebuie s specificai numai variabila handle a contextului de dispozitiv, coordonatele de nceput, textul i lungimea acestuia. Nu trebuie s precizai fontul, culoarea textului, culoarea fondului din spatele textului i spaiul dintre caractere, deoarece aceste atribute fac parte din contextul de dispozitiv. Atunci cnd dorii s modificai unul dintre aceste atribute ale contextului de dispozitiv, apelai o funcie specializat. Urmtoarele apeluri ale funciei TextOut pentru acelai context de dispozitiv vor folosi atributul modificat. Obinerea variabilei handle a contextului de dispozitiv Sistemul de operare Windows v pune la dispoziie mai multe metode pentru obinerea variabilei handle a contextului de dispozitiv. Dac obinei o variabil handle a contextului de dispozitiv n timpul prelucrrii unui mesaj, ar trebui s tergei aceast variabil nainte de ieirea din procedura de fereastr. Dup ce este tears, variabila handle nu mai poate fi folosit (nu mai este valid). Cea mai cunoscut metod de obinere i de tergere a variabilei handle a contextului de dispozitiv implic folosirea funciilor BeginPaint i EndPaint n timpul prelucrrii mesajului WM_PAINT:

hdc - BeginPaint (hwnd, &ps); [alte Unii de program] EndPaint (hwnd, &ps);

Variabila ps este o structur de tip PAINTSTRUCT. Cmpul hdc al acestei structuri conine variabila handle a contextului de dispozitiv. Structura PAINTSTRUCT conine i o structur de tip RECT numit rcPaint, care definete dreptunghiul ce cuprinde regiunea invalid a zonei client a ferestrei. Folosind variabila handle a contextului de dispozitiv, obinut prin apelarea funciei BeginPaint, nu putei s desenai dect n regiunea invalid a ferestrei. Funcia BeginPaint valideaz regiunea invalid. Programele Windows pot s obin variabila handle a contextului de dispozitiv i n timpul prelucrrii altor mesaje dect WM_PAINT:
hdc = GetDC (hwnd); (alte linii de program] ReleaseDC (hwnd, hdc);

Acest context de dispozitiv se aplic zonei client a ferestrei care are variabila handle hwnd. Principala diferen ntre apelul de mai sus i metoda folosirii funciilor BeginPaint i EndPaint este c variabila handle returnat de funcia GetDC v permite s desenai n toat zona client a ferestrei. n plus, funciile GetDC i ReleaseDC nu valideaz eventualele regiuni invalide ale zonei client. Un program Windows poate s obin i o variabil handle a unui context de dispozitiv care se aplic ntregii ferestre, nu numai zonei client a ferestrei:
hdc = GetWindowDC (hwnd); [alte linii de program] ReleaseDC (hwnd, hdc);

Contextul de dispozitiv include, n afar de zona client, bara de titlu a ferestrei, barele de derulare i chenarul. Funcia GetWindowDC este rareori folosit de aplicaii. Dac vrei s experimentai folosirea acestei funcii, trebuie s interceptai mesajele WM_NCPAINT (nonclient paint"), mpiedicnd sistemul de operare s redeseneze poriunile din fereastr care nu fac parte din zona client. Funciile BeginPaint, GetDC i GetWindowDC obin variabila handle a contextului de dispozitiv asociat unei anumite ferestre de pe ecran. O funcie mai general pentru obinerea variabilei handle a unui context de dispozitiv este CreateDC:
hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData); [alte linii de program] DeleteDC (hdc);

De exemplu, putei s obinei variabila handle a contextului de dispozitiv pentru tot spaiul de afiare, cu urmtorul apel:
hdc = CreateDC ("DISPLAY", NULL, NULL, NULL);

Scrierea n afara ferestrei proprii nu este n general recomandat, dar poate fi convenabil pentru unele aplicaii speciale. (Dei aceast metod nu e documentat, se poate obine o variabila handle a contextului de dispozitiv pentru ntregul ecran i prin apelarea funciei GetDC, cu parametrul NULL.) n Capitolul 15 vom folosi funcia CreateDC pentru a obine o variabil handle a contextului de dispozitiv pentru o imprimant. Uneori avei nevoie de unele informaii despre un context de dispozitiv fr s desenai nimic. n aceast situaie putei s obinei o variabila handle a contextului de informaii (information context") folosind funcia CreateIC. Parametrii sunt aceiai ca i pentru funcia CreateDC:
hdclnfo = CreatelC ("DISPLAY", NULL, NULL, NULL); [alte linii de program] DeleteDC (hdclnfo);

Nu putei s executai operaii de scriere la un dispozitiv folosind aceast variabil handle. Atunci cnd lucrai cu imagini bitmap, poate fi uneori util obinerea unui context de dispozitiv n memorie":
hdcMem = CreateCompatibleDC (hdc); [alte linii de program] DeleteDC (hdcHem)

Acesta este un concept destul de abstract. n esen, putei s selectai o imagine bitmap ntr-un context de dispozitiv n memorie i apoi s desenai peste folosind funciile GDI. Vom discuta mai trziu despre aceast tehnic i o vom folosi n programul GRAFMENU din Capitolul 10. Aa cum am menionat mai devreme, un metafiier este o colecie de apeluri GDI codificate ntr-o form binar. Putei s creai un metafiier prin obinerea unui context de dispozitiv pentru metafiiere:
hdcMeta = CreateMetaFile (pszFilename); [alte linii de program] hmf = CloseMetaFile (hdcMeta);

Ct timp acest context este valid, nici un apel GDI pe care l facei folosind parametrul hdcMeta nu afieaz nimic pe ecran, ci devine parte a metafiierului. Apelai apoi funcia CloseMetaFile i contextul de dispozitiv este invalidat. Funcia returneaz o variabil handle a metafiierului (hmf). Obinerea informaiilor despre contextul de dispozitiv Un context de dispozitiv se refer, de obicei, la un dispozitiv fizic de ieire, cum ar fi un monitor video sau o imprimant. Dac avei nevoie de anumite informaii despre acest dispozitiv, cum ar fi dimensiunile ecranului (dimensiunile n pixeli i cele fizice) sau posibilitile de folosire a culorilor, putei s le obinei prin apelarea funciei GetDeviceCaps (get device capabilities"):
iValue = GetDeviceCaps (hdc, iIndex) ;

Parametrul iIndex este unul dintre cei 28 de identificatori definii n fiierele antet din Windows. De exemplu, dac iIndex are valoarea HORZRES funcia GetDeviceCaps returneaz limea dispozitivului n pixeli; VERTRES returneaz nlimea dispozitivului n pixeli. Dac hdc este o variabil handle a contextului de dispozitiv pentru un monitor video, informaiile obinute sunt aceleai cu cele returnate de funcia GetSystemMetrics. Dac hdc este o variabil handle a contextului de dispozitiv pentru o imprimant, funcia GetDeviceCaps returneaz nlimea i limea zonei pe care imprimant o poate tipri. Putei s folosii funcia GetDeviceCaps i ca s obinei informaii despre posibilitile unui dispozitiv de prelucrare a anumitor tipuri de elemente grafice. Aceast posibilitate nu este important pentru ecran, dar poate fi folosit n cazul imprimantelor. De exemplu, majoritatea plotterelor nu pot tipri imagini bitmap - iar funcia GetDeviceCaps v poate comunica acest lucru. Programul DEVCAPS1 Programul DEVCAPS1, prezentat n Figura 4-1, afieaz o parte dintre informaiile pe care poate s le returneze funcia GetDeviceCaps. (O variant mbuntit a acestui program, numit DEVCAPS2, va fi prezentat n Capitolul 15, unde vom obine informaii despre imprimant.)
#-----------------------# DEVCAPS1.MAK make file #-----------------------devcaps1.exe : devcaps1.obj $(LINKER) $(GUIFLAGS) -OUT:devcaps1.exe devcaps1.obj $(GUILIBS) devcaps1.obj : devcaps1.c $(CC) $(CFLAGS) devcaps1.c /*--------------------------------------------------------DEVCAPS1.C -- Device Capabilities Display Program No. 1 (c) Charles Petzold, 1996 ---------------------------------------------------------*/ #include <windows.h> #include <string.h> #define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0])) struct { int iIndex ; char *szLabel ; char *szDesc ; } devcaps [] = { HORZSIZE, "HORZSIZE", "Width in millimeters:", VERTSIZE, "VERTSIZE", "Height in millimeters:", HORZRES, "HORZRES", "Width in pixels:", VERTRES, "VERTRES", "Height in raster lines:", BITSPIXEL, "BITSPIXEL", "Color bits per pixel:", PLANES, "PLANES", "Number of color planes:", NUMBRUSHES, "NUMBRUSHES", "Number of device brushes:", NUMPENS, "NUMPENS", "Number of device pens:", NUMMARKERS, "NUMMARKERS", "Number of device markers:", NUMFONTS, "NUMFONTS", "Number of device fonts:", NUMCOLORS, "NUMCOLORS", "Number of device colors:", PDEVICESIZE, "PDEVICESIZE", "Size of device structure:",

ASPECTX, "ASPECTX", "Relative width of pixel:", ASPECTY, "ASPECTY", "Relative height of pixel:", ASPECTXY, "ASPECTXY", "Relative diagonal of pixel:", LOGPIXELSX, "LOGPIXELSX", "Horizontal dots per inch:", LOGPIXELSY, "LOGPIXELSY", "Vertical dots per inch:", SIZEPALETTE, "SIZEPALETTE", "Number of palette entries:", NUMRESERVED, "NUMRESERVED", "Reserved palette entries:", COLORRES, "COLORRES", "Actual color resolution:" }; LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "DevCaps1" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Device Capabilities", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar ; char szBuffer[10] ; HDC hdc ; int i; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE: hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; return 0 ;

case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; for (i = 0 ; i < NUMLINES ; i++) { TextOut (hdc, cxChar, cyChar * (1 + i), devcaps[i].szLabel, strlen (devcaps[i].szLabel)) ; TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i), devcaps[i].szDesc, strlen (devcaps[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar * (1 + i), szBuffer, wsprintf (szBuffer, "%5d", GetDeviceCaps (hdc, devcaps[i].iIndex))) ; SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; } Figura 4-1. Programul DEVCAPS1.

Aa cum se poate vedea, acest program este foarte asemntor cu programul SYSMETS din Capitolul 3. Pentru a micora dimensiunea codului, nu am inclus bare de derulare, deoarece tiam c textul afiat va ncpea pe ecran. Rezultatele afiate de acest program pe un monitor VGA cu 256 de culori sunt prezentate n Figura 4-2.

Figura 4-2. Fereastra afiata de programul DEVCAPSl pe un monitor VGA cu 256 de culori.

Dimensiunea dispozitivului Cea mai important informaie pe care poate s o obin programul despre monitorul video prin apelarea funciei GetDeviceCaps este dimensiunea ecranului (msurat att n pixeli, cat i n milimetri) i proporia dimensiunilor unui pixel. Aceste informaii pot fi folosite pentru scalarea imaginilor afiate pe ecran. Valorile HORZSIZE i VERTSIZE reprezint limea i nlimea suprafeei de afiare, n milimetri. Desigur, driverul Windows nu tie exact ce tip de monitor este ataat la placa video. Dimensiunile returnate sunt bazate pe dimensiunile standard cunoscute de placa video.

Valorile HORZRES i VERTRES reprezint limea i nlimea suprafeei de afiare, n pixeli. Pentru contextul de dispozitiv al unui ecran, aceste dimensiuni sunt aceleai cu cele returnate de funcia GetSystemMetrics. Folosind aceste valori mpreun cu HORZSIZE i VERTSIZE putei s obinei rezoluia dispozitivului n pixeli pe milimetru. Dac tii c un inci are 25,4 milimetri, putei s calculai rezoluia n puncte pe inci (dpi - dots per inch). Valorile ASPECTX, ASPECTY i ASPECTXY reprezint dimensiunile aproxi mative pentru limea, nlimea i diagonal unui pixel, rotunjite Ia cel mai apropiat numr ntreg. ASPECTXY este rdcina ptrat a sumei ptratelor valorilor ASPECTX i ASPECTY, aa cum v amintii din teorema lui Pitagora. 106Capitolul 4 Noiuni elementare de grafic Valorile LOGPIXELSX i LOGPIXELSY reprezint numrul de pixeli pe un inci logic", pe orizontal i pe vertical. n cazul monitoarelor, un inci logic nu este egal cu un inci fizic (25,4 milimetri) aa cum putei s v dai seama imediat dac facei cteva calcule cu valorile HORZSIZE, VERTSIZE, HORZRES i VERTRES. Probabil ai observat c majoritatea procesoarelor de text sub Windows afieaz o rigl care ns nu este prea corect: dac msurai rigla aa cum este aceasta afiat pe un ecran VGA, vei vedea c un inci de pe aceasta are de fapt cam 1,5 inci reali. Aceste programe folosesc pentru afiarea riglei valorile LOGPIXELSX i LOGPIXELSY. Dac programele ar folosi dimensiunile fizice reale, textul de 10 sau 12 puncte ar fi att de mic, nct nu ar putea fi citit. Dimensiunile logice mresc elementele afiate, astfel nt textul s fie corect dimensionat. Atunci cnd vom ncepe s lucrm cu texte, vom relua aceast problem. Aceast problem privete doar ecranele; toate dimensiunile returnate de funcia GetDeviceCaps pentru imprimante sunt consecvente.
Obinerea informaiilor despre culori

Pentru afiarea culorilor este nevoie de mai muli bii. Cu ct se folosesc mai muli bii, cu att pot fi afiate mai multe culori. Sau, mai precis, numrul culorilor care pot fi afiate simultan este egal cu 2 la o putere egal cu numrul de bii folosii. De obicei, biii sunt organizai n planuri de culori - un plan pentru rou, un plan pentru verde, unul pentru albastru i unul pentru intensitatea culorii. Adaptoarele video cu 8,16 sau 24 de bii pentru fiecare pixel au un singur plan de culoare, n care un numr de bii adiaceni reprezint culoarea fiecrui pixel. Funcia GetDeviceCaps v permite s determinai modul de organizare a memoriei n adaptoarele video i numrul de culori care pot fi reprezentate. Apelul de mai jos returneaz numrul de planuri de culoare:
iPlanes = GetDeviceCaps (hdc, PLANES);

Apelul urmtor returneaz numrul de bii de culoare folosii pentru fiecare pixel:
iBitsPixel = GetDeviceCaps (hdc, BITSPIXEL)

Majoritatea adaptoarelor video care pot afia culori folosesc fie mai multe planuri de culoare, fie mai muli bii de culoare pentru fiecare pixel, dar nu pe amndou; cu alte cuvinte, unul dintre cele dou apeluri de mai sus va returna valoarea 1. Numrul de culori care pot fi redate de o plac video se poate calcula cu formula urmtoare:
iColors = 1<<(iPlanes * iBitsPixel);

Aceast valoare poate s nu fie identic cu numrul de culori obinut prin apelarea funciei GetDeviceCaps cu parametrul NUMCOLORS:
iColors = GetDeviceCaps (hdc, NUMCOLORS);

De exemplu, cele dou numere vor fi diferite pentru majoritatea plotterelor. n cazul unui plotter, PLANES i BITSPIXEL vor avea valoarea 1, dar NUMCOLORS va reprezenta numrul de penie pe care le folosete plotterul. Pentru dispozitivele monocrome, funcia GetDeviceCaps apelat cu parametrul NUMCOLORS returneaz valoarea 2. 107PARTEA 1 NCEPUTUL Aceste valori pot fi diferite i n cazul adaptoarelor video care permit ncrcarea paletelor de culori. Funcia GetDeviceCaps apelat cu parametrul NUMCOLORS returneaz numrul de culori rezervate de Windows, adic 20. Celelalte 236 de culori pot fi stabilite de program folosind un manager de palete. Windows folosete pentru reprezentarea culorilor o valoare ntreag fr semn, pe 32 de bii. Tipul de date folosit pentru culori se numete COLORREF. Ultimii trei octei ai numrului (cei mai puin semnificativi) specific valorile pentru culorile rou, verde i albastru, de la O la 255, aa cum se poate vedea n Figura 4-3. Rezult o palet potenial de 224 culori (aproximativ 16 milioane de culori).

Figura 4-3. Valoarea pe 32 de bii folosita pentru reprezentarea culorilor. Valoarea pe 32 de bii de mai sus e numit deseori culoare RGB". n fisierele antet din Windows sunt definite mai multe macroinstruciuni pentru lucrul cu valorile RGB. Macroinstructiunea RGB accept trei argumente, care reprezint valorile pentru culorile rou, verde i albastru i le combin ntr-o valoarea ntreag pe 32 de bii, fr semn:
#define RBG (r, g, b) ((COLORREF)(((BYTE)(r)|\ ((WORD)(g)<<8))|\ (((DWORD) (BYTE) (b))<<16)))

Astfel, valoarea
RGB (255, 0, 255)

este de fapt 0x00FF00FF, valoarea RGB pentru magenta. Dac toate cele trei argumente au valoarea 0, se obine negrul, iar dac au valoarea 255, se obine albul. Macroinstruciunile GetRValue, GetGValue i GetBValue extrag valorile pentru culorile primare, sub forma unor octei fr semn, din valoarea RGB a culorii. Aceste macroinstructiuni sunt utile atunci cnd apelai funcii Windows care returneaz culori RGB. Numrul de culori returnat de funcia GetDeviceCaps reprezint numrul de culori pure pe care le poate afia 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 combinaiile de valori pentru rou, verde i albastru produc modele diferite de amestecare a culorilor. De exemplu, n cazul unui monitor VGA pe care pot fi afiate 16 culori, valorile pentru rou, verde i albastru trebuie s fie incrementate cu patru, ca s genereze un model diferit de amestecare. Ca urmare, pentru aceste tipuri de adaptoare avei la dispoziie 2 18 (sau 262.144) culori amestecate. Putei s determinai cea mai apropiat culoare pur pentru o anumit valoare apelnd funcia GetNearestColor:
rgbPureColor = GetNearestColor (hdc, rgbColor) ;

Atributele contextului de dispozitiv Aa cum am menionat mai sus. Windows folosete contextul de dispozitiv ca s stocheze atributele care specific modul de lucru al funciilor GDI pentru afiare. De exemplu, atunci cnd afiai un text folosind funcia TextOut nu trebuie s specificai culoarea textului sau fontul folosit. Windows folosete contextul de dispozitiv ca s obin aceste informaii.

Atunci cnd un program obine 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 urmtor. Un program poate s schimbe sau s obin aceste atribute. Salvarea contextului de dispozitiv
n acest capitol vei ntlni diferite funcii folosite pentru modificarea atributelor contextului de dispozitiv. n mod normal, Windows creeaz un nou context de dispozitiv cu valori prestabilite atunci cnd apelai una dintre funciile GetDC sau BeginPaint. Toate modificrile fcute n atributele contextului de dispozitiv se pierd atunci cnd contextul de dispozitiv este ters din memorie prin apelarea funciei ReleaseDC sau a funciei EndPaint. Dac programul trebuie s foloseasc un atribut cu o valoarea diferit de cea prestabilit va trebui s iniializai contextul de dispozitiv de fiecare dat cnd obinei o variabil handle:
caseWM_Paint: hd = BeginPaint (hwnd, &ps); [iniializeaz atributele contextului de dispozitiv] [deseneaz zona client a ferestrei] EndPaint (hwnd, &ps); return 0;

Dei aceast abordare este n general satisfctoare, s-ar putea s preferai s salvai modificrile fcute asupra contextului de dispozitiv ia distrugerea acestuia, astfel nct valorile salvate s redevin active la apelarea funciilor GetDC sau BeginPaint. Atributul contextului de dispozitiv Mod de mapare Originea ferestrei Originea vizorului Extensia ferestrei Valoare prestabilit MM_TEXT (0,0) (0,0) (1,1) Funcia folosit pentru modificarea atributului SetMapMode SetWindowOrgEx OffsetWindowOrgEx SetViewportOrgEx OffsetViewportOrgEx SetWindowExtEx SetMapMode ScaleWindowExtEx SetViewportExtEx SetMapMode ScaleViewportExtEx SelectObject SelectObject SelectObject SelectObject MoveToEx LineTo PolylineTo PolyBezierTo SetBkMode SetBkColor Funcia folosit pentru obinerea atributului GetMapMode GetWindowOrgEx GetViewportOrgEx GetWindowExtEx

Extensia vizorului

(1,1)

GetViewportExExt

Peni (pen) Pensul (brush) Font Bitmap Poziia curent a peniei Modul de afiare a fondului Culoarea fondului Culoarea textului Mod de desenare Mod de ntindere Mod de umplere a prigoanelor Spaiu ntre caractere Originea pensulei Regiune de decupare

BLACK_PEN WHITE_BRUSH SYSTEM_FONT Nu exist (0,0)

SelectObject SelectObject SelectObject SelectObject GetCurrentPositionEx

OPAQUE Alb Negru R2COPYPEN

GetBkMode GetBkColor GetTextColor GetROP2 GetStretchBltMode GetPolyFillMode GetTextCharacterExtra GetBrushOrgEx GetClipBox

SetTextColor
SetROPZ

BLACKONWHITE SetStretchBltMode

ALTERNATE 0

SetPolyFillMode SetTextCharacterExtra

(0,0) n coordonate SetBrushOrgEx ecran Nu exist SelectObject SelectClipRgn

Putei realiza acest lucru incluznd indicatorul flag CS_OWNDC n clasa ferestrei, atunci cnd o nregistrai:
wndclass.style = CS_HREDRAW | CS_VREDRAH | CS_OWNDC;

Acum fiecare fereastr pe care o creai pe baza acestei clase va avea propriul context de dispozitiv, care va fi pstrat pn la distrugerea ferestrei. Atunci cnd folosii stilul de fereastr CS_OWNDC, trebuie s iniializai contextul de dispozitiv o singur dat, de obicei n timpul prelucrrii mesajului WM_CREATE:
case WM_CREATE: hdc = GetDC (hwnd); [iniiallzeaz atributele contextului de dispozitiv] ReleaseDC (hwnd, hdc);

Atributele sunt valide pn cnd le modificai n mod explicit. Stilul de fereastr CS_OWNDC afecteaz numai contextele de dispozitiv obinute prin apelarea funciilor GetDC sau BeginPaint, nu i cele obinute prin apelarea altor funcii (cum ar fi GetWindowDC). Stilul de fereastr CS_OWNDC are i un dezavantaj: sistemul de operare Windows consum aproximativ 800 de octei pentru salvarea contextului de dispozitiv al fiecrei ferestre create cu acest stil. Chiar dac folosii stilul de fereastr CS_OWNDC, trebuie s distrugei contextul de dispozitiv nainte de ieirea din procedura de fereastr. De asemenea, putei s folosii i stilul CS_CLASSDC:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC;

Acest stil face ca fiecare clas de fereastr s aib un context de dispozitiv, folosit n comun de toate ferestrele create pe baza clasei respective. n general, stilul de fereastr CS.CLASSDC este mai greu de folosit dect stilul CS_OWNDC, deoarece orice modificare pe care o facei asupra atributelor contextului de dispozitiv afecteaz toate ferestrele create pe baza aceleiai clase. Uneori efectele pot fi destul de bizare. Dac s-ar putea s dorii s modificai unele dintre atributele contextului de dispozitiv, s desenai ceva folosind noile atribute i apoi s revenii la vechile valori, salvai starea curent a contextului de dispozitiv prin urmtorul apel:
iSavedID = SaveDC (hdc);

Modificai atributele dorite. Atunci cnd vrei s revenii la starea contextului de dispozitiv dinaintea apelrii funciei SaveDC, folosii funcia RestoreDC:
RestoreDC (hdc, iSavedID);

Putei s apelai funcia SaveDC de oricte ori nainte de a apela funcia RestoreDC. Dac vrei s revenii la starea contextului de dispozitiv de dinaintea ultimului apel al funciei SaveDC, folosii urmtorul apel:
RestoreDC (hdc, -1);

Desenarea liniilor Teoretic, singura funcie necesar pentru desenare este SetPixel (i, n unele situaii, GetPixel). Orice altceva poate fi fcut prin apelarea unor funcii de nivel nalt, implementate chiar n modulul GDI sau chiar n codul aplicaiei. De exemplu, pentru desenarea unei linii nu trebuie dect s apelai de mai multe ori funcia de scriere" a unui pixel i s modificai corespunztor coordonatele x i y. n realitate, putei s executai orice operaie de desenare folosind numai funciile de scriere i de citire a unui pixel, doar dac nu v deranjeaz s ateptai rezultatele. Pentru un sistem grafic este mult mai eficient manipularea operaiilor de desenare a unei linii sau a altor operaii grafice complexe la nivelul driverului dispozitivului, care poate conine un cod optimizat pentru executarea acestor operaii. Mai mult, pe msur ce tehnologia de afiare devine tot mai complex, plcile specializate conin coprocesoare tot mai performante, care permit chiar componentelor video hardware s fac desenarea figurilor. Interfaa Windows GDI conine i funciile SetPixel i GetPixel. Dei vom folosi funcia SetPixel n programul CONNECT din Capitolul 7, n programele de grafic propriu-zis aceste funcii 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 circumferina unei elipse) i curbe Bezier. Cele apte funcii 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. (Interfaa GDI din Windows NT accept nc trei funcii pentru desenarea liniilor: ArcTo, AngleArc i PolyDraw. Aceste funcii nu sunt ns acceptate n Windows 95.) Aspectul liniilor desenate cu aceste funcii poate fi influenat de cinci atribute ale contextului de dispozitiv: poziia curent a peniei (numai pentru funciile LineTo, PolylineTo i PolyBezierTo), penia selectat, modul de afiare a fondului (numai pentru peniele care nu deseneaz cu o culoare compact), culoarea fondului (pentru modul OPAQUE de afiare a fondului) i modul de desenare. Din motive pe care le voi arta puin mai trziu, tot n aceast seciune voi prezenta i funciile Rectangle, Ellipse, RoundRect, Chord i Pie, chiar dac aceste funcii sunt folosite i pentru desenarea suprafeelor pline. Funcia LineTo este una dintre puinele funcii GDI care nu are nevoie de dimensiunile complete ale obiectului ce urmeaz s fie desenat. Funcia LineTo deseneaz o linie de la poziia curent" definit n contextul de dispozitiv pan la punctul specificat la apelarea funciei (exclusiv acest punct). Poziia curent este folosit ca

punct de plecare i de alte funcii GDI. n contextul de dispozitiv prestabilit, poziia curent are iniial valoarea (0,0). Dac apelai funcia LineTo fr s stabilii mai nti poziia curent, funcia deseneaz o linie pornind din colul din stnga-sus al zonei client. Dac dorii s desenai o linie din punctul (xStart, yStart) pan n punctul (xEnd, yEnd) trebuie s apelai mai nti funcia MoveToEx ca s stabilii ca poziie curent punctul (xStart, ySfart):
MoveToEx (hdc, xStart, yStart, &pt) ;

unde pt este o structur de tip POINT, definit n fiierele antet din Windows astfel:
typedef struct tag POINT { LONG x ; LONG y ; } POINT ;

Funcia MoveToEx nu deseneaz nimic, ci doar schimb poziia curent. Poziia curent anterioar este stocat n structura de tip POINT. Putei apoi s folosii funcia LineTo ca s desenai linia:
LineTo (hdc, xEnd, yEnd);

Apelul de mai sus deseneaz o linie pn n punctul (xEnd, yEnd), exclusiv acest punct. Dup apelul funciei LineTo, poziia curent devine (xEnd, yEnd). O scurt not istoric: n versiunile pe 16 bii ale sistemului de operare Windows, funcia folosit pentru modificarea poziiei curente era MoveTo, care avea numai trei parametri - variabila handle a contextului de dispozitiv i coordonatele x i y. Funcia MoveTo returna poziia curent anterioar sub forma a dou valori pe 16 bii mpachetate ntr-un ntreg fr semn pe 32 de bii. n versiunile pe 32 de bii ale sistemului de operare (Windows NT i Windows 95) coordonatele sunt valori pe 32 de bii. Deoarece n versiunea pe 32 de bii a limbajului C nu este definit un tip de date ntreg pe 64 de bii, funcia MoveTo a fost nlocuit cu funcia MoveToEx. Aceast modificare a fost necesar chiar dac valoarea returnat de funcia MoveTo nu a fost folosit aproape niciodat cu adevrat n programare ! Iat o veste bun: dac nu avei nevoie de poziia curent anterioar - ceea ce se ntmpl destul de des - putei s dai ultimului parametru al funciei MoveToEx valoarea NULL. De fapt, dac dorii s transformai codul pe 16 bii existent n cod pentru Windows 95, putei s definii o macroinstruciune, astfel:
#define MoveTo (hdc, x, y) MoveToEx (hdc, x, y, NULL)

Vom folosi aceast macroinstruciune n mai multe programe din capitolele urmtoare. Iat ns i o veste proast. Chiar dac n Windows 95 coordonatele par a fi valori pe 32 de bii, din acestea sunt folosii numai cei 16 bii mai puin semnificativi. Coordonatele pot avea valori numai ntre -32.768 i 32.767. Putei s obinei poziia curent a peniei apelnd funcia GetCurrentPosition astfel:
GetCurrentPositionEx (hdc, &pt) ;

Secvena de cod care urmeaz deseneaz n zona client a ferestrei o gril format din linii aflate la distana de 100 de pixeli una de alta, ncepnd din colul din stnga-sus. Variabila hwnd reprezint variabila handle a ferestrei, hdc este variabila handle a contextului de dispozitiv, iar x i y sunt numere ntregi:
GetClientRect (hwnd, &rect); for (x = 0 ; x < rect.right ; x +=100) { MoveToEx (hdc, x, 0, NULL) ; LineTo (hdc, x, rect.bottom) ; } for (y = 0 ; y < rect.bottom ; y +=100) { MoveToEx (hdc, 0, y. NULL) ; LineTo (hdc, rect.right, y) ; }

Dei pare incomod folosirea a dou funcii pentru desenarea unei singure linii, poziia curent devine util atunci cnd trebuie s desenai o serie de linii conectate. De exemplu, s presupunem c definii o matrice de cinci puncte (10 valori) care marcheaz un dreptunghi:

POINT pt [5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;

Remarcai faptul c ultimul punct este acelai cu primul. Acum trebuie doar s folosii funcia MoveToEx pentru primul punct i apoi funcia LineTo pentru punctele urmtoare:
MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ; for (i = 1; i < 5; i++) LineTo (hdc, pt[i].x, pt[i].y) ;

Deoarece funcia 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. Dei suprascrierea unui punct nu este, n mod normal, o problem n cazul monitoarelor video, s-ar putea s nu aib un rezultat prea plcut n cazul unui plotter sau n alte moduri de afiare (despre care vom discuta n curnd). Atunci cnd avei o matrice de puncte pe care vrei s le conectai prin linii, putei s desenai mai uor liniile folosind funcia Polyline. Instruciunea de mai jos deseneaz acelai dreptunghi ca i secvena de cod din exemplul anterior:
Polyline (hdc, pt, 5) ;

Ultimul parametru reprezint numrul de puncte. Aceast valoare putea fi reprezentat i prin expresia (sizeof(pt) / sizeof(POINT)). Funcia Polyline are acelai efect ca i o funcie MoveToEx urmat de mai multe funcii LineTo, exceptnd faptul c funcia Polyline nu schimb poziia curent. Funcia PolylineTo este puin diferit. Aceasta folosete ca punct de plecare poziia curent i stabilete ca poziie curent sfritul ultimei linii desenate. Codul de mai jos deseneaz acelai dreptunghi folosit ca exemplu i mai sus:
MoveToEx (hdc, pt[0].x, pt[0].y, NULL) ; PolylineTo (hdc, pt + 1,4) ;

Dei putei s folosii funciile Polyline i PolylineTo i pentru un numr mic de funcii, acestea sunt mai utile atunci cnd desenai o curb complex format din sute sau chiar mii de linii. De exemplu, s presupunem c vrei s desenai o sinusoid. Programul SINEWAVE, prezentat n Figura 4-4, v arat cum s facei acest lucru.
/*----------------------------------------SINEWAVE.C -- Sine Wave Using Polyline (c) Charles Petzold, 1996 -----------------------------------------*/ #include <windows.h> #include <math.h> #define NUM 1000 #define TWOPI (2 * 3.14159) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "SineWave" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Sine Wave Using Polyline", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ;

DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient ; HDC hdc ; int i; PAINTSTRUCT ps ; POINT pt [NUM] ; switch (iMsg) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; MoveToEx (hdc, 0, cyClient / 2, NULL) ; LineTo (hdc, cxClient, cyClient / 2) ; for (i = 0 ; i < NUM ; i++) { pt[i].x = i * cxClient / NUM ; pt[i].y = (int) (cyClient / 2 * (1 - sin (TWOPI * i / NUM))) ; } Polyline (hdc, pt, NUM) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 4-4. Programul SINEWAVE

Acest program conine 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 primete valorile corespunztoare unui ciclu al unei sinusoide lrgite astfel nct s umple zona client. Curba este desenat cu funcia Polyline. Deoarece funcia Polyline este implementat la nivelul driverului, desenarea se face mult mai repede dect dac ar fi fost apelat de 1000 de ori funcia LineTo. Rezultatele programului sunt prezentate n Figura 4-5.

Figura 4-5. Fereastra afiat de programul SINEWAVE. Dreptunghiuri de ncadrare n continuare a vrea s discutm despre funcia Arc, funcie care deseneaz o curb eliptic. Dar funcia Arc nu prea are sens dac nu discutm mai nti despre funcia Ellipse, care, la rndul ei, nu are sens fr o discuie despre funcia Rectangle. Si dac discutm despre funciile Rectangle i Ellipse, putem la fel de bine s discutm i despre funciile RoundRect, Chord i Pie. Problema este c funciile Rectangle, Ellipse, RoundRect, Chord i Pie nu sunt tocmai nite funcii de desenare a liniilor. Desigur, ele deseneaz i linii, dar i coloreaz zona nchis, cu pensula curent de colorare a suprafeelor. n mod prestabilit, aceast pensul are culoarea alb, aa c s-ar putea ca operaia s nu fie att de

evident atunci cnd ncercai pentru prima dat s utilizai aceste funcii. Dei, n sens strict, funciile aparin unei alte seciuni din capitolul de fa, Desenarea suprafeelor pline", vom discuta acum despre fiecare. Funciile de mai sus sunt asemntoare prin faptul c sunt construite pe baza unui dreptunghi de ncadrare" (bounding box"). Dumneavoastr definii o caset care ncadreaz obiectul - dreptunghiul de ncadrare - i Windows deseneaz obiectul n acest dreptunghi. Cea mai simpl dintre aceste funcii deseneaz un dreptunghi:
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

Punctul (xLeft, yTop) reprezint colul din stnga-sus al dreptunghiului, iar punctul (xRight, yBottom) reprezint colul din dreapta-jos. O figur desenat prin apelarea funciei Rectangle e prezentat n Figura 4-6. Laturile dreptunghiului sunt ntotdeauna paralele cu laturile orizontal i vertical ale ecranului.
xLeft xRight xTop

xBottom

Figura 4-6. O figur desenat folosind funcia Rectangle. Programatorii care au mai lucrat cu sisteme grafice sunt deja familiarizai probabil cu problema pixelului suplimentar. Unele sisteme grafice deseneaz figurile astfel nct s includ i coordonatele din dreapta i de jos, iar alte sisteme grafice deseneaz figurile pn la aceste coordonate (fr s le includ). Windows folosete cea dea doua metod; voi prezenta n continuare o comparaie, care v va ajuta s nelegei mai bine mecanismul. S presupunem c avem urmtorul apel de funcie:
Rectangle (hdc, 1, 1, 5, 4) ;

Am menionat mai sus faptul c Windows deseneaz figura ntr-un dreptunghi de ncadrare. Putei s v gndii 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 stnga i de sus ale zonei client are limea de un pixel. Windows folosete pensula curent pentru colorarea celor doi pixeli din interiorul dreptunghiului. Dup ce ai aflat cum se deseneaz un dreptunghi, folosind aceiai parametri, vei putea, la fel de simplu, s desenai i o elips:
Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;

n Figura 4-7 este prezentat o figur (mpreun cu dreptunghiul de ncadrare) desenat cu ajutorul funciei Ellipse.

Funcia pentru desenarea unui dreptunghi cu colurile rotunjite folosete acelai dreptunghi de ncadrare ca i funciile Rectangle i Ellipse, dar are nc doi parametri:
RoundRect (hdc, xLeft, yTop, xRight, yBottom, xCornerEllipse, yCornerEllipse) ;

n Figura 4-8 este prezentat o figur desenat cu aceast funcie.

Pentru desenarea colurilor rotunjite, Windows folosete o mic elips. Limea elipsei este xCornerEllipse iar nlimea acesteia este yCornerEllipse. Imaginai-v c Windows mparte elipsa n patru pri egale, folosind fiecare sfert pentru cte un col al dreptunghiului. Colurile dreptunghiului sunt cu att mai rotunjite cu ct valorile parametrilor xCornerEllipse i yCornerEllipse sunt mai mari. Dac xCornerEllipse este egal cu jumtate din diferena dintre xLeft i xRight iar yCornerEllipse este egal cu jumtate din diferena dintre yTop i yBottom, atunci funcia RoundRect va desena o elips. Colurile rotunjite ale dreptunghiului din Figura 4-8 au fost desenate folosind o elips ale crei dimensiuni au fost calculate cu urmtoarele formule:
xCornerEllipse = (xRight - xLeft) / 4 ; yCornerEllipse = (yTop - yBottom) / 4 ;

Aceasta este o abordare simplist, iar rezultatele nu arat foarte bine, deoarece rotunjirea colurilor este mai pronunat pe latura mai mare a dreptunghiului. Pentru corectarea acestei probleme, probabil vei stabili dimensiuni reale pentru parametrii xCornerEllipse i yCornerEllipse. Funciile Arc, Chord i Pie primesc aceiai parametri:
Arc (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ; Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ; Pie (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;

n Figura 4-9 este prezentat o linie desenat cu ajutorul funciei Arc; Figurile 4-10 i 4-11 prezint desene realizate cu ajutorul funciilor Chord i Pie. Windows folosete 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 circumferina elipsei. De asemenea, Windows folosete 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 funciei Arc, Windows i-a terminat treaba, deoarece arcul este o linie eliptic, nu o suprafa plin. n cazul funciei Chord, Windows unete capetele arcului, iar n cazul funciei Pie, unete capetele arcului cu centrul elipsei. Interioarele suprafeelor nchise obinute astfel sunt colorate cu pensula curent. S-ar putea s v ntrebai de ce trebuie s folosii aceste poziii de nceput i de sfrit n funciile Arc, Chord i Pie. De ce s nu specificai chiar punctele de nceput i de sfrit ale arcului pe circumferina elipsei? Ei bine, putei s o facei, dar mai nti trebuie s determinai unde se afl aceste puncte, pe cnd metoda folosit de Windows rezolv problema fr s fie nevoie de o asemenea precizie. Programul LINEDEMO prezentat n Figura 4-12 deseneaz un dreptunghi, o elips, un dreptunghi cu colurile rotunjite i dou linii, dar nu n aceast ordine. Programul demonstreaz faptul c funciile care deseneaz

suprafee nchise coloreaz aceste suprafee, deoarece liniile desenate anterior sunt ascunse n spatele elipsei. Rezultatele rulrii programului LINEDEMO sunt prezentate n Figura 4-13.
/*-------------------------------------------------LINEDEMO.C -- Line-Drawing Demonstration Program (c) Charles Petzold, 1996 --------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "LineDemo" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Line Demonstration", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient ; HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; Rectangle (hdc, cxClient / 8, cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8) ; MoveToEx (hdc, 0, 0, NULL) ; LineTo (hdc, cxClient, cyClient) ; MoveToEx (hdc, 0, cyClient, NULL) ; LineTo (hdc, cxClient, 0) ; Ellipse (hdc, cxClient / 8, cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8) ; RoundRect (hdc, cxClient / 4, cyClient / 4, 3 * cxClient / 4, 3 * cyClient / 4, cxClient / 4, cyClient / 4) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ;

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

Figura 4-12. Programul LINEDEMO. Curbe Bezier (Bezier splines) Termenul spline" se referea odat la o rigl flexibil din lemn, metal sau cauciuc (florar), folosit pentru desenarea curbelor. De exemplu, dac aveai un numr de puncte aparinnd unui grafic i doreai s desenai o curb ntre acestea (prin interpolare sau extrapolare) trebuia s marcai mai nti punctele pe hrtie. Ancornd apoi rigla n punctele respective, nu v rmnea dect s trasai curba de-a lungul riglei. (V rog s nu rdei. Metoda evocat seamn cu practici utilizate n secolul trecut, dar eu tiu din proprie experien c riglele mecanice i riglele de calcul erau nc folosite n anumite instituii acum 15 ani.) n prezent, spline" se refer la o funcie matematic. Aceste funcii au diferite forme. Curbele Bezier sunt unele dintre cele mai cunoscute pentru programarea grafic. Aceast funcie este o achiziie destul de recent n arsenalul instrumentelor grafice de lucru disponibile la nivelul sistemului de operare i are o origine mai puin obinuit. 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.

Figura 4-13. Fereastra afiat de programul LINEDEMO. 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 funciile Bezier sunt folosite pentru toate curbele - elipsele sunt aproximare prin curbe Bezier De asemenea, funciile 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 acioneaz ca nite magnei" care deformeaz linia dreapt dintre cele dou puncte finale. Acest lucru este ilustrat cel mai bine de un program interactiv, numit Bezier prezentat n Figura 4-14.
/*--------------------------------------BEIZER.C -- Bezier Splines Demo (c) Charles Petzold, 1996 ---------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Bezier" ; HWND hwnd ; MSG msg ;

WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Bezier Splines", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void DrawBezier (HDC hdc, POINT apt[]) { PolyBezier (hdc, apt, 4) ; MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ; LineTo (hdc, apt[1].x, apt[1].y) ; MoveToEx (hdc, apt[2].x, apt[2].y, NULL) ; LineTo (hdc, apt[3].x, apt[3].y) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static POINT apt[4] ; HDC hdc ; int cxClient, cyClient ; PAINTSTRUCT ps ; switch (iMsg) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; apt[0].x = cxClient / 4 ; apt[0].y = cyClient / 2 ; apt[1].x = cxClient / 2 ; apt[1].y = cyClient / 4 ; apt[2].x = cxClient / 2 ; apt[2].y = 3 * cyClient / 4 ; apt[3].x = 3 * cxClient / 4 ; apt[3].y = cyClient / 2 ; return 0 ; case WM_MOUSEMOVE: if (wParam & MK_LBUTTON || wParam & MK_RBUTTON) { hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (WHITE_PEN)) ; DrawBezier (hdc, apt) ; if (wParam & MK_LBUTTON) { apt[1].x = LOWORD (lParam) ; apt[1].y = HIWORD (lParam) ; } if (wParam & MK_RBUTTON) { apt[2].x = LOWORD (lParam) ; apt[2].y = HIWORD (lParam) ; } SelectObject (hdc, GetStockObject (BLACK_PEN)) ;

DrawBezier (hdc, apt) ; ReleaseDC (hwnd, hdc) ; } return 0 ; case WM_PAINT: InvalidateRect (hwnd, NULL, TRUE) ; hdc = BeginPaint (hwnd, &ps) ; DrawBezier (hdc, apt) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 4-14. Programul BEZIER.

Deoarece acest program folosete 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 (dei acesta poate s vi se par evident). n schimb, putei s folosii programul ca s experimentai modul de funcionare a curbelor Bezier. n acest program, cele dou puncte finale ale curbei sunt stabilite, pe vertical, la jumtatea zonei client i, pe orizontal, la 1/4 i 3/4 din limea zonei client. Cele dou puncte de control pot fi mutate, primul - apsnd butonul din stnga al mouse-ului i deplasnd mouse-ul pn cnd indicatorul Iui ajunge n locul dorit, iar al doilea -apsnd butonul din dreapta al mouse-ului i deplasnd mouse-ul. Figura 4-15 prezint un rezultat tipic. n afara curbei Bezier, programul deseneaz i dou linii drepte: una de la primul capt al curbei (cel din stnga) pn la primul punct de control, i a doua de la cellalt capt al curbei (din dreapta) pn la al doilea punct de control. Funciile Bezier sunt considerate utile pentru proiectarea asistat de calculator, datorit ctorva caracteristici: n primul rnd, cu puin practic, de obicei putei s manipulai curba pn ajunge la o form apropiat de cea dorit. n al doilea rnd, curbele Bezier sunt foarte uor de controlat. n unele variante ale funciilor 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 pornete calculul formulei Bezier.) De asemenea, unele forme ale funciilor 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 rnd, o alt caracteristic a curbelor Bezier implic relaiile 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 aceeai direcie 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 aceeai direcie cu aceast linie. (Acestea sunt alte dou ipoteze de la care este derivat formula Bezier.) n al patrulea rnd, curbele Bezier sunt satisfctoare i din punct de vedere estetic. tiu c acesta este un criteriu subiectiv, dar nu este numai prerea mea. nainte de apariia sistemului de operare Windows 95, trebuia s creai propriile curbe Bezier folosind funcia Polyline. De asemenea, trebuia s cunoatei urmtoarele ecuaii parametrice ale curbelor Bezier: x(t) = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3 y(t) = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3 unde (x0, y0) este punctul de nceput al curbei, (x3, y3) este punctul de sfrit al curbei, iar cele dou puncte de control sunt (x1, y1) i (x2, y2). Curba este trasat pentru t avnd valori de la 0 la 1.

Figura 4-15. Fereastra afiat de programul BEZIER. n Windows 95 nu mai este nevoie s tii aceste formule. Pentru trasarea uneia sau a mai multor curbe Bezier conexe, putei s folosii instruciunea:
PolyBezier (hdc, pt, iCount) ;

sau instruciunea:
PolyBezierTo (hide, pt, iCount) ;

n ambele cazuri, pt este o matrice de structuri POINT. Pentru funcia 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. Urmtoarele curbe Bezier au nevoie doar de trei puncte, deoarece punctul de nceput al urmtoarei curbe Bezier este punctul de sfrit al primei curbe, i aa mai departe. Parametrul iCount reprezint numrul de puncte din matrice, adic unu plus de trei ori numrul curbelor pe care vrei s le desenai. Funcia PolyBezierTo folosete poziia curent ca punct de nceput pentru prima curb Bezier. Fiecare curb desenat are nevoie doar de trei puncte. La returnarea funciei, poziia curent este punctul final al ultimei curbe desenate. Atenie: atunci cnd trasai o serie de curbe Bezier conectate ntre ele, legtura va fi lin numai dac al doilea punct de control al primei curbe, punctul de sfrit 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 aceeai linie dreapt.
Folosirea penielor de stoc

Atunci cnd apelai oricare dintre funciile de desenare a liniilor despre care am discutat, Windows folosete pentru desenarea liniilor penia" (pen) curent selectat n contextul de dispozitiv. Penia selectat determin culoarea, limea i tipul de linie (acestea pot fi continue, punctate sau ntrerupte). Penia din contextul prestabilit de dispozitiv se numete 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 penie de stoc" (stock pen) furnizate de Windows. Celelalte dou penie de stoc sunt WHITE_PEN i NULL_PEN. NULL_PEN este o peni care nu deseneaz nimic. n plus, putei s creai penie proprii. n programele Windows putei ajunge la aceste penie prin variabile handle. n fiierele antet din Windows este definit tipul de date HPEN, care reprezint o variabil handle a unei penie. Putei s definii o variabil (de exemplu, hPen) folosind aceast definiie de tip:
HPEN hPen ;

Putei s obinei variabila handle a uneia dintre peniele de stoc apelnd funcia GetStockObject. De exemplu, s presupunem c vrei s folosii penia de stoc numit WHITE_PEN. Obinei variabila handle a acesteia astfel:
hPen = GetStockObject (WHITE_PEN) ;

Apoi trebuie s selectai n contextul de dispozitiv penia a crei variabil ai obinut-o, apelnd funcia SelectObject:
SelectObject (hdc, hPen) ;

Dup acest apel toate liniile pe care le desenai vor folosi penia WHITE_PEN, pn cnd selectai o alt peni n contextul de dispozitiv sau tergei contextul de dispozitiv. n loc s definii explicit variabila hPen, putei s combinai funciile GetStockObject i SelectObject ntr-o singur instruciune:
SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

Dac apoi vrei s revenii la penia BLACK_PEN, putei s obinei o variabil handle a acesteia i s o selectai n contextul de dispozitiv tot cu o singur instruciune:
SelectObject (hdc, GetStockObject (WHITE_PEN));

Funcia SelectObject returneaz variabila handle a peniei selectate anterior n contextul de dispozitiv. Dac deschidei un nou context de dispozitiv i executai instruciunea:
hPen = SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

atunci penia curent selectat n contextul de dispozitiv va fi WHITE_PEN i hPen va fi variabila handle a peniei BLACK_PEN. Putei apoi s selectai penia BLACK_PEN n contextul de dispozitiv cu urmtoarea instruciune:
SelectObject (hdc, hPen) ;

Crearea, selectarea i tergerea penielor Dei folosirea penielor definite ca obiecte de stoc este foarte convenabil, avei la dispoziie doar un numr limitat de opiuni: o peni neagr, pentru linie continu, una alb pentru linie continu sau una care nu deseneaz nimic. Dac dorii ceva diferit, trebuie s creai propriile dumneavoastr penie. Iat procedura general: creai mai nti o peni logic" (logical pen) care este doar o descriere a unei penie, folosind funciile CreatePen sau CreatePenIndirect. (De asemenea, putei s folosii i funcia ExtCreatePen, despre care vom discuta atunci cnd vom ajunge la cile GDI.) Aceste funcii returneaz o variabil handle a peniei logice. Selectai penia n contextul de dispozitiv apelnd funcia SelectObject. Apoi putei s desenai linii cu penia selectat. La un moment dat nu poate fi selectat, ntr-un context de dispozitiv, dect o peni. Dup ce tergei contextul de dispozitiv (sau dup ce selectai o alt peni n contextul de dispozitiv) putei s tergei penia logic pe care ai creat-o apelnd funcia DeleteObject. Dup ce facei acest lucru, variabila handle a peniei nu mai este valid. O peni logic este un obiect GDI". Dumneavoastr creai i folosii penia, dar aceasta nu aparine programului, ci modulului GDI. Penia este unul dintre cele ase obiecte GDI pe care putei s le creai. Celelalte cinci sunt pensulele, imaginile bitmap, regiunile, fonturile i paletele. Exceptnd paletele, toate celelalte obiecte pot fi selectate n contextul de dispozitiv folosind funcia SelectObject. Pentru folosirea obiectelor GDI trebuie s respectai urmtoarele reguli: La sfritul programului tergei toate obiectele GDI pe care le-ai creat. Nu tergei obiectele GDI n timp ce sunt selectate ntr-un context de dispozitiv valid. Nu tergei obiectele de stoc. Chiar dac sunt nite reguli rezonabile, neplcerile pot exista. Voi prezenta cteva exemple ca s nelegei cum funcioneaz aceste reguli i pentru a putea evita eventuale probleme. Sintaxa generic a funciei CreatePen este:
hPen = CreatePen (iPenStyle, iWidth, rgbColor) ;

Parametrul iPenStyle precizeaz dac se deseneaz o linie continu, o linie punctat sau una ntrerupt. Parametrul iPenStyle poate fi unul dintre identificatorii definii n fiierele antet din Windows. Figura 4-16 prezint stilul de linie produs de fiecare identificator.

Figura 4-16. Cele apte stiluri de penite.

Pentru stilurile PS_SOLID, PS_NULL i PS_INSIDEFRAME, parametrul iWidth reprezint grosimea liniei. Dac iWidth are valoarea 0, liniile desenate de Windows vor avea grosimea de un pixel. Liniile desenate cu peniele de stoc au grosimea de un pixel. Dac parametrul iWidth are o valoare mai mare de 1, Windows va desena o linie continu. Parametrul rgbColor din funcia CreatePen este un numr ntreg fr semn reprezentnd culoarea peniei. Pentru toate stilurile de penie, exceptnd PS_INSIDEFRAME, atunci cnd selectai penia n contextul de dispozitiv, Windows convertete acest parametru la cea mai apropiat culoare pur pe care o poate reprezenta dispozitivul de afiare. 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 cnd este folosit pentru funcii care definesc o suprafa plin: pentru toate stilurile de penie, exceptnd PS_INSIDEFRAME, dac grosimea liniei este mai mare de un pixel, penia este centrat pe linie, astfel nct 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. Putei s creai o peni i cu ajutorul unei structuri de tip LOGPEN (logical pen"), apelnd funcia CreatePenIndirect. Dac programul folosete un numr mai mare de penie diferite pe care Ie iniializai n codul surs, aceast metod este mai eficient. Mai nti definii o variabil de tip LOGPEN, cum ar fi logpen:
LOGPEN logpen ;

Aceast structur are trei membri: lopnStyle (UINT) este stilul peniei, lopn Width (POINT) este grosimea peniei n uniti logice, iar lopnColor (COLORREF) este culoarea peniei. Membrul lopn Width este o structur de tip POINT, dar Windows folosete numai membrul lopnWidth.x al acestei structuri i ignor membrul lopnWidth.y. Apoi creai penia, transmind funciei CreatePenIndirect adresa structurii logpen:
hPen = CreatePenIndirect (&logpen) ;

De asemenea, putei s obinei informaii despre o peni logic existent. Dac avei deja o variabil handle a unei penie putei s copiai datele care definesc penia logic ntr-o structur de tip LOGPEN folosind funcia GetObject:
GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;

Remarcai faptul c funciile CreatePen i CreatePenIndirect nu au nevoie de o variabil handle a contextului de dispozitiv. Aceste funcii creeaz penie logice care nu au nici o legtur cu contextul de dispozitiv pn cnd nu apelai funcia SelectObject. De exemplu, putei s folosii aceeai peni logic pentru mai multe dispozitive, cum ar fi imprimanta i ecranul. Iat o metod pentru crearea, selectarea i tergerea penielor. S presupunem c programul folosete trei penie: una neagr cu grosimea de un pixel, una roie cu grosimea de trei pixeli i una neagr cu linie punctat. Mai nti definii variabilele pentru stocarea variabilelor handle ale celor trei stilouri:
static HPEN hPen1, hPen2, hPen3;

n timpul prelucrrii mesajului WM_CREATE, creai cele trei penie:


hPen1 = CreatePen (PS_SOLID, 1, 0); hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)); hPen3 = CreatePen (PS_DOT, 0, 0);

n timpul prelucrrii mesajului WM_PAINT (sau n orice alt moment n care avei o variabil handle valid a contextului de dispozitiv) putei s selectai oricare dintre aceste penie n contextul de dispozitiv i s desenai:
SelectObject (hdc, hPen2) ; [funcii de desenare a liniilor] SelectObject (hdc, hPen1) ; [alte funcii de desenare a liniilor]

n timpul prelucrrii mesajului WM_DESTROY putei s tergei cele trei penie create:
DeleteObject (hPen1) ; DeleteObject (hPen2) ; DeleteObject (hPen3) ;

Aceasta este metoda cea mai direct pentru crearea, selectarea i tergerea penielor, dar are dezavantajul c peniele create ocup spaiu n memorie pe toat durata rulrii programului. O alt soluie este s creai peniele necesare n timpul prelucrrii fiecrui mesaj WM_PAINT i s le tergei dup apelarea funciei EndPaint. (Putei s le tergei i nainte de apelarea funciei EndPaint, dar trebuie s avei grij s nu tergei penia curent selectat n contextul de dispozitiv.) De asemenea, putei s creai peniele atunci cnd sunt necesare, combinnd funciile CreatePen i SelectObject n aceeai instruciune:
SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Din acest moment, vei folosi o peni pentru linii ntrerupte de culoare roie. Dup ce terminai de fcut desenele cu acest tip de linie, putei s tergei penia. Hopa! Cum putei s tergei penia dac nu ai salvat variabila handle a acesteia? V amintii c funcia SelectObject returneaz variabila handle a peniei selectate anterior n contextul de dispozitiv. Aa c putei s tergei penia selectnd n contextul de dispozitiv penia de stoc BLACK_PEN i tergnd variabila handle returnat de funcia SelectObject:
DeleteObject (SelectObject (hdc, GetStockObject (BLACK PEN))) ;

Exist i o alt metod. Atunci cnd selectai n contextul de dispozitiv o peni nou creat, salvai variabila handle returnat de funcia SelectObject;
hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Ce este hPen? Dac aceasta este prima apelare a funciei SelectObject de la obinerea contextului de dispozitiv, atunci hPen este variabila handle a obiectului de stoc BLACK_PEN. Putei acum s selectai penia BLACK_PEN n contextul de dispozitiv i s o tergei pe cea creat (variabila handle returnat la a doua apelare a funciei SelectObject) printr-o singur instruciune:
DeleteObject (SelectObject (hdc, hPen)) ;

Umplerea golurilor Folosirea penielor pentru linii punctate sau pentru linii ntrerupte ridic o ntrebare interesant: ce se ntmpl cu pauzele dintre puncte sau dintre liniue? Culoarea acestor spaii 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 spaiile cu culoarea fondului, care n mod prestabilit este alb. Rezultatele sunt aceleai n cazul pensulei WHITE_BRUSH, folosit de majoritatea programelor n clasa ferestrei pentru tergerea fondului. Putei s schimbai culoarea fondului pentru completarea spaiilor goale dintre linii, prin apelarea funciei SetBkColor:
SetBkColor (hdc, rgbColor) ;

Ca i n cazul valorii rgbColor folosit pentru culoarea peniei, Windows convertete valoarea specificat ntro culoare pur. Putei s obinei culoarea defint n contextul de dispozitiv prin apelarea funciei GetBkColor. De asemenea, putei s mpiedicai colorarea spaiilor stabilind modul TRANSPARENT de desenare a fondului:
SetBkMode (hdc, TRANSPARENT) ;

Windows va ignora culoarea fondului i nu va mai colora spaiile goale. Modul de desenare a fondului (TRANSPARENT sau OPAQUE) poate fi obinut cu ajutorul funciei GetBkMode.

Moduri de desenare

Apectul liniilor desenate pe ecran este afectat i de modul de desenare definit n contextul de dispozitiv. Imaginai-v c desenai o linie a crei culoare este bazat nu numai pe culoarea peniei, ci i pe culoarea original a suprafeei pe care se face desenarea. Imaginai-v o cale prin care s putei folosi aceeai peni ca s desenai 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, putei s o obinei prin schimbarea modului de desenare. Atunci cnd folosete o peni pentru desenarea unei linii, Windows face de fapt o operaie boolean ntre pixelii peniei i pixelii de pe suprafaa destinaie. Efectuarea operaiei booleene ntre pixeli se numete operaie rastru" (ROP - raster operation). Deoarece desenarea unei linii implic numai dou modele de pixeli (penia i destinaia), operaia boolean se numete operaie rastru binar" sau ROP2. Windows definete 16 coduri ROP2 care specific modul de combinare ntre pixelii peniei i pixelii suprafeei, n contextul de dispozitiv prestabilit, modul de desenare definit este R2_COPYPEN, ceea ce nseamn c Windows copiaz pixelii peniei pe suprafaa destinaie - adic modul normal n care suntem obinuii s funcioneze 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 destinaiei (culoarea zonei client a ferestrei) poate fi negru (reprezentat prin valorea 0) sau alb (1). La fel, culoarea peniei poate fi alb sau negru. Exist patru combinaii posibile n cazul folosirii unei penie alb/negru pe o suprafa alb/negru: alb pe alb, alb pe negru, negru pe alb i negru pe negru. Ce se ntmpl cu destinaia dup ce desenai cu penia? O posibilitate este ca linia s fie desenat ntotdeauna cu negru, indiferent de culoarea peniei i a destinaiei: acest mod de desenare este indicat de codul R2_BLACK. O alt posibilitate este ca linia s fie desenat cu negru, exceptnd situaia n care att penia ct i destinaia sunt negre, caz n care linia este desenat cu alb. Dei pare puin ciudat, Windows are un nume pentru acest mod de desenare: R2_NOTMERGEPEN. Windows face o operaie orientat pe bii de tip SAU ntre pixelii destinaie i pixelii peniei, i inverseaz rezultatul. Tabelul de mai jos prezint toate cele 16 moduri de desenare ROP2. Tabelul indic modul n care sunt combinate culoarea peniei (P) i culoarea destinaiei (D) pentru obinerea culorii afiate. n coloana Operaia boolean" sunt folosite notaiile C pentru indicarea modului n care sunt combinai pixelii peniei cu pixelii destinaiei. Peni (P): Destinaie (D): Rezultate: 1 1 0 1 0 0 0 1 0 0 0 0 Operaia boolean 0 Mod de desenare R2_BLACK

0 0 0 1 ~(P | D) R2_NOTMERGEPEN 0 0 1 0 ~P & D R2_MASKNOTPEN 0 0 1 1 ~P R2_NOTCOPYPEN 0 1 0 0 P & ~D R2_MASKPENNOT 0 1 0 1 ~D R2_NOT 0 1 1 0 P^D R2_XORPEN 0 1 1 1 ~(P & D) R2_NOTMASKPEN 1 0 0 0 P&D R2_MASKPEN 1 0 0 1 ~(P ^ D) R2_NOTXORPEN 1 0 1 0 D R2_NOP 1 0 1 1 ~P | D R2_MERGENOTPEN 1 1 0 0 P R2_COPYPEN (prestabilit) 1 1 0 1 P | ~D R2_MERGEPENNOT 1 1 1 0 P|D R2_MERGEPEN 1 1 1 1 1 R2_WHITE Putei s selectai un nou mod de desenare n contextul de dispozitiv astfel:
SetROP2 (hdc, iDrawMode) ;

unde parametrul iDrawMode este una dintre valorile din coloana Mod de desenare" a tabelului de mai sus. Putei s obinei modul curent de desenare astfel:
iDrawMode = GetROP2 (hdc) ;

Modul de desenare prestabilit al contextului de dispozitiv este R2_COPYPEN, care transfer la destinaie culoarea peniei. Modul R2_NOTCOPYPEN deseneaz cu alb dac penia este neagr i cu negru dac penia este alb. Modul R2_BLACK deseneaz ntotdeauna cu negru, indiferent de culoarea peniei i a destinaiei. n mod similar, modul R2_WHITE deseneaz ntotdeauna cu alb. Modul R2_NOP este o operaie nul" - destinaia rmne nemodificat. Pentru nceput, am folosit ca exemplu un sistem monocrom. n realitate, pe un monitor monocrom Windows poate simula diferite tonuri de gri prin amestecarea pixelilor albi cu cei negri. Atunci cnd deseneaz cu o peni pe un fond avnd culori amestecate, Windows execut o operaie orientat pe bii pentru fiecare pixel. Modul R2_NOT inverseaz ntotdeauna destinaia, indiferent de culoarea acesteia i de culoarea peniei. Acest mod de desenare este util atunci cnd nu cunoatei culoarea destinaiei, deoarece liniile vor fi ntotdeauna vizibile. (Sau aproape ntotdeauna, pentru c n cazul unui fond 50% gri, liniile vor fi virtual invizibile.) Folosirea modului R2_NOT este ilustrat n programul BLOKOUT din Capitolul 6.
Desenarea suprafeelor pline

S mergem un pas mai departe i s trecem la desenarea figurilor. Cele apte funcii Windows pentru desenarea figurilor sunt prezentate n tabelul urmtor: Funcie Rectangle Ellipse RoundRect Chord Pie Polygon PolyPolygon Figura Dreptunghi cu coluri drepte Elips Dreptunghi cu coluri rotunjite Arc pe circumferina unei elipse, avnd capetele unite printr-o coard Suprafa de forma unei felii de plcint, reprezentnd un segment de elips. Figur geometric avnd mai multe laturi Mai multe figuri geometrice cu mai multe laturi

Windows deseneaz conturul figurilor folosind penia 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 desenrii unei linii simple. Tot ceea ce ai nvat 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 definete ase pensule de stoc: WHITE-_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH i NULL_BRUSH (sau HOLLOW_BRUSH). Putei s selectai una dintre aceste pensule n contextul de dispozitiv la fel cum selectai o peni de stoc. Windows definete tipul HBRUSH ca variabil handle a unei pensule, aa c putei s definii mai nti o variabil de acest tip:
HBRUSH hBrush ;

Putei s obinei variabila handle a pensulei de stoc GRAY_BRUSH apelnd funcia GetStockObject:.
hBrush = Get&toCkObject (GB*Y_BRUSH) ;

Putei s o selectai apoi n contextul de dispozitiv folosind funcia SelectObject:


SelectObject (hdc, hBrush) ;

Din acest moment, figurile desenate vor avea interiorul gri. Dac vrei s desenai o figur fr contur, selectai n contextul de dispozitiv penia NULL_PEN:
SelectObject (hdc, GetStockObject (NULL_PEN)) ;

Dac vrei s desenai o figur fr s i umplei interiorul, selectai n contextul de dispozitiv pensula NULL_BRUSH:

SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

Aa cum putei s creai penie proprii, putei s creai i pensule proprii. Vom trata n curnd i acest subiect Funcia Polygon i modul de umplere a poligoanelor Am discutat deja despre primele cinci funcii de desenare a suprafeelor pline. Polygon este cea de-a asea funcie de desenare a unei figuri colorate n interior, cu contur. Apelul funciei Polygon este asemntor cu cel al funciei Polyline:
Polygon (hdc, pt, iCount) ;

Parametrul pt este un pointer la o matrice de structuri POINT, iar iCount reprezint numrul 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 ntmpl i n cazul funciei Polyline.) Suprafaa nchis de poligon este colorat folosind pensula curent n dou moduri, n funcie 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 suprafeele nchise la care se poate ajunge din exteriorul poligonului prin traversarea unui numr impar de laturi (1, 3, 5 i aa mai departe). Celelalte suprafee interioare nu sunt umplute. Dac selectai WINDING ca mod de umplere, Windows coloreaz toate suprafeele nchise de poligon. Putei s selectai modul de umplere cu ajutorul funciei SetPolyFillMode: SetPolyFillMode (hdc, iMode) ; Cele dou moduri de umplere a poligoanelor sunt ilustrate cel mai bine pe modelul unei stele cu cinci coluri. n Figura 4-17 steaua din partea stng 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 (stng) i WINDING

Umplerea suprafeelor interioare


Interiorul figurilor Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon i PollyPolygon este umplut cu pensula - numit uneori i model" (pattern") - selectat n contextul de dispozitiv. O pensul este de fapt o imagine bitmap 8x8 repetat pe vertical i pe orizontal, pentru umplerea unei suprafee. Atunci cnd Windows folosete metoda amestecrii culorilor (dithering) pentru afiarea unui numr mai mare de culori dect ar fi n mod normal disponibile pe monitorul respectiv, de fapt folosete o pensul pentru fiecare culoare. Pe un ecran monocrom, Windows poate s afieze 64 de tonuri de gri prin amestecarea pixelilor pentru alb cu cei pentru negru. Pentru negru toi biii din matricea 8x8 au valoarea zero. Unul dintre cei 64 de bii are valoarea 1 (adic alb) pentru primul ton de gri, doi bii au valoarea 1 pentru al doilea ton de gri i aa mai departe, pn cnd toi cei 64 de bii au valoarea 1 ca s se obin albul pur. n cazul unui monitor color, culorile amestecate sunt tot imagini bitmap, dar domeniul disponibil de culori este mult mai mare. Windows conine patru funcii pe care putei s le folosii pentru crearea pensulelor logice. Selectai o pensul n contextul de dispozitiv folosind funcia Select-Object. Ca i peniele logice, pensulele logice sunt obiecte GDI. Trebuie s tergei toate pensulele pe care le-ai creat, dar nu trebuie s le tergei ct timp sunt selectate n contextul de dispozitiv. Iat prima funcie pentru crearea unei pensule logice:
hBrush = CreateSolidBrush (rgbColor) ;

Cuvntul Solid din numele acestei funcii nu nseamn c pensula folosete o culoare pur. Atunci cnd selectai pensula n contextul de dispozitiv, Windows creeaz o imagine bitmap 8x8 pentru culorile amestecate i folosete imaginea respectiv atunci cnd este necesar pensula selectat. Putei s creai i o pensul haurat" 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. Funcia care creeaz o pensul haurat este:
hBrush = CreateHatchBrush (iHatchStyle, rgbColor) ;

Parametrul iHatchStyle precizeaz aspectul haurii i poate avea una dintre urmtoarele valori: HS_HORIZONTAL, HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGONAL, HS_CROSS i HS_DIAGCROSS. Figura 4-18 prezint haurile produse de fiecare dintre stilurile de mai sus. Parametrul rgbColor din funcia CreateHatchBrush reprezint culoarea liniilor cu care se face haurarea. Atunci cnd selectai pensula n contextul de dispozitiv, Windows convertete aceast culoare n cea mai apropiat culoare pur. Spaiul dintre liniile de haur sunt colorate n funcie 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 rndul ei, ntr-o culoare pur) este folosit pentru umplerea spaiilor dintre linii, n acest caz, nici liniile de haur, nici culoarea de umplere nu pot fi culori amestecate. Dac modul de desenare a fondului este TRANSPARENT, Windows deseneaz liniile de haura fr s umple spaiile dintre acestea.

Figura 4-18. Cele ase stiluri de haura. Deoarece pensulele sunt ntotdeauna imagini bitmap 8x8, aspectul pensulelor haurate va depinde de rezoluia dispozitivului pe care se face afiarea. Fiecare dintre haurile 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 rezoluia 300 dpi (dots per inch) acelai dreptunghi de 32x16 pixeli ar ocupa o suprafa cu nlimea de 1 /9 inci si limea de 1/19 inci. Cu ajutorul funciei CreatePatternBrush putei s creai pensule proprii, bazate pe o imagine bitmap:
hBrush = CreatePatternBrush (hBitmap) ;

Vom discuta despre aceast funcie n seciunea rezervat imaginilor bitmap din capitolul de fa. Windows conine o funcie care poate nlocui toate celelalte trei funcii de creare a pensulelor (CreateSolidBrush, CreateHatchBrush i CreatePatternBrush):
hBrush = CreateBrushIndirect (&logbrush) ;

Variabila logbrush este o structur de tip LOGBRUSH (logical brush"). Cele trei cmpuri ale structurii sunt prezentate mai jos. Valoarea cmpului lbStyle determin modul n care Windows interpreteaz celelalte dou cmpuri: lbStyle (UINT) BS_SOLID BS_HOLLOW BS_HATCHED BS_PATTERN lbColor (COLORREF) Culoarea pensulei Ignorat Culoarea haurii Ignorat lbHatch (LONG) Ignorat Ignorat Stilul de haur Variabila handle a unei imagini bitmap

Am folosit mai devreme funcia SelectObject ca s selectm n contextul de dispozitiv o peni logic, funcia DeleteObject ca s tergem o peni logic i funcia GetObject ca s obinem informaii despre o peni logic. Putei s folosii aceleai funcii i pentru pensule. Dac avei o variabil handle a unei pensule, putei s selectai pensula n contextul de dispozitiv folosind funcia SelectObject:
SelectObject (hdc, hBrush) ;

O pensul creat poate fi tears cu ajutorul funciei DeleteObject:


DeleteObject (hBrush) ;

Totui, nu tergei pensula selectat n contextul de dispozitiv. Dac vrei s obinei informaii despre o pensul, apelai funcia GetObject:
GetObject (hBrush, sizeof (LOGBRUSH), (LPVOID) &logbrush) ;

unde variabila logbrush este o structur de tip LOGBRUSH (logical brush"). Modurile de mapare Pn acum am presupus c toate desenele se fac ntr-un sistem de coordonate raportate la colul, din stngasus al zonei client i folosind ca unitate de msur pixelul. Acesta este modul de lucru prestabilit, dar nu este singurul mod de lucru. Un atribut al contextului de dispozitiv care afecteaz aproape toate operaiile 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 strns legate de modul de mapare. Majoritatea funciilor GDI primesc coordonate sau dimensiuni ca parametri. Iat, ca exemplu, funcia TextOut:
TextOut (hdc, x, y, szBuffer, iLength) ;

Parametrii x i y indic poziia n care ncepe textul. Parametrul x reprezint poziia pe axa orizontal, iar parametrul y reprezint poziia pe axa vertical. Deseori, pentru indicarea acestui punct este folosit notaia (x, y). n funcia TextOut i, de fapt, n toate funciile GDI, coordonatele sunt furnizate n uniti logice". Windows trebuie s transforme unitile logice" n uniti 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 stabilete i orientarea axelor x i y, adic determin sensul n care cresc valorile coordonatelor x i y. Windows definete opt moduri de mapare. Acestea sunt prezentate n tabelul urmtor, folosind identificatorii definii n fiierele antet din Windows: Mod de mapare MM_TEXT MM_LOMETRIC MM_HIMETRIC MM_LOENGLISH MM_HIENGLISH MM_TWIPS MM_ISOTROPIC MM_ANISOTROPIC
SetMapMode (hdc, iMapHode) ;

Uniti logice Pixel 0,1 mm 0,01mm 0,01 mei 0,001 inci 1/1440 inci Arbitrar (x = y) Arbitrar (x != y)

Creterea valorilor axax axay Spre dreapta Spre dreapta Spre dreapta Spre dreapta Spre dreapta Spre dreapta Selectabil Selectabil n jos n sus n sus n sus nsus n sus Selectabil Selectabil

Putei s selectai modul de mapare folosind funcia SetMapMode: unde iMapMode este unul dintre cei opt identificatori definii pentru modurile de mapare. Putei s obinei modul de mapare curent folosind funcia GefMapMode:
iMapMode = GetMapMode (hdc) ;

Modul de mapare prestabilit este MM_TEXT. n acest mod de mapare unitile logice sunt aceleai cu unitile fizice, ceea ce v permite (sau, privind dintr-o alt perspectiv, v foreaz) s lucrai n pixeli. ntr-un apel al funciei TextOut care arat astfel:
TextOut (hdc, 8, 16, szBuffer, iLength) ;

textul ncepe la o distan de opt pixeli fa de marginea din stnga a zonei client i de 16 pixeli fa de marginea de sus a acesteia.

Twip este un cuvnt fabricat, care nseamn a douzecea parte dintr-un punct" (twentieth of a point"). Un punct - unitate de msur folosit n tipografie - este aproximativ egal cu 1/72 dintr-un inci, dar n sistemele grafice precum GDI se consider de cele mai multe ori c este exact 1/72 dintr-un inci. Un twip este 1/20 dintr-un punct, deci 1/1440 dintr-un inci.

Dac modul de mapare este MM_LOENGLISH, o unitate logic este egal cu o sutime de inci:
SetMapHode (hdc, HM_LOENGLISH) ;

Acum apelul de mai sus al funciei TextOut trebuie s arate astfel:


TextOut (hdc, 50, -100, szBuffer, iLength) ;

Textul afiat ncepe la 0,5 inci fa de marginea din stnga 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 trziu, atunci cnd 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 msur, putei s folosii numai modul de mapare MM_TEXT. Dac trebuie s afiai o imagine la dimensiunile reale n inci sau milimetri, putei s obinei informaiile necesare cu ajutorul funciei GetDeviceCaps i s facei singur scalarea. Celelalte moduri de mapare sunt doar metode convenabile de evitare a acestor operaii de scalare. Indiferent de modul de mapare folosit, toate coordonatele specificate la apelarea funciilor Windows trebuie s fie numere ntregi cu semn avnd valori de la -32 768 la 32 767. n plus, unele funcii care folosesc coordonate pentru punctele de nceput i de sfrit ale unui dreptunghi cer ca limea i nlimea dreptunghiului s nu depeasc 32 767. Coordonate de dispozitiv i coordonate logice S-ar putea s v punei urmtoarea ntrebare: Dac folosesc modul de mapare MM_LOENGLISH voi primi mesajele WM_SIZE n sutimi de inci?" Bineneles 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 funciile care nu fac parte din interfaa GDI i chiar pentru unele funcii GDI. Lucrurile se petrec n felul urmtor: modul de mapare fiind un atribut al contextului de dispozitiv, are efect numai atunci cnd folosii funcii GDI care primesc o variabil handle a contextului de dispozitiv ca parametru. GetSystemMetrics nu este o funcie GDI, aa c va returna n continuare dimensiunile n uniti de dispozitiv, adic n pixeli. Dei GetDeviceCaps este o funcie GDI care primete ca parametru o variabil handle a contextului de dispozitiv, Windows continu s returneze uniti de dispozitiv pentru identificatorii HORZRES i VERTRES, deoarece unul dintre scopurile acestei funcii este s furnizeze programului dimensiunea n pixeli a dispozitivului. Totui, valorile din structura TEXTMETRIC pe care le obinei prin apelarea funciei GetTextMetrics sunt furnizate n uniti logice. Dac modul de mapare este MM_LOENGLISH n momentul apelrii funciei, GetTextMetrics returneaz limea i nlimea caracterelor, n sutimi de inci. Atunci cnd apelai funcia GetTextMetrics ca s obinei nlimea i limea caracterelor, modul de mapare trebuie s fie acelai cu cel pe care l vei folosi atunci cnd afiai textul pe baza acestor dimensiuni. Pe msur ce voi prezenta diferite funcii GDI n acest capitol, voi preciza dac acestea utilizeaz coordonate logice sau coordonate de dispozitiv. Toate funciile despre care am discutat pn acum utilizeaz coordonate logice, exceptnd cele pentru stabilirea spaiilor ntre puncte sau liniue pentru stilurile de linii i ntre liniile de haur din modele. Acestea sunt independente de modul de mapare. Sistemele de coordonate ale dispozitivului Windows mapeaz coordonatele logice specificate n funciile GDI la coordonatele dispozitivului. nainte de a discuta despre sistemele de coordonate logice folosite de diferitele moduri de mapare, vom discuta despre diferitele sisteme de coordonate de dispozitiv definite n Windows pentru zona de afiare. Dei n general am lucrat doar n zona client a ferestrei, n anumite situaii Windows folosete alte dou sisteme de coordonate de dispozitiv. n toate sistemele de coordonate de dispozitiv sunt folosii pixelii ca unitate de msur. Valorile de pe axa orizontal (x) cresc de la stnga la dreapta iar valorile de pe axa vertical (y) cresc de sus n jos. Atunci cnd folosim ntregul ecran, spunem c lucrm n coordonate ecran". Colul din stnga-sus este punctul de coordonate (0, 0). Coordonatele ecran sunt folosite n mesajul WM_MOVE (pentru alte ferestre dect ferestrele descendent) i n urmtoarele funcii Windows: CreateWinriow i MoveWindow (ambele pentru alte ferestre dect ferestrele descendent), GetMessagePos, GetCursorPos, SetCursorPos, GetWindowRect, WindowFromPoint i SetBrushOrgEx. Acestea sunt funcii care fie nu au o fereastr asociat (cum ar fi cele dou funcii pentru cursor), fie trebuie s mute (sau s gseasc) o fereastr pe baza unui punct de pe ecran. Dac folosii funcia CreateDC cu parametrul DISPLAY ca s obinei un context de dispozitiv pentru ntregul ecran, atunci coordonatele logice specificate la apelarea funciilor 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 colul din stnga-sus al chenarului de redimensionare. Coordonatele de fereastr sunt folosite mai rar n Windows, dar dac obinei un context de dispozitiv cu ajutorul funciei GetWindowDC, atunci coordonatele logice specificate la apelarea funciilor GDI vor fi mapate la coordonatele ferestrei. Al treilea sistem de coordonate de dispozitiv - cu care vom lucra cel mai des -folosete coordonatele zonei client". Punctul (0,0) este colul din stnga-sus al zonei client. Dac obinei un context de dispozitiv cu ajutorul funciei GetDC sau al funciei BeginPaint, atunci coordonatele logice specificate Ia apelarea funciilor GDI vor fi mapate la coordonatele zonei client. Putei s transformai coordonatele zonei client n coordonatele ecranului i invers folosind funciile ClientToScreen i ScreenToClient. De asemenea, putei obinei poziia i dimensiunea ntregii ferestre n coordonate ecran folosind funcia GetWindowRect. Aceste funcii v furnizeaz suficiente informaii ca s transferai 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 funciile GDI la coordonatele de dispozitiv, pe baza faptului c sistemul de coordonate de dispozitiv folosit depinde de funcia folosit pentru obinerea contextului de dispozitiv. Pentru a ne continua discuia despre modurile de mapare, trebuie s mai facem o precizare legat de terminologie: se spune c modul de mapare definete maparea coordonatelor de fereastr" (window) -coordonate logice - la coordonatele vizorului (viewport) - coordonate de dispozitiv. Folosirea termenilor window (fereastr) i viewport (vizor) nu este totui cea mai potrivit alegere. n alte limbaje pentru interfeele grafice, termenul viewport se refer la o regiune de decupare (clipping region). Termenul window definete, n general, zona pe care o ocup un program pe ecran. n timpul discuiilor de fa va trebui s renunm la vechile definiii 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 ai obinut contextul de dispozitiv prin apelarea funciilor GetWindowDC sau CreateDC. Punctul de coordonate (0, 0) este colul din stnga-sus al zonei client (sau al ferestrei, ori al ecranului). Valorile coordonatei x cresc ctre 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 msur dorii. Coordonatele logice ale ferestrei sunt specificate la apelarea funciilor 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 corespunztoare 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 semnificaie, dar raportul ntre extensia vizorului i extensia ferestrei reprezint un factor de scalare folosit pentru convertirea unitilor logice n

uniti de dispozitiv. Extensiile pot avea valori negative: aceasta nseamn c nu este obligatoriu ca valorile pe axa x s creasc spre dreapta i valorile pe axa y s creasc n jos. Windows poate s transforme i coordonatele vizorului (coordonate de dispozitiv) n coordonate ale ferestrei (coordonate logice):

Windows include dou funcii care v permit s convertii punctele de dispozitiv n puncte logice i invers. Funcia urmtoare convertete punctele de dispozitiv n puncte logice:
DPtoLP (hdc, pPoints, iNumber) ;

Variabila pPoints este o matrice de structuri POINT, iar iNumber este numrul de puncte ce urmeaz s fie convertite. Vei vedea c aceast funcie este foarte util pentru convertirea dimensiunii zonei client obinute prin apelarea funciei GetClientRect (care returneaz valorile n uniti de dispozitiv) n coordonate logice:
GetClientRect (hwnd, &rect); DPtoLP (hdc, (PPoint) &rect, 2);

Funcia urmtoare convertete punctele logice n puncte de dispozitiv:


LPtoDP (hdc, pPoints, iNumber) ;

Folosirea modului de mapare MM_TEXT Pentru modul de mapare MM_TEXT, originile i extensiile prestabilite sunt urmtoarele: Originea ferestrei: Originea vizorului: Extensia ferestrei: Extensia vizorului: (0, 0) (0, 0) (1, 1) (1, 1) Poate fi modificat Poate fi modificat Nu poate fi modificat Nu poate fi modificat

Raportul din extensia ferestrei i extensia vizorului este 1, aa c nu este necesar nici o operaie de scalare pentru transformarea coordonatelor logice n coordonate de dispozitiv. Formulele prezentate anterior se reduc la acestea: xViewport = xWindow - xWinOrg + xViewOrg yViexport = yWindow - yWinOrg + yViewOrg Acest mod de mapare se numete mapare de tip text", nu fiindc este cea mai potrivit pentru text, ci datorit orientrii axelor. n general, citim textul de la stnga spre dreapta i de sus n jos, iar n modul de mapare MM_TEXT, valorile cresc n acelai sens:

Windows furnizeaz funciile SetViewportOrgEx i SetWindowOrgEx pentru modificarea originii vizorului i a ferestrei. Aceste funcii au ca efect deplasarea axelor, astfel nct punctul de coordonate (0, 0) nu se mai refer la colul din stnga-sus al ecranului. n general, vei folosi ori funcia SetViewportOrgEx, ori SetWindowOrgEx, dar nu pe amndou. Iat cum lucreaz aceste funcii: dac schimbai originea vizorului la (xViewOrg, yViewOrg), atunci punctul logic de coordonate (0, 0) va fi mapat la punctul de dispozitiv (xViewOrg, yViewOrg). Dac schimbai originea ferestrei la (xWinOrg, yWinOrg), atunci punctul logic (xWinOrg, yWinOrg) va fi mapat la punctul de dispozitiv (0,

0). Indiferent de modificrile pe care le facei asupra originii ferestrei i a vizorului, punctul de dispozitiv (0, 0) este ntotdeauna colul din stnga-sus al zonei client. De exemplu, s presupunem c zona client are limea cxClient i nlimea cyClient. Dac vrei ca punctul logic de coordonate (0, 0) s se afle n centrul zonei client, putei s apelai funcia urmtoare:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Argumentele funciei SetViewportEx sunt exprimate ntotdeauna n uniti de dispozitiv. Punctul logic (0, 0) va fi acum mapat la punctul .de dispozitiv (cxClient/2, cyClient/2). Din acest moment folosii zona client ca i cum ar avea urmtorul sistem de coordonate:

Valorile logice ale axei x sunt cuprinse n intervalul de la -cxClient/2 la +cxClient/2 iar valorile logice ale axei y sunt cuprinse n intervalul de la -cyClient/2 la +cyClient/2. Colul din dreapta-jos al zonei client are coordonatele (cxClient/2, cyClient/2). Dac vrei s afiai text ncepnd din colul din stnga-sus al zonei client, care are coordonatele de dispozitiv (0, 0), trebuie s folosii coordonate logice negative:
TextOut (hdc, -cxClient/2, -cyClient/2, "Hello", 5) ;

Acelai rezultat poate fi obinut cu ajutorul funciei SetWindowOrgEx n locul funciei SetViewportOrgEx:
SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Argumentele funciei SetWindowOrgEx sunt exprimate ntotdeauna n coordonate logice. Dup apelul de mai sus, punctul logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv (0, 0), care este colul din stngasus al zonei client. Ceea ce probabil nu dorii s facei (dect dac tii ce rezultat vei obine) este s folosii cele dou funcii mpreun:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ; SetWindowOrgEx (hdc, -cxClient/2, -cyClient/2, NULL) ;

Aceasta nseamn c punctul logic (-cxClient/2, -cyClient/2) este mapat la punctul de dispozitiv (cxClient/2, cyClient/2), rezultnd urmtorul sistem de coordonate:

Putei s obinei originea vizorului apelnd funcia GetViewportOrgEx:


GetViewportOrgEx (hdc, &pt) ;

i originea ferestrei apelnd funcia GetWindowOrgEx:


GetWindowOrgEx (hdc, &pt) ;

unde pt este o structur de tip POINT. Valorile returnate de funcia GetViewportOrgEx sunt n coordonate de dispozitiv, iar valorile returnate de funcia GetWindowOrgEx sunt n coordonate logice. Uneori este de dorit s modificai originea ferestrei sau a vizorului ca s deplasai imaginea afiat pe ecran de exemplu, ca rspuns la acionarea barei de derulare de ctre utilizator. Modificarea originii nu determin deplasarea imediat a imaginii afiate. Mai nti modificai originea, apoi redesenai ecranul. De exemplu, n programul SYSMETS2 din Capitolul 2 am folosit valoarea iVscrollPos (poziia curent a casetei de derulare de pe bara de derulare vertical) ca s ajustm coordonatele de afiare pe axa y:
case WM_PAINT : BeginPaint (hwnd, &ps) ; for 0 = 0; i<NUMLINES ; i++) { y = cyChar * (1 - iVscrollPos + 1) ; [afieaz textul] } EndPaint (hwnd, &ps) ; return 0 ;

Putem s obinem acelai rezultat cu ajutorul funciei SetWindowOrgEx:


case WM_PAINT : BeginPaint (hwnd, &ps); SetWindowOrgEx (ps.hdc, 0, cyChar * iVscrollPos) ; for (i = 0; i < NUMLINES ; i++) { y = cyChar * (1 + i) ; [afieaz textul] } EndPaint (hwnd, &ps) ; return 0 ;

Acum nu mai este nevoie s fie folosit valoarea iVscrollPos pentru calcularea coordonatei y pentru funcia TextOut. Aceasta nseamn c putei s apelai funciile de afiare a textului dintr-o subrutin, fr s transmitei subrutinei valoarea iVscrollPos, deoarece ajustarea poziiei de afiare a textului s-a fcut prin modificarea originii ferestrei. Dac ai mai lucrat cu sistemele de coordonate rectangulare (carteziene), probabil deplasarea punctului logic de coordonate (0, 0) n centrul zonei client vi se pare o operaie logic. Totui, 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 cnd n modul de mapare MM_TEXT acestea cresc n jos. In acest sens, modul de mapare MM_TEXT este un caz izolat, aceast problem fiind corectat de celelalte cinci moduri de mapare. Modurile de mapare metrice" Windows include dou moduri de mapare n care coordonatele logice sunt exprimate n uniti fizice. Deoarece coordonatele logice pe axele x i y sunt mapate la uniti fizice identice, aceste moduri de mapare v ajut s desenai cercuri mai rotunde" i ptrate mai ptrate". Cele cinci moduri de mapare metrice" sunt prezentate mai jos n ordinea cresctoare a preciziei. Cele dou coloane din partea dreapt prezint dimensiunile unitilor logice n inci (in.) i n milimetri (mm) pentru comparare: Mod de mapare MM_LOENGLISH MM_LOMETRIC MM_HIENGLISH MM_TWIPS* MM_HIMETRIC Uniti logice 0,01 in. 0,1 mm 0,001 in. 1/1440 in. 0,01 mm Inci 0,001 0,00394 0,001 0,000694 0,000394 Milimetri 0,254 0,1 0,0254 0,0176 0,01

* Un twip este 1/20 dintr-un punct (care este 1/72 dintr-un inci), deci 1 /1440 dintr-un inci.

Ca s putei face o comparaie ntre modul de mapare MM_TEXT i aceste rezoluii, trebuie s v amintesc faptul c, pe un monitor VGA standard, fiecare pixel este un ptrat cu latura de 0,325 mm, ceea ce nseamn c coordonatele unui dispozitiv VGA sunt mult mai grosiere dect oricare dintre modurile de mapare metrice.

Pentru o imprimant laser cu rezoluia de 300 dpi (puncte pe inci) fiecare pixel are 0,0033 inci - o rezoluie mai mare dect cea a modurilor de mapare MM_LOENGLISH si MM_LOMETRIC, dar mai mic dect cea a modurilor de mapare MM_HIENGLISH, MM_TWIPS i MM_HIMETRIC. Originile i extensiile prestabilite sunt urmtoarele: Originea ferestrei: Originea vizorului: Extensia ferestrei: Extensia vizorului: (0,0) (0,0) (?, ?) (?, ?) Poate fi modificat Poate fi modificat Nu poate fi modificat Nu poate fi modificjat

Extensiile ferestrei i ale vizorului depind de modul de mapare i de raportul de afiare a dispozitivului. Aa cum am menionat anterior, extensiile nu sunt impor tante ca atare, avnd o semnificaie numai exprimate ca rapoarte. Iat care sunt formulele de transformare a coordonatelor:

De exemplu, pentru modul de mapare MM_LOENGLISH, Windows folosete extensiile pentru urmtoarele calcule:

Pentru multe dispozitive de afiare (cum ar fi VGA) acest raport va fi mai mic dect 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. Remarcai semnul minus din faa raportului extensiilor pe axa vertical. Acest semn modific orientarea axei y. Pentru cele cinci moduri de mapare, valorile y cresc pe msur ce v deplasai n sus pe dispozitiv. Originile prestabilite ale ferestrei i ale vizorului sunt (0,0). Consecinele sunt interesante: atunci cnd trecei la unul dintre aceste cinci moduri de mapare, sistemul de coordonate arat astfel:

Singurul mod n care putei s afiai ceva pe ecran este s folosii valori negative pentru coordonata y. De exemplu, codul urmtor:
SetMapMode (hdc, MM_LOENGLISH) ; TextOut (hdc, 100, -100, "Hello", 5 ) ;

afieaz textul Hello" la o distan de un inci de marginea din stnga i de marginea de sus a zonei client. Ca s v pstrai judecata limpede, probabil vei dori s schimbai aceast situaie. O soluie este s mutai punctul de coordonate (0,0) n colul din stnga-jos al zonei client. Presupunnd c cyClient este nlimea zonei client n pixeli, putei s facei acest lucru apelnd funcia SetViewportOrgEx astfel:
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Acum sistemul de coordonate arat astfel:

O alt soluie este s mutai punctul de coordonate (0,0) n centrul zonei client:
SetViewportOrgEx (hdc, cxClient/2, cyClient/2, NULL) ;

Acum sistemul de coordonate arat astfel:

Acum avem un sistem de coordonate cartezian, cu patru cadrane, cu aceleai uniti de msur pe axele x i y, n inci, milimetri sau twips. Pentru mutarea punctului logic (0,0) putei s folosii i funcia SetWindowOrgEx, dar aceast cale este puin mai dificil, deoarece parametrii funciei SetWindowOrgEx trebuie s fie n coordonate logice. Mai nti trebuie s transformai coordonatele de dispozitiv (cxClient, cyClient) n coordonate logice, folosind funcia DPtoLP. Presupunnd c pt este o structur de tip POINT, codul de mai jos mut punctul logic (0,0) n centrul zonei client:
pt.x = cxClient ; pt.y = cyClient ; DPtoLP (hdc, &pt, 1) ; SetWindowOrgEx (hdc, -pt.x/2, -pt.y/2, NULL) ;

Moduri de mapare proprii Ultimele dou moduri de mapare se numesc MM_ISOTROPIC i MM_ANISOTROPIC. Acestea sunt singurele moduri de mapare care v permit s modificai extensiile ferestrei i ale vizorului, ceea ce nseamn c v permit s modificai factorul de scalare pe care Windows l folosete pentru convertirea coordonatelor logice i a coordonatelor de dispozitiv. Cuvntul izotrop" nseamn egal n toate direciile"; anizotrop" este antonimul acestuia. Ca i modurile de mapare metrice prezentate mai nainte, MM_ISOTROPIC folosete axe scalate egal. Unitile logice de pe axa x au aceeai dimensiune fizic ca i unitile logice de pe axa y. Acest fapt v ajut s creai imagini care pstreaz raportul corect de afiare, indiferent de raportul de afiare al dispozitivului. Diferena dintre modurile de mapare metrice i modul de mapare MM_ISOTROPIC const n faptul c putei s controlai dimensiunea fizic a unitilor logice. Dac dorii, putei s ajustai dimensiunea fizic a unitilor logice n funcie de dimensiunea zonei client, astfel nct imaginile pe care le desenai s fie ntotdeauna coninute de zona client, mrindu-se i micorndu-se corespunztor. Programul ANACLOCK (ceasul analogic) din Capitolul 7 este un exemplu de imagine izotrop. Ceasul este ntotdeauna rotund. Dac redimensionai 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 aceleai uniti logice la apelarea funciilor de desenare, indiferent de dimensiunea ferestrei.

Uneori, modurile de mapare metrice i modul MM_TEXT sunt numite moduri de mapare complet restricionate" (fully constrained"). Aceasta nseamn c nu putei s modificai 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 restricionat" (partly constrained"). Windows v permite s modificai extensiile ferestrei i ale vizorului, dar le ajusteaz astfel nct unitile logice x i y s aib aceleai dimensiuni fizice. Modul de mapare MM_ANISOTROPIC este nerestricionat". Putei s modificai extensiile ferestrei i ale vizorului, fr ca Windows s mai fac vreo ajustare a valorilor. Modul de mapare MM_ISOTROPIC Modul de mapare MM_ISOTROPIC este ideal pentru folosirea unor axe arbitrare, cu uniti logice egale pe cele dou axe. Dreptunghiurile cu limi i nlimi logice egale sunt afiate ca ptrate. Elipsele cu limi i nlimi logice egale sunt afiate ca cercuri. Atunci cnd selectai pentru prima dat modul de mapare MM_ISOTROPIC, Windows folosete aceleai extensii pentru fereastr i pentru vizor, ca i pentru modul de mapare MM_LOMETRIC. (Totui, nu v bazai pe acest fapt.) Diferena este c acum putei s schimbai extensiile dup cum dopi, apelnd funciile SetWindowExtEx i SetViewportExtEx. Windows va ajusta apoi aceste extensii astfel nct unitile logice de pe ambele axe s reprezinte distane fizice egale. n general, vei folosi ca parametri ai funciei SetWindowExtEx dimensiunile dorite pentru fereastra logic, iar ca parametri ai funciei SetViewportExtEx dimensiunile reale ale zonei client. Atunci cnd ajusteaz aceste extensii, Windows trebuie s ncadreze fereastra logic n vizorul fizic, ceea ce nseamn c o parte a zonei client poate s rmn n afara ferestrei logice. Pentru folosirea mai eficient a spaiului din zona client trebuie s apelai funcia SetWindowExtEx nainte de apelarea funciei SetViewportExtEx. De exemplu, s presupunem c vrei s folosii un sistem de coordonate virtual, tradiional", cu un singur cadran n care punctul (0, 0) este colul din stnga-jos al zonei client, iar limea i nlimea au valori de la 0 la 32.767. Dac dorii ca unitile logice x i y s aib aceleai dimensiuni, iat ce trebuie s facei:
SetMapMode (hdc, HH_ISOTROPIC) ; SetWindowExtEx (hdc, 32767, 32767, NULL) ; SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ; SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Dac apoi obinei extensiile ferestrei i ale vizorului apelnd funciile GetWindowExtEx i GetViewportExtEx vei vedea c acestea au alte valori dect cele specificate. Windows ajusteaz extensiile n funcie de raportul de afiare (aspect ratio) al dispozitivului, astfel nct unitile logice de pe cele dou axe s reprezinte aceleai dimensiuni fizice. Dac zona client este mai mult lat dect nalt (n dimensiuni fizice), Windows ajusteaz extensia x astfel nct fereastra logic s fie mai mic dect vizorul zonei client. Fereastra logic va fi poziionat n partea stng a zonei client:

Nu putei s afiai nimic n partea dreapt a zonei client dincolo de captul axei x, deoarece pentru aceasta ar trebui s folosii o coordonat logic x mai mare de 32.767. Dac zona client este mai mult nalt dect lat (n dimensiuni fizice), Windows ajusteaz extensia y. Fereastra logic va fi poziionat n partea de jos a zonei client:

Nu putei s afiai nimic n partea de sus a zonei client dincolo de captul axei y, deoarece pentru aceasta ar trebui s folosii o coordonat logic y mai mare de 32.767. Dac preferai ca fereastra logic s fie poziionat ntotdeauna n partea stng i n partea de sus a zonei client, putei s modificai astfel codul precedent:
SetMapMode (hdc, HH_ISOTROPIC) ; SetWindowExtEx (hdc, 32767, 32767, NULL) ; SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ; SetWindowOrgEx (hdc, 0, 32767, NULL) ;

n acest fel, prin apelul funciei SetWihdowOrgEx precizm c dorim ca punctul logic (0,32767) s fie mapat la punctul de dispozitiv (0,0). Dac zona client este mai mult nalt dect lat, sistemul de coordonate este poziionat astfel:

Pentru imaginile de genul celei folosite de programul ANACLOCK putei s folosii un sistem de coordonate cartezian cu patru cadrane avnd axe arbitrar scalate n cele patru direcii i cu punctul de coordonate (0, 0) n centrul zonei client. Dac, de exemplu, vrei ca fiecare ax s ia valori de la 0 la 1000, folosii codul urmtor:
SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, 1000, 1000, NULL); SetViewportExtEx (hdc, cxClient/2, -cyClient/2, NULL) ; SetViewportOrgEx (hdc, cxClient/2, cyCllent/2, NULL) ;

Dac zona client este mai mult lat dect nalt, sistemul logic de coordonate arat astfel:

Sistemul logic de coordonate este centrat i atunci cnd zona client este mai mult nalt dect lat:

Reinei faptul c extensiile ferestrei i ale vizorului nu implic nici o operaie de decupare. La apelarea funciilor GDI putei s folosii pentru coordonatele x i y valori mai mici de -1000 sau mai mari de +1000. n funcie de forma zonei client, aceste puncte pot s fie sau s nu fie vizibile. Folosind modul de mapare MM_ISOTROPIC putei s facei ca unitile logice s fie mai mari dect pixelii. De exemplu, s presupunem c dorii s creai un sistem de coordonate n care punctul de coordonate (0, 0) se afl n colul din stnga-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 msurate n uniti de 1/16 dintr-un inci. Acest mod de mapare v-ar permite s desenai o rigl avnd un capt n colul din stnga-sus al zonei client, cu diviziuni de aisprezecimi de inci:
SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx(hdc, 160*GetDeviceCaps (hdc, HORZSIZE)/254,160*GetDeviceCaps(hdc, VERTSIZE)/254, NULL); SetViewportExtEx(hdc, GetDeviceCaps(hdc, HORZRES),GetDeviceCaps(hdc, VERTRES), NULL);

n aceast secven de cod extensiile vizorului sunt stabilite la dimensiunile n pixeli ale ntregului ecran. Extensiile ferestrei trebuie s fie stabilite la dimensiunile ntregului ecran n uniti de aisprezecimi de inci. Indicii HORZSIZE i VERTSIZE ai funciei GetDeviceCaps returneaz dimensiunile dispozitivului n milimetri. Dac lucrai cu numere n virgul mobil, trebuie s transformai milimetrii n inci mprind valorile obinute la 2,54, i apoi s transformai incii n aisprezecimi de inci nmulind rezultatul operaiei anterioare cu 16. Deoarece aici lucrm cu numere ntregi, am nmulit rezultatul cu 160 i l-am mprit la 254. Pentru majoritatea dispozitivelor, acest cod face ca unitile logice s fie mult mai mari dect unitile fizice. Tot ce desenai pe dispozitiv va avea pentru coordonate valori mapate la multipli de 1/16 inci. Nu putei s desenai dou linii orizontale aflate la distana de 1/32 de inci, deoarece pentru aceasta ai avea nevoie de o coordonat logic fracionar. Modul de mapare MM_ANISOTROPIC sau ajustarea imaginii Atunci cnd stabilii extensiile ferestrei i pe ale vizorului n modul de mapare MM_ISOTROPIC, Windows ajusteaz valorile astfel nct unitile logice de pe cele dou axe s aib aceleai dimensiuni. n modul de mapare MM_ANISOTROPIC, Windows nu face nici o ajustare a valorilor pe care le stabilii. Aceasta nseamn c n modul de mapare MM_ANISOTROPIC nu este obligatorie pstrarea raportului corect de afiare. Pentru a folosi modul de mapare MM_ANISOTROPIC, stabilii un sistem arbitrar de coordonate pentru zona client, ca i pentru modul de mapare MM_ISOTROPIC. Codul de mai jos stabilete punctul de coordonate (0, 0) n colul din stnga-jos al zonei client, iar axele de coordonate x i y pot lua valori de la 0 la 32.767:
SetMapMode (hdc, MM_ANISOTROPIC) ; SetWindowExtEx (hdc, 32767, 32767, NULL) ; SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ; SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

156

152

CAPITOLUL 5. TASTATURA Ca orice program interactiv rulat pe un calculator personal, aplicaiile Windows 95 se bazeaz foarte mult pe tastatur pentru intrrile utilizatorului. Dei Windows accept i mouse-ul ca dispozitiv de intrare, tastatur deine nc poziia principal. Deoarece utilizatorii mai vechi ai calculatoarelor personale prefer s foloseasc tastatura n locul mouse-ului, recomand dezvoltatorilor de programe s ncerce implementarea complet a funciilor programului de la tastatur. (Desigur, n unele situaii - este cazul utilizrii unui program de desenare sau a unui program de editare a publicaiilor (DTP), tastatura se dovedete a fi ineficient, motiv pentru care mouse-ul este indispensabil). Tastatura nu poate fi tratat ca un dispozitiv de intrare independent de celelalte funcii ale programului. De exemplu, programele rspund deseori la intrrile de la tastatur afind caracterele introduse n zona client a ferestrei. Astfel, manipularea intrrilor de la tastatur i afiarea textului trebuie s fie tratate mpreun. Dac adaptarea programului pentru limbi sau piee strine este important pentru dumneavoastr, atunci va trebui s tii cteva lucruri i despre suportul asigurat de Windows 95 pentru setul extins de caractere ASCII (codurile de la 128 n sus), pentru setul de caractere pe doi octei (double-byte character set - DBCS) i despre suportul asigurat de Windows NT pentru codificarea pe 16 bii a caracterelor de la tastatur (cunoscut sub numele de Unicode). Tastatura elemente de baz Aa cum probabil v dai seama, arhitectura bazat pe mesaje a sistemului de operare Windows este ideal pentru lucrul cu tastatur. Programul afl" despre apsarea unor taste prin intermediul mesajelor care ajung la procedura de fereastr. De fapt, lucrurile sunt puin mai complicate: atunci cnd utilizatorul apas i elibereaz tastele, driverul de tastatur transmite sistemului de operare informaiile legate de aciunile asupra tastelor. Windows salveaz aceste aciuni (sub form de mesaje) n coada de ateptare a sistemului. Mesajele de la tastatur sunt apoi transferate, unul cte unul, n coada de mesaje a programului cruia i aparine fereastra ce deine cursorul de intrare" (despre care vom discuta imediat). Programul distribuie mesajele procedurii de fereastr corespunztoare. Motivul acestui proces n dou etape - stocarea mesajelor mai nti n coada de mesaje a sistemului i apoi transferarea acestora n coada de mesaje a aplicaiilor - este legat de sincronizare. Atunci cnd utilizatorul apas pe taste ntr-un ritm mai rapid dect cel n care programul prelucreaz mesajele primite, Windows stocheaz aciunile neprelucrate n coada de ateptare a sistemului, deoarece una dintre aciunile asupra tastaturii poate avea ca efect comutarea cursorului de intrare (input focus) ctre un alt program. n consecin, urmtoarele 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, fr probleme. De asemenea, n majoritatea cazurilor, aceste mesaje conin mai multe informaii despre tastatur dect avei nevoie n program. O parte a sarcinii de manipulare a tastaturii const n a ti ce mesaje sunt importante. Ignorarea tastaturii Dei tastatura este principala surs de intrri de la utilizatori a unui program pentru Windows, programele nu trebuie s reacioneze la toate mesajele pe care le primete de la tastatur. Multe funcii ale tastaturii sunt tratate chiar de Windows. De exemplu, putei s ignorai apsrile de taste legate de funcii sistem. Acestea sunt, n general, combinaii cu tasta Alt. Programul nu este obligat s monitorizeze aceste taste, deoarece Windows i comunic efectul acestora. (Totui, 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. (Despre meniuri vom discuta n Capitolul 10.) Unele programe pentru Windows folosesc acceleratori" (sau taste de accelerare") pentru opiunile de meniu folosite mai frecvent. Acceleratorii sunt, de obicei, combinaii de taste funcionale - sau alte taste corespunztoare unor caractere - cu tasta Ctrl. Tastele de accelerare sunt definite n fiierul de resurse al programului, (n Capitolul 10 vom vedea cum convertete Windows acceleratorii n mesaje pentru comenzile din meniu. Nu trebuie s facei dumneavoastr aceast conversie.) Casetele de dialog (despre care vom discuta n Capitolul 11) au i ele o interfa cu tastatura, dar programele, n general, nu trebuie s monitorizeze tastatura ct timp este activ o caset de dialog. Interfaa cu tastatura este manipulat de Windows i acesta trimite programului mesaje prin care i comunic efectele tastelor apsate. Casetele de dialog pot conine controale de editare pentru introducerea textului. Acestea sunt mici casete, n care utilizatorul poate s scrie un ir de caractere. Windows asigur logica de funcionare a controlului de editare i furnizeaz programului coninutul final al controlului, dup ce utilizatorul a terminat. Chiar i n fereastra principal putei s definii ferestre descendent, care s funcioneze ca i controalele de editare. Un astfel de exemplu este programul POPPAD din Capitolul 8. Acesta este ceva mai mult dect un control de editare cu mai multe linii, care se bazeaz pe sistemul de operare pentru partea mai grea a operaiilor.

89

Cursorul, cursorul, cine a luat cursorul de intrare? Tastatura trebuie s fie partajat de toate aplicaiile rulate simultan sub Windows. Unele aplicaii pot avea mai multe ferestre, iar tastatura trebuie s fie partajat de toate ferestrele din cadrul aceleiai aplicaii. Atunci cnd este apsat o tast, o singur fereastr trebuie s primeasc mesajul privind apsarea tastei respective. Fereastra care primete acest mesaj este fereastra care deine cursorul de intrare" (input focus"). Conceptul cursorului de intrare este strns legat de conceptul de fereastr activ". Fereastra care deine cursorul de intrare este fie fereastra activ, fie o fereastr descendent a ferestrei active. De obicei, fereastra activ este uor de identificat. Dac fereastra activ are o bar de titlu, Windows evideniaz bara de titlu a acesteia. Dac fereastra activ are un cadru de dialog (o form des ntlnit n casetele de dialog) n locul unei bare de titlu, Windows evideniaz acest cadru. Dac fereastra activ a fost redus la o pictogram (minimizat), Windows evideniaz textul afiat sub pictogram. Ferestrele descendent mai des ntlnite sunt controale, precum butoanele de apsare, 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 deine cursorul de intrare, atunci fereastra activ este fereastra printe. Controalele de tip fereastr descendent indic faptul c dein cursorul de intrare prin afiarea unui cursor care clipete sau a unui cursor de editare. Dac fereastra activ a fost redus la o pictogram, atunci nici o fereastr nu deine cursorul de intrare. Windows continu s trimit programului mesaje de la tastatur, dar acestea sunt trimise ntr-o alt form dect mesajele trimise unei ferestre active normale. O procedur de fereastr poate s afle cnd are cursorul de intrare prin interceptarea mesajelor WM_SETFOCUS i WM_KILLFOCUS. Mesajul WM_SETFOCUS indic faptul c fereastra primete cursorul de intrare (input focus), iar mesajul WM_KILLFOCUS indic faptul c fereastra pierde cursorul de intrare. Acionri de taste i caractere Mesajele privind tastatura pe care un program le recepioneaz de la sistemul de operare fac diferena ntre acionrile de taste" (keystrokes") i caractere". Aceste noiuni sunt legate de cele dou moduri n care putei s privii tastatura. n primul rnd, tastatura este o colecie de taste. Tastatura are o singur tast A; apsarea tastei A este o acionare de tast, iar eliberarea tastei A este tot o acionare de tast. Tastatura este, ns, n acelai timp, i un dispozitiv de intrare care genereaz caractere afiabile. Tasta A poate s genereze mai multe caractere, n funcie de starea tastelor Ctrl, Shift i Caps Lock. n mod normal, caracterul generat este a. Dac tasta Shift este apsat, sau tasta Caps Lock este activ, caracterul generat este A. Dac tasta Ctrl este apsat, caracterul generat este Ctrl+A. Dac se folosete un driver de tastatur pentru o limb strin, apsarea tastei A poate s fie precedat de un caracter mort" (dead-character key") sau de tastele Shift, Ctrl sau Alt n diferite combinaii. Combinaiile pot s genereze un caracter a sau A cu un accent. Pentru acionrile de taste care genereaz caractere afiabile, Windows trimite programului att mesaje pentru acionarea de taste, ct i mesaje pentru caractere. Unele taste nu genereaz caractere. Astfel de taste sunt Shift, tastele funcionale, tastele de deplasare i tastele speciale, precum Insert i Delete. n cazul acestor taste, Windows genereaz numai mesaje pentru acionari de taste.

Mesaje pentru acionri de taste


Atunci cnd apsai o tast, Windows insereaz n coada de ateptare a ferestrei care deine cursorul de intrare un mesaj WM_KEYDOWN sau un mesaj WM_SYSKEYDOWN. Atunci cnd eliberai fasta, Windows insereaz n coada de ateptare a ferestrei un mesaj WM_KEYUP sau un mesaj WM_SYSKEYUP.

TASTA A FOST APSAT


Tast obinuit Tast de sistem WM_KEYDOWN WM_SYSKEYDOWN

Tasta a fost eliberat WM_KEYUP WM_SYSKEYUP

De obicei, mesajele de apsare i de eliberare a tastei sunt trimise n pereche. Totui, dac inei apsat o tast pan cnd 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 ateptare, mesajele pentru acionri de taste conin informaii de timp. Putei s obinei momentul relativ n care a fost apsat sau eliberat o tast apelnd funcia GetMessageTime. Taste obinuite i taste de sistem

Particula SYS" din mesajele WM_SYSKEYDOWN i WM_SYSKEYUP provin de la cuvntul system" i se refer la acionrile de taste care prezint mai mult importan pentru Windows dect pentru aplicaiile Windows. Mesajele WM_SYSKEYDOWN i WM_SYSKEYUP sunt generate, de obicei, pentru taste apsate n combinaie cu tasta Alt. Aceste acionari de taste apeleaz opiuni din meniul programului ori din meniul sistem, sunt folosite pentru funcii ale sistemului, cum ar fi comutarea ferestrei active (Alt+Tab sau Alt+Esc) sau sunt folosite pentru acceleratori de meniu (Alt n combinaie cu o tast funcional). De obicei, programul ignor mesajele WM_SYSKEYDOWN i WM_SYSKEYUP i le retransmite funciei DefWindowProc. Deoarece Windows manipuleaz combinaiile Alt+tast, nu este nevoie s interceptai aceste mesaje. Procedura de fereastr va primi alte mesaje, legate de rezultatul acestor acionri de taste (cum ar fi selectarea unei opiuni de meniu). Chiar dac dorii s includei n program codul pentru interceptarea acestor mesaje (aa cum vom face n programul KEYLOCK din acest capitol), retransmitei mesajele funciei DefWindowProc dup ce le prelucrai, astfel nct Windows s le poat folosi n scopurile obinuite. Dar s ne gndim puin la aceste lucruri. Aproape toate mesajele care afecteaz fereastra programului trec mai nti prin procedura de fereastr. Windows prelucreaz aceste mesaje numai dac le retransmitei funciei DefWindowProc. De exemplu, dac n procedura de fereastr adugai urmtoarele linii:
case WM_SYSKEYDOWN : case WM_SYSKEYUP : case WM_SYSCHAR : return 0 ;

dezactivai toate operaiile legate de combinaiile Alt+tast (comenzi de meniu, Alt+Tab, Alt+Esc i aa mai departe) atunci cnd programul deine cursorul de intrare. Mesajele WM_KEYDOWN i WM_KEYUP sunt generate, de obicei, pentru tastele apsate i eliberate fr tasta Alt. Programul poate s foloseasc sau s ignore aceste mesaje. Sistemul de operare le ignor. Variabila lParam Pentru toate mesajele legate de acionrile de taste variabila lParam (pe 32 de bii) transmis procedurii de fereastr este mprit n ase cmpuri: contorul de repetare, codul de scanare OEM, indicatorul flag pentru taste extinse, codul de context, starea anterioar a tastei i starea de tranziie (vezi Figura 5-1). Indicator flag pentru taste extinse

Figura 5-1. Cele sase cmpuri de bii ale variabilei IParam din mesajele pentru acionari de taste. Contorul de repetare Contorul de repetare (Repeat Count) specific numrul de acionari de taste reprezentat de un mesaj. n majoritatea cazurilor, contorul de repetare are valoarea 1. Totui, dac procedura de fereastr nu reuete s prelucreze mesajele de apsare 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 corespunztor contorul de repetare. Contorul de repetare are ntotdeauna valoarea 1 pentru mesajele WM_KEYUP i WM_SYSKEYUP. Deoarece o valoare mai mare dect 1 a contorului de repetare indic faptul c rata de autorepetare a tastelor depete posibilitile de prelucrare ale programului, exist situaii n care putei s ignorai contorul de repetare atunci cnd prelucrai 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 dect trebuie datorit comenzilor suplimentare stocate n bufferul de tastatur. Ignorarea contorului de repetare va reduce foarte mult posibilitatea apariiei unei asemenea situaii. n alte cazuri ns este bine s folosii contorul de repetare. Putei s ncercai ambele variante i s vedei pe care o preferai pentru programul dumneavoastr. Codul de scanare OEM Codul de scanare OEM (OEM Scan Code) este codul de scanare al tastaturii, generat de componentele hardware. (Dac ai scris programe n limbaj de asamblare, acest cod este identic cu cel transmis n registrul AH, n timpul ntreruperii apelului BIOS 16H.) n general, aplicaiile Windows ignor acest cod, deoarece exist metode mai bune de decodificare a informaiilor de la tastatur.

Indicatorul flag pentru taste extinse Indicatorul flag pentru taste extinse (Extended Key Flag) are valoarea 1 dac mesajul este generat de una dintre tastele suplimentare de pe tastatura IBM extins. (Tastatura IBM extins are tastele funcionale 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 apsat 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 excepii: O fereastr activ redus la o pictogram nu deine cursorul de intrare. Toate acionrile de taste genereaz mesaje WM_SYSKEYUP i WM_SYSKEYDOWN. Dac tasta Alt nu este apsat, bitul pentru codul de context are valoarea 0. (Windows folosete aceste mesaje astfel nct fereastra activ redus la o pictogram s nu prelucreze mesajele de la tastatur.) n cazul folosirii unui driver de tastatur pentru alte limbi dect limba englez, unele caractere sunt generate prin combinarea tastelor Shift, Ctrl sau Alt cu alte taste. n aceast situaie, bitul pentru codul de context din variabila IParam care nsoete mesajele WM_KEYUP i WM_KEYDOWN are valoarea 1, dar mesajele nu reprezint acionri de taste de sistem. Starea anterioar a tastei Starea anterioar a tastei (Previous Key State) are valoarea 0 dac tasta nu a fost anterior apsat, i valoarea 1 dac tasta a fost apsat. Are ntotdeauna valoarea 1 pentru mesajele WM_KEYUP i WM_SYSKEYUP, dar poate fi 0 sau 1 pentru mesajele WM_KEYDOWN i WM_SYSKEYDOWN. Valoarea 1 indic faptul c s-a primit un mesaj generat de autorepetarea unei taste. Starea de tranziie Starea de tranziie (Transition State) are valoarea 0 dac tasta este apsat i valoarea 1 dac tasta este eliberat. Acest bit are valoarea 1 pentru mesajele WM_KEYUP si WM_SYSKEYUP si valoarea 0 pentru mesajele WM_KEYDOWN i WM_SYSKEYDOWN. Coduri virtuale de taste Dei unele informaii 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 conine codul virtual care identific tasta apsat 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 ntlnite la tastaturile aparinnd altor productori. Codurile virtuale pe care le vei folosi cel mai des au nume definite n fiierele antet din Windows. Tabelul de mai jos prezint aceste nume, mpreun cu codurile numerice i tastele de pe tastatura IBM care corespund tastelor virtuale. Dei toate tastele genereaz mesaje de acionare (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 internaionale. Putei s determinai aceste coduri virtuale folosind programul KEYLOOK, prezentat n acest capitol, dar n general nu este necesar s prelucrai mesajele de acionare ale acestor taste. Coduri virtuale de taste
Zecimal Hexa Identificator WINDOWS.H Necesar Tastatur IBM

1 2 3 4 8 9 12 13 16

01 02 03 04 08 09 0C 0D 10

VK_BUTTON VK_RBUTTON VK_CANCEL VK_MBUTTON VK_BACK VK_TAB VK_CLEAR VK_RETURN VK_SHIFT

Ctrl-Break Backspace Tab Tasta numeric 5 cu tasta Num Lock inactiv Enter Shift

17 18

11 12

VK_CONTROL VK_MENU

Ctrl Alt (continuare)

Zecimal 19 20 27 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48-57 65-90 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

Hexa 13 14 1B 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30-39 41-5A 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B

Identificator WINDOWS.H VK_PAUSE VK_CAPITAL VK_ESCAPE VK_SPACE VK_PRIOR VK_NEXT VK_END VK_HOME VK_LEFT VK_UP VK_RIGHT VK_DOWN VK_SELECT VK_PRINT VK_EXECUTE VK_SNAPSHOT VK_INSERT VK_DELETE VK_HELP VK_NUMPAD0 VK_NUMPAD1 VK_NUMPAD2 VK_NUMPAD3 VK_NUMPAD4 VK_NUMPAD5 VK_NUMPAD6 VK_NUMPAD7 VK_NUMPAD8 VK_NUMPAD9 VK_MULTIPLY VK_ADD VK_SEPARATOR VK_SUBTRACT VK_DECIMAL VK_DIVIDE VK_F1 VK_F2 VK_F3 VK_P4 VK_F5 VK_F6 VK_F7 VK_F8 VK_F9 VK_F10 VK_F11 VK_F12 .

NECESAR

TASTATURA IBM Pause Caps Lock Esc Bara de spaiu Page Up Page Down End Home Sgeat stnga Sgeat n sus Sgeat n dreapta Sgeat n jos

Print Screen Insert Delete De la 0 la 9 pe blocul principal de taste De la A la Z Tasta numeric 0 cu tasta Num Lock activ Tasta numeric 1 cu tasta Num Lock activ Tasta numeric 2 cu tasta Num Lock activ Tasta numeric 3 cu tasta Num Lock activ Tasta numeric 4 cu tasta Num Lock activ Tasta numeric 5 cu tasta Num Lock activ Tasta numeric 6 cu tasta Num Lock activ Tasta numeric 7 cu tasta Num Lock activ Tasta numeric 8 cu tasta Num Lock activ Tasta numeric 9 cu tasta Num Lock activ Tasta numeric * Tasta numeric + Tasta numeric Tasta numeric . Tasta numeric / Tasta funcional F1 Tasta funcional F2 Tasta funcional F3 Tasta funcional F4 Tasta funcional F5 Tasta funcional F6 Tasta funcionat F7 Tasta funcional F8 Tasta funcional F9 Tasta funcional F10 Tasta funcional F11 (tastatura extins) Tasta funcional F12 (tastatura extins)

7C VK_F13 7D VK_F14 7E VK_F15 7F VK_F16 90 VK_NUMLOCK Num Lock 91 VK_SCROLL Scroll Lock Semnul de validare din coloana Necesar" indic faptul c tasta respectiv este obligatorie pentru orice implementare Windows. De asemenea, sistemul de operare cere ca tastatura i driverul de tastatur s permit combinarea tastelor Ctrl, Shift sau a ambelor cu orice liter, orice tast de deplasare i orice tast funcional. VK_LBUTTON, VK_MBUTTON i VK_RBUTTON sunt codurile virtuale corespunztoare buloanelor din stnga, din mijloc i din dreapta ale mouse-ului. Cu toate acestea, nu vei primi niciodat mesaje pentru care parametrul wParam s conin aceste valori. Mouse-ul genereaz mesaje proprii, aa cum vom vedea n capitolul urmtor. Starea tastelor de modificare Parametrii lParam i wParam care nsoesc mesajele WM_KEYUP, WM_SYSKEYUP, WM_KEYDOWN i WM_SYSKEYDOWN nu spun nimic programului despre starea tastelor de modificare. Putei s obinei starea oricrei taste virtuale folosind funcia GetKeyState. Aceast funcie este folosit, de obicei, pentru obinerea strii tastelor de modificare (Shift, Alt i Ctrl) i a tastelor de comutare (Caps Lock, Num Lock i Scroll Lock). De exemplu:
GetKeyState (VK_SHIFT) ;

124 125 126 127 144 145

returneaz o valoare negativ (adic un numr n care primul bit are valoarea 1) dac tasta Shift este apsat. Valoarea returnat de apelul:
GetKeyState (VK_CAPITAL) ;

are un 1 n bitul cel mai puin semnificativ dac tasta Caps Lock este activ. De asemenea, putei s obinei starea butoanelor mouse-ului folosind codurile virtuale VK_LBUTTON, VK_MBUTTON i VK_RBUTTON. Totui, majoritatea programelor pentru Windows care trebuie s monitorizeze combinaiile ntre taste i butoanele mouseului utilizeaz calea invers - verificnd starea tastelor atunci cnd primesc un mesaj de la mouse. De fapt, starea tastelor de modificare este inclus n mesajele primite de la mouse (aa cum vei vedea n capitolul urmtor). Avei grij cum folosii funcia GetKeyState. Aceasta nu verific starea tastaturii n timp real. GetKeyState returneaz starea tastaturii pn n momentul mesajului curent. Funcia GetKeyState nu v permite s obinei informaii despre starea tastaturii independent de mesajele obinuite. De exemplu, s presupunem c vrei s ntrerupei prelucrarea n procedura de fereastr pn cnd utilizatorul apas tasta
while (GetKeyState (VK_F1) >= 0) ; // Greit!

Nu facei acest lucru! Programul trebuie s preia mesajul de la tastatur din coada de ateptare, nainte ca funcia GetKeyState s poat obine starea tastelor. De fapt, aceast sincronizare este n folosul dumneavoastr, deoarece putei s obinei starea corect a tastelor de modificare n timpul generrii unui anumit mesaj de la tastatur, chiar dac prelucrai mesajul (i apelai funcia GetKeyState) dup ce tasta de modificare respectiv a fost eliberat. Dac ntr-adevr avei nevoie de starea curent a unei taste, putei s folosii funcia GetAsyncKeyState. Utilizarea mesajelor de acionare a tastelor Ideea unui program care s obin informaii despre toate aciunile exercitate asupra tastelor este drgu. Cu toate acestea, majoritatea programelor Windows ignor cea mai mare parte a acestor mesaje. Mesajele WM_SYSKEYUP i WM_SYSKEYDOWN sunt folosite pentru funciile sistemului, aa c nu este nevoie s le interceptai. De asemenea, dac prelucrai mesajele WM_KEYDOWN, de obicei putei s ignorai mesajele WM_KEYUP. n general, programele pentru Windows folosesc mesajele WM_KEYDOWN pentru tastele care nu genereaz caractere. Dei s-ar putea s v vin ideea s folosii mesajele pentru acionri de taste n combinaie cu informaiile despre tastele de modificare (furnizate de funcia GetKeyState) ca s transformai mesajele pentru acionri de taste n mesaje pentru caractere, nu facei acest lucru. Vei avea probleme datorit diferenelor ntre tastaturile internaionale. De exemplu, dac primii un mesaj WM_KEYDOWN pentru care parametrul wParam are valoarea 33H, tii c utilizatorul a apsat tasta 3. Pn aici totul este bine. Dac apelai funcia GetKeyState vei afla c tasta Shift este apsat i s-ar putea s presupunei c utilizatorul a introdus un caracter diez (#), lucru care poate s nu fie adevrat. Un utilizator britanic ar fi introdus caracterul . Mesajele WM_KEYDOWN sunt utile pentru tastele de deplasare, tastele funcionale i tastele speciale, precum Insert, Delete. Uneori tastele Insert, Delete si tastele funcionale sunt folosite ca acceleratori pentru comenzi de meniu. Deoarece Windows transform acceleratorii n comenzi de meniu, nu este nevoie s prelucrai n program mesajele pentru acionarea tastelor de accelerare. Unele programe non-Windows folosesc foarte des tastele funcionale n combinaie cu tastele Alt, Ctrl

i Shift. Putei face acest lucru i n programele pentru Windows, dar nu este recomandat. Dac vrei s folosii tastele funcionale, 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 reuit s eliminm aproape totul, cu excepia unui singur lucru: de cele mai multe ori vei prelucra mesajele WM_KEYDOWN numai pentru tastele de deplasare a cursorului. Atunci cnd prelucrai mesajele trimise de tastele de deplasare putei s verificai i starea tastelor Shift i Ctrl, folosind funcia GetKeyState. Unele funcii Windows folosesc de multe ori tastele de deplasare n combinaie cu tasta Shift pentru extinderea unei selecii - de exemplu ntr-un procesor de texte. Tasta Ctrl este folosit deseori pentru a schimba semnificaia unei taste de deplasare. De exemplu, tasta Ctrl n combinaie cu sgeata spre dreapta mut cursorul cu un cuvnt Ia dreapta. Una dintre cele mai bune metode de a v hotr cum s folosii tastatura este s studiai modul de utilizare a acesteia n programele Windows existente. Dac nu v place cum este folosit, putei s facei ceva diferit. Dar nu uitai c orice noutate nseamn creterea perioadei de care are nevoie un utilizator pentru a se obinui cu programul dumneavoastr. MBUNTIREA PROGRAMULUI SYSMETS: ADUGAREA INTERFEEI CU TASTATURA Atunci cnd ai scris cele trei versiuni ale programului SYSMETS prezentate n Capitolul 3 nu tiai nc nimic despre tastatur. Puteai s derulai textul numai folosind mouse-ul pe barele de derulare. Acum tim s prelucrm mesajele de la tastatur, aa c este timpul s adugm programului SYSMETS o interfa cu tastatur. Aceasta este, evident, sarcina tastelor de deplasare. Cea mai mare parte a tastelor de deplasare (Home, End, Page Up, Page Down, sgeile n sus i n jos) vor fi folosite pentru derularea pe vertical. Sgeile spre dreapta i spre stnga vor fi folosite pentru derularea pe orizontal, facilitate care n acest program este mai puin important. Adugarea logicii de tratare a mesajului WM_KEYDOWN O metod evident de creare a unei interfee este adugarea codului necesar pentru tratarea mesajului WM_KEYDOWN, dublnd astfel logica de tratare a mesajelor WM_VSCROLL i WM_HSCROLL:
case WM_KEYDOWN : iVscrollInc = iHscrollInc = 0 ; switch (wParam) { case VK_HOME: // la fel ca HM_VSCROLL, SB_TOP iVscrollInc = -iVscrollPos ; break ; case VK_END : // la fel ca HM_VSCROLL, SB_BOTTOM: iVscrollInc = iVscrollMax - iVscrollPos ; break ; case VK_UP : // la fel ca HM_VSCROLL, SB_LINEUP : iVscrollInc = -1 ; break ; case VK_DOWN : // la fel ca WM_VSCROLL, SB_LINEDOWN iVscrollInc = 1 ; break ; case VK_PRIOR : // la fel ca WM_VSCROLL, SB_PAGEUP iVscrollInc = min (-1, -cyClient / cyChar) ; break ; case VK_NEXT : // la fel ca WH_VSCROLL, SB_PAGEDOWN : iVscrollInc = max (1, cyClient / cyChar) ; break ; case VK_LEFT : // la fel ca WM_HSCROLL, SB_PAGEUP iHscrollInc = -8 ; break ; case VK_RIGHT : // la fel ca WM_HSCROLL, SB_PAGEDOWN iHscrollInc = 8 ; break ; default : break ; } if (iVscrollInc = max (-iVscrollPos, min(iVscrollInc, iVscrollMax-iVscrollPos))) { iVscrollPos += iVscrollInc ; ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL); SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE); UpdateWindow (hwnd) ; } if (iHscrollInc = max (-iHscrollPos,min(iHscrollInc, iHscrollMax - iHscrollPos)))

{ iHscrollPos += iHscrollInc; ScrollWindow (hwnd, -cxChar*iHscrollInc, 0, NULL, NULL) ; SetScrollPos (hwnd, SB_ HORZ, iHscrollPos, TRUE) ; } return 0 ;

V displace acest cod la fel de mult ca i mie? Dublarea codului pentru tratarea mesajelor de la barele de derulare este o soluie de-a dreptul proast, deoarece dac vom dori vreodat s modificm logica de funcionare a barelor de derulare va trebui s facem aceleai modificri i n codul de tratare a mesajului WM_KEYDOWN. Trebuie s existe o soluie mai elegant. i chiar exist. Transmiterea mesajelor Nu ar fi mai bine s transformm mesajele WM_KEYDOWN n mesaje echivalente WM_VSCROLL i WM_HSCROLL i s facem cumva funcia WndProc s cread c a primit mesajele WM_VSCROLL i WM_HSCROLL, poate chiar transmind procedurii de fereastr aceste mesaje contrafcute? Windows v permite s facei acest lucru. Funcia necesar este SendMessage i primete aceiai parametri ca i procedura de fereastr:
SendMessage (hwnd, message, wParam, lParam) ;

Atunci cnd apelai funcia 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 execuia de la urmtoarea linie dup apelul funciei SendMessage. Procedura de fereastr creia i trimitei mesajul poate fi aceeai procedur de fereastr, o alt procedur de fereastr din acelai program sau chiar o procedur de fereastr dintr-o alt aplicaie. Iat cum putem folosi funcia SendMessage pentru prelucrarea mesajelor WM_KEYDOWN n programul SYSMETS:
case WH_KEYDOWN : switch (wParam) case VK_HOME : SendMessage (hwnd, WM_VSCROLL, SB_TOP, break; 0L);

case VK_END: SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L); break ; case VK_PRIOR : SendMessage (hwnd, WH_VSCROLL, SB_PAGEUP, 0L); break ;

[alte linii de program] OK, ai prins ideea. Scopul nostru era s adugm o interfa cu tastatura pentru barele de derulare i exact acest lucru l-am fcut. Am permis folosirea tastelor de deplasare pentru a dubla logica barelor de derulare trimind procedurii de fereastr mesajele specifice barelor de derulare. Acum vedei de ce am inclus n programul SYSMETS3 din Capitolul 3 secvena 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 modificri discutate. Pentru compilarea programului avei nevoie i de fiierul SYSMETS.H, prezentat n Figura 3-4 din Capitolul 3.
#----------------------# SYSMETS.MAK make file #----------------------sysmets.exe : sysmets.obj $(LINKER) $(GUIFLAGS) -OUT:sysmets.exe sysmets.obj $(GUILIBS) sysmets.obj : sysmets.c sysmets.h $(CC) $(CFLAGS) sysmets.c /*----------------------------------------------------SYSMETS.C -- System Metrics Display Program (Final) (c) Charles Petzold, 1996 -----------------------------------------------------*/ #include <windows.h> #include <string.h> #include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "SysMets" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "System Metrics", WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth, iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ; char szBuffer[10] ; HDC hdc ; int i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; iMaxWidth = 40 * cxChar + 22 * cxCaps ; return 0 ; case WM_SIZE : cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; iVscrollMax = max (0, NUMLINES + 2 - cyClient / cyChar) ; iVscrollPos = min (iVscrollPos, iVscrollMax) ;

SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, FALSE) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; iHscrollMax = max (0, 2 + (iMaxWidth - cxClient) / cxChar) ; iHscrollPos = min (iHscrollPos, iHscrollMax) ; SetScrollRange (hwnd, SB_HORZ, 0, iHscrollMax, FALSE) ; SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ; return 0 ; case WM_VSCROLL : switch (LOWORD (wParam)) { case SB_TOP : iVscrollInc = -iVscrollPos ; break ; case SB_BOTTOM : iVscrollInc = iVscrollMax - iVscrollPos ; break ; case SB_LINEUP : iVscrollInc = -1 ; break ; case SB_LINEDOWN : iVscrollInc = 1 ; break ; case SB_PAGEUP : iVscrollInc = min (-1, -cyClient / cyChar) ; break ; case SB_PAGEDOWN : iVscrollInc = max (1, cyClient / cyChar) ; break ; case SB_THUMBTRACK : iVscrollInc = HIWORD (wParam) - iVscrollPos ; break ; default : iVscrollInc = 0 ; } iVscrollInc = max (-iVscrollPos, min (iVscrollInc, iVscrollMax - iVscrollPos)) ; if (iVscrollInc != 0) { iVscrollPos += iVscrollInc ; ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ; UpdateWindow (hwnd) ; } return 0 ; case WM_HSCROLL : switch (LOWORD (wParam)) { case SB_LINEUP : iHscrollInc = -1 ; break ; case SB_LINEDOWN : iHscrollInc = 1 ; break ; case SB_PAGEUP : iHscrollInc = -8 ; break ; case SB_PAGEDOWN :

iHscrollInc = 8 ; break ; case SB_THUMBPOSITION : iHscrollInc = HIWORD (wParam) - iHscrollPos ; break ; default : iHscrollInc = 0 ; } iHscrollInc = max (-iHscrollPos, min (iHscrollInc, iHscrollMax - iHscrollPos)) ; if (iHscrollInc != 0) { iHscrollPos += iHscrollInc ; ScrollWindow (hwnd, -cxChar * iHscrollInc, 0, NULL, NULL) ; SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ; } return 0 ; case WM_KEYDOWN : switch (wParam) { case VK_HOME : SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ; break ; case VK_END : SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ; break ; case VK_PRIOR : SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ; break ; case VK_NEXT : SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L) ; break ; case VK_UP : SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0L) ; break ; case VK_DOWN : SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0L) ; break ; case VK_LEFT : SendMessage (hwnd, WM_HSCROLL, SB_PAGEUP, 0L) ; break ; case VK_RIGHT : SendMessage (hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L) ; break ; } return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; iPaintBeg = max (0, iVscrollPos + ps.rcPaint.top / cyChar - 1) ; iPaintEnd = min (NUMLINES, iVscrollPos + ps.rcPaint.bottom / cyChar) ; for (i = iPaintBeg ; i < iPaintEnd ; i++) { x = cxChar * (1 - iHscrollPos) ; y = cyChar * (1 - iVscrollPos + i) ; TextOut (hdc, x, y, sysmetrics[i].szLabel, strlen (sysmetrics[i].szLabel)) ;

TextOut (hdc, x + 22 * cxCaps, y, sysmetrics[i].szDesc, strlen (sysmetrics[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf (szBuffer, "%5d", GetSystemMetrics (sysmetrics[i].iIndex))) ; SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 5-2. Programul SYSMETS (final). MESAJE CARACTER Am discutat mai devreme despre ideea transformrii mesajelor generate de aciona rea tastelor n mesaje caracter innd cont de starea tastelor de modificare i v-am avertizat c acest lucru nu este suficient: trebuie s inei seama i de configuraia diferit a tastaturii de la o ar la alta. Din acest motiv, nu trebuie s ncercai s transformai dumneavoastr mesajele generate de acionarea tastelor n mesaje caracter. Windows o poate face n locul dumneavoastr. Ai mai vzut aceast secven de cod:
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }

Acesta este un ciclu tipic de tratare a mesajelor din funcia WinMain. Funcia GetMessage preia urmtorul mesaj din coada de ateptare i completeaz cmpurile structurii msg. Funcia DispatchMessage transmite mesajul procedurii de fereastr corespunztoare. ntre cele dou funcii este apelat funcia TranslateMessage, care transform mesajele generate de acionarea tastelor n mesaje caracter. Dac mesajul este WM_KEYDOWN sau WM_SYSKEYDOWN i dac tasta apsat, n funcie de starea tastelor de modificare, genereaz un caracter, atunci funcia TranslateMessage insereaz un mesaj caracter n coada de ateptare. Acesta va fi urmtorul mesaj pe care l va prelua funcia GetMessage dup mesajul generat de acionarea tastei. Exist patru mesaje caracter: Caractere WM_CHAR WM_SYSCHAR Caractere moarte" WM_DEADCHAR WM_SYSDEADCHAR

Caractere non-sistem Caractere sistem

Mesajele WM_CHAR i WM_DEADCHAR sunt obinute din mesaje WM_KEYDOWN. Mesajele WM_SYSCHAR i WM_SYSDEADCHAR sunt obinute din mesaje WM_SYSKEYDOWN. n majoritatea cazurilor programul poate s ignore toate celelalte mesaje n afar de WM_CHAR. Parametrul lParam transmis procedurii de fereastr are acelai coninut ca i parametrul lParam al mesajului generat de acionarea tastei din care a fost obinut mesajul caracter. Parametrul wParam conine codul ASCII al caracterului (da, chiar btrnul ASCII!). Mesajele caracter sunt transmise procedurii de fereastr ntre mesajele generate de acionarea tastelor. De exemplu, dac tasta Caps Lock nu este activ i apsai i eliberai tasta A, procedura de fereastr primete urmtoarele trei mesaje: Mesaj WM_KEYDOWN WM_CHAR WM_KEYUP Tast sau cod Tasta virtual A Codul ASCII al caracterului a Tasta virtual A

Dac introducei un caracter A apsnd tasta Shift, apoi tasta A, elibernd tasta A i apoi elibernd tasta Shift, procedura de fereastr primete cinci mesaje: Mesaj Tast sau cod WM_KEYDOWN Tasta virtual VK_SHIFT WM_KEYDOWN Tasta virtual A WM_CHAR WM_KEYUP Codul ASCII al caracterului a WM_KEYUP Tasta virtual A Tasta virtual VK_SHIFT Tasta Shift nu genereaz un mesaj caracter. Dac inei tasta A apsat pn cnd intr n aciune autorepetarea, vei primi un mesaj caracter pentru fiecare caracter WM_KEYDOWN: Mesaj Tast sau cod WM_KEYDOWN WM_CHAR WM_KEYDOWN WM_CHAR WM_KEYDOWN WM_CHAR WM_KEYDOWN WM_CHAR WM_KEYUP Tasta virtual A Codul ASCII al caracterului a Tasta virtual A Codul ASCII al caracterului a Tasta virtual A Codul ASCII al caracterului a Tasta virtual A Codul ASCII al caracterului a Tasta virtual A

Dac unul dintre mesajele WM_KEYDOWN are contorul de repetare mai mare dect 1, mesajul WM_CHAR va avea acelai contor de repetare. Tasta Ctrl n combinaie cu o liter genereaz codurile de control ASCII de la 01H (Ctrl+A) la 1AH (Ctrl+Z). Putei s folosii i alte taste pentru generarea acestor coduri de control. Tabelul urmtor prezint valorile parametrului wParam dintr-un mesaj WM_CHAR pentru tastele care genereaz coduri de control: Tast Cod ASCII Dublat de Backspace Tab Ctrl+Enter Enter Esc 08H 09H 0AH 0DH 1BH Ctrl+H Ctrl+I Ctrl+J Ctrl+M Ctrl+[

Programele Windows folosesc uneori combinaiile tastei Ctrl cu litere ca acceleratori pentru meniuri, caz n care literele respective nu sunt transformate n mesaje caracter. Mesajul WM_CHAR Atunci cnd trebuie s prelucreze caracterele introduse de la tastatur (de exemplu, ntr-un procesor de texte sau ntr-un program de comunicaii) programul prelucreaz mesajele WM_CHAR. Probabil dorii s prelucrati ntr-un mod mai special tastele Backspace, Tab i Enter (eventual si tasta de salt la linie nou) dar toate celelalte caractere sunt tratate la fel:
case WH_CHAR : switch (wParam) { case '\b' :

// backspace [alte linii de program] break ; case '\f' : // tab [alte linii de program] break ; case '\n' : // salt la linie nou [alte linii de program] break ;

case '\r' :

// retur de car [alte linii de program] break ; default : // cod de caractere [alte linii de program] break ; } return 0 ;

Acest fragment de program este asemntor cu secvenele de cod pentru tratarea caracterelor dintr-un program MS-DOS obinuit. Mesaje pentru caractere moarte" De obicei, programele pot s ignore mesajele WM_DEADCHAR si WM_SYSDEADCHAR. Pe unele tastaturi, n afar de tastatura de tip american, o serie de taste sunt folosite pentru adugarea semnelor diacritice la o liter. Acestea se numesc taste moarte" (dead keys") deoarece nu pot crea singure caractere. De exemplu, pe o tastatura german, n locul tastei +/= de pe tastatura american se afl o tast moart pentru accentul ascuit ('') dac nu este apsat tasta Shift, si pentru accentul grav (`) dac este apsat tasta Shift. Atunci cnd utilizatorul apas aceast tast moart, procedura de fereastr pri mete un mesaj WM_DEADCHAR pentru care parametrul wParam conine codul ASCII al semnului diacritic. Dac utilizatorul apas apoi o liter (de exemplu, tasta A), procedura de fereastr primete un mesaj WM_CHAR pentru care parametrul wParam conine codul ASCII al literei a" cu semnul diacritic respectiv. Ca urmare, programul nu trebuie s prelucreze mesajul WM_DEADCHAR, deoarece mesajul WM_CHAR urmtor i furnizeaz toate informaiile 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 primete dou mesaje WM_CHAR - pentru primul parametrul wParam conine codul ASCII al semnului diacritic, iar pentru al doilea parametrul wParam conine codul ASCII al literei s".
Examinarea mesagelor de la tastatur

Dac dorii s vedei cum trimite sistemul de operare mesajele de la tastatur ctre o aplicaie, programul KEYLOOK (prezentat n figura 5.3) v poate ajuta. Acest program afieaz n zona client toate informaiile pe care Windows le trimite procedurii de fereastr pentru opt mesaje diferite de la tastatur.
#----------------------# KEYLOOK.MAK make file #----------------------keylook.exe : keylook.obj $(LINKER) $(GUIFLAGS) -OUT:keylook.exe keylook.obj $(GUILIBS) keylook.obj : keylook.c $(CC) $(CFLAGS) keylook.c /*------------------------------------------------------KEYLOOK.C -- Displays Keyboard and Character Messages (c) Charles Petzold, 1996 -------------------------------------------------------*/ #include <windows.h> #include <stdio.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; RECT rect ; int cxChar, cyChar ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "KeyLook" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Keyboard Message Looker", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void ShowKey (HWND hwnd, int iType, char *szMessage, WPARAM wParam, LPARAM lParam) { static char *szFormat[2] = { "%-14s %3d %c %6u %4d %3s %3s %4s %4s", "%-14s %3d %c %6u %4d %3s %3s %4s %4s" } ; char szBuffer[80] ; HDC hdc ; ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; TextOut (hdc, cxChar, rect.bottom - cyChar, szBuffer, wsprintf (szBuffer, szFormat [iType], szMessage, wParam, (BYTE) (iType ? wParam : ' '), LOWORD (lParam), HIWORD (lParam) & 0xFF, (PSTR) (0x01000000 & lParam ? "Yes" : "No"), (PSTR) (0x20000000 & lParam ? "Yes" : "No"), (PSTR) (0x40000000 & lParam ? "Down" : "Up"), (PSTR) (0x80000000 & lParam ? "Up" : "Down"))) ; ReleaseDC (hwnd, hdc) ; ValidateRect (hwnd, NULL) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static char szTop[] = "Message Key Char Repeat Scan Ext ALT Prev Tran"; static char szUnd[] = "_______ ___ ____ ______ ____ ___ ___ ____ ____"; HDC hdc ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight ;

ReleaseDC (hwnd, hdc) ; rect.top = 3 * cyChar / 2 ; return 0 ; case WM_SIZE : rect.right = LOWORD (lParam) ; rect.bottom = HIWORD (lParam) ; UpdateWindow (hwnd) ; return 0 ; case WM_PAINT : InvalidateRect (hwnd, NULL, TRUE) ; hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; SetBkMode (hdc, TRANSPARENT) ; TextOut (hdc, cxChar, cyChar / 2, szTop, (sizeof szTop) - 1) ; TextOut (hdc, cxChar, cyChar / 2, szUnd, (sizeof szUnd) - 1) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_KEYDOWN : ShowKey (hwnd, 0, "WM_KEYDOWN", wParam, lParam) ; return 0 ; case WM_KEYUP : ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam) ; return 0 ; case WM_CHAR : ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam) ; return 0 ; case WM_DEADCHAR : ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam) ; return 0 ; case WM_SYSKEYDOWN : ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam) ; break ; // ie, call DefWindowProc case WM_SYSKEYUP : ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam) ; break ; // ie, call DefWindowProc case WM_SYSCHAR : ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam) ; break ; // ie, call DefWindowProc case WM_SYSDEADCHAR : ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam) ; break ; // ie, call DefWindowProc case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 5.3. Programul KEYLOOK Programul KEYLOOK folosete ecranul dup modelul teleimprimatorului. Atunci cnd primete un mesaj generat de acionarea unei taste, programul KEYLOOK apeleaz funcia ScrollWindow, care deruleaz n sus coninutul ntregii zone client cu nlimea unui caracter. Funcia TextOut este folosit pentru afiarea unei noi linii de informaii, ncepnd afiarea ntr-un punct aflat la o distan egal cu nlimea 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 afiat de programul KEYLOOK atunci cnd tastai cuvntul Windows". Prima coloan conine mesajele de Ia tastatur; a

doua coloan conine codurile de taste virtuale pentru mesajele generate de acionarea fastelor; a treia coloan conine codul caracterului (i caracterul); celelalte ase coloane prezint starea celor ase cmpuri din parametrul lParam al mesajului.

Figura 5-4. Fereastra afiat de programul KEYLOOK.

n cea mai mare parte, programul KEYLOOK folosete caracteristici Windows despre care deja am discutat n programele SYSMETS prezentate pn acum, dar sunt folosite i cteva funcii noi. Remarcai, totui, c formatarea coloanelor n programul KEYLOOK ar fi fost destul de dificil dac s-ar fi folosit fontul prestabilit proporional. Codul pentru afiarea fiecrei linii ar trebui mprit n nou seciuni pentru a pstra coloanele aliniate. Pentru rezolvarea unei astfel de situaii o soluie mai simpl este s folosii un font cu dimensiune fix. Aa cum am artat n capitolul anterior, pentru aceasta este nevoie de dou funcii, pe care le-am combinat ntr-o singur instruciune:
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

Programul KEYLOOK apeleaz aceste funcii de fiecare dat cnd obine un context de dispozitiv. Acest lucru se ntmpl n trei locuri din program: n funcia ShowKey, n timpul prelucrrii mesajului WM_CREATE din funcia WndProc i n timpul prelucrrii mesajului WM_PAINT. Funcia GetStockObject obine 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. Funcia SelectObject selecteaz obiectul n contextul de dispozitiv. Dup acest apel, orice text va fi afiat cu fontul de dimensiune fix selectat. Putei s v ntoarcei la fontul proporional prestabilit, cu urmtoarea instruciune:
SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

Funcia ShowKey apeleaz funcia ScrollWindow, care deruleaz liniile anterioare naintea afirii unei noi linii. n mod normal, aceast operaie ar duce la invalidarea parial a ferestrei i ar gener un mesaj WM_PAINT. Pentru a mpiedica acest lucru, nainte de terminare, funcia ShowKey apeleaz funcia ValidateRect. Programul KEYLOOK nu salveaz mesajele pe care le primete, aa c la primirea unui mesaj WM_PAINT nu poate s re-creeze fereastra. Din acest motiv, la primirea mesajului WM_PAINT programul KEYLOOK afieaz n partea superioar a ferestrei capetele celor nou coloane. naintea apelrii funciei BeginPaint, programul KEYLOOK invalideaz ntreaga fereastr. n acest fel, este tears toat suprafaa ferestrei, nu numai dreptunghiul invalid. (Faptul c programul KEYLOOK nu salveaz mesajele pe care le primete i deci la primirea unui mesaj WM_PAINT nu poate s re-creeze fereastra este un neajuns al programului. Programul TYPER, prezentat puin mai trziu, corecteaz aceast eroare.) Programul KEYLOOK afieaz n partea superioar a zonei client un antet pentru identificarea celor nou coloane. Dei este posibil crearea unui font subliniat, n acest program am ales o abordare puin diferit. Am definit dou iruri de caractere, numite szTop (care conine textul antetului) i szUnd (care conine liniuele de subliniere) i le-am afiat n aceeai poziie n partea de sus a ecranului, n timpul prelucrrii mesajului WM_PAINT. n mod normal, Windows afieaz textul n modul opac" (OPAQUE), ceea ce nseamn c terge zona de fond din spatele caracterelor afiate. 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 afiare n contextul de dispozitiv din opac" n transparent":

SetBkMode (hdc, TRANSPARENT) ;

CURSORUL DE EDITARE (CARET) Atunci cnd introducei text ntr-un program, poziia n care va aprea urmtorul caracter este indicat de o liniu de subliniere sau de un mic dreptunghi, n Windows pentru acest semn se folosete termenul cursor de editare".

Funcii pentru cursorul de editare


Exist cinci funcii principale pentru cursorul de editare: CreateCaret - creeaz un cursor de editare asociat unei ferestre. SetCaretPos - stabilete poziia cursorului de editare n fereastr. ShowCaret - afieaz cursorul de editare. HideCaret - mascheaz cursorul de editare. DestroyCaret - distruge cursorul de editare creat. Mai exist i alte funcii pentru obinerea poziiei cursorului de editare (GetCaretPos) i pentru stabilirea i obinerea intervalelor de licrire 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 proporionale nu au aceeai lime, linia sau blocul orizontal nu poate avea limea exact a unui caracter. Cursorul de editare nu poate fi creat pur i simplu n timpul prelucrrii mesajului WM_CREATE i nici distrus n timpul prelucrrii mesajului WM_DESTROY. El este ceea ce se numete o resurs de sistem". Aceasta nseamn c n sistem exist un singur cursor de editare. De fapt, atunci cnd un program trebuie s afieze un cursor de editare n fereastra proprie, el mprumut" acest semn de la sistem. Pare prea restrictiv? Ei bine, n realitate nu este. Gndii-v puin: afiarea unui cursor de editare ntr-o fereastr are sens numai dac fereastra respectiv deine 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 deine cursorul de intrare, aa c existena unui singur cursor de editare este logic. Un program poate determina dac deine cursorul de intrare prin prelucrarea mesajelor WM_SETFOCUS i WM_KILLFOCUS. O procedur de fereastr recepioneaz un mesaj WM_SETFOCUS atunci cnd primete cursorul de intrare i un mesaj WM_KILLFOCUS atunci cnd pierde cursorul de intrare. Aceste mesaje sunt transmise n pereche. O procedur de fereastr primete ntotdeauna un mesaj WM_SETFOCUS nainte de a primi un mesaj WM_KILLFOCUS i ntotdeauna va primi un numr 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 funcia CreateCaret n timpul prelucrrii mesajului WM_SETFOCUS i funcia DestroyCaret n timpul prelucrrii mesajului WMJKILLFOCUS. Exist i alte cteva reguli: la creare, cursorul de editare nu este afiat. Dup apelarea funciei CreateCaret, programul trebuie s apeleze funcia ShowCaret pentru a face vizibil cursorul de editare. n plus, procedura de fereastr trebuie s-l mascheze, apelnd funcia HideCaret, ori de cte ori deseneaz ceva pe ecran n timpul prelucrrii unui alt mesaj dect WM_PAINT. Dup terminarea operaiei de desenare, programul apeleaz funcia ShowCaret ca s afieze din nou cursorul de editare. Efectul funciei HideCaret este aditiv: dac apelai funcia HideCaret de mai multe ori fr s apelai funcia ShowCaret, atunci cnd dorii ca acest cursor de editare s devin din nou vizibil, trebuie s apelai funcia ShowCaret de tot attea ori de cate ori ai apelat i funcia HideCaret. Programul TYPER Programul TYPER, prezentat n Figura 5-5, combin cea mai mare parte a lucrurilor nvate n acest capitol. Putei s v gndii la programul TYPER ca la un editor de text mai rudimentar. Putei s introducei text n fereastr, s mutai cursorul de editare cu ajutorul tastelor de deplasare i s tergei coninutul ferestrei apsnd tasta Esc. Coninutul ferestrei este ters, de asemenea, i atunci cnd fereastra este redmensionat. Nu exist posibilitatea de derulare, de cutare sau de nlocuire, de salvare a fiierelor sau de verificare ortografic, dar putem spune c este un nceput! Pentru a face lucrurile mai uoare pentru mine, TYPER folosete un font cu dimensiune fix. Aa cum v putei imagina, scrierea unui editor pentru un font proportional este mult mai dificil. Programul obine un context de dispozitiv n timpul prelucrrii mai multor mesaje: WM_CREATE, WM_KEYDOWN, WM_CHAR si WM_PAINT. De fiecare dat, prin apelarea funciilor GetStockObject i SelectObject, este selectat n contextul de dispozitiv un font cu dimensiune fix.

n timpul prelucrrii mesajului WM_SIZE, TYPER calculeaz limea i nlimea n caractere a ferestrei i salveaz aceste valori n variabilele cxBuffer i cyBuffer. Apoi apeleaz funcia malloc pentru alocarea unei zone de memorie suficient de mare pentru a conine toate caracterele care pot fi introduse ntr-o fereastr. Variabilele xCaret i yCaret sunt folosite pentru stocarea poziiei cursorului de editare. n timpul prelucrrii mesajului WM_SETFOCUS, TYPER apeleaz funcia CreateCaret pentru crearea unui semn caret cu dimensiunile unui caracter, apoi funcia SetCaretPos pentru stabilirea poziiei cursorului de editare i funcia ShowCaret, pentru a-l face vizibil. n timpul prelucrrii mesajului WM_KILLFOCUS, TYPER apeleaz funciile HideCaret i DestroyCaret.
#--------------------# TYPER.MAK make file #--------------------typer.exe : typer.obj $(LINKER) $(GUIFLAGS) -OUT:typer.exe typer.obj $(GUILIBS) typer.obj : typer.c $(CC) $(CFLAGS) typer.c /*-------------------------------------TYPER.C -- Typing Program (c) Charles Petzold, 1996 --------------------------------------*/ #include <windows.h> #include <stdlib.h> #define BUFFER(x,y) *(pBuffer + y * cxBuffer + x) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Typer" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Typing Program", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static char *pBuffer = NULL ; static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, xCaret, yCaret ; HDC hdc ; int x, y, i ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE : hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight ; ReleaseDC (hwnd, hdc) ; return 0 ; case WM_SIZE : // obtain window size in pixels cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; // calculate window size in characters cxBuffer = max (1, cxClient / cxChar) ; cyBuffer = max (1, cyClient / cyChar) ; // allocate memory for buffer and clear it if (pBuffer != NULL) free (pBuffer) ; if ((pBuffer = (char *) malloc (cxBuffer * cyBuffer)) == NULL) MessageBox (hwnd, "Window too large. Cannot " "allocate enough memory.", "Typer", MB_ICONEXCLAMATION | MB_OK) ; else for (y = 0 ; y < cyBuffer ; y++) for (x = 0 ; x < cxBuffer ; x++) BUFFER(x,y) = ' ' ; // set caret to upper left corner xCaret = 0 ; yCaret = 0 ; if (hwnd == GetFocus ()) SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; return 0 ; case WM_SETFOCUS : // create and show the caret CreateCaret (hwnd, NULL, cxChar, cyChar) ; SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; ShowCaret (hwnd) ; return 0 ; case WM_KILLFOCUS : // hide and destroy the caret HideCaret (hwnd) ; DestroyCaret () ; return 0 ; case WM_KEYDOWN : switch (wParam)

{ case VK_HOME : xCaret = 0 ; break ; case VK_END : xCaret = cxBuffer - 1 ; break ; case VK_PRIOR : yCaret = 0 ; break ; case VK_NEXT : yCaret = cyBuffer - 1 ; break ; case VK_LEFT : xCaret = max (xCaret - 1, 0) ; break ; case VK_RIGHT : xCaret = min (xCaret + 1, cxBuffer - 1) ; break ; case VK_UP : yCaret = max (yCaret - 1, 0) ; break ; case VK_DOWN : yCaret = min (yCaret + 1, cyBuffer - 1) ; break ; case VK_DELETE : for (x = xCaret ; x < cxBuffer - 1 ; x++) BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ; BUFFER (cxBuffer - 1, yCaret) = ' ' ; HideCaret (hwnd) ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; TextOut (hdc, xCaret * cxChar, yCaret * cyChar, & BUFFER (xCaret, yCaret), cxBuffer - xCaret) ; ShowCaret (hwnd) ; ReleaseDC (hwnd, hdc) ; break ; } SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; return 0 ; case WM_CHAR : for (i = 0 ; i < (int) LOWORD (lParam) ; i++) { switch (wParam) { case '\b' : // backspace if (xCaret > 0) { xCaret-- ; SendMessage (hwnd, WM_KEYDOWN, VK_DELETE, 1L) ; } break ; case '\t' : do // tab

{ SendMessage (hwnd, WM_CHAR, ' ', 1L) ; } while (xCaret % 8 != 0) ; break ; case '\n' : // line feed if (++yCaret == cyBuffer) yCaret = 0 ; break ; case '\r' : xCaret = 0 ; // carriage return

if (++yCaret == cyBuffer) yCaret = 0 ; break ; case '\x1B' : // escape for (y = 0 ; y < cyBuffer ; y++) for (x = 0 ; x < cxBuffer ; x++) BUFFER (x, y) = ' ' ; xCaret = 0 ; yCaret = 0 ; InvalidateRect (hwnd, NULL, FALSE) ; break ; default : // character codes BUFFER (xCaret, yCaret) = (char) wParam ; HideCaret (hwnd) ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; TextOut (hdc, xCaret * cxChar, yCaret * cyChar, & BUFFER (xCaret, yCaret), 1) ; ShowCaret (hwnd) ; ReleaseDC (hwnd, hdc) ; if (++xCaret == cxBuffer) { xCaret = 0 ; if (++yCaret == cyBuffer) yCaret = 0 ; } break ; } } SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; for (y = 0 ; y < cyBuffer ; y++) TextOut (hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

Figura 5.5. Programul TYPER Prelucrarea mesajelor WM_KEYDOWN i WM_CHAR este ceva mai complicat. Prelucrarea mesajului WM_KEYDOWN implic, n cea mai mare parte, tastele de deplasare a cursorului. Tasta Home duce cursorul de editare la nceputul liniei, tasta End l duce la sfritul liniei, iar tastele Page Up i Page Down l mut la nceputul, respectiv la sfritul ferestrei. Tastele cu sgei funcioneaz normal, ca i n alte programe. n cazul apsrii tastei Delete, programul TYPER trebuie s mute spre stnga cu o poziie tot coninutul bufferului de la urmtorul caracter, pn la sfritul liniei i apoi s afieze un spaiu la sfritul liniei. n timpul prelucrrii mesajului WM_CHAR sunt tratate tastele Backspace, Tab, Linefeed (Ctrl+Enter), Enter, Esc i caracterele. Remarcai 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 funciei SendMessage. Tasta Backspace este emulat prin logica de tratare a tastei Delete iar tasta Tab este emulat prin mai multe spaii. Aa cum am menionat anterior, este recomandat s mascai cursorul de editare atunci cnd efectuai operaii de desenare n timpul prelucrrii altor mesaje dect WM_PAINT. Programul face acest lucru n timpul prelucrrii mesajului WM_KEYDOWN pentru tasta Delete i n timpul prelucrrii mesajului WM_CHAR pentru caractere. n ambele cazuri, programul TYPER schimb coninutul bufferului i apoi afieaz noile caractere n fereastr. Aa cum se poate vedea n Figura 5.6, am folosit programul TYPER atunci cnd am lucrat la discursuri.

Figura 5.6. Fereastra afiat de programul TYPER. SETUL DE CARACTERE WINDOWS Am menionat anterior c apsarea unei taste moarte naintea tastei corespunztoare unei litere precedat de o tasta moart genereaz un mesaj WM_CHAR pentru care parametrul wParam conine codul ASCII al caracterului cu semnul diacritic. S-ar putea ca aceast afirmaie s nu fie prea clar, deoarece setul de caractere ASCII nu conine nici un cod pentru caractere cu semne diacritice. Atunci, ce conine parametrul wParam? Pentru a rspunde la aceast ntrebare trebuie s discutm despre seturile de caractere, un subiect care, dei la prima vedere pare a-i avea mai degrab locul ntr-o discuie despre fonturi, are importana lui i pentru manipularea tastaturii. Setul de caractere ASCII standard pe 7 bii definete 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 afiabile. Nici unul dintre aceste caractere nu are semne diacritice. Deoarece calculatoarele personale folosesc octei de cte opt bii, productorii de calculatoare definesc un set de caractere care conine 256 de coduri n loc de 128. Codurile suplimentare pot conine 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, afiarea caracterelor cu diacritice ar fi foarte simpl, dar Windows nu accept un set de caractere extins, ci dou. Din nefericire, ns, prezena celor dou seturi de caractere nu face lucrurile de dou ori mai uoare.

Setul de caractere OEM Mai nti, haidei 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, aa cum se poate vedea n Figura 5.7. Codurile de la 0x20 la 0x7E sunt caracterele afiabile din setul de caractere ASCII. Celelalte sunt nestandard - sau cel puin aa erau n momentul respectiv. Acest set de caractere nu poate fi ignorat. Este codificat hardware n cipurile ROM ale plcilor grafice, imprimantelor i circuitelor BIOS ale plcilor de baz IBM. A fost copiat de numeroii productori de calculatoare i periferice compatibile IBM. Acest set de caractere face parte din ceea ce se numete standardul IBM". Multe programe non-Windows n mod caracter scrise pentru calculatoarele IBM PC au nevoie de acest set de caractere, deoarece folosesc pentru afiare 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 rnd, 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 vrei s desenai o linie orizontal, n Windows este mai uor s apelai o funcie de desenare dect s afiai un ir de caractere 0xC4. n al doilea rnd, alfabetul grecesc i simbolurile matematice sunt mai puin importante n Windows dect literele accentuate, folosite de majoritatea limbilor europene. Un program care trebuie s afieze simboluri matematice o poate face mai uor folosind funcii grafice. Pe scurt, sistemul de operare Windows accept setul de caractere IBM, dar i acord o importan mai mic n principal pentru aplicaii mai vechi, rulate n ferestre. n mod normal, aplicaiile Windows nu folosesc setul de caractere IBM. n documentaia Windows, setul de caractere IBM este numit setul de caractere OEM". Setul de caractere OEM este mai precis definit ca setul de caractere nativ pe calculatorul pe care ruleaz sistemul de operare Windows.

Asigurarea suportului internaional sub MS-DOS


Exist mai multe variante ale setului de caractere IBM PC, variante numite pagini de cod ". Varianta folosit n Statele Unite i n majoritatea rilor europene este pagina 437. Calculatoarele vndute n Norvegia, Danemarca, Portugalia i alte cteva ri europene folosesc alte pagini, care conin multe dintre caracterele speciale folosite n rile respective. n ultimul timp, o parte dintre aceste ri au nceput s folosesc pagina 850, care conine mai puine 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 aplicaiilor MS-DOS n ferestre i pentru afiarea memoriei clipboard) corespunztoare paginii de coduri a sistemului i prin instalarea tabelelor de conversie corespunztoare pentru funciile CharToOem i OemToChar (despre care vom discuta ceva mai trziu). Programul de instalarea Windows Setup va selecta paginile de cod corespunztoare, n funcie de atributele regionale ale configuraiei curente a sistemului.

Setul de caractere ANSI


Setul extins de caractere folosit de obicei de Windows i de programele Windows se numete setul de caractere ANSI" i este, de fapt, un standard ISO. Atunci cnd programul dumneavoastr primete un mesaj WM_CHAR, parametrul wParam con ine codul ANSI al caracterului. Setul de caractere ANSI este prezentat n Figura 5.8. Aa cum se poate vedea, codurile de la 0x20 la 0x7E reprezint aceleai caractere ca i n setul de caractere OEM i n setul de caractere ASCII. Caracterele afiate sub forma unor blocuri nu sunt definite. Acestea pot aprea 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 ieire (cum ar fi imprimanta). Fonturile TrueType definesc unele caractere suplimentare pentru codurile ANSI de la 0x80 la 0x9F. OEM, ANSI i fonturile Windows conine diferite fonturi pentru afiarea seturilor de caractere ANSI i OEM. Atunci cnd obinei 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 folosete setul de caractere ANSI. Dac vrei s afiai caractere din setul OEM, putei s selectai fontul OEM_FIXED_FONT (numit i font de terminal") n contextul de dispozitiv, folosind urmtoarea instruciune:
SelectObject (hdc, GetStockObject (OEM_FIXED_FONT)) ;

PROBLEME LEGATE DE INTERNAIONALIZARE Am vzut c atunci cnd un utilizator Windows cu o tastatur diferit de cea definit pentru Statele Unite introduce un caracter cu semn diacritic, parametrul wPavam al mesajului WM_CHAR conine codul ANSI al caracterului respectiv. Ca urmare, dac trebuie s afiai pe ecran caracterul respectiv, este bine s folosii un font pentru setul de caractere ANSI (cum ar fi SYSTEM_FONT sau SYSTEM_FIXED_FONT). Dac folosii fontul OEM_FIXED_FONT, caracterul afiat pe ecran va fi incorect i l va surprinde pe utilizator. Alte cteva reguli simple v vor ajuta s pstrai nemodificat codul de manipulare a tastaturii atunci cnd scriei un program pentru piaa european. Folosirea seturilor de caractere Atunci cnd primii un mesaj WM_CHAR, inei seama de faptul c parametrul wParam poate avea i valori mai mari de 128. Nu pornii de la ideea c orice cod mai mare de 128 reprezint un caracter invalid. S-ar putea s avei uneori nevoie de un caracter scris cu majuscul. Nu folosii un algoritm propriu, cum ar fi: if (ch = 'a' && ch <= 'z') ch -=32 ; // Greit!!! Aceasta este o metod greit n programarea sub Windows. Dar nu folosii nici funciile standard de conversie din C: ch = toupper (ch) ; // Greit!!! Rezultatele acestor funcii sunt corecte numai pentru prima jumtate a setului de caractere. Funciile standard din C nu vor converti codul 0xE0 n 0xC0. n schimb, putei s folosii funciile Windows CharUpper i CharLower. Dac pString este un ir de caractere terminat cu zero, putei s l convertii n litere mari folosind funcia CharUpper:
CharUpper (pString) ;

Dac irul de caractere nu este terminat cu zero folosii funcia CharUpperBuff:


CharUpperBuff (pString, nLength) ;

Putei s folosii funcia CharUpper i pentru un singur caracter, dar sunt necesare unele conversii forate de tip, deoarece cuvntul mai semnificativ al parametrului trebuie s aib valoarea 0:
ch = CharUpper ((PSTR) (LONG) (BYTE) ch) ;

Dac ch este definit ca un caracter fr semn (unsigned character) prima conversie forat (BYTE) nu este necesar. Windows include i funciile CharLower i CharLowerBuff pentru convertirea irurilor de caractere n litere mici. Dac v gndii cu seriozitate la scrierea unor programe Windows care s poat fi modificate cu uurin pentru alte limbi, ar trebui s examinai i funciile CharNext i CharPrev. Aceste funcii v ajut s manipulai seturile de caractere pe mai muli octei, deseori folosite n rile orientale. Aceste seturi conin mai mult de 256 de caractere, dintre care unele folosesc doi octei. Dac v bazai pe aritmetica de pointeri normal din C pentru parcurgerea unui ir de caractere (de exemplu pentru cutarea unui caracter backslash ntr-o cale de acces la un director) s-ar putea s credei c ai gsit caracterul cutat i, de fapt, s folosii al doilea caracter al unui cod. Funciile 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 octei. Comunicarea cu MS-DOS Dac Windows ar fi singurul sistem de operare rulat pe calculator, ai putea s uitai de setul de caractere OEM i s folosii numai setul de caractere ANSI. Dar utilizatorii pot s creeze fiiere n MS-DOS i s le foloseasc n Windows, sau s creeze fiiere n Windows i s le foloseasc n MS-DOS. Din pcate, sistemul de operare MS-DOS folosete setul de caractere OEM. Iat o posibil problem de comunicare. S presupunem c utilizatorul unui calculator personal, vorbitor de limb german, creeaz un fiier numit UBUNGEN.TXT (exerciii practice") ntr-un program MS-DOS, cum ar fi EDIT. Pe un calculator IBM PC caracterul U face parte din setul de caractere IBM (adic OEM) i are codul

154 sau 0x9A. (Folosind tastatura de tip american sub MS-DOS pe un calculator IBM PC putei s creai aceast liter introducnd codul Alt+154 folosind cifrele din blocul de taste numerice.) MS-DOS folosete acest cod de caracter pentru numele fiierului din director. Dac un program Windows apeleaz o funcie MS-DOS ca s obin fiierele dintr-un director i afieaz numele fiierelor pe ecran folosind un font pentru setul de caractere ANSi, prima liter din numele fiierului UBUNGEN.TXT va fi afiat 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 fcut de funcia Windows OemToChar. Funcia OemToChar primete ca parametri doi pointeri de tip far ctre dou iruri de caractere. Caracterele OEM din primul ir sunt convertite n caractere ANSI i sunt stocate n cel de-al doilea ir:
OemToChar (lpszOemStr, lpszAnsiStr) ;

Acum s vedem acelai exemplu, dar n sens invers. Vorbitorul de limb german dorete ca programul Windows s creeze un fiier numit UBUNGEN.TXT. Primul caracter al numelui de fiier introdus de utilizator are codul 220 (0xDC). Dac folosii o funcie MS-DOS pentru deschiderea fiierului, MS-DOS folosete caracterul respectiv n numele fiierului. Dac ulterior utilizatorul vrea s vad numele fiierului sub MS-DOS, primul caracter va fi afiat sub forma unui bloc. naintea apelrii funciei MS-DOS trebuie s convertii numele fiierului n caractere OEM:
CharToOem (IpszAnsiStr, IpszOemStr) ;

Funcia CharToOem convertete caracterul 220 (0xDC) n caracterul 154 (0x9A). n Windows exist dou funcii cu acelai rezultat, CharToOemBuff i OemToCharBuff, pentru care, ns, irurile de caractere nu trebuie s se termine cu 0. Conversiile de mai sus sunt fcute i de funcia Windows OpenFile. Dac folosii funcia OpenFile nu apelai i funcia CharToOem. Dac folosii funcii MS-DOS ca s obinei lista de fiiere (aa cum face programul Windows File Manager), atunci numele fiierelor trebuie convertite cu funcia OemToChar, nainte de a fi afiate. Convertirea coninutului fiierelor este o alt problem care apare atunci cnd fiierele sunt folosite att sub Windows, ct i sub MS-DOS. Dac programul Windows folosete fiiere despre care tii cu siguran c au fost create ntr-un program MS-DOS, atunci coninutul acestora ar trebui s fie convertit cu ajutorul funciei OemToChar. La fel, dac programul dumneavoastr creeaz fiiere care vor fi folosite de un program MS-DOS, trebuie s folosii funcia CharToOem ca s convertii coninutul acestora. Funciile OemToChar i CharToOem sunt stocate n driverul de tastatur i conin tabele de cutare foarte simple. Funcia OemToChar convertete codurile OEM de Ia 0x80 la 0xFF ntr-un cod ANSI reprezentnd caracterul care seamn cel mai mult cu caracterul OEM corespunztor, n unele cazuri aceast conversie este aproximativ. De exemplu, majoritatea caracterelor de desenare din setul de caractere IBM sunt convertite n semne plus, liniue de desprire i linii verticale. Majoritatea codurilor OEM de la 0x00 la 0x1F nu sunt convertite n coduri ANSI. Funcia CharToOem convertete 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 corespunztor, fr semnele diacritice. Folosirea blocului de taste numerice Dup cum probabil tii, tastatura IBM PC i sistemul BIOS v permit s introducei coduri pentru caracterele din setul extins IBM; astfel, apsai tasta Alt, introducei cu ajutorul tastelor numerice codul din trei cifre al caracterului OEM, apoi eliberai tasta Alt. n Windows, aceast posibilitate este dublat n dou feluri. n primul rnd, atunci cnd introducei codul Alt+[cod OEM] de la tastele numerice, Windows returneaz (prin parametrul wParam al mesajului WM_CHAR) codul caracterului ANSI care seamn cel mai mult cu caracterul al crui cod OEM l-ai introdus. Aceasta nseamn c Windows trece codul prin funcia OemToChar nainte de generarea mesajului WM_CHAR. Aceast operaie se face n folosul utilizatorului. Dac nu avei o tastatur pentru limba strin pe care dorii s o folosii i suntei obinuit s introducei caracterul U folosind combinaia de taste Alt+154, putei s facei acelai lucru i n Windows. Nu este nevoie s nvai i codurile ANSI. n al doilea rnd, dac vrei s generai un cod ANSI extins folosind tastatura de tip american, introducei codul Alt+0[cod ANSI] de la blocul de taste numerice. Parametrul wParam al mesajului WM_CHAR va conine codul ANSI respectiv. Ca urmare, Alt+0220 este tot U. Putei s ncercai acest lucru n programele KEYLOCK sauTYPER. Soluia Unicode pentru Windows NT Dezvoltatorii de programe implicai n crearea unor aplicaii pentru piaa internaional au fost nevoii s inventeze diferite soluii pentru rezolvarea deficienelor codului ASCII pe 7 bii, cum ar fi paginile de coduri sau seturile de caractere pe doi octei. Este nevoie de o soluie mai bun, i aceasta ar putea fi Unicode.

Unicode este un standard de codificare a caracterelor, care folosete un cod uniform pe 16 bii pentru fiecare caracter. Acest cod permite reprezentarea tuturor caracterelor, din toate limbajele scrise, care pot fi folosite n comunicaiile prin calculator, inclusiv simbolurile chinezeti, japoneze i coreene. Unicode a fost dezvoltat de un consoriu 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 diferenele fa de Windows NT, care include suportul pentru standardul Unicode. Evident, adaptarea programelor (i a modului de gndire al programatorilor) la ideea folosirii codurilor pe 16 bii este o sarcin laborioas, dar merit dac n acest fel vom putea afia pe ecran i vom putea tipri la imprimant texte n toate limbajele scrise existente.

Capitolul 6. Mouse-ul
Mouse-ul este un dispozitiv de indicare cu unul sau mai multe butoane. n ciuda experimentelor fcute cu mai multe tipuri de dispozitive de indicare, cum ar fi ecranele sensibile la atingere (touch screen) sau creioanele optice (light pen), mouse-ul (i alte produse asemntoare precum bilele - trackballs - folosite pentru calculatoarele portabile) este singurul dispozitiv care a ptruns pe scar larg pe piaa calcu-latoarelor personale. Lucrurile nu au fost, ns, ntotdeauna aa. Primii dezvoltatori ai sistemului de operare Windows i-au dat seama c nu pot s oblige utilizatorii s cumpere un mouse, pentru a utiliza produsul software. Ca urmare, au considerat mouse-ul un accesoriu opional i au furnizat o interfa cu tastatura pentru toate operaiile din Windows i din aplicaiile livrate mpreun cu acesta, lucru pe care l-au fcut i ali dezvoltatori. Dei mouse-ul a devenit un dispozitiv aproape universal pentru calculatoarele pe care ruleaz Windows, eu cred nc n acest principiu. Cei care se descurc foarte bine cu tastatura (i n special dactilografele) prefer s in minele deasupra tastaturii i presupun c multe persoane au avut surpriza de a pierde" uneori mouse-ul pe un birou aglomerat. Din aceste motive, eu nc mai recomand - atunci cnd este posibil - includerea n program a unei interfee cu tastatura, care s dubleze funciile mouse-ului. Elemente de baz despre mouse Windows 95 permite folosirea mouse-ului cu un buton, cu dou butoane sau cu trei butoane, precum i folosirea unui joystick sau a unui creion optic, pentru simularea unui mouse cu un buton. Deoarece mouse-ul cu un singur buton reprezint un numitor comun, muli programatori Windows au evitat mult vreme s foloseasc celelalte butoane ale mouse-ului. Totui, mouse-ul cu trei butoane a devenit un standard de facto, aa c reticena tradiional 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 operaii speciale de tragere (despre care vom discuta n curnd). Putei s determinai dac mouse-ul este prezent folosind funcia GetSystemMetrics:
fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;

Variabila fMouse va avea valoarea TRUE (diferit de zero) dac mouse-ul este instalat. Pentru determinarea numrului de butoane ale mouse-ului instalat, folosii tot funcia GetSystemMetrics:
cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;

Aceast funcie returneaz valoarea 0 dac mouse-ul nu este instalat. Utilizatorii care folosesc mna stng pot s inverseze butoanele mouse-ului folosind Panoul de control din Windows. Dei o aplicaie poate s determine dac butoanele mouse-ului au fost inversate apelnd funcia GetSystemMetrics cu para-metrul SM_SWAPBUTTON, de obicei acest lucru nu este necesar. Butonul apsat cu indexul este considerat butonul din stng al mouse-ului, chiar dac fizic se afl n partea dreapt a mouse-ului. Totui, dac scriei un program de instruire n care desenai un mouse pe ecran, poate c vei avea nevoie s tii dac butoanele au fost inversate. Cteva scurte definiii Atunci cnd 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 poziie precis pe ecran. Driverul de afiare conine cteva indicatoare de mouse predefinite, care pot fi folosite de programe. Indicatorul cel mai obinuit este sgeata oblic definit n fiierele antet din Windows cu numele IDC_ARROW. Zona senzitiv a acestui indicator este vrful sgeii. Indicatorul IDC_CROSS (folosit n programele BLOKOUT dm acest capitol) are zona senzitiv situat la intersecia unor linii subiri. Indicatorul IDC_WAIT are forma unei clepsidre i, n general, indic faptul c programul execut o operaie de durat. Programatorii pot s creeze indicatoare proprii (aa cum vom face n Capitolul 9). Indicatorul predefinit al unei ferestre este specificat la definirea structurii clasei de ferestre. De exemplu:
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

Termenii urmtori descriu aciunile care pot fi executate cu mouse-ul: Clic - apsarea i eliberarea unui buton al mouse-ului. Dublu clic - apsarea i eliberarea unui buton al mouse-ului de dou ori, ntr-o succesiune rapid.

Tragere - mutarea mouse-ului innd apsat unul dintre butoane.

n cazul unui mouse cu trei butoane, acestea sunt desemnate: butonul din stng, butonul din mijloc i butonul din dreapta. Identificatorii definii n fiierele antet din Windows n legtur cu mouse-ul folosesc abrevierile LBUTTON, MBUTTON i RBUTTON. Mouse-ul cu dou butoane are numai butonul din stnga i butonul din dreapta. Mouse-ul cu un singur buton are numai butonul din stnga. MESAJE GENERATE DE MOUSE N ZONA CLIENT n capitolul anterior ai vzut c Windows trimite mesaje de la tastatur numai ferestrei care deine cursorul de intrare. n cazul mouse-ului, lucrurile se petrec altfel: o procedur de fereastr primete mesaje de la mouse de fiecare dat cnd indicatorul mouse-ului trece pe deasupra ferestrei sau cnd se execut clic n fereastra respectiv, chiar dac fereastra nu este activ sau nu deine cursorul de intrare. n Windows sunt definite 21 de mesaje generate de mouse. Totui, 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 cnd mouse-ul este deplasat peste zona client a unei ferestre, procedura de fereastr primete mesajul WM_MOUSEMOVE. Dac un buton al mouse-ului este apsat sau eliberat n zona client a unei ferestre, procedura de fereastr primete urmtoarele mesaje:
Buton Stnga Mijloc
Apsat Eliberat Apsat (al doilea clic)

WM_LBUTTONDOWN WM_MBUTTONDOWN

WM_LBUTTONUP WM_MBUTTONUP

WM_LBUTTONDBLCLK WM_MBUTTONDBLCLK

Dreapta

WM_RBUTTONDOWN WM_RBUTTONUP

WM_RBUTTONDBLCLK

Procedura de fereastr primete mesaje MBUTTON" numai de la un mouse cu trei butoane i mesaje RBUTTON" numai de la un mouse cu dou sau cu trei butoane. Procedura de fereastr primete mesaje DBLCLK" numai n cazul n care clasa de ferestre a fost definit astfel nct s recepioneze aceste mesaje (aa cum vom vedea mai jos). Pentru toate mesajele, parametrul lParam conine poziia mouse-ului. Cuvntul mai puin semnificativ conine coordonata pe axa x, iar cuvntul mai semnificativ conine coordonata pe axa y, relative la colul din stnga-sus al zonei client a ferestrei. Putei s extragei coordonatele pe axele x i y din parametrul IParam folosind macroinstruciunile LOWORD i HIWORD, definite n fiierele antet din Windows. Valoarea parametrului wParam indic starea buloanelor mouse-ului i starea tastelor Shift i Ctrl. Putei s testai parametrul wParam folosind o serie de mti definite n fiierele antet din Windows. Prefixul MK vine de la mouse key" (tast de mouse"). MK_LBUTTON MK_MBUTTON MK_RBUTTON MK_SHIFT MK_CONTROL Butonul din stnga al mouse-ului este apsat Butonul din mijloc al mouse-ului este apsat Butonul din dreapta al mouse-ului este apsat Tasta Shift este apsat Tasta Ctrl este apsat

Atunci cnd deplasai indicatorul mouse-ului peste zona client a unei ferestre nu se genereaz un mesaj WM_MOUSEMOVE pentru fiecare pixel peste care trece indicatorul. Numrul mesajelor WM_MOUSEMOVE depinde de componentele hardware ale mouse-ului i de viteza cu care procedura de fereastr poate s prelucreze aceste mesaje. V vei forma o idee despre rata de transmitere a mesajelor WM_MOUSEMOVE atunci cnd vei experimenta programul CONNECT descris mai jos. Dac executai clic cu butonul din stnga al mouse-ului n zona client a unei ferestre inactive, Windows activeaz fereastra respectiv i i transmite un mesaj WM_LBUTTONDOWN. Atunci cnd procedura de fereastr primete un mesaj WM_LBUTTONDOWN putei s fii sigur c fereastra respectiv este activ. Totui, procedura de fereastr poate s primeasc un mesaj WM_LBUTTONUP fr s fi primit mai nti un mesaj WM_LBUTTONDOWN. Acest lucru se ntmpl dac utilizatorul apas butonul din stnga 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 fr s primeasc mesajul WM_LBUTTONUP corespunztor atunci cnd butonul mouse-ului este eliberat dup ce indicatorul a fost mutat n alt fereastr. Exist dou excepii de la aceste reguli:

O procedur de fereastr poate s captureze mouse-ul" i s continue s primeasc mesajele transmise de acesta, chiar dac indicatorul se afl n afara zonei client. Vei vedea cum putei s capturai mouse-ul ceva mai trziu n acest capitol. 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 ct timp sunt active. (O astfel de caset de mesaje modal de sistem este afiat atunci cnd ncheiai o sesiune de lucru n Windows.)

Operaii simple de prelucrare a mesajelor de Ia mouse: exemplu Programul CONNECT- prezentat n Figura 6.1 execut operaii simple de prelucrare a mesajelor de la mouse, pentru a v ajuta s v formai o idee despre modul n care Windows trimite programului dumneavoastr mesajele generate de mouse.
/*-------------------------------------------------CONNECT.C -- Connect-the-Dots Mouse Demo Program (c) Charles Petzold, 1996 --------------------------------------------------*/ #include <windows.h> #define MAXPOINTS 1000 #define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Connect" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Connect-the-Points Mouse Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static POINT points[MAXPOINTS] ; static int iCount ;

HDC hdc ; PAINTSTRUCT ps ; int i, j ; switch (iMsg) { case WM_LBUTTONDOWN : iCount = 0 ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_MOUSEMOVE : if (wParam & MK_LBUTTON && iCount < 1000) { points[iCount ].x = LOWORD (lParam) ; points[iCount++].y = HIWORD (lParam) ; hdc = GetDC (hwnd) ; SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L) ; ReleaseDC (hwnd, hdc) ; } return 0 ; case WM_LBUTTONUP : InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; for (i = 0 ; i < iCount - 1 ; i++) for (j = i + 1 ; j < iCount ; j++) { MoveTo (hdc, points[i].x, points[i].y) ; LineTo (hdc, points[j].x, points[j].y) ; } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 6-1. Programul CONNECT. Programul CONNECT prelucreaz trei mesaje generate de mouse: WM_LBUTTONDOWN - CONNECT terge zona client a ferestrei. WM_MOUSEMOVE - Dac butonul din stnga este apsat, CONNECT deseneaz n poziia indicatorului un punct negru. WM_LBUTTONUP - CONNECT conecteaz fiecare punct desenat n zona client cu toate celelalte puncte desenate. Uneori rezultatul este un desen drgu, alteori doar o mzgleal foarte dens (vezi Figura 6-2). Pentru folosirea programului CONNECT mutai indicatorul mouse-ului n zona client a ferestrei, apsai butonul din stnga, deplasai indicatorul puin i eliberai butonul apsat. Rezultatele cele mai frumoase se obin dac deplasai indicatorul rapid pe traiectoria unei curbe, ct timp butonul din stnga este apsat, CONNECT folosete cteva funcii simple ale interfeei GDI (Graphics Device Interface). Funcia SetPixel deseneaz un punct cu dimensiunea de un pixel cu o anumit culoare - negru n acest caz. (Pe ecranele cu rezoluie mare acest punct este abia vizibil.) Pentru desenarea liniilor sunt folosite alte dou funcii: funcia MoveTo marcheaz coordonatele

x i y ale ale nceputului liniei iar funcia LineTo deseneaz linia. (Remarcai faptul c am definit funcia MoveTo ca o macroinstruciune folosind funcia MoveToEx.) Dac mutai indicatorul mouse-ului n afara zonei client nainte de a elibera butonul din stnga, programul CONNECT nu mai conecteaz punctele, deoarece nu primete mesajul WM_LBUTTONUP. Dac mutai indicatorul mouse-ului napoi n zona client a programului i apsai din nou butonul din stnga, CONNECT terge zona client. (Dac vrei s continuai un desen nceput, dup ce eliberai butonul mouse-ului n afara zonei client, apsai din nou butonul n afara zonei client, mutai indicatorul n fereastra programului i eliberai butonul mouse-ului.) Programul CONNECT stocheaz cel mult 1000 de puncte. Numrul de linii desenate este egal cu Px(P-1), unde P este numrul de puncte. Pentru 1000 de puncte rezult aproape 500.000 de linii, pentru desenarea crora este nevoie de aproximativ cinci minute. Deoarece Windows 95 este un mediu cu multitasking controlat, n acest timp putei s trecei la alt program. Totui, pn la terminarea operaiei de desenare nu putei s facei nimic cu programul CONNECT (de exemplu, nu putei s mutai sau s redi mensionai fereastra programului). n Capitolul 14 vom examina cteva soluii pentru aceast problem.

Figura 6.2. Fereastra afiat 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 apelai de dou ori funcia SetCursor, folosind dou cursoare de stoc. De asemenea, CONNECT apeleaz de dou ori funcia ShowCursor, prima dat cu parametrul TRUE i a doua oar cu parametrul FALSE. Vom discuta mai pe larg despre aceste apeluri n seciunea Emularea mouse-ului cu tastatura" din acest capitol. Dac programul este ocupat cu desenarea liniilor, putei s apsai butonul mouse-ului, s mutai mouse-ul i s eliberai butonul mouse-ului - nu se va ntmpla nimic. Programul CONNECT nu primete aceste mesaje, deoarece este ocupat i nu apeleaz funcia GetMessage. Dup ce termin de desenat liniile, mesajele nu mai sunt valabile, deoarece butonul din stnga al mouse-ului a fost deja eliberat. Dm acest punct de vedere mouse-ul nu funcioneaz la fel ca tastatura. Windows consider important fiecare acionare de tast. Spre deosebire de acestea, dac butonul mouse-ului este apsat i eliberat n zona client a unui program ocupat cu o alt operaie, mesajele generate de mouse sunt anulate. Facei urmtorul experiment: ct timp programul CONNECT este ocupat cu o operaie de desenare ndelungat inei apsat butonul din stnga al mouse-ului i micai mouse-ul. Dup ce programul termin desenul, va prelua din coada de ateptare mesajul WM_LBUTTONDOWN (i va terge zona client) deoarece butonul este nc apsat. Totui, programul va recepion numai mesajele WM_MOUSEMOVE generate dup preluarea mesajului WM_LBUTTONDOWN.

Uneori, pentru modul n care programul trateaz micrile mouse-ului se folosete termenul urmrire" (tracking"). Aceasta nu nseamn c programul intr n procedura de fereastr ntr-un ciclu ncercnd s urmreasc micrile mouse-ului pe ecran. Procedura de fereastr prelucreaz fiecare mesaj generat de mouse i cedeaz controlul. PRELUCRAREA TASTELOR DE MODIFICARE Atunci cnd receptioneaz un mesaj WM_MOUSEMOVE, programul CONNECT face o operaie I pe bii ntre parametrul wParam al mesajului i masca de bii MK_LBUTTON ca s determine dac butonul din stnga este apsat. Putei s folosii parametrul wParam ca s determinai starea tastelor de modificare (Shift i Ctrl). De exemplu, dac prelucrarea mesajelor depinde de starea tastelor Shift i Ctrl, putei s folosii o secven de cod asemntoare cu aceasta:
if (MK_SHIFT & wParam) if (MK_CONTROL & wParam) { [Tastele Shift i Ctrl sunt apsate] } else { [Tasta Shift este apsata] } else if (MK_CONTROL&wParam) { [Tasta Shift este apsata] } else { [Tastele Shift i Ctrl nu sunt apsate] }

Dac n program sunt folosite att butonul din stnga ct i cel din dreapta al mouse-ului i vrei s permitei folosirea programului i de ctre utilizatorii care folosesc un mouse cu un singur buton, putei s scriei codul astfel nct apsarea tastei Shift n combinaie cu butonul din stnga s fie echivalent cu butonul din dreapta. n acest caz, codul de prelucrare a mesajelor generate de mouse pentru acionarea buloanelor ar trebui s arate astfel:
case WM_ LBUTTONDOWN if (!MK_SHIFT & wParam) { [codul pentru butonul din stnga al mouse-ulul] return 0 ; } // trece la urmtoarea secvena case, deoarece nu am folosit instruciunea break case WM_RBUTTONDOMN [codul pentru butonul din dreapta al mouse-ului] return 0 ;

i funcia Windows GetKeyState (descris n Capitolul 4) returneaz starea butoanelor i a tastelor de modificare, dac folosii codurile virtuale VK_LBUTTON, VK.RBUTTON, VK_MBUTTON, VK.SHIFT i VK_CONTROL. Butonul sau tasta sunt apsate dac valoarea returnat de funcia GetKeyState este negativ. Deoarece funcia GetKeyState returneaz starea butoanelor sau a tastelor pentru mesajul prelucrat, informaiile de stare sunt sincronizate cu mesajul respectiv. Dar aa cum nu putei s folosii funcia GetKeyState pentru o tast care urmeaz s fie apsat, nu putei s o folosii nici ca s ateptai apsarea unui buton. Nu facei aa ceva:
while (GetKeyState (VK_LBUTTON) >= 0) ; // Greit !!!

Funcia GetKeyState va raporta c butonul din stnga al mouse-ului este apsat numai dac butonul era deja apsat atunci cnd a fost generat mesajul n timpul prelucrrii cruia apelai funcia GetKeyState. DUBLU CLIC CU MOUSE-UL Dublu clic nseamn s executai de dou ori clic, ntr-o secven rapid. Pentru a se considera c s-a executat dublu clic, cele dou clicuri trebuie s fie executate ntr-un interval de timp numit interval de dublu clic" (double click time"). Dac vrei ca procedura de fereastr s primeasc mesajele generate de dublu clic, trebuie s includei identificatorul CS_DBLCLKS n stilul ferestrei, atunci cnd definii structura clasei de ferestre, naintea apelrii funciei RegisterClassEx:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS

Dac n stilul ferestrei nu este inclus identificatorul CS_DBLCLKS i utilizatorul execut de dou ori clic pe butonul din stnga al mouse-ului ntr-o succesiune rapid, procedura de fereastr va recepion urmtoarele 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 vrei s implementai o logic proprie pentru interpretarea mesajelor care reprezint un dublu clic putei s folosii funcia Windows GetMessageTime ca s obinei timpul relativ de generare a mesajelor WM_LBUTTONDOWN. Despre aceast funcie vom discuta netaliu n Capitolul 7. Dac n stilul ferestrei este inclus identificatorul CS_DBLCLKS, procedura de fereastr va recepion urmtoarele mesaje: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK si WM_LBUTTONUP. Mesajul WM_LBUTTONDBLCLK nlocuiete al doilea mesaj WM_LBUTTONDOWN. Prelucrarea mesajelor generate de dublu clic este mai simpl dac primul dintre cele dou clicuri declaneaz executarea acelorai aciuni ca i un clic simplu. Al doilea clic (care genereaz mesajul WM_LBUTTONDBLCLK) poate s determine execuia unor operaii suplimentare fa de primul clic. Ca exemplu, gndii-v la modul n care este folosit mouse-ul pentru lista de fiiere afiat de Windows Explorer. Un singur clic selecteaz fiierul; Windows Explorer marcheaz fiierul respectiv afindu-i numele n mod video invers. Un dublu clic execut dou aciuni: primul clic selecteaz fiierul, aa cum face i un clic simplu; al doilea clic cere programului Windows Explorer s execute fiierul 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 aceleai operaii ca i un clic simplu. Mesaje generate de mouse n afara zonei client Cele zece mesaje discutate pn acum sunt generate atunci cnd mouse-ul este deplasat sau cnd 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 prelucrai mesajele generate n afara zonei client. Le transmitei pur i simplu funciei DefWindowProc pentru a permite sistemului de operare Windows s execute funciile de sistem corespunztoare. Din acest punct de vedere, mesajele non-client" sunt asemntoare 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 primete urmtoarele mesaje: Buton Stnga Mijloc Dreapta Apsat WM_NCLBUTTON DOWN WM_NCMBUTTONDOWN WM_NCRBUTTONDOWN

Eliberat

Apsat (al doilea clic)

WM_NCLBUTTONUP WM_NCLBUTTONDBLCLK WM_NCMBUTTONUP WM_NCMBUTTONDBLCLK WM_NCRBUTTONUP WM_NCRBUTTONDBLCLK

Totui, 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 conine unul dintre identificatorii cu prefixul HT (hit test") definii n fiierele antet din Windows. Variabila lParam conine coordonata pe axa x n cuvntul mai puin semnificativ i coordonata pe axa y n cuvntul mai semnificativ. Totui, aceste coordonate sunt relative la ecran, nu la zona client. Pentru coordonatele de ecran, punctul de origine (0,0) este colul din stnga-sus al zonei de afiare a ecranului. Valorile coordonatei x cresc ctre dreapta, iar valorile coordonatei y cresc n jos (vezi Figura 6.3).

Figura 6-3. Coordonatele ecranului i coordonatele zonei client. Putei s transformai coordonatele ecranului n coordonate ale zonei client i invers, folosind dou funcii Windows:
ScreenToClient (hwnd, pPoint) ; ClientToScreen (hwnd, pPoint) ;

Parametrul pPoint este un pointer la o structur de tip POINT. Aceste funcii transform valorile stocate n structura transmis ca parametru fr s pstreze vechile valori. Remarcai faptul c dac un punct se afl deasupra zonei client a unei ferestre, n urma transformrii coordonatelor de ecran n coordonate ale zonei client, valoarea coordonatei pe axa y va fi negativ. Similar, dac un punct se afl n stnga zonei client a unei ferestre, n urma transformrii coordonatelor de ecran n coordonate ale zonei client, valoarea coordonatei pe axa x va fi negativ. MESAJUL DE TESTARE A POZIIEI Dac ai inut socoteala, am discutat pn acum despre 20 dintre cele 21 de mesaje generate de mouse. Ultimul mesaj este WM_NCHITTEST (non client hit test") folosit pentru verificarea poziiei din care a fost generat mesajul. Acest mesaj precede toate celelalte mesaje generate de mouse, din zona client sau din afara acesteia. Parametrul IParam al mesajului conine coordonatele x i y ale indicatorului de mouse. Parametrul wParam nu este folosit. De obicei, aplicaiile Windows transmit acest mesaj funciei DefWindowProc. Windows folosete mesajul WM_NCHITTEST ca s genereze celelalte mesaje, n funcie de poziia mouse-ului. Pentru mesajele din afara zonei client, valoarea returnat de funcia DefWindowProc n urma prelucrrii mesajului WM_NCHITTEST devine parametrul wParam al mesajului generat. Aceast valoare poate fi oricare dintre valorile wParam care nsoesc mesajele generate de mouse din afara zonei client, plus urmtoarele: HTCLIENT HTNOWHERE HTTRANSPARENT HTERROR Zona client Nici o fereastr O fereastr acoperit de o alt fereastr Determin funcia DefWindowProc s emit un semnal sonor

Dac funcia DefWindowProc genereaz un mesaj HTCLIENT n urma prelucrrii mesajului WM_NCHITTEST, Windows transform coordonatele ecran n coordonate ale zonei client i genereaz un mesaj pentru zona client. Dac v amintii cum am dezactivat toate funciile de sistem activate de la tastatur, prin interceptarea mesajului WM_SYSKEYDOWN, probabil v ntrebai dac putei s facei ceva asemntor i prin interceptarea mesajelor generate de mouse. Desigur, putei face acest lucru incluznd n procedura de fereastr liniile:
case WM_NCHITTEST

return (LRESULT) HTNOWHERE ;

Vei dezactiva toate mesajele de mouse trimise procedurii de fereastr pentru zona client i pentru poriunile din afara zonei client. Butoanele mouse-ului nu vor mai funciona ct timp indicatorul mouse-ului se afl n fereastra dumneavoastr, inclusiv deasupra pictogramei meniului sistem, a butoanelor de redimensionare i a butonului pentru nchiderea ferestrei.

MESAJELE GENEREAZ MESAJE


Windows folosete mesajul WM_NCHITTEST ca s genereze alte mesaje de mouse. Ideea mesajelor care genereaz alte mesaje este des ntlnit n Windows. Dup cum tii, dac executai dublu clic pe pictograma meniului sistem a unui program Windows, execuia acestuia se ncheie. Executarea unui dublu clic genereaz o serie de mesaje WM_NCHITTEST. Deoarece indicatorul mouse-ului se afl deasupra pictogramei meniului sistem, funcia DefWindowProc returneaz valoarea HTSYSMENU i Windows insereaz n coada de ateptare a programului un mesaj WM_NCLBUTTONDBLCLK care conine n parametrul wParam valoarea HTSYSMENU. De obicei, procedura de fereastr retransmite acest mesaj funciei DefWindowProc. Atunci cnd recepioneaz mesajul cu parametrul wParam egal cu HTSYSMENU, funcia DefWindowProc insereaz n coada de ateptare un mesaj WM_SYSCOMMAND cu parametrul wParam egal cu SC_CLOSE. (Acest mesaj WM_SYSCOMMAND este generat i atunci cnd utilizatorul selecteaz opiunea Close din meniul sistem.) Din nou, procedura de fereastr transmite acest mesaj funciei DefWmdowProc. Funcia DefWindowProc l prelucreaz trimind procedurii de fereastr mesajul WM_CLOSE. Dac dorii ca programul s cear confirmarea utilizatorului nainte de terminare, procedura de fereastr trebuie sunt intercepteze mesajul WM_CLOSE. n caz contrar, funcia DefWmdowProc prelucreaz mesajul WM_CLOSE apelnd funcia DestroyWindow. Printre alte aciuni, funcia DestroyWindow trimite procedurii de fereastr un mesaj WM_DESTROY. n mod normal, procedura de fereastr prelucreaz mesajul WM.DESTROY astfel:
case WM_DESTROY : PostQuitMessage (0) ; return 0 ;

Funcia PostQuitMessage determin sistemul de operare s insereze n coada de mesaje un mesaj WM_QUIT. Acest mesaj nu ajunge niciodat la procedura de fereastr, deoarece determin funcia GetMessage s retumeze valoarea 0, care ncheie ciclul de tratare a mesajelor. VERIFICAREA POZIIEI N PROGRAMELE DUMNEAVOASTR Am discutat mai devreme despre modul n care programul Windows Explorer rspunde la executarea unui clic sau a unui dublu clic. Evident, programul trebuie s determine fiierul indicat cu ajutorul mouse-ului. Aceast operaie se numete verificarea poziiei" (hit-testing"). Aa cum funcia DefWmdowProc execut unele operaii de testare a poziiei n timpul prelucrrii mesajului WM_NCHITTEST, deseori procedura de fereastr trebuie s fac unele operaii de verificare a poziiei n zona client. n general, testarea poziiei implic unele calcule pe baza coordonatelor x i y transmise procedurii de fereastr prin parametrul lParam al mesajului de mouse. Exemplu Iat un exemplu. S presupunem c programul dumneavoastr afieaz pe mai multe coloane fiiere aranjate alfabetic. Lista de fiiere ncepe n partea de sus a zonei client, care are limea cxClient si nlimea cyClient dimensiuni msurate n pixeli; fiecare caracter are nlimea cyChar. Numele de fiiere sunt stocate ntr-o matrice ordonat de pointeri, numit szFileNames. S presupunem c fiecare coloan are limea cxColWidth. Numrul de fiiere care poate fi afiat n fiecare coloan este:
iNumInCol = cyClient/cyChar ;

Recepionai un mesaj generat de un clic de mouse cu coordonatele cxMouse i cyMouse. Putei s determinai coloana indicat de utilizator folosind formula:
iColumn = cxMouse/cxColWidth ;

Poziia fiierului relativ la partea de sus a coloanei este:


iFromTop = cyMouse/cyChar ;

Acum putei s calculai un index n matricea szFileNames:

iIndex = iColumn * iNumlnCol + iFromTop ;

Evident, dac valoarea ilndex depete numrul de fiiere din matrice, nseamn c utilizatorul a executat clic pe o zon liber de pe ecran. n multe situaii operaiile de testare a poziiei sunt mai complexe dect sugereaz acest exemplu. Ele pot deveni de-a dreptul ncurcate ntr-un procesor de texte (cum ar fi WORDPAD) care folosete fonturi de dimensiuni variabile. Atunci cnd afiai ceva n zona client, trebuie s determinai coordonatele fiecrui element afiat. n calculele de verificare a poziiei trebuie s pornii de la coordonatele obiectului. Totui daca obiectul este un ir de caractere, aceast operaie implic determinarea poziiei caracterului n ir. Un program de exemplificare Programul CHECKER1, prezentat n Figura 6-4, ilustreaz cteva operatii simple de testare a poziiei. Programul mparte zona client ntr-o matrice de 5x5 dreptunghiuri Daca executai clic pe unul dintre dreptunghiuri, n interiorul acestuia este desenat un X. Daca executai din nou un clic n acelai dreptunghi, X-ul este ters.
/*------------------------------------------------CHECKER1.C -- Mouse Hit-Test Demo Program No. 1 (c) Charles Petzold, 1996 -------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 #define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Checker1" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Checker1 Mouse Hit-Test Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{ static BOOL fState[DIVISIONS][DIVISIONS] ; static int cxBlock, cyBlock ; HDC hdc ; PAINTSTRUCT ps ; RECT rect ; int x, y ; switch (iMsg) { case WM_SIZE : cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; return 0 ; case WM_LBUTTONDOWN : x = LOWORD (lParam) / cxBlock ; y = HIWORD (lParam) / cyBlock ; if (x < DIVISIONS && y < DIVISIONS) { fState [x][y] ^= 1 ; rect.left = x * cxBlock ; rect.top = y * cyBlock ; rect.right = (x + 1) * cxBlock ; rect.bottom = (y + 1) * cyBlock ; InvalidateRect (hwnd, &rect, FALSE) ; } else MessageBeep (0) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) { Rectangle (hdc, x * cxBlock, y * cyBlock, (x + 1) * cxBlock, (y + 1) * cyBlock) ; if (fState [x][y]) { MoveTo (hdc, x * cxBlock, y * cyBlock) ; LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ; MoveTo (hdc, x * cxBlock, (y+1) * cyBlock) ; LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ; } } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 6.4. Programul CHECKER1. Figura 6.5 prezint fereastra afiat de programul CHECKER1. Toate cele 25 de dreptunghiuri au aceleai dimensiuni. nlimea i limea sunt stocate n variabilele cxBlock i cyBlock i sunt recalculate de fiecare dat cnd dimensiunile zonei client se modific. Codul de tratare a butonului WM_LBUTTONDOWN folosete 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 limea sau nlimea zonei client nu se mparte exact la cinci, n partea stng sau n partea de jos a zonei client rmne o

poriune neacoperit de un dreptunghi. Pentru prelucrarea erorilor, programul CHECKER1 rspunde la executarea unui clic n aceast poriune prin apelarea funciei MessageBeep. Dac primete un mesaj WM_PAINT, programul CHECKER1 actualizeaz ntreaga zon client, desennd dreptunghiurile cu ajutorul funciei GDI Rectangle. n dreptunghiurile pentru care matricea fState conine un 1, CHECKER1 deseneaz dou linii cu ajutorul funciilor MoveTo i LineTo. n timpul prelucrrii mesajului WM_PAINT, CHECKER1 nu verific validitatea fiecrei seciuni dreptunghiulare nainte de a o redesena, dar ar putea s fac acest lucru. O metod de verificare a validittii impune construirea unei structuri RECT pentru fiecare bloc dreptunghiular (folosind aceleai formule ca i n codul de prelucrare a mesajului WM_LBUTTONDOWN) i verificarea interseciei cu dreptunghiul invalid (ps.rcPaint) cu ajutorul funciei IntersectRect. O alt metod este s folosii funcia PtInRect ca s determinai dac oricare dintre cele patru coluri ale blocului dreptunghiular se situeaz n cadrul dreptunghiului invalid.

Figura 6-5. Fereastra afiata de programul CHECKER1. EMULAREA MOUSE-ULUI CU TASTATURA Programul CHECKER1 funcioneaz numai dac avei un mouse. i vom aduga n curnd o interfa cu tastatura, aa cum am fcut cu programul SYSMETS din Capitolul 5. Totui, includerea unei interfee cu tastatura ntr-un program care folosete mouse-ul, pentru indicarea unor obiecte, nseamn rezolvarea problemelor legate de afiarea i deplasarea indicatorului. Chiar dac mouse-ul nu este instalat, Windows poate afia indicatorul mouse-ului. Pentru aceasta, Windows pstreaz un contor de afiare" (display count"). Dac mouse-ul este instalat, contorul de afiare are initial valoarea 0; dac nu, contorul are valoarea -1. Indicatorul mouse-ului este vizibil numai n cazul n care contorul de afiare are valoarea egal cu 0 sau mai mare. Putei s incrementai contorul de afiare apelnd funcia ShowCursor:
ShowCursor (TRUE)

i s l decrementati apelnd aceeai funcie:


ShowCursor (FALSE) ;

Nu trebuie s determinai dac mouse-ul este instalat nainte de apelarea funciei ShowCursor. Dac vrei s afiai indicatorul mouse-ului indiferent dac mouse-ul este prezent sau nu, incrementai contorul de afiare. n cazul n care incrementai contorul o singur dat, dup decrementare indicatorul va disprea dac mouse-ul nu este instalat, dar va rmne afiat dac mouse-ul este instalat. Contorul de afiare este unic pentru toate programele din Windows, aa c trebuie s v asigurai c l incrementai i l decrementai de tot attea ori. Putei s folosii n procedura de fereastr urmtoarea logic de prelucrare:
case WM SETFOCUS : ShowCursor (TRUE) ;

return 0 ; case WM_KILLFOCUS : ShowCursor (FALSE) ; return 0 ;

Procedura de fereastr primete mesajul WM_SETFOCUS atunci cnd fereastra obine cursorul de intrare (input focus) i mesajul WM_KILLFOCUS atunci cnd pierde cursorul de intrare. Acestea sunt momentele cele mai potrivite pentru afiaREA I MASCAREA INDICATORULUI. N PRIMUL RND, mesajele WM_SETFOCUS i WM_KILLFOCUS sunt transmise n numr egal - ceea ce nseamn c procedura de fereastr va incrementa i va decrementa de tot attea ori contorul de afiare a indicatorului. n al doilea rnd, pentru calculatoarele pe care nu este instalat un mouse folosirea mesajelor WM_SETFOCUS i WM_KILLFOCUS va determina afiarea indicatorului numai atunci cnd fereastra dumneavoastr deine cursorul de intrare. n acest fel, utilizatorul poate s mute indicatorul mouse-ului folosind interfaa cu tastatura pe care o proiectai. Windows pstreaz poziia curent a mouse-ului chiar dac acesta nu este instalat. Dac mouse-ul nu este instalat i afiai indicatorul, acesta poate aprea n orice punct al ecranului i va rmne acolo pan cnd l mutai n mod explicit. Puteti s obinei poziia indicatorului folosind funcia GetCursorPos:
GetCursorPos (pPoint) ;

unde point este un pointer la o structur de tip POINT. Funcia GetCursorPos completeaz cmpurile structurii POINT cu coordonatele x si y ale mouse-ului. Putei s stabilii poziia indicatorului folosind funcia 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 funcia nu primete un parametru hwnd). Aa cum am artat anterior, putei s transformai coordonatele ecran n coordonate ale zonei client i invers, folosind funciile ScreenToClient i ClientToScreen. Dac apelai funcia GetCursorPos n timpul prelucrrii unui mesaj generat de mouse i transformai valorile obinute n coordonate ale zonei client, rezultatele obinute s-ar putea s fie diferite de cele transmise prin parametrul lParam al mesajului. Coordonatele returnate de funcia GetCursorPos indic pozitia curent a mouseului, iar coordonatele transmise prin parametrul lParam indic pozitia mouse-ului n momentul generrii mesajului. Probabil vei dori s implementai o interfa cu tastatura care s permit deplasarea indicatorului mouse-ului cu sgeile de pe tastatur i simularea butoanelor cu bara de spaiu i tasta Enter. Desigur, nu dorii s mutai indicatorul cu un singur pixel la fiecare apsare de tast. Aceasta ar obliga utilizatorul s in o tasta cu sgeat apsat mai mult de un minut ca s mute indicatorul mouse-ului dintr-o parte n alta a ecranului. Dac dorii s implementai o interfa eficient cu tastatura pentru indicatorul mouse-ului, dar s avei totui posibilitatea s-l poziionai cu precizie, trebuie s prelucrai mesajele generate de acionarea tastelor astfel nct atunci cnd inei apsat o tast, indicatorul s se deplaseze mai nti ncet, apoi din ce n ce mai repede. V amintii c parametrul IParam al mesajului WM_KEYDOWN precizeaz dac mesajul respectiv este rezultatul autorepetrii tastei apsate. Aceasta poate fi o aplicaie excelent a informaiei respective. Adugarea unei interfee cu tastatura la programul CHECKER Programul CHECKER2, prezentat n Figura 6.6, este identic cu programul CHECKER1, exceptnd faptul c include i o interfa cu tastatura. Putei s folosii sgeile ca s deplasai indicatorul ntre cele 25 de dreptunghiuri. Tasta Home trimite indicatorul n dreptunghiul din colul din stnga-sus; tasta End l trimite n dreptunghiul din colul din dreapta-jos. Tasta Enter i bara de spaiu au acelai rol: terg sau afieaz semnul X din dreptunghiul n care se afl indicatorul.
/*------------------------------------------------CHECKER2.C -- Mouse Hit-Test Demo Program No. 2 (c) Charles Petzold, 1996 -------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 #define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Checker2" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Checker2 Mouse Hit-Test Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static BOOL fState[DIVISIONS][DIVISIONS] ; static int cxBlock, cyBlock ; HDC hdc ; PAINTSTRUCT ps ; POINT point ; RECT rect ; int x, y ; switch (iMsg) { case WM_SIZE : cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; return 0 ; case WM_SETFOCUS : ShowCursor (TRUE) ; return 0 ; case WM_KILLFOCUS : ShowCursor (FALSE) ; return 0 ; case WM_KEYDOWN : GetCursorPos (&point) ; ScreenToClient (hwnd, &point) ; x = max (0, min (DIVISIONS - 1, point.x / cxBlock)) ; y = max (0, min (DIVISIONS - 1, point.y / cyBlock)) ;

switch (wParam) { case VK_UP : y-- ; break ; case VK_DOWN : y++ ; break ; case VK_LEFT : x-- ; break ; case VK_RIGHT : x++ ; break ; case VK_HOME : x=y=0; break ; case VK_END : x = y = DIVISIONS - 1 ; break ; case VK_RETURN : case VK_SPACE : SendMessage (hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG (x * cxBlock, y * cyBlock)) ; break ; } x = (x + DIVISIONS) % DIVISIONS ; y = (y + DIVISIONS) % DIVISIONS ; point.x = x * cxBlock + cxBlock / 2 ; point.y = y * cyBlock + cyBlock / 2 ; ClientToScreen (hwnd, &point) ; SetCursorPos (point.x, point.y) ; return 0 ; case WM_LBUTTONDOWN : x = LOWORD (lParam) / cxBlock ; y = HIWORD (lParam) / cyBlock ; if (x < DIVISIONS && y < DIVISIONS) { fState[x][y] ^= 1 ; rect.left = x * cxBlock ; rect.top = y * cyBlock ; rect.right = (x + 1) * cxBlock ; rect.bottom = (y + 1) * cyBlock ; InvalidateRect (hwnd, &rect, FALSE) ; } else MessageBeep (0) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) { Rectangle (hdc, x * cxBlock, y * cyBlock, (x + 1) * cxBlock, (y + 1) * cyBlock) ; if (fState [x][y]) { MoveTo (hdc, x

* cxBlock, y

* cyBlock) ;

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

Figura 6-6. Programul CHECKER2. n timpul tratrii mesajului WM_KEYDOWN, CHECKER2 determin poziia indicatorului (funcia GetCursorPos), convertete coordonatele ecran n coordonate ale zonei client (funcia ScreenToClient) i mparte coordonatele obinute la limea i la nlimea unui bloc dreptunghiular. Rezult dou valori x i y care indic poziia dreptunghiului ntr-o matrice 5x5. Cursorul mouse-ului poate s nu fie n zona client atunci cnd utilizatorul apas o tast, aa c valorile x i y trebuie s fie trecute prin macroinstruciunile min i max pentru a se verifica dac sunt n intervalul de la 0 la 4. Pentru tastele cu sgei programul incrementeaz sau decrementeaz corespunztor valorile x i y. Dac este apsat tasta Enter (VK_ENTER) sau bara de spaiu (VK_SPACE), programul CHECKER2 folosete funcia SendMessage ca s i trimit un mesaj WM_LBUTTONDOWN. Aceast tehnic este asemntoare cu cea folosit n programul SYSMETS din Capitolul 5 atunci cnd am adugat o interfa cu tastatura pentru barele de derulare. Prelucrarea mesajului WM_KEYDOWN se termin cu calcularea coordonatelor din zona client, care indic centrul dreptunghiului. Acestea sunt transformate n coordonate ecran (funcia ClientToScreen) i apoi este stabilit poziia indicatorului (funcia SetCursorPos). Folosirea ferestrelor descendent pentru verificarea poziiei 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 stng 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 cnd verific poziia 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, totui, chiar asa. n realitate, programul PAINT simplific desenarea meniurilor i operaiile de verificare a poziiei 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 dient. Fiecare procedur de fereastr recepioneaz mesaje de mouse care se aplic numai respectivei ferestre descendent. Parametrul IParam al mesajului generat de mouse conine coordonatele relative la colul din stnga-sus al ferestrei descendent, nu al ferestrei printe. Figura 6.7. Programul Windows PAINT. Folosirea n acest mod a ferestrelor descendent contribuie la modularizarea i structurarea programului. Dac ferestrele descendent utilizeaz clase diferite, fiecare poate avea o procedur de fereastr proprie. Fiecare clas de fereastr poate defini alte culori de fond i diferite cursoare. n Capitolul 8 vom discuta despre controale de tip fereastr descendent" - ferestre descendent predefinite care pot fi bare de derulare, butoane sau casete de editare. Pentru moment, haidei s vedem cum putem s folosim ferestrele descendent n programul CHECKER. Ferestre descendent n programul CHECKER Figura 6.8 prezint programul CHECKER3. Aceast versiune a programului creeaz 25 de ferestre descendent pentru prelucrarea clicurilor executate cu mouse-ul. Programul nu include o interfa cu tastatura, dar aceasta ar putea fi adugat cu uurin. Programul CHECKER3 are dou proceduri de fereastr, numite WndProc i ChildWndProc. WndProc este procedura de fereastr a ferestrei principale (fereastra printe). 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 nregistrai n Windows folosind funcia RegisterClassEx, pentru declararea celor dou proceduri de fereastr din programul CHECKER3 trebuie s nregistrai dou clase de fereastr. Prima clas este folosit pentru fereastra principal i se numete Checker3". A doua clas este folosit pentru ferestrele descendent i se numete Checker3_Child".

n funcia WinMain, pentru nregistrarea dasei Checker3_Child" sunt refolosite majoritatea cmpurilor structurii wndclass. Cmpul lpszClasssName primete valoarea Checker3_Child" (numele clasei). Cmpul IpfnWndProc primete valoarea ChildWndProc, procedura de fereastr pentru aceast clas iar cmpurile hIcon i hlconSm primesc vatoarea NULL, deoarece ferestrele descendent nu folosesc pictograme. Pentru clasa de fereastr Checker3_Child" cmpul cbWndExtra din structura vndclass primete valoarea 2 sau, mai precis, sizeof(WORD). n acest fel se cere sistemului de operare s rezerve un spaiu suplimentar de doi octei n structura pe care Windows o pstreaz pentru fiecare fereastr declarat pe baza acestei clase. Putei s folosii acest spaiu pentru stocarea unor informaii care pot s difere de la o fereastr la alta.
/*------------------------------------------------CHECKER3.C -- Mouse Hit-Test Demo Program No. 3 (c) Charles Petzold, 1996 -------------------------------------------------*/ #include <windows.h> #define DIVISIONS 5 #define MoveTo(hdc, x, y) MoveToEx (hdc, x, y, NULL) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ; char szChildClass[] = "Checker3_Child" ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Checker3" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; wndclass.lpfnWndProc = ChildWndProc ; wndclass.cbWndExtra = sizeof (WORD) ; wndclass.hIcon = NULL ; wndclass.lpszClassName = szChildClass ; wndclass.hIconSm = NULL ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Checker3 Mouse Hit-Test Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; }

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static HWND hwndChild[DIVISIONS][DIVISIONS] ; int cxBlock, cyBlock, x, y ; switch (iMsg) { case WM_CREATE : for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) { hwndChild[x][y] = CreateWindow (szChildClass, NULL, WS_CHILDWINDOW | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU) (y << 8 | x), (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL) ; } return 0 ; case WM_SIZE : cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) MoveWindow (hwndChild[x][y], x * cxBlock, y * cyBlock, cxBlock, cyBlock, TRUE) ; return 0 ; case WM_LBUTTONDOWN : MessageBeep (0) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; } LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_CREATE : SetWindowWord (hwnd, 0, 0) ; return 0 ;

// on/off flag

case WM_LBUTTONDOWN : SetWindowWord (hwnd, 0, 1 ^ GetWindowWord (hwnd, 0)) ; InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; Rectangle (hdc, 0, 0, rect.right, rect.bottom) ; if (GetWindowWord (hwnd, 0)) { MoveTo (hdc, 0, 0) ; LineTo (hdc, rect.right, rect.bottom) ; MoveTo (hdc, 0, rect.bottom) ; LineTo (hdc, rect.right, 0) ; }

EndPaint (hwnd, &ps) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Funcia WinMain apeleaz funcia Create Window, care creeaz fereastra principal pe baza clasei Checkers". Totul este normal. Totui, atunci cnd primete mesajul WM_CREATE, procedura WndProc apeleaz de 25 de ori funcia CreateWindow ca s creeze 25 de ferestre descendent pe baza clasei Checker3_Child". Tabelul urmtor prezint o comparaie ntre parametrii funciei CreateWindow apelat din funcia WinMain pentru crearea ferestrei principale i parametrii funciei Create Window apelat din funcia WndProc pentru crearea celor 25 de ferestre descendent: Parametrii clasa ferestrei titlul ferestrei stilul ferestrei poziia pe orizontal poziia pe vertical lime nlime variabila handle a ferestrei printe variabila handle a meniului/identificatorul ferestrei descendent (child ID) variabila handle a instanei ali parametri Fereastra principal Checker3" Checker3..." WS_OVERLAPPED-WINDOW CW_USEDEFAULT CW_USEDEFAULT CW_USEDEFAULT CW_USEDEFAULT NULL NULL hInstance NULL Fereastra descendent Checker3_Child" NULL WS_CHILDWINDOW| WS_VISIBLE 0 0 0 0 hwnd (HMENU) (y << 8 | x) (HINSTANCE) GetWindowLong (hwnd,GWL_HINSTANCE) NULL

n mod normal, poziia, limea i nlimea sunt necesare pentru crearea ferestrelor descendent, dar n programul CHECKER3 ferestrele descendent sunt dimensionate i poziionate mai trziu n funcia WndProc. Variabila handle a ferestrei printe are valoarea NULL pentru fereastra principal, deoarece chiar aceasta este fereastra printe. n schimb, pentru crearea terestrei descendent, funcia CreateWindow are nevoie de o variabil handle a ferestrei printe. Fereastra principal nu are meniu, aa c parametrul variabila handle a meniului" are valoarea NULL. Pentru ferestrele descendent, acelai parametru se numete identificatorul ferestrei descendent (child ID)". Acesta este un numr care identific n mod unic fereastra descendent. Identificatorul este mai important atunci cnd ferestrele descendent sunt folosite pentru controale, deoarece identificarea mesajelor trimise ctre fereastra printe se face cu ajutorul acestui identificator, aa cum vom vedea n Capitolul 8. n programul CHECKER3, identificatorul ferestrei descendent este egal cu poziia ocupat de fereastra descendent ntr-o matrice 5x5. Variabila handle a instanei este hInstance n ambele cazuri. Atunci cnd este creat fereastra descendent, valoarea hlnstance este obinut prin apelarea funciei GetWindowLong, care citete valoarea hlnstance din structura pstrat de Windows pentru fiecare fereastr. (n loc s apelm de fiecare dat funcia GetWindowLong puteam s salvm variabila hlnstance ntr-o variabil global i s o folosim direct.) Fiecare fereastr descendent are o variabil handle diferit, stocat n matricea hwndChild. Afunci cnd primete un mesaj WM_SIZE, funcia WndProc apeleaz funcia MoveWindow pentru fiecare dintre cele 25 de ferestre descendent. Parametrii transmisi funciei MoveWindow indic poziia colului din stnga-sus al ferestrei descendent, n coordonatele zonei client a ferestrei printe, limea i nlimea ferestrei descendent i dac fereastra descendent trebuie s fie redesenat. Haidei s acordm puin atenie funciei ChildWndProc. Aceast procedur de fereastr prelucreaz mesajele pentru toate cele 25 de ferestre descendent. Parametrul hwnd al funciei ChildWndProc este variabila handle a ferestrei descendent care a primit mesajul. Atunci cnd prelucreaz mesajului WM_CREATE (ceea ce se n-tmpl de 25 de ori, deoarece sunt 25 de ferestre descendent), funcia ChildWndProc folosete funcia SetWindowWord ca s stocheze valoarea 0 n zona suplimentar rezervat n structura ferestrei. (V amintii c am rezervat acest spaiu folosind cm-pul cbWndExtra atunci cnd am definit structura clasei de fereastr.) ChildWndProc folosete aceast valoare ca s stocheze starea curent a ferestrei (marcat sau nu cu un X). Atunci

cnd se execut clic pe fereastra descendent, codul de prelucrare a mesajului WM_LBUTTONDOWN schimb valoarea acestui cuvnt (din 0 n 1 sau din 1 n zero) i invalideaz ntreaga zon client a ferestrei descendent. Aceast zon reprezint chiar dreptunghiul n care utilizatorul a executat clic. Prelucrarea mesajului WM_PAINT este foarte simpl, deoarece dreptunghiul n care se face desenarea are aceleai dimensiuni ca i zona client a ferestrei descendent. Deoarece codul surs C i fiierul executabil al programului CHECKER3 sunt mai lungi dect cele ale programului CHECKER1 (ca s nu mai vorbim de explicaiile mele) nu voi ncerca s v conving c CHECKER3 este mai simplu dect CHECKER1. Remarcai ns c nu mai avem nevoie de nici o operaie de verificare a poziiei 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 informaiile necesare. Dac vrei s includei n programul CHECKER3 o interfa cu tastatura, reinei c mesajele de la tastatur sunt primite tot de fereastra principal, deoarece aceasta deine cursorul de intrare. Vom discuta mai mult despre ferestrele descendent n Capitolul 8. CAPTURAREA MOUSE-ULUI n mod normal, o procedur de fereastr primete mesaje de la mouse doar atta timp ct indicatorul mouseului se afl deasupra ferestrei, n zona client sau n afara acesteia. S-ar putea ca un program s aib nevoie s recepioneze mesaje de ia mouse i atunci cnd indicatorul mouse-ului este n afara ferestrei programului. n aceast situaie, programul poate s captureze" mouse-ul. Desenarea unui dreptunghi Pentru a vedea de ce este uneori necesar capturarea mouse-ului, haidei s studiem programul BLOKOUT1, prezentat n Figura 6.9.
#-----------------------# BLOKOUT1.MAK make file #-----------------------blokout1.exe : blokout1.obj $(LINKER) $(GUIFLAGS) -OUT:blokout1.exe blokout1.obj $(GUILIBS) blokout1.obj : blokout1.c $(CC) $(CFLAGS) blokout1.c /*----------------------------------------BLOKOUT1.C -- Mouse Button Demo Program (c) Charles Petzold, 1996 -----------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "BlokOut1" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Mouse Button Demo", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd) { HDC hdc ; hdc = GetDC (hwnd) ; SetROP2 (hdc, R2_NOT) ; SelectObject (hdc, GetStockObject (NULL_BRUSH)) ; Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ; ReleaseDC (hwnd, hdc) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static BOOL fBlocking, fValidBox ; static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ; HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_LBUTTONDOWN : ptBeg.x = ptEnd.x = LOWORD (lParam) ; ptBeg.y = ptEnd.y = HIWORD (lParam) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ; fBlocking = TRUE ; return 0 ; case WM_MOUSEMOVE : if (fBlocking) { SetCursor (LoadCursor (NULL, IDC_CROSS)) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; ptEnd.x = LOWORD (lParam) ; ptEnd.y = HIWORD (lParam) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; } return 0 ; case WM_LBUTTONUP : if (fBlocking) { DrawBoxOutline (hwnd, ptBeg, ptEnd) ; ptBoxBeg = ptBeg ; ptBoxEnd.x = LOWORD (lParam) ; ptBoxEnd.y = HIWORD (lParam) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

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

Figura 6.9. Programul BLOKOUT1. Acest program ilustreaz o metod care poate fi implementat de un program de desenare sub Windows. ncepei cu apsarea butonului din stnga al mouse-ului, ca s marcai primul col al unui dreptunghi. Tragei apoi mouse-ul. Programul deseneaz conturul unui dreptunghi, poziia curent a indicatorului marcnd colul opus primului col marcat. Figura 6.10 prezint un dreptunghi deja desenat i un dreptunghi n curs de desenare.

Figura 6-10. Fereastra afiat de programul BLOKOUT1. Atunci cnd butonul din stnga al mouse-ului este apsat, BLOKOUT1 salveaz coordonatele mouse-ului i apeleaz funcia DrawBoxOutline pentru prima dat. Funcia DrawBoxOutline deseneaz un dreptunghi folosind operaia rastru R2_NOT. Aceasta inverseaz culoarea zonei client a ferestrei. n timpul mesajelor WM_MOUSEMOVE care urmeaz, programul deseneaz din nou acelai dreptunghi, tergnd versiunile anterioare. De fiecare dat programul folosete coordonatele mouse-ului ca s deseneze un nou dreptunghi. In sfrit, atunci cnd primete un mesaj WM_LBUTTONUP, programul BLOKOUT1 salveaz coordonatele mouse-ului i invalideaz fereastra, genernd un mesaj WM_PAINT pentru desenarea unui dreptunghi fix. Aadar, care este problema? Facei urmtorul experiment: apsai butonul din stnga al mouse-ului n zona client a programului BLOKOUT1, apoi mutai indicatorul mouse-ului n afara zonei client. Din acest moment, programul nu mai primete mesajele WM_MOUSEMOVE generate de mouse. Eliberai butonul din stnga al mouse-ului. Programul BLOKOUT1 nu primete mesajul WM_LBUTTONUP, deoarece indicatorul mouse-ului se afl n afara zonei client a ferestrei. Mutai din nou indicatorul n zona client a programului. Procedura de fereastr nc mai crede c butonul mouse-ului este apsat. i nu este bine. Programul nu mai tie ce se ntmpl.

SOLUIA CAPTURRII
Programul BLOKOUT1 prezint o funcie frecvent ntlnit n programele Windows, dar este evident c n cod s-a strecurat o greeal. 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 pstreze controlul asupra mouse-ului. Capturarea mouse-ului se face foarte simplu. Nu trebuie dect s apelai funcia SetCapture:
SetCapture (hwnd) ;

Dup apelarea funciei SetCapture, Windows trimite toate mesajele generate de mouse ctre 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 poziia indicatorului mouse-ului n coordonatele zonei client. Aceste coordonate pot avea valori negative dac mouse-ul se afl n stnga sau deasupra zonei client a ferestrei. Dup capturarea mouse-ului, funciile de sistem ale tastaturii sunt dezactivate. Atunci cnd dorii s eliberai mouse-ul, apelai funcia ReleaseCapture:
ReleaseCapture () ;

Funcia ReleaseCapture determin revenirea la normal a operaiilor de prelucrare. n Windows 95, capturarea mouse-ului este puin mai restrictiv dect n versiunile anterioare ale sistemului de operare Windows. Mai precis, dac mouse-ul a fost capturat, dar nici unul dintre butoanele acestuia nu este apsat i indicatorul lui trece peste o alt fereastr, mesajele generate de acesta sunt trimise ferestrei deasupra

cruia se afl indicatorul, nu ferestrei care a capturat mouse-ul. Acest lucru este necesar pentru a mpiedica blocarea ntregului sistem atunci cnd un program captureaz mouse-ul i uit" s l mai elibereze. Cu alte cuvinte, trebuie s capturai mouse-ul numai atunci cnd butonul este apsat n zona client a ferestrei dumneavoastr. Eliberai mouse-ul dup ce butonul mouse-ului este eliberat. Programul BLOKOUT2 Programul BLOKOUT1 ilustreaz metoda capturrii mouse-ului i este prezentat n Figura 6.11.
#-----------------------# BLOKOUT2.MAK make file #-----------------------blokout2.exe : blokout2.obj $(LINKER) $(GUIFLAGS) -OUT:blokout2.exe blokout2.obj $(GUILIBS) blokout2.obj : blokout2.c $(CC) $(CFLAGS) blokout2.c /*--------------------------------------------------BLOKOUT2.C -- Mouse Button & Capture Demo Program (c) Charles Petzold, 1996 ---------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "BlokOut2" ; HWND hwnd ; MSG msg ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof (wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ; RegisterClassEx (&wndclass) ; hwnd = CreateWindow (szAppName, "Mouse Button & Capture Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd) { HDC hdc ; hdc = GetDC (hwnd) ;

SetROP2 (hdc, R2_NOT) ; SelectObject (hdc, GetStockObject (NULL_BRUSH)) ; Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ; ReleaseDC (hwnd, hdc) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static BOOL fBlocking, fValidBox ; static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ; HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_LBUTTONDOWN : ptBeg.x = ptEnd.x = LOWORD (lParam) ; ptBeg.y = ptEnd.y = HIWORD (lParam) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; SetCapture (hwnd) ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ; fBlocking = TRUE ; return 0 ; case WM_MOUSEMOVE : if (fBlocking) { SetCursor (LoadCursor (NULL, IDC_CROSS)) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; ptEnd.x = LOWORD (lParam) ; ptEnd.y = HIWORD (lParam) ; DrawBoxOutline (hwnd, ptBeg, ptEnd) ; } return 0 ; case WM_LBUTTONUP : if (fBlocking) { DrawBoxOutline (hwnd, ptBeg, ptEnd) ; ptBoxBeg = ptBeg ; ptBoxEnd.x = LOWORD (lParam) ; ptBoxEnd.y = HIWORD (lParam) ; ReleaseCapture () ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; fBlocking = FALSE ; fValidBox = TRUE ; InvalidateRect (hwnd, NULL, TRUE) ; } return 0 ; case WM_CHAR : if (fBlocking & wParam == '\x1B') // i.e., Escape { DrawBoxOutline (hwnd, ptBeg, ptEnd) ; ReleaseCapture () ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; fBlocking = FALSE ; } return 0 ;

case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; if (fValidBox) { SelectObject (hdc, GetStockObject (BLACK_BRUSH)) ; Rectangle (hdc, ptBoxBeg.x, ptBoxBeg.y, ptBoxEnd.x, ptBoxEnd.y) ; } if (fBlocking) { SetROP2 (hdc, R2_NOT) ; SelectObject (hdc, GetStockObject (NULL_BRUSH)) ; Rectangle (hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

Figura 6.11. Programul BLOKOUT2. Programul BLOKOUT2 este acelai cu programul BLOKOUT1, dar are trei linii de cod n plus: un apel al funciei SetCapture n timpul prelucrrii mesajului WM_LBUTTONDOWN si dou apeluri ale funciei ReleaseCapture, n timpul prelucrrii mesajelor WM_LBUTTONUP i WM_CHAR. (Prelucrarea mesajului WM_CHAR permite eliberarea mouse-ului atunci cnd utilizatorul apas tasta Esc.) Facei urmtorul experiment redimensionai fereastra astfel nct s fie mai mic dect ecranul, marcai nceputul unui dreptunghi n zona client, mutai indicatorul n afara zonei client, ctre dreapta sau n jos, apoi eliberai butonul mouse-ului ca s marcai al doilea col al dreptunghiului. Programul va pstra coordonatele ntregului dreptunghi. Mrii din nou fereastra ca s v convingei. Capturarea mouse-ului nu este o funcie care se poate realiza numai n aplicaiile mai ciudate. Ar trebui s o folosii de fiecare dat cnd trebuie s urmrii mesajele WM_MOUSEMOVE dup ce butonul mouse-ului a fost apsat n zona client a programului dumneavoastr, pan cnd butonul mouse-ului este eliberat. Programul va fi mai simplu i va satisface ateptrile utilizatorilor.

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