Sunteți pe pagina 1din 18

Capitolul 3.

Afişarea textului
În capitolul anterior aţi văzut un program simplu pentru Windows 95, care afişa o singură linie de text în centrul unei
ferestre sau - ca să fiu mai precis - în centrul zonei client. Diferenţa dintre fereastra aplicaţiei şi zona client este destul de
importantă: zona client este partea din fereastra aplicaţiei 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 porţiunea din
fereastră în care programul poate să deseneze ceva, oferind astfel utilizatorilor diverse informaţii vizuale.
În zona client a programului puteţi să faceţi aproape orice, evitând însă să plecaţi de la presupunerea că ea are o anumită
dimensiune şi că această dimensiune nu se va modifica în timpul rulării programului. Dacă sunteţi obişnuit cu scrierea
programelor sub MS-DOS, s-ar putea ca această situaţie să vi se pară puţin surprinzătoare. Nu vă mai puteţi baza pe un
ecran de 25 (sau 43 sau 50) de linii de text a câte 80 de caractere. Programul pe care îl scrieţi trebuie să partajeze ecranul cu
celelalte programe rulate sub Windows. Modul în care ferestrele programelor sunt aranjate pe ecran este controlat de
utilizator. Deşi este posibilă crearea unei ferestre cu dimensiuni fixe (situaţie potrivită pentru calculator sau alte utilitare), în
majoritatea cazurilor ferestrele pot fi redimensionate. Programul pe care îl scrieţi trebuie să accepte dimensiunile pe care le
stabiliţi şi să „se descurce" în aceste condiţii.
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ă rezoluţie, în care să aibă la dispoziţie o zonă client suficient de
mare pentru două pagini de text şi destul spaţiu suplimentar liber. Abordarea inteligentă, eficientă a ambelor situaţii este o
parte importantă a programării sub Windows.
Deşi sistemul de operare Windows pune la dispoziţie un număr de funcţii GDI (Graphics Device Interface) pentru
afişarea imaginilor, în acest capitol ne vom rezuma la afişarea liniilor de text. De asemenea, vom ignora numeroasele
fonturi (corpuri de literă) pe care le avem la dispoziţie în Windows şi vom folosi numai fontul prestabilit - „fontul sistem".
Aceasta poate părea o limitare, dar în realitate nu este. Problemele pe care le vom întâlni în acest capitol - şi pe care le vom
rezolva - sunt comune pentru toate programele Windows. Atunci când afişaţi o combinaţie de text şi de imagini (aşa cum
face, de exemplu, programul Calculator) dimensiunea caracterelor din fontul sistem determină, de cele mai multe ori,
dimensiunea imaginilor.
Vom discuta mai puţin despre posibilităţile de desenare, dar, în schimb, veţi întâlni o serie de informaţii privind
programarea independentă de dispozitiv. Programele Windows pot face foarte puţine presupuneri privind dimensiunea
zonei client sau dimensiunea caracterelor; de asemenea, ele trebuie să folosească funcţiile Windows ca să obţină informaţii
despre mediul în care rulează.
Desenarea şi redesenarea
Sub MS-DOS, un program care foloseşte tot ecranul (full-screen mode) poate să scrie în orice parte a acestuia.
Elementele afişate rămân pe ecran şi nu dispar în mod misterios. Programul poate să renunţe apoi la informaţiile necesare
pentru redesenarea ecranului. Dacă un alt program (cum ar fi programele rezidente în memorie) afişează ceva peste o parte
a ecranului, atunci programul respectiv trebuie să-şi refacă ecranul.
În Windows nu puteţi să afişaţi decât în zona client a ferestrei şi nu puteţi să presupuneţi că ceea ce afişaţi în zona client
va rămâne acolo pană când programul afişează explicit altceva. De exemplu, o casetă de dialog a unei alte aplicaţii poate să
se suprapună peste o parte a zonei client a programului dumneavoastră. Deşi sistemul de operare încearcă să salveze şi să
refacă zona de ecran de sub caseta de dialog, uneori nu poate face acest lucru. După ce caseta de dialog va fi închisă,
Windows va cere programului să redeseneze porţiunea afectată din zona client.
Windows este un sistem de operare bazat pe mesaje. El informează aplicaţiile privind apariţia evenimentelor, prin
trimiterea unor mesaje în coada de mesaje a aplicaţiei, sau prin trimiterea unor mesaje către procedurile de fereastră
corespunzătoare. De pildă, Windows informează o procedură de fereastră că o parte a zonei client trebuie să fie
reactualizată prin trimiterea unui mesaj WM_PAINT.
Mesajul WM_PAINT
Majoritatea programelor Windows apelează funcţia UpdateWindow în timpul procesului de iniţializare din procedura
WinMain, chiar înainte de intrarea în ciclul de tratare a mesajelor. Windows profită de această ocazie ca să trimită către
procedura ferestrei primul mesaj WM_PAINT. Acest mesaj informează procedura ferestrei că zona client este pregătită
pentru desen. Din acest moment, procedura ferestrei ar trebui să fie pregătită să prelucreze orice mesaj WM_PAINT în
următoarele situaţii:
 O zonă anterior acoperită a ferestrei este adusă la suprafaţă atunci când utilizatorul mută o fereastră.
 Utilizatorul redimensionează fereastra (dacă stilul clasei ferestrei include seturile de biţi CS_HREDRAW si
CS_VREDRAW).
 Programul foloseşte funcţiile ScrollWindow sau ScrollDC ca să deruleze o parte din zona client a ferestrei.
 Programul foloseşte funcţiile InvalidateRect sau InvalidateRgn pentru a genera în mod explicit un mesaj
WM_PAINT.
În anumite cazuri, când zona client este acoperită parţial cu text, Windows încearcă să salveze o zonă a ecranului, pe
care o va restaura mai târziu. Această metodă, însă, nu dă întotdeauna rezultate bune. Windows poate trimite, de aceea, un
mesaj WM_PAINT, în situaţiile în care:
 Windows a şters o casetă de dialog sau casetă de mesaje care acoperea o parte a ferestrei.
 Un meniu este tras în jos şi apoi eliberat.
Windows salvează zona de ecran pe care a scris şi apoi o restaurează, în cazurile în care:
 Indicatorul mouse-ului este mişcat în zona client.
 O pictogramă este trasă în zona client.

pag 1 din 18
Tratarea mesajelor WM_PAINT implică revizuirea modului de scriere pe ecran. Programul trebuie structurat astfel încât
să acumuleze toate informaţiile necesare pentru redesenarea zonei client, dar să facă această operaţie numai „la cerere" -
atunci când Windows îi trimite un mesaj WM_PAINT. Dacă programul trebuie să actualizeze zona client, poate forţa
sistemul de operare să îi trimită un mesaj WM_PAINT. Deşi această metodă de afişare pare ocolitoare, ea contribuie la
structurarea programului.
Dreptunghiuri valide şi invalide
Deşi procedura unei ferestre trebuie să fie pregătită să actualizeze întreaga zonă client a ferestrei atunci când primeşte
mesajul WM_PAINT, deseori este necesară numai reactualizarea unei porţiuni mai mici (de cele mai multe ori o suprafaţă
dreptunghiulară din zona client). O astfel de situaţie apare atunci când o parte a zonei client este acoperită de o casetă de
dialog. Redesenarea este necesară numai pentru zona dreptunghiulară adusă la suprafaţă după închiderea casetei de dialog.
Această zonă este numită „regiune invalidă" („invalid region") sau „regiune de actualizare" („update region"). Prezenţa
unei regiuni invalide în cadrul zonei client determină sistemul de operare să plaseze un mesaj WM_PAINT în coada de
aşteptare a aplicaţiei. Procedura de fereastră a unui program recepţionează un mesaj WM_PAINT numai dacă o parte a
zonei client a ferestrei este invalidă.
Windows păstrează în interior o „structură cu informaţii pentru desenare" (paint information structure) pentru fiecare
fereastră. Această structură conţine (printre alte informaţii) coordonatele celui mai mic dreptunghi în care se încadrează
regiunea invalidă. Acesta este cunoscut sub numele de „dreptunghi invalid", dar uneori este numit tot „regiune invalidă".
Dacă o altă regiune a zonei client devine invalidă înainte ca mesajul WM_PAINT să fie prelucrat, Windows calculează un
nou dreptunghi invalid care cuprinde ambele regiuni şi stochează informaţiile actualizate în structura de informaţii pentru
desenare, fără să plaseze un nou mesaj WM_PAINT în coada de aşteptare a aplicaţiei.
Procedura unei ferestre poate să invalideze un dreptunghi din zona client proprie prin apelarea funcţiei InvalidateRect.
Dacă în coada de aşteptare există deja un mesaj WM_PAINT, Windows calculează un nou dreptunghi invalid. În caz
contrar, plasează în coada de aşteptare un nou mesaj WM_PAINT. La recepţionarea mesajului WM_PAINT, procedura
ferestrei poate obţine coordonatele dreptunghiului invalid (aşa cum vom vedea ceva mai târziu în acest capitol). De
asemenea, poate obţine aceste coordonate în orice alt moment, apelând funcţia GetUpdateRect.
După ce procedura de fereastră apelează funcţia BeginPaint în timpul prelucrării mesajului WM_PAINT, întreaga zonă
client este validată. De asemenea, programul poate să valideze orice porţiune dreptunghiulară din zona client, apelând
funcţia ValidateRect. Dacă în urma acestui apel întreaga zonă invalidă este validată, toate mesajele WM_PAINT aflate în
coada de aşteptare sunt şterse.
Interfaţa GDI
Pentru desenarea zonei client a ferestrei folosiţi funcţiile din interfaţa Windows pentru dispozitivele grafice (GDI -
Graphics Device Interface). (Vom face o prezentare generală a interfeţei GDI în capitolul următor.) Aţi întâlnit deja funcţia
DrawText în Capitolul 2, dar cea mai cunoscută funcţie este TextOut. Această funcţie are următorul format:
TextOut (hdc, x, y, psString, iLength) ;
Funcţia TextOut afişează pe ecran un şir de caractere. Parametrul psString este un pointer la şirul de caractere iar
iLength este lungimea acestui şir, în caractere. Parametrii x şi y definesc poziţia de început a şirului de caractere. (Vom
prezenta în curând mai multe detalii.) Parametrul hdc este o variabilă handle a contextului de dispozitiv şi reprezintă o parte
importantă a interfeţei GDI. De fapt, toate funcţiile GDI au nevoie de acest parametru la apelare.
Contextul de dispozitiv
O variabilă handle, aşa cum am mai spus, este pur şi simplu un număr pe care Windows îl foloseşte pentru indicarea
unui obiect. Puteţi să obţineţi această variabilă din Windows şi apoi să o folosiţi în alte funcţii. Variabila handle a
contextului de dispozitiv este calea de acces a ferestrei dumneavoastră la funcţiile GDI. Folosind această variabilă sunteţi
liber să desenaţi zona client a ferestrei şi să o faceţi aşa cum doriţi.
Contextul de dispozitiv (prescurtat DC - device context) este o structură de date întreţinută intern de interfaţa GDI.
Fiecare context de dispozitiv este asociat unui anumit dispozitiv de afişare, cum ar fi imprimanta, plotterul sau monitorul
video. În cazul monitoarelor video, un context de dispozitiv este de obicei asociat unei anumite ferestre de pe ecran.
O parte dintre valorile din contextul de dispozitiv sunt atribute grafice. Aceste atribute definesc unele particularităţi
privind modul de lucru al unor funcţii de desenare din interfaţa GDI. În cazul funcţiei TextOut, de exemplu, atributele
contextului de dispozitiv determină culoarea textului, culoarea fondului, modul de mapare a coordonatelor x şi y în zona
client a ferestrei şi fontul folosit de Windows pentru afişarea textului.
Atunci când vrea să deseneze, programul trebuie să obţină mai întâi o variabilă handle a unui context de dispozitiv.
După terminarea operaţiilor de desenare, programul ar trebui să elibereze variabila. După eliberarea variabilei handle,
aceasta nu mai este validă şi, deci, nu mai poate fi folosită. Programul trebuie să obţină şi să elibereze variabila handle în
timpul prelucrării unui singur mesaj. Cu excepţia contextelor de dispozitiv create cu funcţia CreateDC (despre care nu vom
discuta în acest capitol) este recomandat să nu păstraţi variabilele handle ale contextelor de dispozitiv de la un mesaj la
altul.
În general, aplicaţiile Windows folosesc două metode pentru obţinerea variabilelor handle ale contextelor de dispozitiv,
atunci când se pregătesc pentru desenarea ecranului.
Obţinerea unei variabile handle a contextului de dispozitiv: prima metodă
Această metodă este folosită în timpul prelucrării mesajelor WM_PAINT. Sunt implicate două funcţii: BeginPaint şi
EndPaint. Aceste funcţii au nevoie de variabila handle a ferestrei (transmisă procedurii de fereastră ca parametru) şi de
adresa unei variabile de tipul PAINTSTRUCT. De obicei, programatorii Windows numesc această variabilă ps şi o definesc
în procedura de fereastră astfel:
PAINTSTRUCT ps;
pag 2 din 18
În timpul prelucrării mesajului WM_PAINT, procedura de fereastră apelează mai întâi funcţia BeginPaint ca să
completeze câmpurile structurii ps. Valoarea returnată de funcţia BeginPaint este variabila handle a contextului de
dispozitiv. În general, aceasta este salvată într-o variabilă numită hdc. În prcedura de fereastră definiţi această variabilă
astfel:
HDC hdc;
Tipul de date HDC este definit ca un întreg fără semn, pe 32 de biţi. Programul poate apoi să folosească funcţii GDI,
cum ar fi TextOut. Apelul funcţiei EndPaint eliberează variabila handle a contextului de dispozitiv.
În general, prelucrarea mesajului WM_PAINT se face astfel:
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
[apelarea unor funcţii GDI]
EndPaint (hwnd, &ps) ;
return 0 ;
În timpul prelucrării mesajului WM_PAINT, procedura de fereastră trebuie să apeleze funcţiile BeginPaint şi EndPaint.
Dacă o procedură de fereastră nu prelucrează mesajele WM_PAINT, trebuie să le retransmită procedurii DefWindowProc
(procedura de fereastră prestabilită).
DefWindowProc prelucrează mesajele WM_PAINT în următoarea secvenţă de cod:
case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
Apelarea în secvenţă a funcţiilor BeginPaint şi EndPaint fără nici o altă instrucţiune intermediară nu face decât să
valideze regiunea invalidată anterior. Aşadar, nu procedaţi astfel:
case WM_PAINT:
return 0 ; // GREŞIT !!!
Windows plasează un mesaj WM_PAINT în coada de aşteptare, deoarece o parte a zonei client este invalidă. Dacă nu
apelaţi funcţiile BeginPaint şi EndPaint (sau ValidateRect) Windows nu validează zona de fereastră respectivă, ci vă
trimite în continuare mesaje WM_PAINT.
Structura de informaţii pentru desenare
Am vorbit anterior despre „structura de informaţii pentru desenare" („paint information structure") păstrată de Windows
pentru fiecare fereastră. Această structură este definită astfel:
typedef struct tagPAINTSTRUCT
{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
}
PAINTSTRCUT;
Windows completează câmpurile acestei structuri atunci când programul dumneavoastră apelează funcţia BeginPaint.
Programul poate să folosească numai primele trei câmpuri, celelalte fiind folosite intern de sistemul de operare.
Câmpul hdc reprezintă variabila handle a contextului de dispozitiv. Deoarece redundanţa este tipică pentru sistemul de
operare Windows, valoarea returnată de funcţia BeginPaint este aceeaşi variabilă handle.
În majoritatea cazurilor, câmpul fErase va avea valoarea TRUE (diferită de zero), ceea ce înseamnă că Windows a şters
fondul dreptunghiului invalid. Pentru ştergerea fondului, Windows foloseşte pensula specificată în câmpul hbrBackground
al structurii WNDCLASSEX, pe care aţi folosit-o la înregistrarea clasei în timpul iniţializărilor, din funcţia WinMain.
Multe programe Windows folosesc o pensulă de culoare albă:
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
Totuşi, dacă programul invalidează un dreptunghi din zona client apelând funcţia InvalidateRect, ultimul parametru al
funcţiei specifică dacă vreţi ca fondul să fie şters. Dacă acest parametru este FALSE (0), Windows nu va şterge fondul şi
câmpul fErase va avea valoarea FALSE.
Câmpul rcPaint al structurii PAINTSTRUCT este o structură de tip RECT. Aşa cum aţi aflat din Capitolul 2, structura
RECT defineşte un dreptunghi. Cele patru câmpuri ale structurii sunt left, top, right şi bottom. Câmpul rcPaint al structurii
PAINTSTRUCT defineşte limitele unui dreptunghi invalid, aşa cum se poate vedea în figura 3.1. Valorile sunt date în
pixeli, şi se raportează la colţul din stânga-sus al zonei client a ferestrei. Dreptunghiul invalid este suprafaţa pe care ar
trebui să o redesenaţi. Deşi un program Windows ar putea să redeseneze întreaga zonă client a ferestrei de fiecare dată când
primeşte un mesaj WM_PAINT, redesenând numai porţiunea ferestrei definită de dreptunghi programul economiseşte timp.
Dreptunghiul rcPaint din structura PAINTSTRUCT nu este un simplu dreptunghi invalid, ci un dreptunghi „de
decupare" (clipping rectangle). Aceasta înseamnă că Windows restricţionează desenarea în interiorul dreptunghiului. (Mai
precis, dacă regiunea invalidă nu este dreptunghiulară, Windows restricţionează desenarea numai la regiunea respectivă.)
Atunci când folosiţi variabila handle a contextului de dispozitiv din structura PAINTSTRUCT, Windows nu desenează în
afara dreptunghiului rcPaint.

pag 3 din 18
Figura 3-1. Limitele unui dreptunghi invalid.
Pentru desenarea în afara dreptunghiului rcPaint în timpul prelucrării mesajului WM_PAINT, faceţi următorul apel:
InvalidateRect (hWnd, NULL, TRUE); înaintea apelării funcţiei BeginPaint. Apelul de mai sus invalidează întreaga zonă
client şi şterge fondul acesteia. Dacă ultimul parametru are valoarea FALSE, fondul nu este şters şi desenul va fi făcut peste
ceea ce există deja.
În programul HELLOWIN prezentat în Capitolul 2 nu am ţinut seama de dreptunghiurile invalide sau de
dreptunghiurile de decupare în timpul prelucrării mesajului WM_PAINT. Dacă zona în care era afişat textul se afla în
dreptunghiul invalid, funcţia DrawText o refăcea. Dacă nu, în timpul prelucrării apelului DrawText, Windows determina
faptul că pe ecran nu trebuie să afişeze nimic. Dar aceste operaţii de determinare durează. Un programator interesat de
performanţă şi viteză va dori să folosească dimensiunile dreptunghiului invalid în timpul prelucrării mesajului
WM_PAINT, astfel încât să evite apelarea inutilă a unor funcţii GDI.
Obţinerea unei variabile handle a contextului de dispozitiv: a doua metodă
Puteţi să obţineţi o variabilă handle a unui context de dispozitiv şi în timpul prelucrării altor mesaje decât WM_PAINT,
sau atunci când aveţi nevoie de variabila handle a contextului în alte scopuri, cum ar fi obţinerea unor informaţii despre
contextul de dispozitiv. Apelaţi funcţia GetDC pentru a obţine variabila handle şi apoi apelaţi funcţia ReleaseDC atunci
când nu mai aveţi nevoie de aceasta:
hdc = GetDC(hwnd) ;
[apelarea unor funcţii GDI]
ReleaseDC(hwnd, hdc) ;
La fel ca funcţiile BeginPaint şi EndPaint, şi funcţiile GetDC şi ReleaseDC ar trebui apelate în pereche. Atunci când
apelaţi funcţia GetDC în timpul prelucrării unui mesaj, este recomandat să apelaţi funcţia ReleaseDC înainte de a ieşi din
procedura de fereastră. Nu apelaţi funcţia GetDC ca răspuns la un mesaj şi funcţia ReleaseDC ca răspuns la un alt mesaj.
Spre deosebire de variabila handle a contextului de dispozitiv obţinută din structura PAINTSTRUCT, variabila handle
returnată de funcţia GetDC se referă la un dreptunghi cu întreaga zonă client a ferestrei. Puteţi să desenaţi în orice parte a
zonei client, nu doar în dreptunghiul invalid (dacă există un dreptunghi invalid). Spre deosebire de funcţia BeginPaint,
GetDC nu validează nici o regiune invalidă.
În general, folosiţi funcţiile GetDC şi ReleaseDC ca răspuns la mesajele de la tastatură (de exemplu, într-un procesor de
texte) sau la mesajele de la mouse (de exemplu, într-un program pentru desen). În acest fel, programul poate să actualizeze
zona client ca reacţie la informaţiile introduse de utilizator de la tastatură sau cu ajutorul mouse-ului, fără să invalideze în
mod deliberat o zonă a ferestrei pentru generarea unui mesaj WM_PAINT. Totuşi, programul trebuie să acumuleze
suficiente informaţii pentru a putea să reactualizeze ecranul ori de câte ori primeşte un mesaj WM_PAINT.
Funcţia TextOut: detalii
Atunci când obţineţi o variabilă handle a contextului de dispozitiv, Windows completează structura contextului de
dispozitiv, păstrată intern, cu valorile prestabilite. Aşa cum veţi vedea în capitolele următoare, puteţi să modificaţi valorile
prestabilite cu ajutorul funcţiilor GDI. Funcţia GDI care ne interesează în acest moment este TextOut:
TextOut (hdc, x, y, psString, iLength) ;
Haideţi să examinăm în detaliu această funcţie.
Primul parametru este o variabilă handle a contextului de dispozitiv - valoarea hdc returnată de funcţia GetDC sau
valoarea hdc returnată de funcţia BeginPaint în timpul prelucrării mesajului WM_PAINT.
Atributele contextului de dispozitiv controlează caracteristicile textului afişat. De exemplu, unul dintre atributele
contextului de dispozitiv stabileşte culoarea textului. Culoarea prestabilită este negru. De asemenea, contextul prestabilit de
dispozitiv stabileşte ca fondul să fie alb. Atunci când un program afişează un text pe ecran, Windows foloseşte această
culoare de fond ca să umple spaţiul dreptunghiular care înconjoară fiecare caracter, spaţiu numit „casetă caracter"
(„character box").
Fondul textului nu este acelaşi lucru cu fondul pe care îl stabiliţi atunci când definiţi clasa ferestrei. Fondul din clasa
ferestrei este o pensulă - adică un model care poate să fie sau să nu fie o culoare pură - pe care sistemul de operare
Windows o foloseşte ca să şteargă zona client. La definirea structurii de clasă a ferestrei, multe aplicaţii Windows folosesc
pag 4 din 18
identificatorul WHITE_BRUSH, astfel încât culoarea de fond din contextul de dispozitiv prestabilit să fie aceeaşi cu
culoarea pensulei pe care Windows o foloseşte pentru ştergerea zonei client.
Parametrul psString este un pointer la un şir de caractere, iar iLength este lungimea acestui şir de caractere, adică
numărul de caractere conţinut de şir. Şirul de caractere nu poate conţine caractere ASCII de control, cum ar fi CR (carriage
return - retur de car), LF (line feed - salt la linie nouă), tab sau backspace. Windows afişează aceste caractere ca blocuri
negre sau mici casete. Funcţia TextOut nu recunoaşte caracterul 0 ca terminator de şir si are nevoie de parametrul iLength
pentru precizarea lungimii şirului de caractere.
Valorile x şi y din apelul funcţiei TextOut definesc începutul şirului de caractere în zona client a ferestrei. Valoarea x
indică poziţia pe orizontală, iar valoarea y indică poziţia pe verticală. Colţul din stânga-sus al primului caracter se află în
poziţia de coordonate (x,y). În contextul de dispozitiv prestabilit, originea sistemului de coordonate, adică punctul în care x
şi y au valoarea 0, se află în colţul din stânga-sus al zonei client. Dacă în funcţia TextOut folosiţi valoarea 0 pentru
parametrii x şi y, şirul de caractere este afişat începând din colţul stânga-sus al zonei client. În documentaţie, coordonatele
GDI sunt numite „coordonate logice". Vom vorbi mai mult despre semnificaţia acestui termen în capitolul următor. Pentru
moment, reţineţi că în Windows există diferite moduri de mapare care controlează transformarea coordonatelor logice
transmise funcţiilor GDI în coordonate fizice ale pixelilor afişaţi pe ecran. Modul de mapare este definit în contextul de
dispozitiv. Modul de mapare prestabilit este MM_TEXT (folosind identificatorul definit în fişierele antet Windows). În
modul de mapare MM_TEXT, unităţile logice sunt aceleaşi cu unităţile fizice, adică pixelii; ele se raportează la colţul din
stânga-sus al zonei client, iar valorile coordonatei y cresc pe măsură ce coborâţi în zona client a ferestrei (vezi Figura 3.2).
Sistemul de coordonate MM_TEXT este acelaşi cu sistemul de coordonate folosit de Windows pentru definirea
dreptunghiului invalid din structura PAINTSTRUCT. Foarte convenabil. (Nu la fel se întâmplă însă dacă folosiţi alte
moduri de mapare.)

Figura 3.2. Coordonatele x şi y în modul de mapare MM_TEXT.


Contextul de dispozitiv defineşte şi o regiune de decupare (clipping region). Aşa cum aţi văzut, regiunea prestabilită de
decupare este întreaga zonă client, pentru o variabilă handle a contextului de dispozitiv obţinută prin apelarea funcţiei
GetDC, sau numai regiunea invalidă, pentru o variabilă handle a contextului de dispozitiv obţinută prin apelarea funcţiei
BeginPaint. Windows nu afişează partea care se află în afara regiunii de decupare şi care aparţine şirului de caractere, ci
numai porţiunile care sunt cuprinse în regiunea de decupare. Scrierea în afara zonei client a unei ferestre este o operaţiune
dificilă, aşa că nu vă speriaţi - nu este posibil să faceţi acest lucru din greşeală.
Fontul sistem
Tot în contextul de dispozitiv este definit şi fontul pe care sistemul de operare Windows îl foloseşte pentru scrierea
textului în zona client. Fontul prestabilit este numit „font sistem" sau (folosind identificatorul definit în fişierele antet
Windows) SYSTEM_FONT. Fontul sistem este fontul pe care Windows îl foloseşte pentru textul din barele de titlu, barele
de meniu şi casetele de dialog.
La începuturile sistemului de operare Windows, fontul sistem era un font cu dimensiune fixă, ceea ce înseamnă că toate
caracterele aveau aceeaşi lăţime, ca la maşinile de scris. Începând cu versiunea Windows 3.0 şi continuând pană la
Windows 95, fontul sistem este un font cu dimensiune variabilă, ceea ce înseamnă că fiecare caracter are o altă dimensiune.
De exemplu, „W" este mai lat decât „i". Este foarte clar că un text scris cu un font având dimensiune variabilă este mai uşor
de citit decât un font cu dimensiune fixă. Aşa cum vă puteţi imagina, însă, această schimbare a dat peste cap multe
programe Windows scrise pentru primele versiuni şi programatorii au fost nevoiţi să înveţe noi tehnici de lucru cu text.
Fontul sistem este un font de tip „rastru", ceea ce înseamnă că fiecare caracter este definit ca un bloc de pixeli.
Versiunile Windows aflate pe piaţă includ mai multe fonturi sistem, de diferite dimensiuni, folosite pentru diferite tipuri de
plăci video. O firmă care produce un nou driver de afişare trebuie să creeze şi fontul sistem potrivit cu rezoluţia de afişare
respectivă. O altă soluţie este ca producătorul să specifice unul dintre fonturile sistem furnizate împreună cu sistemul de
operare Windows. Fontul sistem trebuie proiectat astfel încât pe ecran să încapă cel puţin 25 de linii cu câte 80 de caractere.
Aceasta este singura garanţie privind compatibilitatea între dimensiunea ecranului şi dimensiunea fontului.
Dimensiunea unui caracter
Pentru afişarea mai multor linii de text cu ajutorul funcţiei TextOut trebuie să determinaţi dimensiunile caracterelor din
fontul folosit. Puteţi să stabiliţi spaţiul dintre liniile succesive de text pe baza înălţimii unui caracter şi spaţiul dintre coloane
pe baza lăţimii medii a caracterelor din font.
Dimensiunile caracterelor sunt obţinute prin apelarea funcţiei GetTextMetrics. Funcţia GetTextMetrics are nevoie de o
variabilă handle a contextului de dispozitiv, deoarece returnează informaţii despre fontul selectat în contextul de dispozitiv.
Windows copiază valorile referitoare la dimensiunile caracterelor într-o structură de tip TEXTMETRIC. Valorile sunt

pag 5 din 18
exprimate în unităţi de măsură care depind de modul de mapare selectat în contextul de dispozitiv. În contextul prestabilit
de dispozitiv, modul de mapare este MM_TEXT, aşa că dimensiunile sunt date în pixeli.
Pentru folosirea funcţiei GetTextMetrics trebuie să definiţi mai întâi o variabilă de tipul TEXTMETRIC (numită, de
obicei, tm):
TEXTMETRIC tm;
În continuare obţineţi o variabilă handle a contextului de dispozitiv şi apelaţi funcţia GetTextMetrics:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm) ;
ReleaseDC(hwnd, hdc);
Apoi puteţi să examinaţi valorile din structura de dimensiuni a textului şi, dacă doriţi, să salvaţi unele dintre aceste
dimensiuni pentru utilizarea în viitor.
Dimensiunile textului: detalii
Structura TEXTMETRIC furnizează o mulţime de informaţii despre fontul curent selectat în contextul de dispozitiv.
Totuşi, aşa cum puteţi vedea în Figura 3-3, dimensiunea verticală a unui font este definită de numai cinci valori.

Figura 3-3. Cele cinci valori care definesc dimensiunea verticala a unui caracter.
Acestea sunt uşor de înţeles. Valoarea tmInternalLeading se referă la spaţiul păstrat deasupra unui caracter pentru
semnele de accentuare. Dacă această valoare este zero, literele mari cu accent sunt afişate ceva mai mici, astfel încât
accentul să încapă în partea de sus a caracterului. Valoarea tmExternalLeading se referă la spaţiul recomandat de către
proiectantul fontului a fi lăsat între rândurile de text. Puteţi să acceptaţi sau nu această recomandare atunci când stabiliţi
distanţa dintre rândurile de text.
Structura TEXTMETRIC conţine două câmpuri care descriu lăţimea unui caracter:
tmAveCharWidth (lăţimea medie a literelor mici) şi tmMaxCharWidth (lăţimea celui mai mare caracter al fontului).
Pentru fonturile cu dimensiune fixă cele două valori sunt egale.
În exemplul de programe din acest capitol am avut nevoie şi de o altă dimensiune - lăţimea medie a majusculelor. O
valoare destul de precisă poate fi obţinută calculând 150% din valoarea tmAveCharWidth.
Este important să reţineţi faptul că dimensiunile fontului sistem depind de rezoluţia ecranului pe care rulează sistemul
de operare Windows. Windows furnizează o interfaţă grafică independentă de dispozitiv, dar este nevoie de un mic efort şi
din partea dumneavoastră. Nu scrieţi programe Windows care se bazează pe ghicirea dimensiunilor unui caracter. Nu
introduceţi în cod valori fixe. Folosiţi funcţia GetTextMetrics ca să obţineţi valorile de care aveţi nevoie.
Fomatarea textului
Deoarece dimensiunile fontului sistem nu se modifică în timpul unei sesiuni Windows, trebuie să apelaţi funcţia
GetTextMetrics o singură dată după lansarea în execuţie a programului. Un loc potrivit pentru acest apel este codul de
prelucrare a mesajului WM_CREATE din procedura de fereastră. Mesajul WM_CREATE este primul mesaj pe care îl
primeşte procedura de fereastră. Windows trimite un mesaj WM_CREATE procedurii de fereastră atunci când apelaţi
funcţia CreateWindow din funcţia WinMain.
Să presupunem că scrieţi un program Windows care afişează mai multe linii de text una sub alta, în zona client a
ferestrei. Trebuie să obţineţi valorile pentru înălţimea şi lăţimea caracterelor. În procedura de fereastră puteţi să definiţi
două variabile în care să salvaţi lăţimea medie (cxChar) şi înălţimea totală a caracterelor (cyChar):
static int cxChar, cyChar ;
Prefixul c adăugat la numele variabilelor provine de la „contor" şi în combinaţie cu x sau y se referă la dimensiunea pe
orizontală sau pe verticală a caracterelor. Aceste variabile sunt declarate ca statice deoarece trebuie să fie valide atunci
când procedura de fereastră prelucrează alte mesaje (cum ar fi WM_PAINT). Dacă variabilele sunt declarate ca globale în
alte funcţii nu mai este necesar să fie declarate statice. Iată codul de prelucrare a mesajului WM_CREATE:
case WH_CREATE:
hdc = GetDC (hwnd);
GetTextMetrics (hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC (hwnd, hdc);
return 0;
Dacă nu vreţi să folosiţi spaţiul suplimentar extern pentru spaţierea liniilor de text, puteţi să utilizaţi instrucţiunea:
cyChar = tm.tmHeight;

pag 6 din 18
Dumneavoastră stabiliţi modul în care folosiţi dimensiunile caracterelor pentru calcularea coordonatelor de afişare. O
metodă simplă este să lăsaţi o margine de dimensiunea cyChar în partea de sus a zonei client şi una de mărimea cxChar în
partea stângă. Pentru afişarea mai multor linii de text aliniate la stânga, folosiţi valoarea cxChar pentru parametrul care
reprezintă coordonata pe axa x în apelul funcţiei TextOut. Valoarea coordonatei pe axa y la apelarea funcţiei TextOut este:
cyChar * (1 + i);
unde i reprezintă numărul liniei, începând de la 0.
Deseori este necesar să afişaţi atât numere formatate, cat şi şiruri simple de caractere. Dacă aţi scris programe DOS
folosind funcţiile standard C de bibliotecă, probabil aţi folosit pentru formatarea numerelor funcţia printf. În Windows nu
puteţi să folosiţi funcţia printf, deoarece ea determină afişarea la dispozitivul standard de ieşire, acesta fiind un concept care
în Windows nu are nici un sens.
În schimb, puteţi să folosiţi funcţia sprintf. Funcţia sprintf lucrează la fel ca şi funcţia printf, cu excepţia faptului că şirul
de caractere formatat este stocat într-o matrice de caractere. Apoi puteţi utiliza funcţia TextOut pentru a scrie şirul ce
trebuie afişat. Funcţia sprintf returnează lungimea şirului de caractere, ceea ce este foarte convenabil - puteţi să transmiteţi
valoarea returnată în locul parametrului iLength din apelul funcţiei TextOut. Secvenţa de cod de mai jos prezintă o
combinaţie tipică a funcţiilor TextOut şi sprintf:
int iLenght;
char szBuffer [40];
[alte Iinii de program]
iLenght = sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB);
TextOut (hdc, x, y, szBuffer, iLength);
Pentru operaţiile simple de afişare precum cea de mai sus puteţi să renunţaţi la definirea variabilei iLength şi să
combinaţi cele două instrucţiuni rămase într-una singură:
TextOut (hdc, x, y, szBuffer, sprintf (szBuffer, "The sum of %d and %d is %d", nA, nB, nA + nB));
Nu arată prea frumos, dar funcţionează.
Dacă nu trebuie să afişaţi numere în virgulă mobilă, puteţi folosi funcţia wsprintf în locul funcţiei sprintf. Funcţia
wsprintf are aceeaşi sintaxă ca şi funcţia sprintf, dar este inclusă în Windows, aşa că nu va mări dimensiunea fişierului
executabil.
Să punem totul la un loc
Acum se pare că avem toate elementele necesare pentru scrierea unui program simplu, care afişează mai multe linii de
text. Ştim cum să obţinem o variabilă handle a contextului de dispozitiv, cum să folosim funcţia TextOut şi cum să spaţiem
textul în funcţie de dimensiunea caracterelor. Singurul lucru care a mai rămas este să găsim ceva interesant de afişat.
Informaţiile returnate de funcţia GetSystemMetrics par destul de interesante. Funcţia GetSystemMetrics returnează
informaţii despre dimensiunea unor elemente grafice din Windows, cum ar fi pictograme, cursoare, bare de titlu şi bare de
derulare. Aceste dimensiuni depind de placa video şi de driverul de afişare. Funcţia GetSystemMetrics acceptă un singur
parametru, numit „index". Indexul este unul dintre cei 73 de identificatori de tip întreg definiţi în fişierele antet din
Windows. GetSystemMetrics returnează o valoare întreagă care reprezintă, de obicei, dimensiunea elementului transmis ca
parametru.
Haideţi să scriem un program care afişează o parte dintre informaţiile returnate de funcţia GetSystemMetrics într-un
format simplu, cu o linie de text pentru fiecare element. Folosirea acestor informaţii este simplificată prin crearea unui
fişier antet care defineşte o structură conţinând atât identificatorii din fişierele antet Windows, folosiţi pentru indexul
transmis funcţiei GetSystemMetrics, cât şi textul pe care vrem să îl afişeze programul pentru fiecare valoare returnată de
funcţie. Acest fişier antet este numit SYSMETS.H şi este prezentat în Figura 3-4.
/*-----------------------------------------------
SYSMETS.H -- System metrics display structure
-----------------------------------------------*/
#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))
struct
{
int iIndex ;
char *szLabel ;
char *szDesc ;
}
sysmetrics [ ] =
{
SM_CXSCREEN, "SM_CXSCREEN", "Screen width in pixels",
SM_CYSCREEN, "SM_CYSCREEN", "Screen height in pixels",
SM_CXVSCROLL, "SM_CXVSCROLL", "Vertical scroll arrow width",
SM_CYHSCROLL, "SM_CYHSCROLL", "Horizontal scroll arrow height",
SM_CYCAPTION, "SM_CYCAPTION", "Caption bar height",
SM_CXBORDER, "SM_CXBORDER", "Window border width",
SM_CYBORDER, "SM_CYBORDER", "Window border height",
SM_CXDLGFRAME, "SM_CXDLGFRAME", "Dialog window frame width",
SM_CYDLGFRAME, "SM_CYDLGFRAME", "Dialog window frame height",
SM_CYVTHUMB, "SM_CYVTHUMB", "Vertical scroll thumb height",
SM_CXHTHUMB, "SM_CXHTHUMB", "Horizontal scroll thumb width",
SM_CXICON, "SM_CXICON", "Icon width",
SM_CYICON, "SM_CYICON", "Icon height",
SM_CXCURSOR, "SM_CXCURSOR", "Cursor width",
SM_CYCURSOR, "SM_CYCURSOR", "Cursor height",
SM_CYMENU, "SM_CYMENU", "Menu bar height",
SM_CXFULLSCREEN, "SM_CXFULLSCREEN", "Full screen client area width",
SM_CYFULLSCREEN, "SM_CYFULLSCREEN", "Full screen client area height",
SM_CYKANJIWINDOW, "SM_CYKANJIWINDOW", "Kanji window height",
SM_MOUSEPRESENT, "SM_MOUSEPRESENT", "Mouse present flag",
SM_CYVSCROLL, "SM_CYVSCROLL", "Vertical scroll arrow height",
SM_CXHSCROLL, "SM_CXHSCROLL", "Horizontal scroll arrow width",

pag 7 din 18
SM_DEBUG, "SM_DEBUG", "Debug version flag",
SM_SWAPBUTTON, "SM_SWAPBUTTON", "Mouse buttons swapped flag",
SM_RESERVED1, "SM_RESERVED1", "Reserved",
SM_RESERVED2, "SM_RESERVED2", "Reserved",
SM_RESERVED3, "SM_RESERVED3", "Reserved",
SM_RESERVED4, "SM_RESERVED4", "Reserved",
SM_CXMIN, "SM_CXMIN", "Minimum window width",
SM_CYMIN, "SM_CYMIN", "Minimum window height",
SM_CXSIZE, "SM_CXSIZE", "Minimize/Maximize icon width",
SM_CYSIZE, "SM_CYSIZE", "Minimize/Maximize icon height",
SM_CXFRAME, "SM_CXFRAME", "Window frame width",
SM_CYFRAME, "SM_CYFRAME", "Window frame height",
SM_CXMINTRACK, "SM_CXMINTRACK", "Minimum window tracking width",
SM_CYMINTRACK, "SM_CYMINTRACK", "Minimum window tracking height",
SM_CXDOUBLECLK, "SM_CXDOUBLECLK", "Double click x tolerance",
SM_CYDOUBLECLK, "SM_CYDOUBLECLK", "Double click y tolerance",
SM_CXICONSPACING, "SM_CXICONSPACING", "Horizontal icon spacing",
SM_CYICONSPACING, "SM_CYICONSPACING", "Vertical icon spacing",
SM_MENUDROPALIGNMENT, "SM_MENUDROPALIGNMENT", "Left or right menu drop",
SM_PENWINDOWS, "SM_PENWINDOWS", "Pen extensions installed",
SM_DBCSENABLED, "SM_DBCSENABLED", "Double-Byte Char Set enabled",
SM_CMOUSEBUTTONS, "SM_CMOUSEBUTTONS", "Number of mouse buttons",
SM_SHOWSOUNDS, "SM_SHOWSOUNDS", "Present sounds visually"
};
Figura 3.4. Fişierul SYSMETS.H
Programul care afişează aceste informaţii se numeşte SYSMETS1. Fişierele necesare pentru crearea fişierului
executabil SYSMETS1.EXE (fişierul de construcţie şi codul sursă C) sunt prezentate în Figura 3-5. Cea mai mare parte a
codului ar trebui să vi se pară deja cunoscută. Cu excepţia numelui programului, fişierul de construcţie este identic cu cel al
programului HELLOWIN. Funcţia WinMain din fişierul SYSMETS1.C este foarte asemănătoare cu cea din programul
HELLOWIN.
#include <windows.h>
#include <string.h>
#include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "SysMets1" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hwnd = CreateWindow (szAppName, "Get System Metrics No. 1",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar ;
char szBuffer[10] ;
HDC hdc ;
int i;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (iMsg)
{
case WM_CREATE :
hdc = GetDC(hwnd) ;
GetTextMetrics(hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i++)
{
TextOut (hdc, cxChar, cyChar * (1 + i), sysmetrics[i].szLabel, strlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, cxChar + 22 * cxCaps, cyChar * (1 + i), sysmetrics[i].szDesc, strlen (sysmetrics[i].szDesc)) ;

pag 8 din 18
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar * (1 + i), szBuffer, wsprintf (szBuffer, "%5d",
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
Figura 3.5. Programul SYSMTES1
Figura 3-6 prezintă fereastra afişată de programul SYSMETSl pe un monitor VGA. Aşa cum se poate vedea din textul
afişat în fereastra programului, ecranul are lăţimea de 640 de pixeli şi înălţimea de 480 de pixeli. Cele două valori, ca şi
multe altele afişate de program, pot fi diferite pentru alte tipuri de monitoare video.
Procedura de fereastră a programului SYSMETS1.C
Procedura de fereastră WndProc din fişierul SYSMETS1.C tratează trei mesaje: WM_CREATE, WM_PAINT şi
WM_DESTROY. Mesajul WM_DESTROY este tratat la fel ca şi în programul HELLOWIN din Capitolul 2.

Figura 3-6. Fereastra afişată de programul SYSMETS1.


WM_CREATE este primul mesaj pe care îl primeşte procedura de fereastră. Acest mesaj este generat de Windows
atunci când funcţia CreateWindow creează fereastra. În timpul prelucrării mesajului WM_CREATE, SYSMETS1 obţine un
context de dispozitiv pentru fereastră, apelând funcţia GetDC, şi dimensiunile fontului sistem prestabilit, apelând funcţia
GetTextMetrics. SYSMETS1 salvează lăţimea medie a caracterelor în variabila cxChar şi înălţimea totală (inclusiv spaţiul
suplimentar extern) a caracterelor în variabila cyChar.
De asemenea, SYSMETS1 salvează lăţimea medie a literelor mari în variabila statică cxCaps. Pentru fonturile cu
dimensiune fixă, cxCaps este egală cu cxChar. Pentru fonturile cu dimensiune variabilă, cxCaps este 150% din cxChar.
Bitul cel mai puţin semnificativ al câmpului tmPitchAndFamily din structura TEXTMETRIC are valoarea 1 pentru
fonturile cu dimensiune variabilă şi valoarea 0 pentru fonturile cu dimensiune fixă. Programul SYSMETS1 foloseşte acest
bit ca să calculeze valoarea cxCaps din cxChar:
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
Toate operaţiile de desenare în fereastră sunt făcute în timpul prelucrării mesajului WM_PAINT. Aşa cum este normal,
procedura de fereastră obţine mai întâi o variabilă handle a contextului de dispozitiv prin apelarea funcţiei BeginPaint. Un
ciclu for parcurge toate liniile structurii sysmetrics definită în fişierul antet SYSMETS.H. Cele trei coloane de text sunt
afişate prin apelarea de trei ori a funcţiei TextOut. La fiecare apel, cel de-al treilea parametru al funcţiei TextOut are
valoarea:
cyChar * (1 + i)
Acest parametru indică în pixeli poziţia părţii de sus a şirului de caractere, relativ la marginea superioară a zonei client.
În acest fel, programul lasă în partea superioară a zonei client o margine de mărimea cyChar. Prima linie (pentru care i are
valoarea 0) începe cu cyChar pixeli mai jos de marginea de sus a zonei client.
Prima instrucţiune TextOut afişează cu litere mari un identificator în prima dintre cele trei coloane. Al doilea parametru
al funcţiei are o valoare egală cu cxChar. În acest fel, în partea stângă a zonei client este lăsat liber un spaţiu egal cu
cxChar, deci de mărimea unui caracter. Textul este obţinut din câmpul szLabel al structurii sysmetrics. Lungimea şirului de
caractere, necesară pentru ultimul parametru al funcţiei TextOut, este obţinută prin apelarea funcţiei strlen.
A doua instrucţiune TextOut afişează descrierea sistemului de valori metrice. Aceste descrieri sunt stocate în câmpul
szDesc al structurii sysmetrics. În acest caz, al doilea parametru al funcţiei TextOut are valoarea:
cxChar + 22 * cxCaps
Cel mai lung identificator afişat în prima coloană are 20 de caractere, aşa că a doua coloană trebuie să înceapă la o
distanţă cel puţin egală cu valoarea de 20*cxCaps faţă de începutul primei coloane.

pag 9 din 18
A treia instrucţiune TextOut afişează valoarea numerică obţinută prin apelarea funcţiei GetSystemMetrics. Fonturile de
dimensiune variabilă fac destul de dificilă formatarea unei coloane de numere aliniate la dreapta. Toate cifrele de la 0 la 9
au aceeaşi lăţime, dar mai mare decât cea a unui spaţiu. Numerele pot fi formate din una sau mai multe cifre, aşa că două
numere diferite pot începe din poziţii diferite pe orizontală.
Nu ar fi mai simplu dacă am putea să afişăm o coloană de numere aliniate la dreapta prin specificarea poziţiei în care
se termină numerele, în locul poziţiei la care încep acestea? După ce programul SYSMETS apelează funcţia:
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
coordonatele transmise funcţiilor TextOut care urmează specifică localizarea colţului din dreapta-sus al şirului de caractere,
în locul colţului din stânga-sus.
Funcţia TextOut care afişează coloana de numere are ca al doilea parametru următoarea expresie:
cxChar + 22 * cxCaps + 40 * cxChar
Valoarea 40 x cxChar reprezintă lăţimea însumată a coloanelor doi şi trei. După apelarea funcţiei TextOut este apelată
din nou funcţia SetTextAlign, pentru a readuce la normal modul de aliniere a textului.
Nu există destul spaţiu!
Programul SYSMETS are un mic neajuns: dacă nu aveţi un ecran gigantic şi o placă video de înaltă rezoluţie, nu puteţi
vedea ultima parte a listei de valori metrice. Dacă îngustaţi fereastra, nu puteţi vedea nici valorile din partea dreaptă.
Programul SYSMETS1 nu ştie cât de mare este zona client a ferestrei. De aceea, el începe afişarea valorilor din partea
de sus a ferestrei şi lasă în seama sistemului de operare decuparea textului care depăşeşte marginile zonei client. Pentru că
nu este cel mai convenabil mod de afişare, prima recomandare este aceea de a determina cât de mult din ceea ce afişează
programul poate încăpea în zona client.
DIMENSIUNEA ZONEI CLIENT
Dacă faceţi câteva experimente cu aplicaţiile Windows existente, veţi vedea că dimensiunea ferestrelor poate să
varieze foarte mult. Dacă nu are meniu şi bare de derulare, fereastra poate fi mărită la maximum şi zona client ocupă
întregul ecran, cu excepţia barei de titlu a programului. Dimensiunile totale ale ferestrei mărite pot fi obţinute prin apelarea
funcţiei GetSystemMetrics cu parametrii SM_CXFULLSCREEN şi SM_CYFULLSCREEN. Pentru un monitor VGA
valorile returnate sunt 640 şi 461 de pixeli. Dimensiunile minime ale ferestrei pot fi destul de mici, uneori apropiate de
zero, eliminând de fapt zona client.
O metodă obişnuită de determinare a dimensiunilor zonei client a unei ferestre este prelucrarea mesajului WM_SIZE în
procedura de fereastră. Windows trimite un mesaj WM_SIZE către procedura de fereastră, de fiecare dată când se modifică
dimensiunile ferestrei. Parametrul lParam transmis procedurii de fereastră conţine lăţimea zonei client în cuvântul mai
puţin semnificativ (LOWORD) şi înălţimea zonei client în cuvântul mai semnificativ (HIWORD). Codul pentru prelucrarea
acestui mesaj arată astfel:
static int cxClient, cyClient ;
[alte linii de program] case WM_SIZE :
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
Macroinstrucţiunile LOWORD şi HIWORD sunt definite în fişierele antet din Windows. Ca şi variabilele cxChar şi
cyChar, variabilele cxClient şi cyClient sunt definite ca statice în cadrul procedurii de fereastră, deoarece vor fi folosite
ulterior pentru prelucrarea altor mesaje.
Mesajul WM_SIZE este urmat de un mesaj WM_PAINT, deoarece la definirea clasei am specificat următorul stil de
fereastră:
CS_HREDRAW | CS_VREDRAW
Acest stil cere sistemului de operare să forţeze redesenarea ferestrei de fiecare dată când se modifică dimensiunea
verticală sau orizontală a acesteia.
Puteţi să calculaţi numărul de linii de text care pot fi afişate în zona client folosind formula:
cyClient / cyChar
Această valoare poate fi zero dacă zona client este prea mică pentru afişarea unui caracter. La fel, puteţi să calculaţi
numărul aproximativ de caractere care pot fi afişate pe orizontală în zona client, folosind formula:
cxClient / cxChar
Dacă determinaţi valorile cxChar şi cyChar în timpul prelucrării mesajului WM_CREATE, nu vă faceţi griji privind
eventualitatea unei împărţiri la zero în formulele de mai sus. Procedura de fereastră primeşte mesajul WM_CREATE atunci
când funcţia WinMain apelează funcţia CreateWindow. Primul mesaj WM_SIZE este primit puţin mai târziu, atunci când
funcţia WinMain apelează funcţia ShowWindow, moment în care variabilelor cxChar şi cyChar le-au fost deja atribuite
valori pozitive diferite de zero.
Aflarea dimensiunilor zonei client a ferestrei este primul pas pentru furnizarea unei metode de deplasare a textului în
zona client atunci când aceasta nu este suficient de mare pentru a cuprinde tot textul afişat. Dacă aţi mai lucrat cu alte
aplicaţii Windows şi aţi întâmpinat aceleaşi probleme, probabil ştiţi deja de ce aveţi nevoie -minunată invenţie cunoscută
sub numele de bară de derulare.
Barele de derulare
Barele de derulare se numără printre cele mai reuşite componente ale unei interfeţe grafice pentru mouse; sunt uşor de
folosit şi determină o reacţie vizuală foarte rapidă. Puteţi să folosiţi bare de derulare oriunde afişaţi ceva - text, imagini, foi
de calcul tabelar, înregistrări din baze de date - pentru care aveţi nevoie de mai mult spaţiu decât este disponibil în zona
client a ferestrei.

pag 10 din 18
Barele de derulare sunt poziţionate vertical (pentru deplasări în sus şi în jos) sau orizontal (pentru deplasări la stânga şi
la dreapta). Puteţi să executaţi clic pe săgeţile de la capetele barei de derulare sau pe zona dintre acestea. Bara de derulare
este parcursă longitudinal de o „casetă de derulare" care indică poziţia aproximativă a părţii afişate pe ecran faţă de întregul
document. De asemenea, puteţi să trageţi caseta de derulare cu ajutorul mouse-ului, ca să o mutaţi într-o anumită poziţie.
Figura 3-7 prezintă modul recomandat de folosire a unei bare de derulare verticale pentru text.
Programatorii au uneori probleme cu terminologia legată de barele de derulare, deoarece perspectiva lor este diferită de
cea a utilizatorilor: un utilizator care derulează un document în jos vrea să aducă pe ecran o parte a documentului aflată mai
jos; pentru aceasta, programul mută de fapt documentul mai sus în raport cu fereastra de pe ecran. Documentaţia Windows
şi identificatorii definiţi în fişierele antet se bazează, însă, pe perspectiva utilizatorului: derularea în sus înseamnă
parcurgerea documentului către început; derularea în jos înseamnă parcurgerea documentului către sfârşit.
Vizualizaţi următoarea linie în sus
executând clic aici (conţinutul
ferestrei se deplasează în jos)

Trageţi caseta de derulare cu mouse-ul,


pentru a fixa o poziţie aproximativă în
cadrul documentului.

Vizualizaţi următoarea linie în jos


executând clic aici (conţinutul
ferestrei se deplasează în sus)
documentului.
Figura 3-7. Bara de derulare verticală.
Este foarte uşor să includeţi în fereastra aplicaţiei o bară de derulare orizontală sau verticală. Tot ce trebuie să faceţi
este să includeţi identificatorul WS_VSCROLL (derulare verticală) şi/sau WS_HSCROLL (derulare orizontală) în stilul de
fereastră din apelul funcţiei CreateWindow. Barele de derulare sunt plasate întotdeauna la marginea de jos sau la cea din
dreapta a ferestrei şi se întind pe toată lăţimea, respectiv înălţimea zonei client. Zona client nu include spaţiul ocupat de
barele de derulare. Lăţimea unei bare de derulare verticale şi înălţimea uneia orizontale sunt constante pentru un driver de
afişare dat. Dacă aveţi nevoie de aceste valori, puteţi să le obţineţi (aşa cum aţi văzut) prin apelarea funcţiei
GetSystemMetrics.
Windows se ocupă de modul de utilizare a mouse-ului pentru barele de derulare, dar barele de derulare ale ferestrelor
nu au o interfaţă automatizată cu tastatura. Dacă vreţi ca tastele de deplasare să dubleze unele dintre funcţiile barelor de
derulare trebuie să furnizaţi explicit o metodă de realizare a acestui lucru (aşa cum vom face în Capitolul 5, când vom
discuta despre tastatură şi vom relua acest program).
Domeniul şi poziţia unei bare de derulare
Fiecare bară de derulare are asociate un „domeniu" (definit printr-o pereche de numere întregi care reprezintă valorile
maximă şi minimă) şi o „poziţie" (punctul în care se află caseta de derulare în domeniul asociat barei de derulare). Atunci
când caseta de derulare se află la capătul de sus (sau la capătul din partea stângă) al barei de derulare, poziţia corespunde
valorii minime. Capătul de jos (sau capătul din partea dreaptă) al barei de derulare reprezintă valoarea maximă.

Poziţia

Poziţia

Poziţia

Poziţia

Poziţia 4

Poziţia 0 Poziţia 1 Poziţia 2 Poziţia 3 Poziţia 4


Figura 3-8. Bare de derulare cu cinci poziţii ale casetei de derulare.
În mod prestabilit, domeniul unei bare de derulare este de la 0 (sus sau în stânga) la 100 (jos sau în dreapta) dar poate fi
uşor modificat astfel încât să aibă o formă mai convenabilă pentru program:

pag 11 din 18
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
Parametrul iBar poate avea una dintre valorile SB_VERT şi SB_HORZ, iar iMin şi iMax sunt poziţiile minimă şi
maximă din domeniu, în timp ce bRedraw trebuie să aibă valoarea TRUE dacă vreţi ca Windows să redeseneze bara de
derulare pe baza noului domeniu stabilit.
Poziţia casetei de derulare este reprezentată întotdeauna printr-o valoare întreagă. De exemplu, o bară de derulare cu
domeniul cuprins între 0 şi 4 are cinci poziţii ale casetei de derulare, aşa cum se poate vedea în Figura 3-8. Puteţi să folosiţi
funcţia SetScrollPos ca să stabiliţi o nouă poziţie a casetei de derulare pe bara de derulare:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
Parametrul iPos reprezintă noua poziţie, care trebuie să fie cuprinsă în domeniul delimitat de iMin şi iMax. Windows
conţine funcţii asemănătoare (GetScrollRange şi GefScrollPos) pentru obţinerea domeniului şi a poziţiei unei bare de
derulare.
Atunci când folosiţi bare de derulare într-un program Windows, răspunderea pentru întreţinerea şi actualizarea acestora
este împărţită între dumneavoastră şi sistemul de operare. Sistemul de operare Windows are următoarele sarcini:
 Tratează operaţiile executate cu mouse-ul asupra barei de derulare. .Afişează în video invers zona pe care
utilizatorul execută clic.
 Mută caseta de derulare atunci când utilizatorul o trage cu ajutorul mouse-ului.
 Trimite mesaje din partea barei de derulare către procedura de fereastră care o conţine.
Funcţiile programului în această privinţă sunt:
 Iniţializarea domeniului barei de derulare.
 Prelucrarea mesajului primit de la bara de derulare.
 Actualizarea poziţiei casetei de derulare de pe bară.
Mesaje de la barele de derulare
Windows trimite procedurii de fereastră mesajul WM_VSCROLL sau mesajul WM_HSCROLL atunci când
utilizatorul execută clic pe bara de derulare sau trage caseta de derulare cu ajutorul mouse-ului. Fiecare acţiune cu mouse-ul
asupra barei de derulare generează cel puţin două mesaje - unul la apăsarea butonului şi al doilea la eliberarea acestuia.
Cuvântul mai puţin semnificativ al parametrului wParam care însoţeşte mesajul WM_VSCROLL sau mesajul
WM_HSCROLL este un număr ce indica acţiunea efectuată cu ajutorul mouse-ului asupra barei de derulare. Aceste
numere corespund unor identificatori care încep cu literele SB_ (de la „scroll bar"). Deşi unii dintre identificatori conţin
cuvintele „UP" şi „DOWN" („sus" şi „jos"), identificatorii se aplică atât barelor de derulare verticale, cât şi celor orizontale,
aşa cum se poate vedea în Figura 3-9. Procedura de fereastră poate să primească mai multe mesaje SB_LINEUP,
SB_PAGEUP, SB_PAGEDOWN, SB_LINEDOWN dacă butonul mouse-ului este ţinut apăsat în timp ce indicatorul lui
este poziţionat pe bara de derulare. Mesajul SB_ENDSCROLL semnalează eliberarea butonului mouse-ului. În general
puteţi ignora mesajele SB_ENDSCROLL.
Atunci când cuvântul mai puţin semnificativ al parametrului wParam are una dintre valorile SB_THUMBTRACK sau
SB_THUMBPOSITION, cuvântul mai semnificativ al parametrului wParam conţine poziţia curentă pe bara de derulare.
Această poziţie se încadrează în domeniul barei de derulare. Pentru alte acţiuni executate asupra barei de derulare, cuvântul
mai semnificativ al parametrului wParam poate fi ignorat. De asemenea, puteţi să ignoraţi parametrul lParam care, de
obicei, este folosit pentru barele de derulare create în casetele de dialog.
Documentaţia Windows indică faptul că în cuvântul mai puţin semnificativ al parametrului wParam pot fi transmise şi
valorile SB_TOP sau SB_BOTTOM, corespunzătoare deplasării casetei de derulare în poziţia minimă, respectiv maximă.
Totuşi, aceste mesaje nu sunt trimise niciodată în cazul barelor de derulare create ca parte a ferestrei unei aplicaţii.

Figura 3-9. Identificatorii conţinuţi de parametrul wParam al mesajelor trimise de barele de derulare.
Tratarea mesajelor SB_THUMBTRACK şi SB_THUMBPOSITION este destul de dificilă. Dacă stabiliţi un domeniu
mare pentru bara de derulare şi utilizatorul mută rapid caseta de derulare. Windows trimite procedurii de fereastră un număr
mare de mesaje SB_THUMBTRACK. Programul s-ar putea să aibă probleme la tratarea unui număr foarte mare de astfel
de mesaje. Din acest motiv, multe aplicaţii Windows ignoră mesajul SB_THUMBTRACK şi intră în acţiune numai la
primirea mesajelor SB_THUMBPOSITION, care indică oprirea casetei de derulare.
Totuşi, dacă puteţi să actualizaţi rapid conţinutul ferestrei, puteţi să prelucraţi şi mesajele SB_THUMBTRACK, dar
trebuie să ţineţi seama de faptul că utilizatorii care vor descoperi că fereastra se actualizează imediat la deplasarea casetei
de derulare vor încerca să o mute cât se poate de repede, ca să vadă dacă programul poate ţine pasul. Trebuie să ştiţi că
satisfacţia le va fi foarte mare dacă programul nu va reuşi să se descurce.
pag 12 din 18
Programul SYSMETS cu posibilităţi de derulare
Destul cu explicaţiile. Este timpul să punem în practică ce am aflat. Să începem cu ceva simplu. Mai întâi, vom include
în program o bară de derulare verticală, deoarece avem nevoie de ea. Derularea orizontală poate să mai aştepte.
Programul SYSMETS2 este prezentat în Figura 3-10.
Funcţia CreateWindow adaugă o bară de derulare la fereastră, datorită includerii stilului de fereastră WS_VSCROLL la
apelare:,
MS OVERLAPPEDWINDOW | WS VSCROLL
#------------------------
# SYSMETS2.MAK make file
#------------------------

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

sysmets2.obj : sysmets2.c sysmets.h


$(CC) $(CFLAGS) sysmets2.c

/*----------------------------------------------------
SYSMETS2.C -- System Metrics Display Program No. 2
(c) Charles Petzold, 1996
----------------------------------------------------*/

#include <windows.h>
#include <string.h>
#include "sysmets.h"

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,


PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "SysMets2" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;

wndclass.cbSize = sizeof (wndclass) ;


wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

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


WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;


UpdateWindow (hwnd) ;

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


{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos ;
char szBuffer[10] ;
HDC hdc ;
int i, y ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;

switch (iMsg)
{
case WM_CREATE :
hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;


cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;

ReleaseDC (hwnd, hdc) ;

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

pag 13 din 18
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
return 0 ;

case WM_SIZE :
cyClient = HIWORD (lParam) ;
return 0 ;

case WM_VSCROLL :
switch (LOWORD (wParam))
{
case SB_LINEUP :
iVscrollPos -= 1 ;
break ;

case SB_LINEDOWN :
iVscrollPos += 1 ;
break ;

case SB_PAGEUP :
iVscrollPos -= cyClient / cyChar ;
break ;

case SB_PAGEDOWN :
iVscrollPos += cyClient / cyChar ;
break ;

case SB_THUMBPOSITION :
iVscrollPos = HIWORD (wParam) ;
break ;

default :
break ;
}
iVscrollPos = max (0, min (iVscrollPos, NUMLINES)) ;

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


{
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
}
return 0 ;

case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;

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


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

TextOut (hdc, cxChar, y,


sysmetrics[i].szLabel,
strlen (sysmetrics[i].szLabel)) ;

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


sysmetrics[i].szDesc,
strlen (sysmetrics[i].szDesc)) ;

SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

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


szBuffer,
wsprintf (szBuffer, "%5d",
GetSystemMetrics (sysmetrics[i].iIndex))) ;

SetTextAlign (hdc, TA_LEFT | TA_TOP) ;


}

EndPaint (hwnd, &ps) ;


return 0 ;

case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}

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


}
Figura 3-10. Programul SYSMETS2.
Procedura de fereastră WndProc are două linii suplimentare, care stabilesc domeniul şi poziţia casetei pe bara de
derulare verticală în timpul prelucrării mesajului WM_CREATE:
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ;
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
Structura sysmetrics are un număr NUMLINES de linii de text, aşa că domeniul barei de derulare este stabilit de la 0 la
acea valoare NUMLINES. Fiecare poziţie de pe bara de derulare corespunde unei linii de text afişate în partea de sus a
zonei client. În cazul în care caseta de derulare se află în poziţia 0, în partea superioară a ecranului este lăsată o margine de
dimensiunea unei linii. Pe măsură ce schimbaţi poziţia casetei, derulând în jos, textul ar trebui să se mute în sus. Atunci
când poziţia casetei de derulare are valoarea maximă, în partea de sus a ecranului este afişată ultima linie a structurii
sysmetrics.

pag 14 din 18
Pentru prelucrarea mesajelor WM_VSCROLL în procedura de fereastră WndProc este definită o variabilă statică
numită iVscrollPos. Această variabilă reprezintă poziţia curentă a casetei de derulare. Pentru mesajele SB_LINEUP şi
SB_LINEDOWN tot ce avem de făcut este să ajustăm poziţia, cu o unitate. Pentru mesajele SB_PAGEUP şi
SB_PAGEDOWN trebuie să deplasăm textul cu numărul de linii afişate pe ecran, deci cu valoarea cyClient împărţită la
cyChar. Pentru mesajul SB_THUMBPOSITION, noua poziţie este indicată în cuvântul mai semnificativ al parametrului
wParam. Mesajele SB_ENDSCROLL şi SB_THUMBTRACK sunt ignorate.
Valoarea iVscrollPos este apoi ajustată cu ajutorul macroinstrucţiunilor min şi max pentru încadrarea valorii între
valorile minimă şi maximă ale domeniului. Dacă poziţia casetei de derulare s-a modificat, valoarea este actualizată prin
apelarea funcţiei SetScrollPos şi întreaga fereastră este invalidată prin apelarea funcţiei InvalidateRect.
Apelul funcţiei InvalidateRect generează un mesaj WM_PAINT. În programul original SYSMETS1, în timpul
prelucrării mesajului WM_PAINT, poziţia pe axa y a fiecărei linii era calculată astfel:
cyChar * (1 + i)
În SYSMETS2, formula folosită este:
cyChar * (1 - iVscrollPos + i)
În ciclu este afişat tot un număr de linii NUMLINES, dar dacă iVscrollPos are valoarea 2 sau mai mare, afişarea
liniilor începe deasupra zonei client. Fiind în afara zonei client, aceste linii nu sunt afişate de Windows.
Am spus că vom începe cu ceva simplu. Codul de mai sus este ineficient şi consumă inutil resurse. Îl vom corecta în
curând, dar mai întâi vom discuta despre modul de actualizare a zonei client după primirea unui mesaj WM_VSCROLL.
Structurarea programului pentru desenare
Procedura de fereastră din programul SYSMETS2 nu redesenează fereastra după primirea unui mesaj de la bara de
derulare, ci apelează funcţia InvalidateRect pentru invalidarea zonei client. Această funcţie determină sistemul de operare
să plaseze un mesaj WM_PAINT în coada de aşteptare a programului.
Cel mai bine este să structuraţi programul astfel încât toate operaţiile de desenare în zona client să se facă în timpul
prelucrării mesajelor WM_PAINT. Deoarece programul trebuie să aibă posibilitatea de redesenare a întregii zone client în
momentul primirii unui mesaj WM_PAINT, probabil veţi duplica unele secvenţe de cod dacă veţi face tipărirea şi în alte
puncte din program.
La început s-ar putea să vă revoltaţi, deoarece acest mod de lucru este foarte diferit de modul normal de programare
pentru PC. Nu voi nega faptul că uneori este mai convenabil să desenaţi numai ca răspuns la alte mesaje decât
WM_PAINT. (Programul KEYLOCK din Capitolul 5 este un astfel de exemplu.) În majoritatea cazurilor, însă, o asemenea
abordare este inutilă şi după ce veţi stăpâni disciplina acumulării tuturor informaţiilor pentru tipărirea ca răspuns la
mesajele WM_PAINT, veţi fi mulţumit de rezultate. Totuşi, de multe ori, programul va stabili că este necesară redesenarea
unor anumite zone din ecran în timpul prelucrării altor mesaje decât WM_PAINT. în astfel de situaţii este utilă funcţia
InvalidateRect. Puteţi să o folosiţi ca să invalidaţi anumite porţiuni sau întreaga zonă client.
În unele aplicaţii s-ar putea să nu fie suficientă simpla transmitere a unor mesaje WM_PAINT prin marcarea unor
porţiuni din fereastră ca invalide. După ce apelaţi funcţia InvalidateRect, Windows plasează în coada de aşteptare un mesaj
WM_PAINT, iar procedura de fereastră va prelucra la un moment dat acest mesaj. Totuşi, Windows tratează mesajele
WM_PAINT ca mesaje de prioritate scăzută şi, dacă în sistem activitatea este intensă, poate să treacă un timp destul de
lung pană la prelucrarea acestora. Toată lumea a văzut „găuri" albe în ferestre după închiderea unor casete de dialog.
Dacă doriţi ca actualizarea porţiunii invalide să se facă imediat, după funcţia InvalidateRect apelaţi imediat funcţia
UpdateWindow:
UpdateWindow (hwnd) ;
Funcţia UpdateWindow determină sistemul de operare să apeleze procedura de fereastră cu un mesaj WM_PAINT
dacă vreo porţiune a zonei client este invalidă. (Dacă întreaga zonă client este validă, procedura de fereastră nu mai este
apelată.) Acest mesaj WM_PAINT sare peste coada de aşteptare, procedura de fereastră fiind apelată direct de Windows.
După ce procedura de fereastră încheie operaţia de redesenare, sistemul de operare cedează din nou controlul programului
şi execuţia continuă de la instrucţiunea de după apelul funcţiei UpdateWindow.
Veţi observa că funcţia UpdateWindow este folosită şi de WinMain pentru generarea primului mesaj WM_PAINT.
Atunci când este creată o fereastră, întreaga zonă client a acesteia este invalidă. Funcţia UpdateWindow cere procedurii de
fereastră să deseneze toată zona client.
Construirea unui program cu posibilităţi mai bune de derulare
Deoarece SYSMETS2 nu este un model destul de eficient pentru a fi imitat de alte programe, îl vom face mai bun.
SYSMETS3 - versiunea finală a programului SYSMETS din acest capitol, este prezentat în Figura 3-11. În această
versiune a fost adăugată o bară de derulare orizontală pentru derularea de la stânga la dreapta şi a fost îmbunătăţit codul de
redesenare a zonei client.
#------------------------
# SYSMETS3.MAK make file
#------------------------

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

sysmets3.obj : sysmets3.c sysmets.h


$(CC) $(CFLAGS) sysmets3.c
/*----------------------------------------------------
SYSMETS3.C -- System Metrics Display Program No. 3
(c) Charles Petzold, 1996
----------------------------------------------------*/

#include <windows.h>
#include "sysmets.h"

pag 15 din 18
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 ;

pag 16 din 18
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",

pag 17 din 18
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}

EndPaint (hwnd, &ps) ;


return 0 ;

case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
Figura 3-11. Programul SYSMETS3.
Iată ce îmbunătăţiri au fost făcute în programul SYSMETS3 şi cum au fost implementate acestea:
 Nu mai puteţi să derulaţi ecranul pană când ultima linie de text este afişată în partea de sus a zonei client - puteţi să
derulaţi numai până când devine vizibilă ultima linie. Spre a realiza acest lucru, este nevoie ca programul să calculeze
un nou domeniu pentru bara de derulare (şi probabil şi o nouă poziţie a casetei de derulare) în timpul prelucrării
mesajului WM_SIZE. Domeniul barei de derulare este calculat în funcţie de numărul de linii de text, de lăţimea
textului şi de dimensiunea zonei client. Ca rezultat se obţine un domeniu mai mic - numai atât cât este nevoie pentru
afişarea textului care iese în afara zonei client.
Această abordare prezintă şi un alt avantaj interesant. Să presupunem că zona client a ferestrei este suficient de
mare pentru afişarea întregului text, inclusiv a marginilor de sus şi de jos. În acest caz, poziţiile minimă şi maximă ale
barei de derulare vor avea valoarea zero. Cum va folosi Windows această informaţie? Va elimina bara de defilare din
fereastră! Aceasta nu mai este necesară. La fel, dacă zona client este suficient de mare ca să afişeze toate cele 60 de
coloane de text, nici bara de derulare orizontală nu mai este afişată.
 Mesajele WM_VSCROLL şi WM_HSCROLL sunt prelucrate prin calcularea unei valori de incrementare a poziţiei
casetei de derulare pentru fiecare acţiune efectuată asupra barei de derulare. Această valoare este apoi folosită pentru
derularea conţinutului ferestrei, folosind funcţia Windows ScrollWindow. Această funcţie are următorul format:
ScrollWindow (hwnd, xInc, yInc, pRect, pClipRect) ;
Valorile xlnc şi ylnc specifică numărul de pixeli cu care se face derularea. În programul SYSMETS3, parametrii pRect
şi pClipRect au valoarea NULL, specificând faptul că trebuie derulată întreaga zonă client. Windows invalidează
dreptunghiul din zona client „descoperit" de operaţia de derulare. Aceasta generează un mesaj WM_PAINT. Nu este
necesară apelarea funcţiei InvalidateRect. (Reţineţi că funcţia ScrollWindow nu este o procedură GDI, deoarece nu are
nevoie de o variabilă handle. ScrollWindow este una dintre puţinele funcţii non-GDI din Windows care modifică
aspectul zonei client a ferestrei.)
 În timpul prelucrării mesajului WM_PAINT programul determină care sunt liniile cuprinse în dreptunghiul invalid şi
rescrie numai liniile respective. Acest lucru se face prin analizarea coordonatelor de sus şi de jos ale dreptunghiului
invalid, stocate în structura PAINTSTRUCT. Programul redesenează numai liniile invalide. Codul este mai complex în
acest caz, dar mai rapid.
 Deoarece prelucrarea mesajului WM_PAINT este mai rapidă, am decis să las programul SYSMETS3 să prelucreze şi
operaţiile SB_THUMBTRACK din mesajele WM_VSCROLL. Versiunile anterioare ale programului SYSMETS
ignorau mesajele SB_THUMBTRACK (trimise atunci când utilizatorul trage cu mouse-ul caseta de derulare) şi
acţionau numai la mesajele SB_THUMBPOSITION (trimise atunci când utilizatorul eliberează caseta de derulare). De
asemenea, mesajele WM_VSCROLL apelează funcţia UpdateWindow, care actualizează imediat zona client. Atunci
când utilizatorul trage cu mouse-ul caseta de pe bara de derulare verticală, SYSMETS3 derulează continuu ecranul şi
actualizează zona client. Vă las pe dumneavoastră să decideţi dacă viteza de lucru a programului (şi a sistemului de
operare) este destul de mare pentru a justifica această modificare.
Dar mie nu îmi place să folosesc mouse-ul
La început, utilizatorii sistemului de operare Windows nu se oboseau să folosească mouse-ul. Într-adevăr, în cazul
sistemului Windows (ca şi în cazul unor aplicaţii scrise pentru Windows) nu este absolut necesar să aveţi un mouse. Deşi
calculatoarele personale fără mouse au dispărut pe acelaşi drum cu monitoarele de tip monocrom şi cu alte dispozitive şi
componente, uneori este recomandat să dublaţi funcţiile mouse-ului cu tastatura. Acest lucru este valabil în special pentru
operaţii simple, cum ar fi derularea ecranului, deoarece pe tastatura există o mulţime de taste de deplasare, care ar trebui să
permită aceleaşi operaţii.
În Capitolul 5 veţi învăţa cum să folosiţi tastatura şi cum să adăugaţi o interfaţă cu tastatura la acest program. Veţi
observa că programul SYSMETS3 prelucrează mesajele WM_VSCROLL atunci când cuvântul mai puţin semnificativ al
parametrului wParam are valoarea SB_TOP sau SB_BOTTOM. Am menţionat mai devreme că o procedură de fereastră nu
primeşte aceste mesaje de la barele de derulare, aşa că pentru moment acest cod pare inutil. Atunci când vom ajunge la
acelaşi program în Capitolul 5, veţi vedea de ce am inclus aceste operaţii.

pag 18 din 18

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