Sunteți pe pagina 1din 22

Capitolul 5.

Tastatura

Ca orice program interactiv rulat pe un calculator personal, aplicaţiile Windows 95 se bazează foarte mult pe
tastatură pentru intrările utilizatorului. Deşi Windows acceptă şi mouse-ul ca dispozitiv de intrare, tastatura deţine
încă poziţia 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 funcţiilor programului
de la tastatură. (Desigur, în unele situaţii - este cazul utilizării unui program de desenare sau a unui program de editare
a publicaţiilor (DTP), tastatura se dovedeşte a fi ineficientă, motiv pentru care mouse-ul este indispensabil).
Tastatura nu poate fi tratată ca un dispozitiv de intrare independent de celelalte funcţii ale programului. De
exemplu, programele răspund deseori la intrările de la tastatură afişând caracterele introduse în zona client a ferestrei.
Astfel, manipularea intrărilor de la tastatură şi afişarea textului trebuie să fie tratate împreună. Dacă adaptarea
programului pentru limbi sau pieţe străine este importantă pentru dumneavoastră, atunci va trebui să ştiţi câteva
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 octeţi (double-byte character set - DBCS) şi despre suportul asigurat de Windows NT
pentru codificarea pe 16 biţi a caracterelor de la tastatură (cunoscută sub numele de Unicode).
Tastatura – elemente de bază
Aşa cum probabil vă daţi seama, arhitectura bazată pe mesaje a sistemului de operare Windows este ideală pentru
lucrul cu tastatura. Programul „află" despre apăsarea unor taste prin intermediul mesajelor care ajung la procedura de
fereastră.
De fapt, lucrurile sunt puţin mai complicate: atunci când utilizatorul apasă şi eliberează tastele, driverul de
tastatură transmite sistemului de operare informaţiile legate de acţiunile asupra tastelor. Windows salvează aceste
acţiuni (sub formă de mesaje) în coada de aşteptare a sistemului. Mesajele de la tastatură sunt apoi transferate, unul
câte unul, în coada de mesaje a programului căruia îi aparţine fereastra ce deţine „cursorul de intrare" (despre care
vom discuta imediat). Programul distribuie mesajele procedurii de fereastră corespunzătoare.
Motivul acestui proces în două etape - stocarea mesajelor mai întâi în coada de mesaje a sistemului şi apoi
transferarea acestora în coada de mesaje a aplicaţiilor - este legat de sincronizare. Atunci când utilizatorul apasă pe
taste într-un ritm mai rapid decât cel în care programul prelucrează mesajele primite, Windows stochează acţiunile
neprelucrate în coada de aşteptare a sistemului, deoarece una dintre acţiunile asupra tastaturii poate avea ca efect
comutarea cursorului de intrare (input focus) către un alt program. În consecinţă, următoarele taste trebuie transmise
celui de-al doilea program.
Windows trimite programelor opt tipuri de mesaje prin care indică diferite evenimente de tastatură. S-ar putea să
pară numeroase, dar programul poate să ignore multe dintre acestea, fără probleme. De asemenea, în majoritatea
cazurilor, aceste mesaje conţin mai multe informaţii despre tastatură decât aveţi nevoie în program. O parte a sarcinii
de manipulare a tastaturii constă în a şti ce mesaje sunt importante.
Ignorarea tastaturii
Deşi tastatura este principala sursă de intrări de la utilizatori a unui program pentru Windows, programele nu
trebuie să reacţioneze la toate mesajele pe care le primeşte de la tastatură. Multe funcţii ale tastaturii sunt tratate chiar
de Windows. De exemplu, puteţi să ignoraţi apăsările de taste legate de funcţii sistem. Acestea sunt, în general,
combinaţii cu tasta Alt.
Programul nu este obligat să monitorizeze aceste taste, deoarece Windows îi comunică efectul acestora. (Totuşi,
dacă este nevoie, programul poate face şi acest lucru.) De exemplu, dacă utilizatorul selectează un articol dintr-un
meniu cu ajutorul tastaturii, Windows trimite programului un mesaj prin care îi comunică articolul de meniu selectat,
indiferent dacă utilizatorul a folosit mouse-ul sau tastatura.
Unele programe pentru Windows folosesc „acceleratori" (sau „taste de accelerare") pentru opţiunile de meniu
folosite mai frecvent. Acceleratorii sunt, de obicei, combinaţii de taste funcţionale - sau alte taste corespunzătoare
unor caractere - cu tasta Ctrl. Tastele de accelerare sunt definite în fişierul de resurse al programului.
Casetele de dialog au şi ele o interfaţă cu tastatura, dar programele, în general, nu trebuie să monitorizeze
tastatura cât timp este activă o casetă de dialog. Interfaţa cu tastatura este manipulată de Windows şi acesta trimite
programului mesaje prin care îi comunică efectele tastelor apăsate. Casetele de dialog pot conţine controale de editare
pentru introducerea textului. Acestea sunt mici casete, în care utilizatorul poate să scrie un şir de caractere. Windows
asigură logica de funcţionare a controlului de editare şi furnizează programului conţinutul final al controlului, după ce
utilizatorul a terminat. Chiar şi în fereastra principală puteţi să definiţi ferestre descendent, care să funcţioneze ca şi
controalele de editare.
Cursorul, cursorul, cine a luat cursorul de intrare?
Tastatura trebuie să fie partajată de toate aplicaţiile rulate simultan sub Windows. Unele aplicaţii pot avea mai
multe ferestre, iar tastatura trebuie să fie partajată de toate ferestrele din cadrul aceleiaşi aplicaţii. Atunci când este

1
apăsată o tastă, o singură fereastră trebuie să primească mesajul privind apăsarea tastei respective. Fereastra care
primeşte acest mesaj este fereastra care deţine „cursorul de intrare" („input focus").
Conceptul cursorului de intrare este strâns legat de conceptul de „fereastră activă". Fereastra care deţine cursorul
de intrare este fie fereastra activă, fie o fereastră descendent a ferestrei active. De obicei, fereastra activă este uşor de
identificat. Dacă fereastra activă are o bară de titlu, Windows evidenţiază bara de titlu a acesteia. Dacă fereastra activă
are un cadru de dialog (o formă des întâlnită în casetele de dialog) în locul unei bare de titlu, Windows evidenţiază
acest cadru. Dacă fereastra activă a fost redusă la o pictogramă (minimizată), Windows evidenţiază textul afişat sub
pictogramă.
Ferestrele descendent mai des întâlnite sunt controale, precum butoanele de apăsare, butoanele radio, casetele de
validare, barele de derulare, casetele de editare şi casetele listă, care, de obicei, apar în casete de dialog. Ferestrele
descendent nu sunt niciodată ferestre active. Dacă o fereastră descendent deţine cursorul de intrare, atunci fereastra
activă este fereastra părinte. Controalele de tip fereastră descendent indică faptul că deţin cursorul de intrare prin
afişarea unui cursor care clipeşte sau a unui cursor de editare.
Dacă fereastra activă a fost redusă la o pictogramă, atunci nici o fereastră nu deţine cursorul de intrare. Windows
continuă să trimită programului mesaje de la tastatură, dar acestea sunt trimise într-o altă formă decât mesajele trimise
unei ferestre active normale.
O procedură de fereastră poate să afle când are cursorul de intrare prin interceptarea mesajelor WM_SETFOCUS
şi WM_KILLFOCUS. Mesajul WM_SETFOCUS indică faptul că fereastra primeşte cursorul de intrare (input focus),
iar mesajul WM_KILLFOCUS indică faptul că fereastra pierde cursorul de intrare.
Acţionări de taste şi caractere
Mesajele privind tastatura pe care un program le recepţionează de la sistemul de operare fac diferenţa între
„acţionările de taste" („keystrokes") şi „caractere". Aceste noţiuni sunt legate de cele două moduri în care puteţi să
priviţi tastatura. În primul rând, tastatura este o colecţie de taste. Tastatura are o singură tastă A; apăsarea tastei A este
o acţionare de tastă, iar eliberarea tastei A este tot o acţionare de tastă. Tastatura este, însă, în acelaşi timp, şi un
dispozitiv de intrare care generează caractere afişabile. Tasta A poate să genereze mai multe caractere, în funcţie de
starea tastelor Ctrl, Shift şi Caps Lock. În mod normal, caracterul generat este a. Dacă tasta Shift este apăsată, sau
tasta Caps Lock este activă, caracterul generat este A. Dacă tasta Ctrl este apăsată, caracterul generat este Ctrl+A.
Dacă se foloseşte un driver de tastatură pentru o limbă străină, apăsarea tastei A poate să fie precedată de un „caracter
mort" („dead-character key") sau de tastele Shift, Ctrl sau Alt în diferite combinaţii. Combinaţiile pot să genereze un
caracter a sau A cu un accent.
Pentru acţionările de taste care generează caractere afişabile, Windows trimite programului atât mesaje pentru
acţionarea de taste, cât şi mesaje pentru caractere. Unele taste nu generează caractere. Astfel de taste sunt Shift,
tastele funcţionale, tastele de deplasare şi tastele speciale, precum Insert şi Delete. În cazul acestor taste, Windows
generează numai mesaje pentru acţionari de taste.
Mesaje pentru acţionări de taste
Atunci când apăsaţi o tastă, Windows inserează în coada de aşteptare a ferestrei care deţine cursorul de intrare un
mesaj WM_KEYDOWN sau un mesaj WM_SYSKEYDOWN. Atunci când eliberaţi fasta, Windows inserează în
coada de aşteptare a ferestrei un mesaj WM_KEYUP sau un mesaj WM_SYSKEYUP.

Tasta a fost apăsată Tasta a fost eliberată


Tastă obişnuită WM_KEYDOWN WM_KEYUP
Tastă de sistem WM_SYSKEYDOWN WM_SYSKEYUP

De obicei, mesajele de apăsare şi de eliberare a tastei sunt trimise în pereche. Totuşi, dacă ţineţi apăsată o tastă
pană când aceasta se autorepetă, Windows trimite procedurii de fereastră o serie de mesaje WM_KEYDOWN (sau
WM_SYSKEYDOWN) şi un singur mesaj WM_KEYUP sau (WM_SYSKEYUP) după eliberarea tastei. Ca toate
mesajele trimise prin coada de aşteptare, mesajele pentru acţionări de taste conţin informaţii de timp. Puteţi să obţineţi
momentul relativ în care a fost apăsată sau eliberată o tastă apelând funcţia GetMessageTime.
Taste obişnuite şi taste de sistem
Particula „SYS" din mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP provin de la cuvântul „system" şi se
referă la acţionările de taste care prezintă mai multă importanţă pentru Windows decât pentru aplicaţiile Windows.
Mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP sunt generate, de obicei, pentru taste apăsate în combinaţie cu
tasta Alt. Aceste acţionări de taste apelează opţiuni din meniul programului ori din meniul sistem, sunt folosite pentru
funcţii ale sistemului, cum ar fi comutarea ferestrei active (Alt+Tab sau Alt+Esc) sau sunt folosite pentru acceleratori
de meniu (Alt în combinaţie cu o tastă funcţională). De obicei, programul ignoră mesajele WM_SYSKEYDOWN şi
WM_SYSKEYUP şi le retransmite funcţiei DefWindowProc. Deoarece Windows manipulează combinaţiile
Alt+tastă, nu este nevoie să interceptaţi aceste mesaje. Procedura de fereastră va primi alte mesaje, legate de rezultatul

2
acestor acţionări de taste (cum ar fi selectarea unei opţiuni de meniu). Chiar dacă doriţi să includeţi în program codul
pentru interceptarea acestor mesaje (aşa cum vom face în programul KEYLOCK din acest capitol), retransmiteţi
mesajele funcţiei DefWindowProc după ce le prelucraţi, astfel încât Windows să le poată folosi în scopurile obişnuite.
Dar să ne gândim puţin la aceste lucruri. Aproape toate mesajele care afectează fereastra programului trec mai
întâi prin procedura de fereastră. Windows prelucrează aceste mesaje numai dacă le retransmiteţi funcţiei
DefWindowProc. De exemplu, dacă în procedura de fereastră adăugaţi următoarele linii:
case WM_SYSKEYDOWN :
case WM_SYSKEYUP :
case WM_SYSCHAR :
return 0 ;
dezactivaţi toate operaţiile legate de combinaţiile Alt+tastă (comenzi de meniu, Alt+Tab, Alt+Esc şi aşa mai departe)
atunci când programul deţine cursorul de intrare.
Mesajele WM_KEYDOWN şi WM_KEYUP sunt generate, de obicei, pentru tastele apăsate şi eliberate fără tasta
Alt. Programul poate să folosească sau să ignore aceste mesaje. Sistemul de operare le ignoră.
Variabila lParam
Pentru toate mesajele legate de acţionările de taste variabila lParam (pe 32 de biţi) transmisă procedurii de
fereastră este împărţită în şase câmpuri: contorul de repetare, codul de scanare OEM, indicatorul flag pentru taste
extinse, codul de context, starea anterioară a tastei şi starea de tranziţie (vezi Figura 5-1).
Indicator flag pentru taste extinse

Figura 5-1. Cele sase câmpuri de biţi ale variabilei IParam din mesajele pentru acţionari de taste.
Contorul de repetare
Contorul de repetare (Repeat Count) specifică numărul de acţionari de taste reprezentat de un mesaj. În
majoritatea cazurilor, contorul de repetare are valoarea 1. Totuşi, dacă procedura de fereastră nu reuşeşte să
prelucreze mesajele de apăsare a unei taste în ritmul de autorepetare (în mod prestabilit aproximativ 10 caractere pe
secundă), Windows combină mai multe mesaje WM_KEYDOWN sau WM_SYSKEYDOWN într-un singur mesaj şi
incrementează corespunzător contorul de repetare. Contorul de repetare are întotdeauna valoarea 1 pentru mesajele
WM_KEYUP şi WM_SYSKEYUP.
Deoarece o valoare mai mare decât 1 a contorului de repetare indică faptul că rata de autorepetare a tastelor
depăşeşte posibilităţile de prelucrare ale programului, există situaţii în care puteţi să ignoraţi contorul de repetare
atunci când prelucraţi mesajele de la tastatură. Aproape toată lumea a avut ocazia să vadă cum un procesor de texte
sau un program de calcul tabelar derulează ecranul mai mult decât trebuie datorită comenzilor suplimentare stocate în
bufferul de tastatură. Ignorarea contorului de repetare va reduce foarte mult posibilitatea apariţiei unei asemenea
situaţii. În alte cazuri însă este bine să folosiţi contorul de repetare. Puteţi să încercaţi ambele variante şi să vedeţi pe
care o preferaţi pentru programul dumneavoastră.
Codul de scanare OEM
Codul de scanare OEM (OEM Scan Code) este codul de scanare al tastaturii, generat de componentele hardware.
(Dacă aţi scris programe în limbaj de asamblare, acest cod este identic cu cel transmis în registrul AH, în timpul
întreruperii apelului BIOS 16H.) În general, aplicaţiile Windows ignoră acest cod, deoarece există metode mai bune
de decodificare a informaţiilor de la tastatură.
Indicatorul flag pentru taste extinse
Indicatorul flag pentru taste extinse (Extended Key Flag) are valoarea 1 dacă mesajul este generat de una dintre
tastele suplimentare de pe tastatura IBM extinsă. (Tastatura IBM extinsă are tastele funcţionale în partea de sus şi un
bloc separat sau combinat de taste pentru tastele de deplasare şi tastele numerice.) Acest indicator are valoarea 1
pentru tastele Alt şi Ctrl din partea dreaptă a tastaturii, pentru tastele de deplasare (inclusiv tastele Insert şi Delete)
care nu fac parte din blocul de taste numerice, pentru tastele Slash (/) şi Enter din blocul de taste numerice şi pentru
tasta Num Lock. În general, programele Windows ignoră acest indicator.

3
Codul de context
Codul de context (Context Code) are valoarea 1 dacă este apăsată tasta Alt. Acest bit va avea întotdeauna
valoarea 1 pentru mesajele WM_SYSKEYUP şi WM_SYSKEYDOWN şi valoarea 0 pentru mesajele WM_KEYUP
si WM_KEYDOWN, cu două excepţii:
 O fereastră activă redusă la o pictogramă nu deţine cursorul de intrare. Toate acţionările de taste generează
mesaje WM_SYSKEYUP şi WM_SYSKEYDOWN. Dacă tasta Alt nu este apăsată, bitul pentru codul de
context are valoarea 0. (Windows foloseşte aceste mesaje astfel încât fereastra activă redusă la o pictogramă să
nu prelucreze mesajele de la tastatură.)
 În cazul folosirii unui driver de tastatură pentru alte limbi decât limba engleză, unele caractere sunt generate
prin combinarea tastelor Shift, Ctrl sau Alt cu alte taste. În această situaţie, bitul pentru codul de context din
variabila lParam care însoţeşte mesajele WM_KEYUP şi WM_KEYDOWN are valoarea 1, dar mesajele nu
reprezintă acţionări de taste de sistem.
Starea anterioară a tastei
Starea anterioară a tastei (Previous Key State) are valoarea 0 dacă tasta nu a fost anterior apăsată, şi valoarea 1
dacă tasta a fost apăsată. Are întotdeauna valoarea 1 pentru mesajele WM_KEYUP şi WM_SYSKEYUP, dar poate fi
0 sau 1 pentru mesajele WM_KEYDOWN şi WM_SYSKEYDOWN. Valoarea 1 indică faptul că s-a primit un mesaj
generat de autorepetarea unei taste.
Starea de tranziţie
Starea de tranziţie (Transition State) are valoarea 0 dacă tasta este apăsată şi valoarea 1 dacă tasta este eliberată. Acest
bit are valoarea 1 pentru mesajele WM_KEYUP si WM_SYSKEYUP si valoarea 0 pentru mesajele
WM_KEYDOWN şi WM_SYSKEYDOWN.
Coduri virtuale de taste
Deşi unele informaţii din parametrul lParam pot fi utile pentru prelucrarea mesajelor WM_KEYUP,
WM_SYSKEYUP, WM_KEYDOWN şi WM_SYSKEYDOWN, parametrul wParam este mult mai important. Acest
parametru conţine codul virtual care identifică tasta apăsată sau eliberată. Dezvoltatorii sistemului de operare
Windows au încercat să definească tastele virtuale într-o manieră independentă de dispozitiv. Din acest motiv, unele
coduri virtuale de taste nu pot fi generate pe calculatoarele IBM PC şi compatibile cu acestea, dar pot fi întâlnite la
tastaturile aparţinând altor producători.
Codurile virtuale pe care le veţi folosi cel mai des au nume definite în fişierele antet din Windows. Tabelul de
mai jos prezintă aceste nume, împreună cu codurile numerice şi tastele de pe tastatura IBM care corespund tastelor
virtuale. Deşi toate tastele generează mesaje de acţionare (keystroke messages), tabelul nu include tastele pentru
simboluri (cum ar fi / şi ?). Acestor taste le corespund coduri virtuale de la 128 în sus şi de cele mai multe ori sunt
definite diferit pentru tastaturile internaţionale. Puteţi să determinaţi aceste coduri virtuale folosind programul
KEYLOOK, prezentat în acest capitol, dar în general nu este necesar să prelucraţi mesajele de acţionare ale acestor
taste.
Coduri virtuale de taste
Identificator Necesar Tastatură IBM
Zecimal Hexa WINDOWS.H
1 01 VK_LBUTTON
2 02 VK_RBUTTON
3 03 VK_CANCEL  Ctrl-Break
4 04 VK_MBUTTON
8 08 VK_BACK  Backspace
9 09 VK_TAB  Tab
12 0C VK_CLEAR  Tasta numerică 5 cu tasta
Num Lock inactivă
13 0D VK_RETURN  Enter
16 10 VK_SHIFT  Shift
17 11 VK_CONTROL  Ctrl
18 12 VK_MENU  Alt

4
(continuare)
Identificator
Zecimal Hexa WINDOWS.H Necesar Tastatura IBM
19 13 VK_PAUSE Pause
20 14 VK_CAPITAL  Caps Lock
27 1B VK_ESCAPE  Esc
32 20 VK_SPACE  Bara de spaţiu
33 21 VK_PRIOR  Page Up
34 22 VK_NEXT  Page Down
35 23 VK_END End
36 24 VK_HOME  Home
37 25 VK_LEFT  Săgeată stânga
38 26 VK_UP  Săgeată în sus
39 27 VK_RIGHT  Săgeată în dreapta
40 28 VK_DOWN  Săgeată în jos
41 29 VK_SELECT
42 2A VK_PRINT
43 2B VK_EXECUTE
44 2C VK_SNAPSHOT Print Screen
45 2D VK_INSERT  Insert
46 2E VK_DELETE  Delete
47 2F VK_HELP
48-57 30-39  De la 0 la 9 pe blocul principal de taste
65-90 41-5A  De la A la Z
96 60 VK_NUMPAD0 Tasta numerică 0 cu tasta Num Lock activă
97 61 VK_NUMPAD1 Tasta numerică 1 cu tasta Num Lock activă
98 62 VK_NUMPAD2 Tasta numerică 2 cu tasta Num Lock activă
99 63 VK_NUMPAD3 Tasta numerică 3 cu tasta Num Lock activă
100 64 VK_NUMPAD4 Tasta numerică 4 cu tasta Num Lock activă
101 65 VK_NUMPAD5 Tasta numerică 5 cu tasta Num Lock activă
102 66 VK_NUMPAD6 Tasta numerică 6 cu tasta Num Lock activă
103 67 VK_NUMPAD7 Tasta numerică 7 cu tasta Num Lock activă
104 68 VK_NUMPAD8 Tasta numerică 8 cu tasta Num Lock activă
105 69 VK_NUMPAD9 Tasta numerică 9 cu tasta Num Lock activă
106 6A VK_MULTIPLY Tasta numerică *
107 6B VK_ADD Tasta numerică +
108 6C VK_SEPARATOR
109 6D VK_SUBTRACT Tasta numerică -
110 6E VK_DECIMAL Tasta numerică .
111 6F VK_DIVIDE Tasta numerică /
112 70 VK_F1  Tasta funcţională F1
113 71 VK_F2  Tasta funcţională F2
114 72 VK_F3  Tasta funcţională F3
115 73 VK_P4  Tasta funcţională F4
116 74 VK_F5  Tasta funcţională F5
117 75 VK_F6  Tasta funcţională F6
118 76 VK_F7  Tasta funcţionată F7
119 77 VK_F8  Tasta funcţională F8
120 78 VK_F9  Tasta funcţională F9
121 79 VK_F10  Tasta funcţională F10
122 7A VK_F11 Tasta funcţională F11 (tastatura extinsă)
123 7B VK_F12 . Tasta funcţională F12 (tastatura extinsă)
124 7C VK_F13
125 7D VK_F14
126 7E VK_F15
127 7F VK_F16
144 90 VK_NUMLOCK Num Lock
145 91 VK_SCROLL Scroll Lock

5
Semnul de validare din coloana „Necesar" indică faptul că tasta respectivă este obligatorie pentru orice
implementare Windows. De asemenea, sistemul de operare cere ca tastatura şi driverul de tastatură să permită
combinarea tastelor Ctrl, Shift sau a ambelor cu orice literă, orice tastă de deplasare şi orice tastă funcţională.
VK_LBUTTON, VK_MBUTTON şi VK_RBUTTON sunt codurile virtuale corespunzătoare buloanelor din stânga,
din mijloc şi din dreapta ale mouse-ului. Cu toate acestea, nu veţi primi niciodată mesaje pentru care parametrul
wParam să conţină aceste valori. Mouse-ul generează mesaje proprii, aşa cum vom vedea în capitolul următor.
Starea tastelor de modificare
Parametrii lParam şi wParam care însoţesc mesajele WM_KEYUP, WM_SYSKEYUP, WM_KEYDOWN şi
WM_SYSKEYDOWN nu spun nimic programului despre starea tastelor de modificare. Puteţi să obţineţi starea
oricărei taste virtuale folosind funcţia GetKeyState. Această funcţie este folosită, de obicei, pentru obţinerea stării
tastelor de modificare (Shift, Alt şi Ctrl) şi a tastelor de comutare (Caps Lock, Num Lock şi Scroll Lock). De
exemplu:
GetKeyState (VK_SHIFT) ;
returnează o valoare negativă (adică un număr în care primul bit are valoarea 1) dacă tasta Shift este apăsată. Valoarea
returnată de apelul:
GetKeyState (VK_CAPITAL) ;
are un 1 în bitul cel mai puţin semnificativ dacă tasta Caps Lock este activă. De asemenea, puteţi să obţineţi starea
butoanelor mouse-ului folosind codurile virtuale VK_LBUTTON, VK_MBUTTON şi VK_RBUTTON. Totuşi,
majoritatea programelor pentru Windows care trebuie să monitorizeze combinaţiile între taste şi butoanele mouse-ului
utilizează calea inversă - verificând starea tastelor atunci când primesc un mesaj de la mouse. De fapt, starea tastelor
de modificare este inclusă în mesajele primite de la mouse (aşa cum veţi vedea în capitolul următor).
Aveţi grijă cum folosiţi funcţia GetKeyState. Aceasta nu verifică starea tastaturii în timp real. GetKeyState
returnează starea tastaturii până în momentul mesajului curent. Funcţia GetKeyState nu vă permite să obţineţi
informaţii despre starea tastaturii independent de mesajele obişnuite. De exemplu, să presupunem că vreţi să
întrerupeţi prelucrarea în procedura de fereastră până când utilizatorul apasă tasta
while (GetKeyState (VK_F1) >= 0) ; // Greşit!
Nu faceţi acest lucru! Programul trebuie să preia mesajul de la tastatură din coada de aşteptare, înainte ca funcţia
GetKeyState să poată obţine starea tastelor. De fapt, această sincronizare este în folosul dumneavoastră, deoarece
puteţi să obţineţi starea corectă a tastelor de modificare în timpul generării unui anumit mesaj de la tastatură, chiar
dacă prelucraţi mesajul (şi apelaţi funcţia GetKeyState) după ce tasta de modificare respectivă a fost eliberată. Dacă
într-adevăr aveţi nevoie de starea curentă a unei taste, puteţi să folosiţi funcţia GetAsyncKeyState.
Utilizarea mesajelor de acţionare a tastelor
Ideea unui program care să obţină informaţii despre toate acţiunile exercitate asupra tastelor este drăguţă. Cu
toate acestea, majoritatea programelor Windows ignoră cea mai mare parte a acestor mesaje. Mesajele
WM_SYSKEYUP şi WM_SYSKEYDOWN sunt folosite pentru funcţiile sistemului, aşa că nu este nevoie să le
interceptaţi. De asemenea, dacă prelucraţi mesajele WM_KEYDOWN, de obicei puteţi să ignoraţi mesajele
WM_KEYUP.
În general, programele pentru Windows folosesc mesajele WM_KEYDOWN pentru tastele care nu generează
caractere. Deşi s-ar putea să vă vină ideea să folosiţi mesajele pentru acţionări de taste în combinaţie cu informaţiile
despre tastele de modificare (furnizate de funcţia GetKeyState) ca să transformaţi mesajele pentru acţionări de taste în
mesaje pentru caractere, nu faceţi acest lucru. Veţi avea probleme datorită diferenţelor între tastaturile internaţionale.
De exemplu, dacă primiţi un mesaj WM_KEYDOWN pentru care parametrul wParam are valoarea 33H, ştiţi că
utilizatorul a apăsat tasta 3. Până aici totul este bine. Dacă apelaţi funcţia GetKeyState veţi afla că tasta Shift este
apăsată şi s-ar putea să presupuneţi că utilizatorul a introdus un caracter diez (#), lucru care poate să nu fie adevărat.
Un utilizator britanic ar fi introdus caracterul £. Mesajele WM_KEYDOWN sunt utile pentru tastele de deplasare,
tastele funcţionale şi tastele speciale, precum Insert, Delete. Uneori tastele Insert, Delete si tastele funcţionale sunt
folosite ca acceleratori pentru comenzi de meniu. Deoarece Windows transformă acceleratorii în comenzi de meniu,
nu este nevoie să prelucraţi în program mesajele pentru acţionarea tastelor de accelerare. Unele programe non-
Windows folosesc foarte des tastele funcţionale în combinaţie cu tastele Alt, Ctrl şi Shift. Puteţi face acest lucru şi în
programele pentru Windows, dar nu este recomandat. Dacă vreţi să folosiţi tastele funcţionale, acestea trebuie să
dubleze comenzi din meniuri. Unul dintre obiectivele sistemului de operare Windows este să nu oblige utilizatorii să
memoreze sau să folosească liste complicate de comenzi.
Am reuşit să eliminăm aproape totul, cu excepţia unui singur lucru: de cele mai multe ori veţi prelucra mesajele
WM_KEYDOWN numai pentru tastele de deplasare a cursorului. Atunci când prelucraţi mesajele trimise de tastele
de deplasare puteţi să verificaţi şi starea tastelor Shift şi Ctrl, folosind funcţia GetKeyState. Unele funcţii Windows
folosesc de multe ori tastele de deplasare în combinaţie cu tasta Shift pentru extinderea unei selecţii - de exemplu într-
un procesor de texte. Tasta Ctrl este folosită deseori pentru a schimba semnificaţia unei taste de deplasare. De
exemplu, tasta Ctrl în combinaţie cu săgeata spre dreapta mută cursorul cu un cuvânt la dreapta.

6
Una dintre cele mai bune metode de a vă hotărî cum să folosiţi tastatura este să studiaţi modul de utilizare a
acesteia în programele Windows existente. Dacă nu vă place cum este folosită, puteţi să faceţi ceva diferit. Dar nu
uitaţi că orice noutate înseamnă creşterea perioadei de care are nevoie un utilizator pentru a se obişnui cu programul
dumneavoastră.
ÎMBUNĂTĂŢIREA PROGRAMULUI SYSMETS:
ADĂUGAREA INTERFEŢEI CU TASTATURA
Atunci când aţi scris cele trei versiuni ale programului SYSMETS prezentate în Capitolul 3 nu ştiaţi încă nimic
despre tastatură. Puteaţi să derulaţi textul numai folosind mouse-ul pe barele de derulare. Acum ştim să prelucrăm
mesajele de la tastatură, aşa că este timpul să adăugăm programului SYSMETS o interfaţă cu tastatură. Aceasta este,
evident, sarcina tastelor de deplasare. Cea mai mare parte a tastelor de deplasare (Home, End, Page Up, Page Down,
săgeţile în sus şi în jos) vor fi folosite pentru derularea pe verticală. Săgeţile spre dreapta şi spre stânga vor fi folosite
pentru derularea pe orizontală, facilitate care în acest program este mai puţin importantă.
Adăugarea logicii de tratare a mesajului WM_KEYDOWN
O metodă evidentă de creare a unei interfeţe este adăugarea codului necesar pentru tratarea mesajului
WM_KEYDOWN, dublând astfel logica de tratare a mesajelor WM_VSCROLL şi WM_HSCROLL:
case WM_KEYDOWN :
iVscrollInc = iHscrollInc = 0 ;
switch (wParam)
{
case VK_HOME: // la fel ca HM_VSCROLL, SB_TOP
iVscrollInc = -iVscrollPos ;
break ;
case VK_END : // la fel ca HM_VSCROLL, SB_BOTTOM:
iVscrollInc = iVscrollMax - iVscrollPos ;
break ;
case VK_UP : // la fel ca HM_VSCROLL, SB_LINEUP :
iVscrollInc = -1 ;
break ;
case VK_DOWN : // la fel ca WM_VSCROLL, SB_LINEDOWN
iVscrollInc = 1 ;
break ;
case VK_PRIOR : // la fel ca WM_VSCROLL, SB_PAGEUP
iVscrollInc = min (-1, -cyClient / cyChar) ;
break ;
case VK_NEXT : // la fel ca WH_VSCROLL, SB_PAGEDOWN :
iVscrollInc = max (1, cyClient / cyChar) ;
break ;
case VK_LEFT : // la fel ca WM_HSCROLL, SB_PAGEUP
iHscrollInc = -8 ;
break ;
case VK_RIGHT : // la fel ca WM_HSCROLL, SB_PAGEDOWN
iHscrollInc = 8 ;
break ;
default :
break ;
}
if (iVscrollInc = max (-iVscrollPos, min(iVscrollInc, iVscrollMax-iVscrollPos)))
{
iVscrollPos += iVscrollInc ;
ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL, NULL);
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE);
UpdateWindow (hwnd) ;
}
if (iHscrollInc = max (-iHscrollPos,min(iHscrollInc, iHscrollMax - iHscrollPos)))
{
iHscrollPos += iHscrollInc;
ScrollWindow (hwnd, -cxChar*iHscrollInc, 0, NULL, NULL) ;
SetScrollPos (hwnd, SB_ HORZ, iHscrollPos, TRUE) ;
}
return 0 ;

Vă displace acest cod la fel de mult ca şi mie? Dublarea codului pentru tratarea mesajelor de la barele de derulare
este o soluţie de-a dreptul proastă, deoarece dacă vom dori vreodată să modificăm logica de funcţionare a barelor de
derulare va trebui să facem aceleaşi modificări şi în codul de tratare a mesajului WM_KEYDOWN. Trebuie să existe
o soluţie mai elegantă. Şi chiar există.

7
Transmiterea mesajelor
Nu ar fi mai bine să transformăm mesajele WM_KEYDOWN în mesaje echivalente WM_VSCROLL şi
WM_HSCROLL şi să facem cumva funcţia WndProc să creadă că a primit mesajele WM_VSCROLL şi
WM_HSCROLL, poate chiar transmiţând procedurii de fereastră aceste mesaje contrafăcute? Windows vă permite să
faceţi acest lucru. Funcţia necesară este SendMessage şi primeşte aceiaşi parametri ca şi procedura de fereastră:
SendMessage (hwnd, message, wParam, lParam) ;
Atunci când apelaţi funcţia SendMessage, Windows apelează procedura ferestrei identificată de parametrul hwnd
şi îi transmite cei patru parametri. După ce procedura de fereastră încheie prelucrarea mesajului, Windows reia
execuţia de la următoarea linie după apelul funcţiei SendMessage. Procedura de fereastră căreia îi trimiteţi mesajul
poate fi aceeaşi procedură de fereastră, o altă procedură de fereastră din acelaşi program sau chiar o procedură de
fereastră dintr-o altă aplicaţie.
Iată cum putem folosi funcţia SendMessage pentru prelucrarea mesajelor WM_KEYDOWN în programul
SYSMETS:
case WH_KEYDOWN :
switch (wParam)

case VK_HOME :
SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L);
break;

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

case VK_PRIOR :
SendMessage (hwnd, WH_VSCROLL, SB_PAGEUP, 0L);
break ;
[alte linii de program]

OK, aţi prins ideea. Scopul nostru era să adăugăm o interfaţă cu tastatura pentru barele de derulare şi exact acest lucru
l-am făcut. Am permis folosirea tastelor de deplasare pentru a dubla logica barelor de derulare trimiţând procedurii de
fereastră mesajele specifice barelor de derulare. Acum vedeţi de ce am inclus în programul SYSMETS3 din Capitolul
3 secvenţa de tratare a codurilor SB_TOP şi SB_BOTTOM din mesajele WM_VSCROLL. În momentul respectiv
acestea nu erau folosite, dar acum permit tratarea tastelor Home şi End. Programul SYSMETS, prezentat în Figura 5-
2, include cele trei modificări discutate. Pentru compilarea programului aveţi nevoie şi de fişierul SYSMETS.H,
prezentat în Figura 3-4 din Capitolul 3.
#----------------------- wndclass.lpfnWndProc = WndProc ;
# SYSMETS.MAK make file wndclass.cbClsExtra = 0 ;
#----------------------- wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
sysmets.exe : sysmets.obj wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
$(LINKER) $(GUIFLAGS) -OUT:sysmets.exe sysmets.obj $ wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
(GUILIBS) wndclass.hbrBackground = (HBRUSH) GetStockObject
(WHITE_BRUSH) ;
sysmets.obj : sysmets.c sysmets.h wndclass.lpszMenuName = NULL ;
$(CC) $(CFLAGS) sysmets.c wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL,
/*----------------------------------------------------- IDI_APPLICATION) ;
SYSMETS.C -- System Metrics Display Program (Final)
(c) Charles Petzold, 1996 RegisterClassEx (&wndclass) ;
-----------------------------------------------------*/
hwnd = CreateWindow (szAppName, "System Metrics",
#include <windows.h> WS_OVERLAPPEDWINDOW | WS_VSCROLL |
#include <string.h> WS_HSCROLL,
#include "sysmets.h" CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, NULL, NULL, hInstance, NULL) ;
LPARAM) ;
ShowWindow (hwnd, iCmdShow) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE UpdateWindow (hwnd) ;
hPrevInstance,
PSTR szCmdLine, int iCmdShow) while (GetMessage (&msg, NULL, 0, 0))
{ {
static char szAppName[] = "SysMets" ; TranslateMessage (&msg) ;
HWND hwnd ; DispatchMessage (&msg) ;
MSG msg ; }
WNDCLASSEX wndclass ; return msg.wParam ;
}
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;

8
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, break ;
WPARAM wParam, LPARAM lParam)
{ case SB_THUMBTRACK :
static int cxChar, cxCaps, cyChar, cxClient, cyClient, iVscrollInc = HIWORD (wParam) - iVscrollPos ;
iMaxWidth, break ;
iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ;
char szBuffer[10] ; default :
HDC hdc ; iVscrollInc = 0 ;
int i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc ; }
PAINTSTRUCT ps ; iVscrollInc = max (-iVscrollPos,
TEXTMETRIC tm ; min (iVscrollInc, iVscrollMax - iVscrollPos)) ;

switch (iMsg) if (iVscrollInc != 0)


{ {
case WM_CREATE : iVscrollPos += iVscrollInc ;
hdc = GetDC (hwnd) ; ScrollWindow (hwnd, 0, -cyChar * iVscrollInc, NULL,
NULL) ;
GetTextMetrics (hdc, &tm) ; SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
cxChar = tm.tmAveCharWidth ; UpdateWindow (hwnd) ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar }
/2; return 0 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
case WM_HSCROLL :
ReleaseDC (hwnd, hdc) ; switch (LOWORD (wParam))
{
iMaxWidth = 40 * cxChar + 22 * cxCaps ; case SB_LINEUP :
return 0 ; iHscrollInc = -1 ;
break ;
case WM_SIZE :
cxClient = LOWORD (lParam) ; case SB_LINEDOWN :
cyClient = HIWORD (lParam) ; iHscrollInc = 1 ;
break ;
iVscrollMax = max (0, NUMLINES + 2 - cyClient /
cyChar) ; case SB_PAGEUP :
iVscrollPos = min (iVscrollPos, iVscrollMax) ; iHscrollInc = -8 ;
break ;
SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax,
FALSE) ; case SB_PAGEDOWN :
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) iHscrollInc = 8 ;
; break ;

iHscrollMax = max (0, 2 + (iMaxWidth - cxClient) / case SB_THUMBPOSITION :


cxChar) ; iHscrollInc = HIWORD (wParam) - iHscrollPos ;
iHscrollPos = min (iHscrollPos, iHscrollMax) ; break ;

SetScrollRange (hwnd, SB_HORZ, 0, iHscrollMax, default :


FALSE) ; iHscrollInc = 0 ;
SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) }
; iHscrollInc = max (-iHscrollPos,
return 0 ; min (iHscrollInc, iHscrollMax - iHscrollPos)) ;

case WM_VSCROLL : if (iHscrollInc != 0)


switch (LOWORD (wParam)) {
{ iHscrollPos += iHscrollInc ;
case SB_TOP : ScrollWindow (hwnd, -cxChar * iHscrollInc, 0, NULL,
iVscrollInc = -iVscrollPos ; NULL) ;
break ; SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ;
}
case SB_BOTTOM : return 0 ;
iVscrollInc = iVscrollMax - iVscrollPos ;
break ; case WM_KEYDOWN :
switch (wParam)
case SB_LINEUP : {
iVscrollInc = -1 ; case VK_HOME :
break ; SendMessage (hwnd, WM_VSCROLL, SB_TOP,
0L) ;
case SB_LINEDOWN : break ;
iVscrollInc = 1 ;
break ; case VK_END :
SendMessage (hwnd, WM_VSCROLL,
case SB_PAGEUP : SB_BOTTOM, 0L) ;
iVscrollInc = min (-1, -cyClient / cyChar) ; break ;
break ;
case VK_PRIOR :
case SB_PAGEDOWN : SendMessage (hwnd, WM_VSCROLL,
iVscrollInc = max (1, cyClient / cyChar) ; SB_PAGEUP, 0L) ;

9
break ; iVscrollPos + ps.rcPaint.bottom / cyChar) ;

case VK_NEXT : for (i = iPaintBeg ; i < iPaintEnd ; i++)


SendMessage (hwnd, WM_VSCROLL, {
SB_PAGEDOWN, 0L) ; x = cxChar * (1 - iHscrollPos) ;
break ; y = cyChar * (1 - iVscrollPos + i) ;

case VK_UP : TextOut (hdc, x, y,


SendMessage (hwnd, WM_VSCROLL, sysmetrics[i].szLabel,
SB_LINEUP, 0L) ; strlen (sysmetrics[i].szLabel)) ;
break ;
TextOut (hdc, x + 22 * cxCaps, y,
case VK_DOWN : sysmetrics[i].szDesc,
SendMessage (hwnd, WM_VSCROLL, strlen (sysmetrics[i].szDesc)) ;
SB_LINEDOWN, 0L) ;
break ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

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


SendMessage (hwnd, WM_HSCROLL, szBuffer,
SB_PAGEUP, 0L) ; wsprintf (szBuffer, "%5d",
break ; GetSystemMetrics (sysmetrics[i].iIndex))) ;

case VK_RIGHT : SetTextAlign (hdc, TA_LEFT | TA_TOP) ;


SendMessage (hwnd, WM_HSCROLL, }
SB_PAGEDOWN, 0L) ;
break ; EndPaint (hwnd, &ps) ;
} return 0 ;
return 0 ;
case WM_DESTROY :
case WM_PAINT : PostQuitMessage (0) ;
hdc = BeginPaint (hwnd, &ps) ; return 0 ;
}
iPaintBeg = max (0, iVscrollPos + ps.rcPaint.top /
cyChar - 1) ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
iPaintEnd = min (NUMLINES, }

Figura 5-2. Programul SYSMETS (final).


MESAJE CARACTER
Am discutat mai devreme despre ideea transformării mesajelor generate de acţionarea tastelor în mesaje caracter
ţinând cont de starea tastelor de modificare şi v-am avertizat că acest lucru nu este suficient: trebuie să ţineţi seama şi
de configuraţia diferită a tastaturii de la o ţară la alta. Din acest motiv, nu trebuie să încercaţi să transformaţi
dumneavoastră mesajele generate de acţionarea tastelor în mesaje caracter. Windows o poate face în locul
dumneavoastră. Aţi mai văzut această secvenţă de cod:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
Acesta este un ciclu tipic de tratare a mesajelor din funcţia WinMain. Funcţia GetMessage preia următorul mesaj
din coada de aşteptare şi completează câmpurile structurii msg. Funcţia DispatchMessage transmite mesajul
procedurii de fereastră corespunzătoare. Între cele două funcţii este apelată funcţia TranslateMessage, care transformă
mesajele generate de acţionarea tastelor în mesaje caracter. Dacă mesajul este WM_KEYDOWN sau
WM_SYSKEYDOWN şi dacă tasta apăsată, în funcţie de starea tastelor de modificare, generează un caracter, atunci
funcţia TranslateMessage inserează un mesaj caracter în coada de aşteptare. Acesta va fi următorul mesaj pe care îl va
prelua funcţia GetMessage după mesajul generat de acţionarea tastei. Există patru mesaje caracter:

Caractere Caractere „moarte"


Caractere non-sistem WM_CHAR WM_DEADCHAR
Caractere sistem WM_SYSCHAR WM_SYSDEADCHAR
Mesajele WM_CHAR şi WM_DEADCHAR sunt obţinute din mesaje WM_KEYDOWN. Mesajele
WM_SYSCHAR şi WM_SYSDEADCHAR sunt obţinute din mesaje WM_SYSKEYDOWN. În majoritatea cazurilor
programul poate să ignore toate celelalte mesaje în afară de WM_CHAR. Parametrul lParam transmis procedurii de
fereastră are acelaşi conţinut ca şi parametrul lParam al mesajului generat de acţionarea tastei din care a fost obţinut
mesajul caracter. Parametrul wParam conţine codul ASCII al caracterului (da, chiar bătrânul ASCII!). Mesajele
caracter sunt transmise procedurii de fereastră între mesajele generate de acţionarea tastelor. De exemplu, dacă tasta
Caps Lock nu este activă şi apăsaţi şi eliberaţi tasta A, procedura de fereastră primeşte următoarele trei mesaje:

10
Mesaj Tastă sau cod
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYUP Tasta virtuală A
Dacă introduceţi un caracter A apăsând tasta Shift, apoi tasta A, eliberând tasta A şi apoi eliberând tasta Shift,
procedura de fereastră primeşte cinci mesaje:
Mesaj Tastă sau cod
WM_KEYDOWN Tasta virtuală VK_SHIFT
WM_KEYDOWN 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ă ţineţi tasta A apăsată până când intră în acţiune autorepetarea,
veţi primi un mesaj caracter pentru fiecare caracter WM_KEYDOWN:
Mesaj Tastă sau cod
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYDOWN Tasta virtuală A
WM_CHAR Codul ASCII al caracterului a
WM_KEYUP Tasta virtuală A

Dacă unul dintre mesajele WM_KEYDOWN are contorul de repetare mai mare decât 1, mesajul WM_CHAR va
avea acelaşi contor de repetare.
Tasta Ctrl în combinaţie cu o literă generează codurile de control ASCII de la 01H (Ctrl+A) la 1AH (Ctrl+Z).
Puteţi să folosiţi şi alte taste pentru generarea acestor coduri de control. Tabelul următor prezintă valorile
parametrului wParam dintr-un mesaj WM_CHAR pentru tastele care generează coduri de control:
Tastă Cod ASCII Dublat de
Backspace 08H Ctrl+H

Tab 09H Ctrl+I


Ctrl+Enter 0AH Ctrl+J
Enter 0DH Ctrl+M
Esc 1BH Ctrl+[

Programele Windows folosesc uneori combinaţiile tastei Ctrl cu litere ca acceleratori pentru meniuri, caz în care
literele respective nu sunt transformate în mesaje caracter.
Mesajul WM_CHAR
Atunci când trebuie să prelucreze caracterele introduse de la tastatură (de exemplu, într-un procesor de texte sau
într-un program de comunicaţii) programul prelucrează mesajele WM_CHAR. Probabil doriţi să prelucrati într-un
mod mai special tastele Backspace, Tab şi Enter (eventual si tasta de salt la linie nouă) dar toate celelalte caractere
sunt tratate la fel:
case WH_CHAR : [alte linii de program]
break ;
switch (wParam) case '\r' : // retur de car
{ [alte linii de program]
case '\b' : // backspace break ;
[alte linii de program] default : // cod de caractere
break ; [alte linii de program]
case '\f' : // tab break ;
[alte linii de program] }
break ; return 0 ;
case '\n' : // salt la linie nouă

11
Acest fragment de program este asemănător cu secvenţele de cod pentru tratarea caracterelor dintr-un program
MS-DOS obişnuit.
Mesaje pentru „caractere moarte"
De obicei, programele pot să ignore mesajele WM_DEADCHAR si WM_SYSDEADCHAR. Pe unele tastaturi,
în afară de tastatura de tip american, o serie de taste sunt folosite pentru adăugarea semnelor diacritice la o literă.
Acestea se numesc „taste moarte" („dead keys") deoarece nu pot crea singure caractere. De exemplu, pe o tastatura
germană, în locul tastei +/= de pe tastatura americană se află o tastă moartă pentru accentul ascuţit ('') dacă nu este
apăsată tasta Shift, si pentru accentul grav (`) dacă este apăsată tasta Shift.
Atunci când utilizatorul apasă această tastă moartă, procedura de fereastră primeşte un mesaj WM_DEADCHAR
pentru care parametrul wParam conţine codul ASCII al semnului diacritic. Dacă utilizatorul apasă apoi o literă (de
exemplu, tasta A), procedura de fereastră primeşte un mesaj WM_CHAR pentru care parametrul wParam conţine
codul ASCII al literei „a" cu semnul diacritic respectiv. Ca urmare, programul nu trebuie să prelucreze mesajul
WM_DEADCHAR, deoarece mesajul WM_CHAR următor îi furnizează toate informaţiile necesare. Codul Windows
se ocupă chiar şi de tratarea erorilor: dacă tasta moartă este urmată de o literă care nu poate avea semnul diacritic
respectiv (cum ar fi litera „s"), procedura de fereastră primeşte două mesaje WM_CHAR - pentru primul parametrul
wParam conţine codul ASCII al semnului diacritic, iar pentru al doilea parametrul wParam conţine codul ASCII al
literei „s".
Examinarea mesagelor de la tastatură
Dacă doriţi să vedeţi cum trimite sistemul de operare mesajele de la tastatură către o aplicaţie, programul
KEYLOOK (prezentat în figura 5.3) vă poate ajuta. Acest program afişează în zona client toate informaţiile pe care
Windows le trimite procedurii de fereastră pentru opt mesaje diferite de la tastatură.
#----------------------- wndclass.hIconSm = LoadIcon (NULL,
# KEYLOOK.MAK make file IDI_APPLICATION) ;
#-----------------------
RegisterClassEx (&wndclass) ;
keylook.exe : keylook.obj
$(LINKER) $(GUIFLAGS) -OUT:keylook.exe keylook.obj $ hwnd = CreateWindow (szAppName, "Keyboard Message
(GUILIBS) Looker",
WS_OVERLAPPEDWINDOW,
keylook.obj : keylook.c CW_USEDEFAULT, CW_USEDEFAULT,
$(CC) $(CFLAGS) keylook.c CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
/*-------------------------------------------------------
KEYLOOK.C -- Displays Keyboard and Character Messages ShowWindow (hwnd, iCmdShow) ;
(c) Charles Petzold, 1996 UpdateWindow (hwnd) ;
-------------------------------------------------------*/
while (GetMessage (&msg, NULL, 0, 0))
#include <windows.h> {
#include <stdio.h> TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, }
LPARAM) ; return msg.wParam ;
}
RECT rect ;
int cxChar, cyChar ; void ShowKey (HWND hwnd, int iType, char *szMessage,
WPARAM wParam, LPARAM lParam)
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE {
hPrevInstance, static char *szFormat[2] = { "%-14s %3d %c %6u %4d %3s
PSTR szCmdLine, int iCmdShow) %3s %4s %4s",
{ "%-14s %3d %c %6u %4d %3s %3s
static char szAppName[] = "KeyLook" ; %4s %4s" } ;
HWND hwnd ; char szBuffer[80] ;
MSG msg ; HDC hdc ;
WNDCLASSEX wndclass ;
ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;
wndclass.cbSize = sizeof (wndclass) ; hdc = GetDC (hwnd) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ; SelectObject (hdc, GetStockObject
wndclass.cbClsExtra = 0 ; (SYSTEM_FIXED_FONT)) ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ; TextOut (hdc, cxChar, rect.bottom - cyChar, szBuffer,
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) wsprintf (szBuffer, szFormat [iType],
; szMessage, wParam,
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; (BYTE) (iType ? wParam : ' '),
wndclass.hbrBackground = (HBRUSH) GetStockObject LOWORD (lParam),
(WHITE_BRUSH) ; HIWORD (lParam) & 0xFF,
wndclass.lpszMenuName = NULL ; (PSTR) (0x01000000 & lParam ? "Yes" : "No"),
wndclass.lpszClassName = szAppName ; (PSTR) (0x20000000 & lParam ? "Yes" : "No"),

12
(PSTR) (0x40000000 & lParam ? "Down" : "Up"), TextOut (hdc, cxChar, cyChar / 2, szTop, (sizeof
(PSTR) (0x80000000 & lParam ? "Up" : szTop) - 1) ;
"Down"))) ; TextOut (hdc, cxChar, cyChar / 2, szUnd, (sizeof
szUnd) - 1) ;
ReleaseDC (hwnd, hdc) ; EndPaint (hwnd, &ps) ;
ValidateRect (hwnd, NULL) ; return 0 ;
}
case WM_KEYDOWN :
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, ShowKey (hwnd, 0, "WM_KEYDOWN", wParam,
WPARAM wParam, LPARAM lParam) lParam) ;
{ return 0 ;
static char szTop[] =
"Message Key Char Repeat Scan Ext ALT Prev case WM_KEYUP :
Tran"; ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam) ;
static char szUnd[] = return 0 ;
"_______ ___ ____ ______ ____ ___ ___ ____
____"; case WM_CHAR :
HDC hdc ; ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam) ;
PAINTSTRUCT ps ; return 0 ;
TEXTMETRIC tm ;
case WM_DEADCHAR :
switch (iMsg) ShowKey (hwnd, 1, "WM_DEADCHAR", wParam,
{ lParam) ;
case WM_CREATE : return 0 ;
hdc = GetDC (hwnd) ;
case WM_SYSKEYDOWN :
SelectObject (hdc, GetStockObject ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam,
(SYSTEM_FIXED_FONT)) ; lParam) ;
break ; // ie, call DefWindowProc
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ; case WM_SYSKEYUP :
cyChar = tm.tmHeight ; ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam,
lParam) ;
ReleaseDC (hwnd, hdc) ; break ; // ie, call DefWindowProc

rect.top = 3 * cyChar / 2 ; case WM_SYSCHAR :


return 0 ; ShowKey (hwnd, 1, "WM_SYSCHAR", wParam,
lParam) ;
case WM_SIZE : break ; // ie, call DefWindowProc
rect.right = LOWORD (lParam) ;
rect.bottom = HIWORD (lParam) ; case WM_SYSDEADCHAR :
UpdateWindow (hwnd) ; ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam,
return 0 ; lParam) ;
break ; // ie, call DefWindowProc
case WM_PAINT :
InvalidateRect (hwnd, NULL, TRUE) ; case WM_DESTROY :
hdc = BeginPaint (hwnd, &ps) ; PostQuitMessage (0) ;
return 0 ;
SelectObject (hdc, GetStockObject }
(SYSTEM_FIXED_FONT)) ; return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
SetBkMode (hdc, TRANSPARENT) ;
Figura 5.3. Programul KEYLOOK
Programul KEYLOOK foloseşte ecranul după modelul teleimprimatorului. Atunci când primeşte un mesaj
generat de acţionarea unei taste, programul KEYLOOK apelează funcţia ScrollWindow, care derulează în sus
conţinutul întregii zone client cu înălţimea unui caracter. Funcţia TextOut este folosită pentru afişarea unei noi linii de
informaţii, începând afişarea într-un punct aflat la o distanţă egală cu înălţimea unui caracter, faţă de marginea de jos
a ecranului. Este la fel de simplu ca şi în cazul unui teleimprimator. Figura 5-4 prezintă fereastra afişată de programul
KEYLOOK atunci când tastaţi cuvântul „Windows". Prima coloană conţine mesajele de la tastatură; a doua coloană
conţine codurile de taste virtuale pentru mesajele generate de acţionarea fastelor; a treia coloană conţine codul
caracterului (şi caracterul); celelalte şase coloane prezintă starea celor şase câmpuri din parametrul lParam al
mesajului.
În cea mai mare parte, programul KEYLOOK foloseşte caracteristici Windows despre care deja am discutat în
programele SYSMETS prezentate până acum, dar sunt folosite şi câteva funcţii noi. Remarcaţi, totuşi, că formatarea
coloanelor în programul KEYLOOK ar fi fost destul de dificilă dacă s-ar fi folosit fontul prestabilit proporţional.
Codul pentru afişarea fiecărei linii ar trebui împărţit în nouă secţiuni pentru a păstra coloanele aliniate. Pentru
rezolvarea unei astfel de situaţii o soluţie mai simplă este să folosiţi un font cu dimensiune fixă. Aşa cum am arătat în
capitolul anterior, pentru aceasta este nevoie de două funcţii, pe care le-am combinat într-o singură instrucţiune:
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
Programul KEYLOOK apelează aceste funcţii de fiecare dată când obţine un context de dispozitiv. Acest lucru se
întâmplă în trei locuri din program: în funcţia ShowKey, în timpul prelucrării mesajului WM_CREATE din funcţia

13
WndProc şi în timpul prelucrării mesajului WM_PAINT. Funcţia GetStockObject obţine o variabilă handle a unui
obiect grafic de stoc - care în acest caz este fontul de dimensiune fixă folosit în versiunile mai vechi Windows 3.0.
Funcţia SelectObject selectează obiectul în contextul de dispozitiv. După acest apel, orice text va fi afişat cu fontul de
dimensiune fixă selectat. Puteţi să vă întoarceţi la fontul proporţional prestabilit, cu următoarea instrucţiune:
SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

Figura 5-4. Fereastra afişată de programul KEYLOOK.


Funcţia ShowKey apelează funcţia ScrollWindow, care derulează liniile anterioare înaintea afişării unei noi linii.
În mod normal, această operaţie ar duce la invalidarea parţială a ferestrei şi ar generă un mesaj WM_PAINT. Pentru a
împiedica acest lucru, înainte de terminare, funcţia ShowKey apelează funcţia ValidateRect.
Programul KEYLOOK nu salvează mesajele pe care le primeşte, aşa că la primirea unui mesaj WM_PAINT nu
poate să re-creeze fereastra. Din acest motiv, la primirea mesajului WM_PAINT programul KEYLOOK afişează în
partea superioară a ferestrei capetele celor nouă coloane. Înaintea apelării funcţiei BeginPaint, programul KEYLOOK
invalidează întreaga fereastră. În acest fel, este ştearsă toată suprafaţa ferestrei, nu numai dreptunghiul invalid.
(Faptul că programul KEYLOOK nu salvează mesajele pe care le primeşte şi deci la primirea unui mesaj
WM_PAINT nu poate să re-creeze fereastra este un neajuns al programului. Programul TYPER, prezentat puţin mai
târziu, corectează această eroare.)
Programul KEYLOOK afişează în partea superioară a zonei client un antet pentru identificarea celor nouă
coloane. Deşi este posibilă crearea unui font subliniat, în acest program am ales o abordare puţin diferită. Am definit
două şiruri de caractere, numite szTop (care conţine textul antetului) şi szUnd (care conţine liniuţele de subliniere) şi
le-am afişat în aceeaşi poziţie în partea de sus a ecranului, în timpul prelucrării mesajului WM_PAINT. În mod
normal, Windows afişează textul în modul „opac" (OPAQUE), ceea ce înseamnă că şterge zona de fond din spatele
caracterelor afişate. Din această cauză, al doilea şir de caractere (szUnd) ar şterge primul şir de caractere (szTop).
Pentru a împiedica acest lucru, am schimbat modul de afişare în contextul de dispozitiv din „opac" în „transparent":
SetBkMode (hdc, TRANSPARENT) ;

CURSORUL DE EDITARE (CARET)


Atunci când introduceţi text într-un program, poziţia în care va apărea următorul caracter este indicată de o
liniuţă de subliniere sau de un mic dreptunghi, în Windows pentru acest semn se foloseşte termenul „cursor de
editare".
Funcţii pentru cursorul de editare
Există cinci funcţii principale pentru cursorul de editare:
 CreateCaret - creează un cursor de editare asociat unei ferestre.
 SetCaretPos - stabileşte poziţia cursorului de editare în fereastră.
 ShowCaret - afişează cursorul de editare.
 HideCaret - maschează cursorul de editare.
 DestroyCaret - distruge cursorul de editare creat.
Mai există şi alte funcţii pentru obţinerea poziţiei cursorului de editare (GetCaretPos) şi pentru stabilirea şi
obţinerea intervalelor de licărire a acestuia (SetCaretBlinkTime şi GetCaretBlinkTime).
Cursorul de editare este, de obicei, o linie ori un bloc orizontal de dimensiunea unui caracter, sau o linie verticală.
Linia verticală este recomandată în cazul folosirii unui font proportional, cum ar fi fontul sistem prestabilit din

14
Windows. Deoarece caracterele din fonturile proporţionale nu au aceeaşi lăţime, linia sau blocul orizontal nu poate
avea lăţimea exactă a unui caracter.
Cursorul de editare nu poate fi creat pur şi simplu în timpul prelucrării mesajului WM_CREATE şi nici distrus în
timpul prelucrării mesajului WM_DESTROY. El este ceea ce se numeşte o „resursă de sistem". Aceasta înseamnă că
în sistem există un singur cursor de editare. De fapt, atunci când un program trebuie să afişeze un cursor de editare în
fereastra proprie, el „împrumută" acest semn de la sistem.
Pare prea restrictiv? Ei bine, în realitate nu este. Gândiţi-vă puţin: afişarea unui cursor de editare într-o fereastră
are sens numai dacă fereastra respectivă deţine cursorul de intrare (input focus). Cursorul de editare indică
utilizatorului faptul că poate introduce text în program. La un moment dat, o singură fereastră poate deţine cursorul de
intrare, aşa că existenţa unui singur cursor de editare este logică.
Un program poate determina dacă deţine cursorul de intrare prin prelucrarea mesajelor WM_SETFOCUS şi
WM_KILLFOCUS. O procedură de fereastră recepţionează un mesaj WM_SETFOCUS atunci când primeşte
cursorul de intrare şi un mesaj WM_KILLFOCUS atunci când pierde cursorul de intrare. Aceste mesaje sunt
transmise în pereche. O procedură de fereastră primeşte întotdeauna un mesaj WM_SETFOCUS înainte de a primi un
mesaj WM_KILLFOCUS şi întotdeauna va primi un număr egal de mesaje WM_SETFOCUS şi WM_KILLFOCUS
pană la distrugerea ferestrei.
Principala regulă de folosire a cursorului de editare este simplă. O procedură de fereastră apelează funcţia
CreateCaret în timpul prelucrării mesajului WM_SETFOCUS şi funcţia DestroyCaret în timpul prelucrării mesajului
WM_KILLFOCUS.
Există şi alte câteva reguli: la creare, cursorul de editare nu este afişat. După apelarea funcţiei CreateCaret,
programul trebuie să apeleze funcţia ShowCaret pentru a face vizibil cursorul de editare. În plus, procedura de
fereastră trebuie să-l mascheze, apelând funcţia HideCaret, ori de câte ori desenează ceva pe ecran în timpul
prelucrării unui alt mesaj decât WM_PAINT. După terminarea operaţiei de desenare, programul apelează funcţia
ShowCaret ca să afişeze din nou cursorul de editare. Efectul funcţiei HideCaret este aditiv: dacă apelaţi funcţia
HideCaret de mai multe ori fără să apelaţi funcţia ShowCaret, atunci când doriţi ca acest cursor de editare să devină
din nou vizibil, trebuie să apelaţi funcţia ShowCaret de tot atâtea ori de cate ori aţi apelat şi funcţia HideCaret.
Programul TYPER
Programul TYPER, prezentat în Figura 5-5, combină cea mai mare parte a lucrurilor învăţate în acest capitol.
Puteţi să vă gândiţi la programul TYPER ca la un editor de text mai rudimentar. Puteţi să introduceţi text în fereastră,
să mutaţi cursorul de editare cu ajutorul tastelor de deplasare şi să ştergeţi conţinutul ferestrei apăsând tasta Esc.
Conţinutul ferestrei este şters, de asemenea, şi atunci când fereastra este redmensionată. Nu există posibilitatea de
derulare, de căutare sau de înlocuire, de salvare a fişierelor sau de verificare ortografică, dar putem spune că este un
început!
Pentru a face lucrurile mai uşoare pentru mine, TYPER foloseşte un font cu dimensiune fixă. Aşa cum vă puteţi
imagina, scrierea unui editor pentru un font proportional este mult mai dificilă. Programul obţine un context de
dispozitiv în timpul prelucrării mai multor mesaje: WM_CREATE, WM_KEYDOWN, WM_CHAR si WM_PAINT.
De fiecare dată, prin apelarea funcţiilor GetStockObject şi SelectObject, este selectat în contextul de dispozitiv un
font cu dimensiune fixă.
În timpul prelucrării mesajului WM_SIZE, TYPER calculează lăţimea şi înălţimea în caractere a ferestrei şi
salvează aceste valori în variabilele cxBuffer şi cyBuffer. Apoi apelează funcţia malloc pentru alocarea unei zone de
memorie suficient de mare pentru a conţine toate caracterele care pot fi introduse într-o fereastră. Variabilele xCaret
şi yCaret sunt folosite pentru stocarea poziţiei cursorului de editare.
În timpul prelucrării mesajului WM_SETFOCUS, TYPER apelează funcţia CreateCaret pentru crearea unui
semn caret cu dimensiunile unui caracter, apoi funcţia SetCaretPos pentru stabilirea poziţiei cursorului de editare şi
funcţia ShowCaret, pentru a-l face vizibil. În timpul prelucrării mesajului WM_KILLFOCUS, TYPER apelează
funcţiile HideCaret şi DestroyCaret.
#include <windows.h>
#--------------------- #include <stdlib.h>
# TYPER.MAK make file
#--------------------- #define BUFFER(x,y) *(pBuffer + y * cxBuffer + x)

typer.exe : typer.obj LRESULT CALLBACK WndProc (HWND, UINT, WPARAM,


$(LINKER) $(GUIFLAGS) -OUT:typer.exe typer.obj $ LPARAM) ;
(GUILIBS)
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE
typer.obj : typer.c hPrevInstance,
$(CC) $(CFLAGS) typer.c PSTR szCmdLine, int iCmdShow)
{
/*-------------------------------------- static char szAppName[] = "Typer" ;
TYPER.C -- Typing Program HWND hwnd ;
(c) Charles Petzold, 1996 MSG msg ;
--------------------------------------*/ WNDCLASSEX wndclass ;

15
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; if ((pBuffer = (char *) malloc (cxBuffer * cyBuffer)) ==
wndclass.lpfnWndProc = WndProc ; NULL)
wndclass.cbClsExtra = 0 ; MessageBox (hwnd, "Window too large. Cannot "
wndclass.cbWndExtra = 0 ; "allocate enough memory.", "Typer",
wndclass.hInstance = hInstance ; MB_ICONEXCLAMATION | MB_OK) ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) else
; for (y = 0 ; y < cyBuffer ; y++)
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; for (x = 0 ; x < cxBuffer ; x++)
wndclass.hbrBackground = (HBRUSH) GetStockObject BUFFER(x,y) = ' ' ;
(WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ; // set caret to upper left corner
wndclass.lpszClassName = szAppName ; xCaret = 0 ;
wndclass.hIconSm = LoadIcon (NULL, yCaret = 0 ;
IDI_APPLICATION) ;
if (hwnd == GetFocus ())
RegisterClassEx (&wndclass) ; SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

hwnd = CreateWindow (szAppName, "Typing Program", return 0 ;


WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, case WM_SETFOCUS :
CW_USEDEFAULT, CW_USEDEFAULT, // create and show the caret
NULL, NULL, hInstance, NULL) ;
CreateCaret (hwnd, NULL, cxChar, cyChar) ;
ShowWindow (hwnd, iCmdShow) ; SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
UpdateWindow (hwnd) ; ShowCaret (hwnd) ;
return 0 ;
while (GetMessage (&msg, NULL, 0, 0))
{ case WM_KILLFOCUS :
TranslateMessage (&msg) ; // hide and destroy the caret
DispatchMessage (&msg) ; HideCaret (hwnd) ;
} DestroyCaret () ;
return msg.wParam ; return 0 ;
}
case WM_KEYDOWN :
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, switch (wParam)
WPARAM wParam, LPARAM lParam) {
{ case VK_HOME :
static char *pBuffer = NULL ; xCaret = 0 ;
static int cxChar, cyChar, cxClient, cyClient, cxBuffer, break ;
cyBuffer,
xCaret, yCaret ; case VK_END :
HDC hdc ; xCaret = cxBuffer - 1 ;
int x, y, i ; break ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ; case VK_PRIOR :
yCaret = 0 ;
switch (iMsg) break ;
{
case WM_CREATE : case VK_NEXT :
hdc = GetDC (hwnd) ; yCaret = cyBuffer - 1 ;
break ;
SelectObject (hdc, GetStockObject
(SYSTEM_FIXED_FONT)) ; case VK_LEFT :
GetTextMetrics (hdc, &tm) ; xCaret = max (xCaret - 1, 0) ;
cxChar = tm.tmAveCharWidth ; break ;
cyChar = tm.tmHeight ;
case VK_RIGHT :
ReleaseDC (hwnd, hdc) ; xCaret = min (xCaret + 1, cxBuffer - 1) ;
return 0 ; break ;

case WM_SIZE : case VK_UP :


// obtain window size in pixels yCaret = max (yCaret - 1, 0) ;
break ;
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ; case VK_DOWN :
yCaret = min (yCaret + 1, cyBuffer - 1) ;
// calculate window size in characters break ;

cxBuffer = max (1, cxClient / cxChar) ; case VK_DELETE :


cyBuffer = max (1, cyClient / cyChar) ; for (x = xCaret ; x < cxBuffer - 1 ; x++)
BUFFER (x, yCaret) = BUFFER (x + 1,
// allocate memory for buffer and clear it yCaret) ;

if (pBuffer != NULL) BUFFER (cxBuffer - 1, yCaret) = ' ' ;


free (pBuffer) ;

16
HideCaret (hwnd) ;
hdc = GetDC (hwnd) ; xCaret = 0 ;
yCaret = 0 ;
SelectObject (hdc,
GetStockObject (SYSTEM_FIXED_FONT)) ; InvalidateRect (hwnd, NULL, FALSE) ;
break ;
TextOut (hdc, xCaret * cxChar, yCaret * cyChar,
& BUFFER (xCaret, yCaret), default : // character codes
cxBuffer - xCaret) ; BUFFER (xCaret, yCaret) = (char) wParam ;

ShowCaret (hwnd) ; HideCaret (hwnd) ;


ReleaseDC (hwnd, hdc) ; hdc = GetDC (hwnd) ;
break ;
} SelectObject (hdc,
GetStockObject
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; (SYSTEM_FIXED_FONT)) ;
return 0 ;
TextOut (hdc, xCaret * cxChar, yCaret *
case WM_CHAR : cyChar,
for (i = 0 ; i < (int) LOWORD (lParam) ; i++) & BUFFER (xCaret, yCaret), 1) ;
{
switch (wParam) ShowCaret (hwnd) ;
{ ReleaseDC (hwnd, hdc) ;
case '\b' : // backspace
if (xCaret > 0) if (++xCaret == cxBuffer)
{ {
xCaret-- ; xCaret = 0 ;
SendMessage (hwnd, WM_KEYDOWN,
VK_DELETE, 1L) ; if (++yCaret == cyBuffer)
} yCaret = 0 ;
break ; }
break ;
case '\t' : // tab }
do }
{
SendMessage (hwnd, WM_CHAR, ' ', SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
1L) ; return 0 ;
}
while (xCaret % 8 != 0) ; case WM_PAINT :
break ; hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject
case '\n' : // line feed (SYSTEM_FIXED_FONT)) ;
if (++yCaret == cyBuffer)
yCaret = 0 ; for (y = 0 ; y < cyBuffer ; y++)
break ; TextOut (hdc, 0, y * cyChar, & BUFFER(0,y),
cxBuffer) ;
case '\r' : // carriage return
xCaret = 0 ; EndPaint (hwnd, &ps) ;
return 0 ;
if (++yCaret == cyBuffer)
yCaret = 0 ; case WM_DESTROY :
break ; PostQuitMessage (0) ;
return 0 ;
case '\x1B' : // escape }
for (y = 0 ; y < cyBuffer ; y++) return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
for (x = 0 ; x < cxBuffer ; x++) }
BUFFER (x, y) = ' ' ;
Figura 5.5. Programul TYPER

Prelucrarea mesajelor WM_KEYDOWN şi WM_CHAR este ceva mai complicată. Prelucrarea mesajului
WM_KEYDOWN implică, în cea mai mare parte, tastele de deplasare a cursorului. Tasta Home duce cursorul de
editare la începutul liniei, tasta End îl duce la sfârşitul liniei, iar tastele Page Up şi Page Down îl mută la începutul,
respectiv la sfârşitul ferestrei. Tastele cu săgeţi funcţionează normal, ca şi în alte programe. În cazul apăsării tastei
Delete, programul TYPER trebuie să mute spre stânga cu o poziţie tot conţinutul bufferului de la următorul caracter,
până la sfârşitul liniei şi apoi să afişeze un spaţiu la sfârşitul liniei.
În timpul prelucrării mesajului WM_CHAR sunt tratate tastele Backspace, Tab, Linefeed (Ctrl+Enter), Enter,
Esc şi caracterele. Remarcaţi că am folosit contorul de repetare (Repet Count) din parametrul IParam pentru
prelucrarea mesajelor WM_CHAR (pornind de la ideea că toate caracterele introduse de utilizator sunt importante)
dar nu şi pentru prelucrarea mesajelor WM_KEYDOWN (pentru a evita derularea nedorită a ecranului). Tratarea
tastelor Backspace şi Tab este simplificată prin folosirea funcţiei SendMessage. Tasta Backspace este emulată prin
logica de tratare a tastei Delete iar tasta Tab este emulată prin mai multe spaţii.

17
Aşa cum am menţionat anterior, este recomandat să mascaţi cursorul de editare atunci când efectuaţi operaţii de
desenare în timpul prelucrării altor mesaje decât WM_PAINT. Programul face acest lucru în timpul prelucrării
mesajului WM_KEYDOWN pentru tasta Delete şi în timpul prelucrării mesajului WM_CHAR pentru caractere. În
ambele cazuri, programul TYPER schimbă conţinutul bufferului şi apoi afişează noile caractere în fereastră.
Aşa cum se poate vedea în Figura 5.6, am folosit programul TYPER atunci când am lucrat la discursuri.

Figura 5.6. Fereastra afişată de programul TYPER.


SETUL DE CARACTERE WINDOWS
Am menţionat anterior că apăsarea unei taste moarte înaintea tastei corespunzătoare unei litere precedată de o
tasta moartă generează un mesaj WM_CHAR pentru care parametrul wParam conţine codul ASCII al caracterului cu
semnul diacritic. S-ar putea ca această afirmaţie să nu fie prea clară, deoarece setul de caractere ASCII nu conţine nici
un cod pentru caractere cu semne diacritice. Atunci, ce conţine parametrul wParam? Pentru a răspunde la această
întrebare trebuie să discutăm despre seturile de caractere, un subiect care, deşi la prima vedere pare a-şi avea mai
degrabă locul într-o discuţie despre fonturi, are importanţa lui şi pentru manipularea tastaturii.
Setul de caractere ASCII standard pe 7 biţi defineşte codurile de la 0 la 31 (0x1F) şi codul 127 (0x7F) pentru
caracterele de control şi codurile de la 32 (0x20) la 126 (0x7E) pentru caracterele afişabile. Nici unul dintre aceste
caractere nu are semne diacritice. Deoarece calculatoarele personale folosesc octeţi de câte opt biţi, producătorii de
calculatoare definesc un set de caractere care conţine 256 de coduri în loc de 128. Codurile suplimentare pot conţine
caractere cu semne diacritice. „Setul de caractere extins" rezultat include şi setul de caractere ASCII cu coduri de la 0
la 127.
Dacă Windows ar accepta un asemenea set de caractere extinse, afişarea caracterelor cu diacritice ar fi foarte
simplă, dar Windows nu acceptă un set de caractere extins, ci două. Din nefericire, însă, prezenţa celor două seturi de
caractere nu face lucrurile de două ori mai uşoare.
Setul de caractere OEM
Mai întâi, haideţi să revedem componentele hardware cu care lucrează sistemul de operare Windows -
calculatoarele IBM PC şi compatibile. La începutul anilor '80, dezvoltatorii calculatoarelor IBM PC au decis să
extindă setul de caractere ASCII, aşa cum se poate vedea în Figura 5.7. Codurile de la 0x20 la 0x7E sunt caracterele
afişabile din setul de caractere ASCII. Celelalte sunt nestandard - sau cel puţin aşa erau în momentul respectiv.
Acest set de caractere nu poate fi ignorat. Este codificat hardware în cipurile ROM ale plăcilor grafice,
imprimantelor şi circuitelor BIOS ale plăcilor de bază IBM. A fost copiat de numeroşii producători de calculatoare şi
periferice compatibile IBM. Acest set de caractere face parte din ceea ce se numeşte „standardul IBM". Multe
programe non-Windows în mod caracter scrise pentru calculatoarele IBM PC au nevoie de acest set de caractere,
deoarece folosesc pentru afişare caracterele de desenare a blocurilor şi a liniilor (codurile de la B0H la DFH).
Singura problemă este faptul că setul extins de caractere definit de IBM nu este potrivit pentru Windows. În
primul rând, caracterele de desenare a blocurilor şi a liniilor folosite de programele în mod text nu sunt necesare sub
Windows, deoarece Windows lucrează în mod grafic. Dacă vreţi să desenaţi o linie orizontală, în Windows este mai
uşor să apelaţi o funcţie de desenare decât să afişaţi un şir de caractere 0xC4. În al doilea rând, alfabetul grecesc şi
simbolurile matematice sunt mai puţin importante în Windows decât literele accentuate, folosite de majoritatea
limbilor europene. Un program care trebuie să afişeze simboluri matematice o poate face mai uşor folosind funcţii
grafice.

18
Pe scurt, sistemul de operare Windows acceptă setul de caractere IBM, dar îi acordă o importanţă mai mică - în
principal pentru aplicaţii mai vechi, rulate în ferestre. În mod normal, aplicaţiile Windows nu folosesc setul de
caractere IBM. În documentaţia Windows, setul de caractere IBM este numit „setul de caractere OEM". Setul de
caractere OEM este mai precis definit ca setul de caractere nativ pe calculatorul pe care rulează sistemul de operare
Windows.
Asigurarea suportului internaţional sub MS-DOS
Există mai multe variante ale setului de caractere IBM PC, variante numite „pagini de cod ". Varianta folosită în
Statele Unite şi în majoritatea ţărilor europene este pagina 437. Calculatoarele vândute în Norvegia, Danemarca,
Portugalia şi alte câteva ţări europene folosesc alte pagini, care conţin multe dintre caracterele speciale folosite în
ţările respective. În ultimul timp, o parte dintre aceste ţări au început să folosescă pagina 850, care conţine mai puţine
caractere grafice şi mai multe litere cu accent şi alte caractere speciale.
Windows asigură suportul pentru paginile de coduri prin instalarea fonturilor OEM (folosite pentru rularea
aplicaţiilor MS-DOS în ferestre şi pentru afişarea memoriei clipboard) corespunzătoare paginii de coduri a sistemului
şi prin instalarea tabelelor de conversie corespunzătoare pentru funcţiile CharToOem şi OemToChar (despre care vom
discuta ceva mai târziu).
Programul de instalarea Windows Setup va selecta paginile de cod corespunzătoare, în funcţie de atributele
regionale ale configuraţiei curente a sistemului.
Setul de caractere ANSI
Setul extins de caractere folosit de obicei de Windows şi de programele Windows se numeşte „setul de caractere
ANSI" şi este, de fapt, un standard ISO. Atunci când programul dumneavoastră primeşte un mesaj WM_CHAR,
parametrul wParam conţine codul ANSI al caracterului. Setul de caractere ANSI este prezentat în Figura 5.8. Aşa
cum se poate vedea, codurile de la 0x20 la 0x7E reprezintă aceleaşi caractere ca şi în setul de caractere OEM şi în
setul de caractere ASCII. Caracterele afişate sub forma unor blocuri nu sunt definite. Acestea pot apărea diferit pentru

Figura 5.7. Setul IBM de caractere extinse, aranjate după codul caracterelor.

19
Figura 5.8. Setul ANSI de caractere extinse, aranjate după codul caracterelor.
alte dispozitive de ieşire (cum ar fi imprimanta). Fonturile TrueType definesc unele caractere suplimentare pentru
codurile ANSI de la 0x80 la 0x9F.
OEM, ANSI şi fonturile
Windows conţine diferite fonturi pentru afişarea seturilor de caractere ANSI şi OEM. Atunci când obţineţi pentru
prima dată o variabilă handle a unui context de dispozitiv, unul dintre atributele acestuia este fontul selectat, în mod
prestabilit, acesta este SYSTEM_FONT („fontul sistem") care foloseşte setul de caractere ANSI. Dacă vreţi să afişaţi
caractere din setul OEM, puteţi să selectaţi fontul OEM_FIXED_FONT (numit şi „font de terminal") în contextul de
dispozitiv, folosind următoarea instrucţiune:
SelectObject (hdc, GetStockObject (OEM_FIXED_FONT)) ;

PROBLEME LEGATE DE INTERNAŢIONALIZARE


Am văzut că atunci când un utilizator Windows cu o tastatură diferită de cea definită pentru Statele Unite
introduce un caracter cu semn diacritic, parametrul wPavam al mesajului WM_CHAR conţine codul ANSI al
caracterului respectiv.
Ca urmare, dacă trebuie să afişaţi pe ecran caracterul respectiv, este bine să folosiţi un font pentru setul de
caractere ANSI (cum ar fi SYSTEM_FONT sau SYSTEM_FIXED_FONT). Dacă folosiţi fontul
OEM_FIXED_FONT, caracterul afişat pe ecran va fi incorect şi îl va surprinde pe utilizator. Alte câteva reguli simple
vă vor ajuta să păstraţi nemodificat codul de manipulare a tastaturii atunci când scrieţi un program pentru piaţa
europeană.
Folosirea seturilor de caractere
Atunci când primiţi un mesaj WM_CHAR, ţineţi seama de faptul că parametrul wParam poate avea şi valori mai
mari de 128. Nu porniţi de la ideea că orice cod mai mare de 128 reprezintă un caracter invalid.
S-ar putea să aveţi uneori nevoie de un caracter scris cu majusculă. Nu folosiţi un algoritm propriu, cum ar fi:
if (ch = 'a' && ch <= 'z') ch -=32 ; // Greşit!!!
Aceasta este o metodă greşită în programarea sub Windows. Dar nu folosiţi nici funcţiile standard de conversie
din C:
ch = toupper (ch) ; // Greşit!!!
Rezultatele acestor funcţii sunt corecte numai pentru prima jumătate a setului de caractere. Funcţiile standard din
C nu vor converti codul 0xE0 în 0xC0.

20
În schimb, puteţi să folosiţi funcţiile Windows CharUpper şi CharLower. Dacă pString este un şir de caractere
terminat cu zero, puteţi să îl convertiţi în litere mari folosind funcţia CharUpper:
CharUpper (pString) ;
Dacă şirul de caractere nu este terminat cu zero folosiţi funcţia CharUpperBuff:
CharUpperBuff (pString, nLength) ;
Puteţi să folosiţi funcţia CharUpper şi pentru un singur caracter, dar sunt necesare unele conversii forţate de tip,
deoarece cuvântul mai semnificativ al parametrului trebuie să aibă valoarea 0:
ch = CharUpper ((PSTR) (LONG) (BYTE) ch) ;
Dacă ch este definit ca un caracter fără semn (unsigned character) prima conversie forţată (BYTE) nu este
necesară. Windows include şi funcţiile CharLower şi CharLowerBuff pentru convertirea şirurilor de caractere în litere
mici.
Dacă vă gândiţi cu seriozitate la scrierea unor programe Windows care să poată fi modificate cu uşurinţă pentru
alte limbi, ar trebui să examinaţi şi funcţiile CharNext şi CharPrev. Aceste funcţii vă ajută să manipulaţi seturile de
caractere pe mai mulţi octeţi, deseori folosite în ţările orientale. Aceste seturi conţin mai mult de 256 de caractere,
dintre care unele folosesc doi octeţi. Dacă vă bazaţi pe aritmetica de pointeri normală din C pentru parcurgerea unui
şir de caractere (de exemplu pentru căutarea unui caracter backslash într-o cale de acces la un director) s-ar putea să
credeţi că aţi găsit caracterul căutat şi, de fapt, să folosiţi al doilea caracter al unui cod. Funcţiile CharNext şi
CharPrev primesc ca parametru un pointer de tip far la un şir de caractere şi returnează un pointer de tip far corect
incrementat sau decrementat pentru codurile de caractere formate din doi octeţi.
Comunicarea cu MS-DOS
Dacă Windows ar fi singurul sistem de operare rulat pe calculator, aţi putea să uitaţi de setul de caractere OEM şi
să folosiţi numai setul de caractere ANSI. Dar utilizatorii pot să creeze fişiere în MS-DOS şi să le folosească în
Windows, sau să creeze fişiere în Windows şi să le folosească în MS-DOS. Din păcate, sistemul de operare MS-DOS
foloseşte setul de caractere OEM.
Iată o posibilă problemă de comunicare. Să presupunem că utilizatorul unui calculator personal, vorbitor de limbă
germană, creează un fişier numit UBUNGEN.TXT („exerciţii practice") într-un program MS-DOS, cum ar fi EDIT.
Pe un calculator IBM PC caracterul U face parte din setul de caractere IBM (adică OEM) şi are codul 154 sau 0x9A.
(Folosind tastatura de tip american sub MS-DOS pe un calculator IBM PC puteţi să creaţi această literă introducând
codul Alt+154 folosind cifrele din blocul de taste numerice.) MS-DOS foloseşte acest cod de caracter pentru numele
fişierului din director.
Dacă un program Windows apelează o funcţie MS-DOS ca să obţină fişierele dintr-un director şi afişează numele
fişierelor pe ecran folosind un font pentru setul de caractere ANSi, prima literă din numele fişierului -
UBUNGEN.TXT va fi afişată ca un bloc negru, deoarece codul 154 este unul dintre caracterele nedefinite din setul de
caractere ANSI. Programul Windows trebuie să convertească codul extins IBM 154 (0x9A) în codul ANSI 220
(0xDC), care reprezintă litera U în setul de caractere ANSI. Acest lucru este făcut de funcţia Windows OemToChar.
Funcţia OemToChar primeşte ca parametri doi pointeri de tip far către două şiruri de caractere. Caracterele OEM din
primul şir sunt convertite în caractere ANSI şi sunt stocate în cel de-al doilea şir:
OemToChar (lpszOemStr, lpszAnsiStr) ;
Acum să vedem acelaşi exemplu, dar în sens invers. Vorbitorul de limbă germană doreşte ca programul
Windows să creeze un fişier numit UBUNGEN.TXT. Primul caracter al numelui de fişier introdus de utilizator are
codul 220 (0xDC). Dacă folosiţi o funcţie MS-DOS pentru deschiderea fişierului, MS-DOS foloseşte caracterul
respectiv în numele fişierului. Dacă ulterior utilizatorul vrea să vadă numele fişierului sub MS-DOS, primul caracter
va fi afişat sub forma unui bloc. Înaintea apelării funcţiei MS-DOS trebuie să convertiţi numele fişierului în caractere
OEM:
CharToOem (IpszAnsiStr, IpszOemStr) ;
Funcţia CharToOem converteşte caracterul 220 (0xDC) în caracterul 154 (0x9A). În Windows există două funcţii
cu acelaşi rezultat, CharToOemBuff şi OemToCharBuff, pentru care, însă, şirurile de caractere nu trebuie să se
termine cu 0.
Conversiile de mai sus sunt făcute şi de funcţia Windows OpenFile. Dacă folosiţi funcţia OpenFile nu apelaţi şi
funcţia CharToOem. Dacă folosiţi funcţii MS-DOS ca să obţineţi lista de fişiere (aşa cum face programul Windows
File Manager), atunci numele fişierelor trebuie convertite cu funcţia OemToChar, înainte de a fi afişate.
Convertirea conţinutului fişierelor este o altă problemă care apare atunci când fişierele sunt folosite atât sub
Windows, cât şi sub MS-DOS. Dacă programul Windows foloseşte fişiere despre care ştiţi cu siguranţă că au fost
create într-un program MS-DOS, atunci conţinutul acestora ar trebui să fie convertit cu ajutorul funcţiei OemToChar.
La fel, dacă programul dumneavoastră creează fişiere care vor fi folosite de un program MS-DOS, trebuie să folosiţi
funcţia CharToOem ca să convertiţi conţinutul acestora.
Funcţiile OemToChar şi CharToOem sunt stocate în driverul de tastatură şi conţin tabele de căutare foarte
simple. Funcţia OemToChar converteşte codurile OEM de Ia 0x80 la 0xFF într-un cod ANSI reprezentând caracterul
care seamănă cel mai mult cu caracterul OEM corespunzător, în unele cazuri această conversie este aproximativă. De
exemplu, majoritatea caracterelor de desenare din setul de caractere IBM sunt convertite în semne plus, liniuţe de
despărţire şi linii verticale. Majoritatea codurilor OEM de la 0x00 la 0x1F nu sunt convertite în coduri ANSI.

21
Funcţia CharToOem converteşte codurile ANSI de Ia 0xA0 la 0xFF în coduri din setul OEM. Literele accentuate
din setul de caractere ANSI care nu apar în setul de caractere OEM sunt convertite în caracterul ASCII corespunzător,
fără semnele diacritice.
Folosirea blocului de taste numerice
După cum probabil ştiţi, tastatura IBM PC şi sistemul BIOS vă permit să introduceţi coduri pentru caracterele din
setul extins IBM; astfel, apăsaţi tasta Alt, introduceţi cu ajutorul tastelor numerice codul din trei cifre al caracterului
OEM, apoi eliberaţi tasta Alt. În Windows, această posibilitate este dublată în două feluri.
În primul rând, atunci când introduceţi codul Alt+[cod OEM] de la tastele numerice, Windows returnează (prin
parametrul wParam al mesajului WM_CHAR) codul caracterului ANSI care seamănă cel mai mult cu caracterul al
cărui cod OEM l-aţi introdus. Aceasta înseamnă că Windows trece codul prin funcţia OemToChar înainte de
generarea mesajului WM_CHAR. Această operaţie se face în folosul utilizatorului. Dacă nu aveţi o tastatură pentru
limba străină pe care doriţi să o folosiţi şi sunteţi obişnuit să introduceţi caracterul U folosind combinaţia de taste
Alt+154, puteţi să faceţi acelaşi lucru şi în Windows. Nu este nevoie să învăţaţi şi codurile ANSI.
În al doilea rând, dacă vreţi să generaţi un cod ANSI extins folosind tastatura de tip american, introduceţi codul
Alt+0[cod ANSI] de la blocul de taste numerice. Parametrul wParam al mesajului WM_CHAR va conţine codul
ANSI respectiv. Ca urmare, Alt+0220 este tot U. Puteţi să încercaţi acest lucru în programele KEYLOCK sauTYPER.
Soluţia Unicode pentru Windows NT
Dezvoltatorii de programe implicaţi în crearea unor aplicaţii pentru piaţa internaţională au fost nevoiţi să
inventeze diferite soluţii pentru rezolvarea deficienţelor codului ASCII pe 7 biţi, cum ar fi paginile de coduri sau
seturile de caractere pe doi octeţi. Este nevoie de o soluţie mai bună, şi aceasta ar putea fi Unicode.
Unicode este un standard de codificare a caracterelor, care foloseşte un cod uniform pe 16 biţi pentru fiecare
caracter. Acest cod permite reprezentarea tuturor caracterelor, din toate limbajele scrise, care pot fi folosite în
comunicaţiile prin calculator, inclusiv simbolurile chinezeşti, japoneze şi coreene. Unicode a fost dezvoltat de un
consorţiu de companii din industria calculatoarelor (inclusiv cele mai mari dintre acestea).
Din nefericire, sistemul de operare Windows 95 asigură doar un suport rudimentar pentru standardul Unicode şi
nu poate furniza caractere Unicode prin driverul de tastatură. Aceasta este una dintre diferenţele faţă de Windows NT,
care include suportul pentru standardul Unicode.
Evident, adaptarea programelor (şi a modului de gândire al programatorilor) la ideea folosirii codurilor pe 16 biţi
este o sarcină laborioasă, dar merită dacă în acest fel vom putea afişa pe ecran şi vom putea tipări la imprimantă texte
în toate limbajele scrise existente.

22

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