Sunteți pe pagina 1din 37

Programarea în Windows

Home / My courses / PWINDOWS / Capitolul 5. Tastatura / Totul despre tastatură

Totul despre tastatură

Tastatura – elemente de bază

Aşa cum probabil vă daţi seama, arhitectura bazată pe mesaje a sistemului de operare Windows este ideală pentru lucrul cu tastatura. Programul
„află" despre apăsarea unor taste prin intermediul mesajelor care ajung la procedura de fereastră.

De fapt, lucrurile sunt puţin mai complicate: atunci când utilizatorul apasă şi eliberează tastele, driverul de tastatură transmite sistemului de operare
informaţiile legate de acţiunile asupra tastelor. Windows salvează aceste acţiuni (sub formă de mesaje) în coada de aşteptare a sistemului. Mesajele
de la tastatură sunt apoi transferate, unul câte unul, în coada de mesaje a programului căruia îi aparţine fereastra ce deţine „cursorul de intrare"
(despre care vom discuta imediat). Programul distribuie mesajele procedurii de fereastră corespunzătoare.

Motivul acestui proces în două etape - stocarea mesajelor mai întâi în coada de mesaje a sistemului şi apoi transferarea acestora în coada de
mesaje a aplicaţiilor - este legat de sincronizare. Atunci când utilizatorul apasă pe taste într-un ritm mai rapid decât cel în care programul
prelucrează mesajele primite, Windows stochează acţiunile neprelucrate în coada de aşteptare a sistemului, deoarece una dintre acţiunile asupra
tastaturii poate avea ca efect comutarea cursorului de intrare (input focus) către un alt program. În consecinţă, următoarele taste trebuie transmise
celui de-al doilea program.

Windows trimite programelor opt tipuri de mesaje prin care indică diferite evenimente de tastatură. S-ar putea să pară numeroase, dar programul
poate să ignore multe dintre acestea, fără probleme. De asemenea, în majoritatea cazurilor, aceste mesaje conţin mai multe informaţii despre
tastatură decât aveţi nevoie în program. O parte a sarcinii de manipulare a tastaturii constă în a şti ce mesaje sunt importante.

Ignorarea tastaturii
Deşi tastatura este principala sursă de intrări de la utilizatori a unui program pentru Windows, programele nu trebuie să reacţioneze la toate
mesajele pe care le primeşte de la tastatură. Multe funcţii ale tastaturii sunt tratate chiar de Windows. De exemplu, puteţi să ignoraţi apăsările de
taste legate de funcţii sistem. Acestea sunt, în general, combinaţii cu tasta Alt.

Programul nu este obligat să monitorizeze aceste taste, deoarece Windows îi comunică efectul acestora. (Totuşi, dacă este nevoie, programul poate
face şi acest lucru.) De exemplu, dacă utilizatorul selectează un articol dintr-un meniu cu ajutorul tastaturii, Windows trimite programului un mesaj
prin care îi comunică articolul de meniu selectat, indiferent dacă utilizatorul a folosit mouse-ul sau tastatura.

Unele programe pentru Windows folosesc „acceleratori" (sau „taste de accelerare") pentru opţiunile de meniu folosite mai frecvent. Acceleratorii
sunt, de obicei, combinaţii de taste funcţionale - sau alte taste corespunzătoare unor caractere - cu tasta Ctrl. Tastele de accelerare sunt definite în
fişierul de resurse al programului.

Casetele de dialog au şi ele o interfaţă cu tastatura, dar programele, în general, nu trebuie să monitorizeze tastatura cât timp este activă o casetă de
dialog. Interfaţa cu tastatura este manipulată de Windows şi acesta trimite programului mesaje prin care îi comunică efectele tastelor apăsate.
Casetele de dialog pot conţine controale de editare pentru introducerea textului. Acestea sunt mici casete, în care utilizatorul poate să scrie un şir
de caractere. Windows asigură logica de funcţionare a controlului de editare şi furnizează programului conţinutul final al controlului, după ce
utilizatorul a terminat. Chiar şi în fereastra principală puteţi să definiţi ferestre descendent, care să funcţioneze ca şi controalele de editare.

Cursorul, cursorul, cine a luat cursorul de intrare?

Tastatura trebuie să fie partajată de toate aplicaţiile rulate simultan sub Windows. Unele aplicaţii pot avea mai multe ferestre, iar tastatura trebuie să
fie partajată de toate ferestrele din cadrul aceleiaşi aplicaţii. Atunci când este apăsată o tastă, o singură fereastră trebuie să primească mesajul
privind apăsarea tastei respective. Fereastra care primeşte acest mesaj este fereastra care deţine „cursorul de intrare" („input focus").

Conceptul cursorului de intrare este strâns legat de conceptul de „fereastră activă". Fereastra care deţine cursorul de intrare este fie fereastra activă,
fie o fereastră descendent a ferestrei active. De obicei, fereastra activă este uşor de identificat. Dacă fereastra activă are o bară de titlu, Windows
evidenţiază bara de titlu a acesteia. Dacă fereastra activă are un cadru de dialog (o formă des întâlnită în casetele de dialog) în locul unei bare de
titlu, Windows evidenţiază acest cadru. Dacă fereastra activă a fost redusă la o pictogramă (minimizată), Windows evidenţiază textul afişat sub
pictogramă.

Ferestrele descendent mai des întâlnite sunt controale, precum butoanele de apăsare, butoanele radio, casetele de validare, barele de derulare,
casetele de editare şi casetele listă, care, de obicei, apar în casete de dialog. Ferestrele descendent nu sunt niciodată ferestre active. Dacă o
fereastră descendent deţine cursorul de intrare, atunci fereastra activă este fereastra părinte. Controalele de tip fereastră descendent indică faptul
că deţin cursorul de intrare prin afişarea unui cursor care clipeşte sau a unui cursor de editare.
Dacă fereastra activă a fost redusă la o pictogramă, atunci nici o fereastră nu deţine cursorul de intrare. Windows continuă să trimită programului
mesaje de la tastatură, dar acestea sunt trimise într-o altă formă decât mesajele trimise unei ferestre active normale.

O procedură de fereastră poate să afle când are cursorul de intrare prin interceptarea mesajelor WM_SETFOCUS şi WM_KILLFOCUS. Mesajul
WM_SETFOCUS indică faptul că fereastra primeşte cursorul de intrare (input focus), iar mesajul WM_KILLFOCUS indică faptul că fereastra pierde
cursorul de intrare.

Acţionări de taste şi caractere


Mesajele privind tastatura pe care un program le recepţionează de la sistemul de operare fac diferenţa între „acţionările de taste" („keystrokes") şi
„caractere". Aceste noţiuni sunt legate de cele două moduri în care puteţi să priviţi tastatura. În primul rând, tastatura este o colecţie de taste.
Tastatura are o singură tastă A; apăsarea tastei A este o acţionare de tastă, iar eliberarea tastei A este tot o acţionare de tastă. Tastatura este, însă, în
acelaşi timp, şi un dispozitiv de intrare care generează caractere afişabile. Tasta A poate să genereze mai multe caractere, în funcţie de starea
tastelor Ctrl, Shift şi Caps Lock. În mod normal, caracterul generat este a. Dacă tasta Shift este apăsată, sau tasta Caps Lock este activă, caracterul
generat este A. Dacă tasta Ctrl este apăsată, caracterul generat este Ctrl+A. Dacă se foloseşte un driver de tastatură pentru o limbă străină,
apăsarea tastei A poate să fie precedată de un „caracter mort" („dead-character key") sau de tastele Shift, Ctrl sau Alt în diferite combinaţii.
Combinaţiile pot să genereze un caracter a sau A cu un accent.

Pentru acţionările de taste care generează caractere afişabile, Windows trimite programului atât mesaje pentru acţionarea de taste, cât şi mesaje
pentru caractere. Unele taste nu generează caractere. Astfel de taste sunt Shift, tastele funcţionale, tastele de deplasare şi tastele speciale, precum
Insert şi Delete. În cazul acestor taste, Windows generează numai mesaje pentru acţionari de taste.

Mesaje pentru acţionări de taste


Atunci când apăsaţi o tastă, Windows inserează în coada de aşteptare a ferestrei care deţine cursorul de intrare un mesaj WM_KEYDOWN sau un
mesaj WM_SYSKEYDOWN. Atunci când eliberaţi fasta, Windows inserează în coada de aşteptare a ferestrei un mesaj WM_KEYUP sau un mesaj
WM_SYSKEYUP.

  Tasta a fost Tasta a fost eliberată

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

De obicei, mesajele de apăsare şi de eliberare a tastei sunt trimise în pereche. Totuşi, dacă ţineţi apăsată o tastă pană când aceasta se autorepetă,
Windows trimite procedurii de fereastră o serie de mesaje WM_KEYDOWN (sau WM_SYSKEYDOWN) şi un singur mesaj WM_KEYUP sau
(WM_SYSKEYUP) după eliberarea tastei. Ca toate mesajele trimise prin coada de aşteptare, mesajele pentru acţionări de taste conţin informaţii de
timp. Puteţi să obţineţi momentul relativ în care a fost apăsată sau eliberată o tastă apelând funcţia GetMessageTime.

Taste obişnuite şi taste de sistem

Particula „SYS" din mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP provin de la cuvântul „system" şi se referă la acţionările de taste care prezintă
mai multă importanţă pentru Windows decât pentru aplicaţiile Windows. Mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP sunt generate, de obicei,
pentru taste apăsate în combinaţie cu tasta Alt. Aceste acţionări de taste apelează opţiuni din meniul programului ori din meniul sistem, sunt
folosite pentru funcţii ale sistemului, cum ar fi comutarea ferestrei active (Alt+Tab sau Alt+Esc) sau sunt folosite pentru acceleratori de meniu (Alt în
combinaţie cu o tastă funcţională). De obicei, programul ignoră mesajele WM_SYSKEYDOWN şi WM_SYSKEYUP şi le retransmite funcţiei
DefWindowProc. Deoarece Windows manipulează combinaţiile Alt+tastă, nu este nevoie să interceptaţi aceste mesaje. Procedura de fereastră va
primi alte mesaje, legate de rezultatul acestor acţionări de taste (cum ar fi selectarea unei opţiuni de meniu). Chiar dacă doriţi să includeţi în
program codul pentru interceptarea acestor mesaje (aşa cum vom face în programul KEYLOCK din acest capitol), retransmiteţi mesajele funcţiei
DefWindowProc după ce le prelucraţi, astfel încât Windows să le poată folosi în scopurile obişnuite.

Dar să ne gândim puţin la aceste lucruri. Aproape toate mesajele care afectează fereastra programului trec mai întâi prin procedura de fereastră.
Windows prelucrează aceste mesaje numai dacă le retransmiteţi funcţiei DefWindowProc. De exemplu, dacă în procedura de fereastră adăugaţi
următoarele linii:

case WM_SYSKEYDOWN :

case WM_SYSKEYUP :

case WM_SYSCHAR :

return 0 ;

dezactivaţi toate operaţiile legate de combinaţiile Alt+tastă (comenzi de meniu, Alt+Tab, Alt+Esc şi aşa mai departe) atunci când programul deţine
cursorul de intrare.

Mesajele WM_KEYDOWN şi WM_KEYUP sunt generate, de obicei, pentru tastele apăsate şi eliberate fără tasta Alt. Programul poate să folosească
sau să ignore aceste mesaje. Sistemul de operare le ignoră.
Variabila lParam

Pentru toate mesajele legate de acţionările de taste variabila lParam (pe 32 de biţi) transmisă procedurii de fereastră este împărţită în şase câmpuri:
contorul de repetare, codul de scanare OEM, indicatorul flag pentru taste extinse, codul de context, starea anterioară a tastei şi starea de tranziţie
(vezi Figura 5-1).

Indicator flag pentru taste extinse

Figura 5-1. Cele sase câmpuri de biţi ale variabilei IParam din mesajele pentru acţionari de taste.

Contorul de repetare
Contorul de repetare (Repeat Count) specifică numărul de acţionari de taste reprezentat de un mesaj. În majoritatea cazurilor, contorul de repetare
are valoarea 1. Totuşi, dacă procedura de fereastră nu reuşeşte să prelucreze mesajele de apăsare a unei taste în ritmul de autorepetare (în mod
prestabilit aproximativ 10 caractere pe secundă), Windows combină mai multe mesaje WM_KEYDOWN sau WM_SYSKEYDOWN într-un singur mesaj
şi incrementează corespunzător contorul de repetare. Contorul de repetare are întotdeauna valoarea 1 pentru mesajele WM_KEYUP şi
WM_SYSKEYUP.

Deoarece o valoare mai mare decât 1 a contorului de repetare indică faptul că rata de autorepetare a tastelor depăşeşte posibilităţile de prelucrare
ale programului, există situaţii în care puteţi să ignoraţi contorul de repetare atunci când prelucraţi mesajele de la tastatură. Aproape toată lumea a
avut ocazia să vadă cum un procesor de texte sau un program de calcul tabelar derulează ecranul mai mult decât trebuie datorită comenzilor
suplimentare stocate în bufferul de tastatură. Ignorarea contorului de repetare va reduce foarte mult posibilitatea apariţiei unei asemenea situaţii.
În alte cazuri însă este bine să folosiţi contorul de repetare. Puteţi să încercaţi ambele variante şi să vedeţi pe care o preferaţi pentru programul
dumneavoastră.

Codul de scanare OEM

Codul de scanare OEM (OEM Scan Code) este codul de scanare al tastaturii, generat de componentele hardware. (Dacă aţi scris programe în limbaj
de asamblare, acest cod este identic cu cel transmis în registrul AH, în timpul întreruperii apelului BIOS 16H.) În general, aplicaţiile Windows ignoră
acest cod, deoarece există metode mai bune de decodificare a informaţiilor de la tastatură.

Indicatorul flag pentru taste extinse

Indicatorul flag pentru taste extinse (Extended Key Flag) are valoarea 1 dacă mesajul este generat de una dintre tastele suplimentare de pe
tastatura IBM extinsă. (Tastatura IBM extinsă are tastele funcţionale în partea de sus şi un bloc separat sau combinat de taste pentru tastele de
deplasare şi tastele numerice.) Acest indicator are valoarea 1 pentru tastele Alt şi Ctrl din partea dreaptă a tastaturii, pentru tastele de deplasare
(inclusiv tastele Insert şi Delete) care nu fac parte din blocul de taste numerice, pentru tastele Slash (/) şi Enter din blocul de taste numerice şi
pentru tasta Num Lock. În general, programele Windows ignoră acest indicator.

Codul de context
Codul de context (Context Code) are valoarea 1 dacă este apăsată tasta Alt. Acest bit va avea întotdeauna valoarea 1 pentru mesajele
WM_SYSKEYUP şi WM_SYSKEYDOWN şi valoarea 0 pentru mesajele WM_KEYUP si WM_KEYDOWN, cu două excepţii:

§ O fereastră activă redusă la o pictogramă nu deţine cursorul de intrare. Toate acţionările de taste generează mesaje WM_SYSKEYUP şi
WM_SYSKEYDOWN. Dacă tasta Alt nu este apăsată, bitul pentru codul de context are valoarea 0. (Windows foloseşte aceste mesaje astfel încât
fereastra activă redusă la o pictogramă să nu prelucreze mesajele de la tastatură.)

§ În cazul folosirii unui driver de tastatură pentru alte limbi decât limba engleză, unele caractere sunt generate prin combinarea tastelor Shift, Ctrl
sau Alt cu alte taste. În această situaţie, bitul pentru codul de context din variabila lParam care însoţeşte mesajele WM_KEYUP şi WM_KEYDOWN are
valoarea 1, dar mesajele nu reprezintă acţionări de taste de sistem.

Starea anterioară a tastei


Starea anterioară a tastei (Previous Key State) are valoarea 0 dacă tasta nu a fost anterior apăsată, şi valoarea 1 dacă tasta a fost apăsată. Are
întotdeauna valoarea 1 pentru mesajele WM_KEYUP şi WM_SYSKEYUP, dar poate fi 0 sau 1 pentru mesajele WM_KEYDOWN şi WM_SYSKEYDOWN.
Valoarea 1 indică faptul că s-a primit un mesaj generat de autorepetarea unei taste.

Starea de tranziţie
Starea de tranziţie (Transition State) are valoarea 0 dacă tasta este apăsată şi valoarea 1 dacă tasta este eliberată. Acest bit are valoarea 1 pentru
mesajele WM_KEYUP si WM_SYSKEYUP si valoarea 0 pentru mesajele WM_KEYDOWN şi WM_SYSKEYDOWN.

Coduri virtuale de taste

Deşi unele informaţii din parametrul lParam pot fi utile pentru prelucrarea mesajelor WM_KEYUP, WM_SYSKEYUP, WM_KEYDOWN şi
WM_SYSKEYDOWN, parametrul wParam este mult mai important. Acest parametru conţine codul virtual care identifică tasta apăsată sau eliberată.
Dezvoltatorii sistemului de operare Windows au încercat să definească tastele virtuale într-o manieră independentă de dispozitiv. Din acest motiv,
unele coduri virtuale de taste nu pot fi generate pe calculatoarele IBM PC şi compatibile cu acestea, dar pot fi întâlnite la tastaturile aparţinând altor
producători.

Codurile virtuale pe care le veţi folosi cel mai des au nume definite în fişierele antet din Windows. Tabelul de mai jos prezintă aceste nume,
împreună cu codurile numerice şi tastele de pe tastatura IBM care corespund tastelor virtuale. Deşi toate tastele generează mesaje de acţionare
(keystroke messages), tabelul nu include tastele pentru simboluri (cum ar fi / şi ?). Acestor taste le corespund coduri virtuale de la 128 în sus şi de
cele mai multe ori sunt definite diferit pentru tastaturile internaţionale. Puteţi să determinaţi aceste coduri virtuale folosind programul KEYLOOK,
prezentat în acest capitol, dar în general nu este necesar să prelucraţi mesajele de acţionare ale acestor taste.

                                                                                                  Coduri virtuale de taste


ZecimalHexa Identificator WINDOWS.H Necesar Tastatură IBM

1 01
VK_LBUTTON   

2 02 VK_RBUTTON    

3 03 VK_CANCEL v Ctrl-Break

4 04 VK_MBUTTON    

8 08 VK_BACK v Backspace

9 09 VK_TAB v Tab

12 0C VK_CLEAR v Tasta numerică 5 cu


tasta

        Num Lock inactivă

13 0D VK_RETURN v Enter

16 10 VK_SHIFT v Shift

17 11 VK_CONTROL v Ctrl

18 12 VK_MENU v Alt

(continuare)

Zecimal Hexa Identificator Necesar Tastatura IBM


WINDOWS.H

19 13 VK_PAUSE   Pause

20 14 VK_CAPITAL v Caps Lock

27 1B VK_ESCAPE v Esc

32 20 VK_SPACE v Bara de spaţiu

33 21 VK_PRIOR v Page Up

34 22 VK_NEXT v Page Down

35 23 VK_END   End

36 24 VK_HOME v Home
37 25 VK_LEFT v Săgeată stânga

38 26 VK_UP v Săgeată în sus

39 27 VK_RIGHT v Săgeată în dreapta

40 28 VK_DOWN v Săgeată în jos

41 29 VK_SELECT    

42 2A VK_PRINT    

43 2B VK_EXECUTE    

44 2C VK_SNAPSHOT   Print Screen

45 2D VK_INSERT Ú Insert

46 2E VK_DELETE Ú Delete

47 2F VK_HELP    

48-57 30-39   Ú De la 0 la 9 pe blocul principal de taste

65-90 41-5A   Ú De la A la Z

96 60 VK_NUMPAD0   Tasta numerică 0 cu tasta Num Lock activă

97 61 VK_NUMPAD1   Tasta numerică 1 cu tasta Num Lock activă

98 62 VK_NUMPAD2   Tasta numerică 2 cu tasta Num Lock activă

99 63 VK_NUMPAD3   Tasta numerică 3 cu tasta Num Lock activă

100 64 VK_NUMPAD4   Tasta numerică 4 cu tasta Num Lock activă

101 65 VK_NUMPAD5   Tasta numerică 5 cu tasta Num Lock activă

102 66 VK_NUMPAD6   Tasta numerică 6 cu tasta Num Lock activă

103 67 VK_NUMPAD7   Tasta numerică 7 cu tasta Num Lock activă

104 68 VK_NUMPAD8   Tasta numerică 8 cu tasta Num Lock activă

105 69 VK_NUMPAD9   Tasta numerică 9 cu tasta Num Lock activă

106 6A vk_multiply   Tasta numerică *

107 6B VK_ADD   Tasta numerică +

108 6C VK_SEPARATOR    

109 6D VK_SUBTRACT   Tasta numerică -

110 6E VK_DECIMAL   Tasta numerică .

111 6F VK_DIVIDE   Tasta numerică /

112 70 VK_F1 Ú Tasta funcţională F1

113 71 VK_F2 Ú Tasta funcţională F2

114 72 VK_F3 Ú Tasta funcţională F3

115 73 VK_P4 Ú Tasta funcţională F4

116 74 VK_F5 Ú Tasta funcţională F5

117 75 VK_F6 Ú Tasta funcţională F6

118 76 VK_F7 Ú Tasta funcţionată F7

119 77 VK_F8 Ú Tasta funcţională F8

120 78 VK_F9 Ú Tasta funcţională F9

121 79 VK_F10 Ú Tasta funcţională F10


122 7A VK_F11   Tasta funcţională F11 (tastatura extinsă)

123 7B VK_F12 .   Tasta funcţională F12 (tastatura extinsă)

124 7C VK_F13    

125 7D VK_F14    

126 7E VK_F15    

127 7F VK_F16    

144 90 VK_NUMLOCK   Num Lock

145 91 VK_SCROLL   Scroll Lock

Semnul de validare din coloana „Necesar" indică faptul că tasta respectivă este obligatorie pentru orice implementare Windows. De asemenea,
sistemul de operare cere ca tastatura şi driverul de tastatură să permită combinarea tastelor Ctrl, Shift sau a ambelor cu orice literă, orice tastă de
deplasare şi orice tastă funcţională. VK_LBUTTON, VK_MBUTTON şi VK_RBUTTON sunt codurile virtuale corespunzătoare buloanelor din stânga, din
mijloc şi din dreapta ale mouse-ului. Cu toate acestea, nu veţi primi niciodată mesaje pentru care parametrul wParam să conţină aceste valori.
Mouse-ul generează mesaje proprii, aşa cum vom vedea în capitolul următor.

Starea tastelor de modificare


Parametrii lParam şi wParam care însoţesc mesajele WM_KEYUP, WM_SYSKEYUP, WM_KEYDOWN şi WM_SYSKEYDOWN nu spun nimic programului
despre starea tastelor de modificare. Puteţi să obţineţi starea oricărei taste virtuale folosind funcţia GetKeyState. Această funcţie este folosită, de
obicei, pentru obţinerea stării tastelor de modificare (Shift, Alt şi Ctrl) şi a tastelor de comutare (Caps Lock, Num Lock şi Scroll Lock). De exemplu:

GetKeyState (VK_SHIFT) ;

returnează o valoare negativă (adică un număr în care primul bit are valoarea 1) dacă tasta Shift este apăsată. Valoarea returnată de apelul:

GetKeyState (VK_CAPITAL) ;

are un 1 în bitul cel mai puţin semnificativ dacă tasta Caps Lock este activă. De asemenea, puteţi să obţineţi starea butoanelor mouse-ului folosind
codurile virtuale VK_LBUTTON, VK_MBUTTON şi VK_RBUTTON. Totuşi, majoritatea programelor pentru Windows care trebuie să monitorizeze
combinaţiile între taste şi butoanele mouse-ului utilizează calea inversă - verificând starea tastelor atunci când primesc un mesaj de la mouse. De
fapt, starea tastelor de modificare este inclusă în mesajele primite de la mouse (aşa cum veţi vedea în capitolul următor).

Aveţi grijă cum folosiţi funcţia GetKeyState. Aceasta nu verifică starea tastaturii în timp real. GetKeyState returnează starea tastaturii până în
momentul mesajului curent. Funcţia GetKeyState nu vă permite să obţineţi informaţii despre starea tastaturii independent de mesajele obişnuite. De
exemplu, să presupunem că vreţi să întrerupeţi prelucrarea în procedura de fereastră până când utilizatorul apasă tasta

while (GetKeyState (VK_F1) >= 0) ; // Greşit!

Nu faceţi acest lucru! Programul trebuie să preia mesajul de la tastatură din coada de aşteptare, înainte ca funcţia GetKeyState să poată obţine
starea tastelor. De fapt, această sincronizare este în folosul dumneavoastră, deoarece puteţi să obţineţi starea corectă a tastelor de modificare în
timpul generării unui anumit mesaj de la tastatură, chiar dacă prelucraţi mesajul (şi apelaţi funcţia GetKeyState) după ce tasta de modificare
respectivă a fost eliberată. Dacă într-adevăr aveţi nevoie de starea curentă a unei taste, puteţi să folosiţi funcţia GetAsyncKeyState.

Utilizarea mesajelor de acţionare a tastelor

Ideea unui program care să obţină informaţii despre toate acţiunile exercitate asupra tastelor este drăguţă. Cu toate acestea, majoritatea
programelor Windows ignoră cea mai mare parte a acestor mesaje. Mesajele WM_SYSKEYUP şi WM_SYSKEYDOWN sunt folosite pentru funcţiile
sistemului, aşa că nu este nevoie să le interceptaţi. De asemenea, dacă prelucraţi mesajele WM_KEYDOWN, de obicei puteţi să ignoraţi mesajele
WM_KEYUP.

În general, programele pentru Windows folosesc mesajele WM_KEYDOWN pentru tastele care nu generează caractere. Deşi s-ar putea să vă vină
ideea să folosiţi mesajele pentru acţionări de taste în combinaţie cu informaţiile despre tastele de modificare (furnizate de funcţia GetKeyState) ca
să transformaţi mesajele pentru acţionări de taste în mesaje pentru caractere, nu faceţi acest lucru. Veţi avea probleme datorită diferenţelor între
tastaturile internaţionale. De exemplu, dacă primiţi un mesaj WM_KEYDOWN pentru care parametrul wParam are valoarea 33H, ştiţi că utilizatorul a
apăsat tasta 3. Până aici totul este bine. Dacă apelaţi funcţia GetKeyState veţi afla că tasta Shift este apăsată şi s-ar putea să presupuneţi că
utilizatorul a introdus un caracter diez (#), lucru care poate să nu fie adevărat. Un utilizator britanic ar fi introdus caracterul £. Mesajele
WM_KEYDOWN sunt utile pentru tastele de deplasare, tastele funcţionale şi tastele speciale, precum Insert, Delete. Uneori tastele Insert, Delete si
tastele funcţionale sunt folosite ca acceleratori pentru comenzi de meniu. Deoarece Windows transformă acceleratorii în comenzi de meniu, nu este
nevoie să prelucraţi în program mesajele pentru acţionarea tastelor de accelerare. Unele programe non-Windows folosesc foarte des tastele
funcţionale în combinaţie cu tastele Alt, Ctrl şi Shift. Puteţi face acest lucru şi în programele pentru Windows, dar nu este recomandat. Dacă vreţi să
folosiţi tastele funcţionale, acestea trebuie să dubleze comenzi din meniuri. Unul dintre obiectivele sistemului de operare Windows este să nu
oblige utilizatorii să memoreze sau să folosească liste complicate de comenzi.

Am reuşit să eliminăm aproape totul, cu excepţia unui singur lucru: de cele mai multe ori veţi prelucra mesajele WM_KEYDOWN numai pentru
tastele de deplasare a cursorului. Atunci când prelucraţi mesajele trimise de tastele de deplasare puteţi să verificaţi şi starea tastelor Shift şi Ctrl,
folosind funcţia GetKeyState. Unele funcţii Windows folosesc de multe ori tastele de deplasare în combinaţie cu tasta Shift pentru extinderea unei
selecţii - de exemplu într-un procesor de texte. Tasta Ctrl este folosită deseori pentru a schimba semnificaţia unei taste de deplasare. De exemplu,
tasta Ctrl în combinaţie cu săgeata spre dreapta mută cursorul cu un cuvânt la dreapta.

Una dintre cele mai bune metode de a vă hotărî cum să folosiţi tastatura este să studiaţi modul de utilizare a acesteia în programele Windows
existente. Dacă nu vă place cum este folosită, puteţi să faceţi ceva diferit. Dar nu uitaţi că orice noutate înseamnă creşterea perioadei de care are
nevoie un utilizator pentru a se obişnui cu programul dumneavoastră.

ÎMBUNĂTĂŢIREA PROGRAMULUI SYSMETS:

ADĂUGAREA INTERFEŢEI CU TASTATURA


Atunci când aţi scris cele trei versiuni ale programului SYSMETS prezentate în Capitolul 3 nu ştiaţi încă nimic despre tastatură. Puteaţi să derulaţi
textul numai folosind mouse-ul pe barele de derulare. Acum ştim să prelucrăm mesajele de la tastatură, aşa că este timpul să adăugăm programului
SYSMETS o interfaţă cu tastatură. Aceasta este, evident, sarcina tastelor de deplasare. Cea mai mare parte a tastelor de deplasare (Home, End, Page
Up, Page Down, săgeţile în sus şi în jos) vor fi folosite pentru derularea pe verticală. Săgeţile spre dreapta şi spre stânga vor fi folosite pentru
derularea pe orizontală, facilitate care în acest program este mai puţin importantă.

Adăugarea logicii de tratare a mesajului WM_KEYDOWN

O metodă evidentă de creare a unei interfeţe este adăugarea codului necesar pentru tratarea mesajului WM_KEYDOWN, dublând astfel logica de
tratare a mesajelor WM_VSCROLL şi WM_HSCROLL:

case WM_KEYDOWN :

iVscrollInc = iHscrollInc = 0 ;

               switch (wParam)

                              {

                              case VK_HOME:     // la fel ca HM_VSCROLL, SB_TOP

                                             iVscrollInc = -iVscrollPos ;

                                             break ;

                              case VK_END :       // la fel ca HM_VSCROLL, SB_BOTTOM:

                                             iVscrollInc = iVscrollMax - iVscrollPos ;                                

break ;

                              case VK_UP :                        // la fel ca HM_VSCROLL, SB_LINEUP :

                                             iVscrollInc =  -1 ;

                                             break ;

                              case VK_DOWN :   // la fel ca WM_VSCROLL, SB_LINEDOWN

                                             iVscrollInc = 1 ;

                                             break ;

                              case VK_PRIOR :   // la fel ca WM_VSCROLL, SB_PAGEUP

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

                                             break ;

                              case VK_NEXT :     // la fel ca WH_VSCROLL, SB_PAGEDOWN :

                                             iVscrollInc = max (1, cyClient / cyChar) ;

                                             break ;

                              case VK_LEFT :      // la fel ca WM_HSCROLL, SB_PAGEUP

                                             iHscrollInc = -8 ;

                                             break ;

                              case VK_RIGHT :    // la fel ca WM_HSCROLL, SB_PAGEDOWN

                                             iHscrollInc = 8 ;

                                             break ;
                              default :

                                             break ;

                              }

if (iVscrollInc = max (-iVscrollPos, min(iVscrollInc, iVscrollMax-iVscrollPos)))

               {             

iVscrollPos += iVscrollInc ;

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

               SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE);

UpdateWindow (hwnd) ;

               }

if (iHscrollInc = max (-iHscrollPos,min(iHscrollInc, iHscrollMax - iHscrollPos)))     

{             

iHscrollPos += iHscrollInc;

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

               SetScrollPos (hwnd, SB_ HORZ, iHscrollPos, TRUE) ;

               }

return 0 ;

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

Transmiterea mesajelor
Nu ar fi mai bine să transformăm mesajele WM_KEYDOWN în mesaje echivalente WM_VSCROLL şi WM_HSCROLL şi să facem cumva funcţia
WndProc să creadă că a primit mesajele WM_VSCROLL şi WM_HSCROLL, poate chiar transmiţând procedurii de fereastră aceste mesaje
contrafăcute? Windows vă permite să faceţi acest lucru. Funcţia necesară este SendMessage şi primeşte aceiaşi parametri ca şi procedura de
fereastră:

SendMessage (hwnd, message, wParam, lParam) ;

Atunci când apelaţi funcţia SendMessage, Windows apelează procedura ferestrei identificată de parametrul hwnd şi îi transmite cei patru parametri.
După ce procedura de fereastră încheie prelucrarea mesajului, Windows reia execuţia de la următoarea linie după apelul funcţiei SendMessage.
Procedura de fereastră căreia îi trimiteţi mesajul poate fi aceeaşi procedură de fereastră, o altă procedură de fereastră din acelaşi program sau chiar
o procedură de fereastră dintr-o altă aplicaţie.

Iată cum putem folosi funcţia SendMessage pentru prelucrarea mesajelor WM_KEYDOWN în programul SYSMETS:

case WH_KEYDOWN :

                              switch (wParam)

        case VK_HOME :

        SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L);

        break;

        case VK_END:

        SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L);

        break ;

       

        case VK_PRIOR :


        SendMessage (hwnd, WH_VSCROLL, SB_PAGEUP, 0L);

        break ;

        [alte linii de program]

OK, aţi prins ideea. Scopul nostru era să adăugăm o interfaţă cu tastatura pentru barele de derulare şi exact acest lucru l-am făcut. Am permis
folosirea tastelor de deplasare pentru a dubla logica barelor de derulare trimiţând procedurii de fereastră mesajele specifice barelor de derulare.
Acum vedeţi de ce am inclus în programul SYSMETS3 din Capitolul 3 secvenţa de tratare a codurilor SB_TOP şi SB_BOTTOM din mesajele
WM_VSCROLL. În momentul respectiv acestea nu erau folosite, dar acum permit tratarea tastelor Home şi End. Programul SYSMETS, prezentat în
Figura 5-2, include cele trei modificări discutate. Pentru compilarea programului aveţi nevoie şi de fişierul SYSMETS.H, prezentat în Figura 3-4 din
Capitolul 3.

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

# SYSMETS.MAK make file

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

sysmets.exe : sysmets.obj

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

sysmets.obj : sysmets.c sysmets.h

     $(CC) $(CFLAGS) sysmets.c

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

   SYSMETS.C -- System Metrics Display Program (Final)

                (c) Charles Petzold, 1996

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

#include <windows.h>

#include <string.h>

#include "sysmets.h"

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "SysMets" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;


     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "System Metrics",

                          WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,

                          CW_USEDEFAULT, CW_USEDEFAULT,

                          CW_USEDEFAULT, CW_USEDEFAULT,

                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;

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

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

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

     {

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

                 iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax ;

     char        szBuffer[10] ;

     HDC         hdc ;

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

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

 
     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

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

               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;

               iMaxWidth = 40 * cxChar + 22 * cxCaps ;

               return 0 ;

          case WM_SIZE :

               cxClient = LOWORD (lParam) ;

               cyClient = HIWORD (lParam) ;

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

               iVscrollPos = min (iVscrollPos, iVscrollMax) ;

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

               SetScrollPos   (hwnd, SB_VERT, iVscrollPos, TRUE) ;

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

               iHscrollPos = min (iHscrollPos, iHscrollMax) ;

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

               SetScrollPos   (hwnd, SB_HORZ, iHscrollPos, TRUE) ;

               return 0 ;

          case WM_VSCROLL :

               switch (LOWORD (wParam))

                    {

                    case SB_TOP :

                         iVscrollInc = -iVscrollPos ;

                         break ;

                    case SB_BOTTOM :


                         iVscrollInc = iVscrollMax - iVscrollPos ;

                         break ;

                    case SB_LINEUP :

                         iVscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iVscrollInc = 1 ;

                         break ;

                    case SB_PAGEUP :

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

                         break ;

                    case SB_PAGEDOWN :

                         iVscrollInc = max (1, cyClient / cyChar) ;

                         break ;

                    case SB_THUMBTRACK :

                         iVscrollInc = HIWORD (wParam) - iVscrollPos ;

                         break ;

                    default :

                         iVscrollInc = 0 ;

                    }

               iVscrollInc = max (-iVscrollPos,

                             min (iVscrollInc, iVscrollMax - iVscrollPos)) ;

               if (iVscrollInc != 0)

                    {

                    iVscrollPos += iVscrollInc ;

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

                    SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;

                    UpdateWindow (hwnd) ;

                    }

               return 0 ;

          case WM_HSCROLL :

               switch (LOWORD (wParam))


                    {

                    case SB_LINEUP :

                         iHscrollInc = -1 ;

                         break ;

                    case SB_LINEDOWN :

                         iHscrollInc = 1 ;

                         break ;

                    case SB_PAGEUP :

                         iHscrollInc = -8 ;

                         break ;

                    case SB_PAGEDOWN :

                         iHscrollInc = 8 ;

                         break ;

                    case SB_THUMBPOSITION :

                         iHscrollInc = HIWORD (wParam) - iHscrollPos ;

                         break ;

                    default :

                         iHscrollInc = 0 ;

                    }

               iHscrollInc = max (-iHscrollPos,

                             min (iHscrollInc, iHscrollMax - iHscrollPos)) ;

               if (iHscrollInc != 0)

                    {

                    iHscrollPos += iHscrollInc ;

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

                    SetScrollPos (hwnd, SB_HORZ, iHscrollPos, TRUE) ;

                    }

               return 0 ;

          case WM_KEYDOWN :

               switch (wParam)

                    {

                    case VK_HOME :

                         SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ;


                         break ;

                    case VK_END :

                         SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ;

                         break ;

                    case VK_PRIOR :

                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0L) ;

                         break ;

                    case VK_NEXT :

                         SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L) ;

                         break ;

                    case VK_UP :

                         SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0L) ;

                         break ;

                    case VK_DOWN :

                         SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0L) ;

                         break ;

                    case VK_LEFT :

                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEUP, 0L) ;

                         break ;

                    case VK_RIGHT :

                         SendMessage (hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L) ;

                         break ;

                    }

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

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

               iPaintEnd = min (NUMLINES,

                                iVscrollPos + ps.rcPaint.bottom / cyChar) ;

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


                    {

                    x = cxChar * (1 - iHscrollPos) ;

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

                    TextOut (hdc, x, y,

                             sysmetrics[i].szLabel,

                             strlen (sysmetrics[i].szLabel)) ;

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

                             sysmetrics[i].szDesc,

                             strlen (sysmetrics[i].szDesc)) ;

                    SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

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

                             szBuffer,

                             wsprintf (szBuffer, "%5d",

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

                    SetTextAlign (hdc, TA_LEFT | TA_TOP) ;

                    }

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

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

     }

Figura 5-2. Programul SYSMETS (final).

mesaje CARACTER
Am discutat mai devreme despre ideea transformării mesajelor generate de acţionarea tastelor în mesaje caracter ţinând cont de starea tastelor de
modificare şi v-am avertizat că acest lucru nu este suficient: trebuie să ţineţi seama şi de configuraţia diferită a tastaturii de la o ţară la alta. Din
acest motiv, nu trebuie să încercaţi să transformaţi dumneavoastră mesajele generate de acţionarea tastelor în mesaje caracter. Windows o poate
face în locul dumneavoastră. Aţi mai văzut această secvenţă de cod:

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


{

TranslateMessage (&msg);

DispatchMessage (&msg);

Acesta este un ciclu tipic de tratare a mesajelor din funcţia WinMain. Funcţia GetMessage preia următorul mesaj din coada de aşteptare şi
completează câmpurile structurii msg. Funcţia DispatchMessage transmite mesajul procedurii de fereastră corespunzătoare. Între cele două funcţii
este apelată funcţia TranslateMessage, care transformă mesajele generate de acţionarea tastelor în mesaje caracter. Dacă mesajul este
WM_KEYDOWN sau WM_SYSKEYDOWN şi dacă tasta apăsată, în funcţie de starea tastelor de modificare, generează un caracter, atunci funcţia
TranslateMessage inserează un mesaj caracter în coada de aşteptare. Acesta va fi următorul mesaj pe care îl va prelua funcţia GetMessage după
mesajul generat de acţionarea tastei. Există patru mesaje caracter:

  Caractere Caractere „moarte"

Caractere non-sistem WM_CHAR WM_DEADCHAR


Caractere sistem WM_SYSCHAR WM_SYSDEADCHAR

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

Mesaj Tastă sau cod

WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a

WM_KEYUP Tasta virtuală A

Dacă introduceţi un caracter A apăsând tasta Shift, apoi tasta A, eliberând tasta A şi apoi eliberând tasta Shift, procedura de fereastră primeşte cinci
mesaje:

Mesaj Tastă sau cod

WM_KEYDOWN Tasta virtuală VK_SHIFT


WM_KEYDOWN
WM_CHAR WM_KEYUP Tasta virtuală A
WM_KEYUP
Codul ASCII al caracterului a

Tasta virtuală A

Tasta virtuală VK_SHIFT

Tasta Shift nu generează un mesaj caracter. Dacă ţineţi tasta A apăsată până când intră în acţiune autorepetarea, veţi primi un mesaj caracter pentru
fiecare caracter WM_KEYDOWN:

Mesaj Tastă sau cod

WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a

WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a

WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a

WM_KEYDOWN Tasta virtuală A

WM_CHAR Codul ASCII al caracterului a


WM_KEYUP Tasta virtuală A

Dacă unul dintre mesajele WM_KEYDOWN are contorul de repetare mai mare decât 1, mesajul WM_CHAR va avea acelaşi contor de repetare.

Tasta Ctrl în combinaţie cu o literă generează codurile de control ASCII de la 01H (Ctrl+A) la 1AH (Ctrl+Z). Puteţi să folosiţi şi alte taste pentru
generarea acestor coduri de control. Tabelul următor prezintă valorile parametrului wParam dintr-un mesaj WM_CHAR pentru tastele care
generează coduri de control:

Tastă Cod ASCII Dublat de

Backspace 08H Ctrl+H

Tab 09H Ctrl+I

Ctrl+Enter 0AH Ctrl+J

Enter 0DH Ctrl+M

Esc 1BH Ctrl+[

Programele Windows folosesc uneori combinaţiile tastei Ctrl cu litere ca acceleratori pentru meniuri, caz în care literele respective nu sunt
transformate în mesaje caracter.

Mesajul WM_CHAR

Atunci când trebuie să prelucreze caracterele introduse de la tastatură (de exemplu, într-un procesor de texte sau într-un program de comunicaţii)
programul prelucrează mesajele WM_CHAR. Probabil doriţi să prelucrati într-un mod mai special tastele Backspace, Tab şi Enter (eventual si tasta de
salt la linie nouă) dar toate celelalte caractere sunt tratate la fel:

 case WH_CHAR :

switch (wParam)

case '\b' :                // backspace

[alte linii de program]

break ;

case '\f' :                 // tab

[alte linii de program]

break ;

case '\n' :                // salt la linie nouă

[alte linii de program]

break ;

case '\r' :                 // retur de car

[alte linii de program]

break ;

default :                  // cod de caractere

[alte linii de program]

break ;

return 0 ;
Acest fragment de program este asemănător cu secvenţele de cod pentru tratarea caracterelor dintr-un program MS-DOS obişnuit.

Mesaje pentru „caractere moarte"


De obicei, programele pot să ignore mesajele WM_DEADCHAR si WM_SYSDEADCHAR. Pe unele tastaturi, în afară de tastatura de tip american, o
serie de taste sunt folosite pentru adăugarea semnelor diacritice la o literă. Acestea se numesc „taste moarte" („dead keys") deoarece nu pot crea
singure caractere. De exemplu, pe o tastatura germană, în locul tastei +/= de pe tastatura americană se află o tastă moartă pentru accentul ascuţit
('') dacă nu este apăsată tasta Shift, si pentru accentul grav (`) dacă este apăsată tasta Shift.

Atunci când utilizatorul apasă această tastă moartă, procedura de fereastră primeşte un mesaj WM_DEADCHAR pentru care parametrul wParam
conţine codul ASCII al semnului diacritic. Dacă utilizatorul apasă apoi o literă (de exemplu, tasta A), procedura de fereastră primeşte un mesaj
WM_CHAR pentru care parametrul wParam conţine codul ASCII al literei „a" cu semnul diacritic respectiv. Ca urmare, programul nu trebuie să
prelucreze mesajul WM_DEADCHAR, deoarece mesajul WM_CHAR următor îi furnizează toate informaţiile necesare. Codul Windows se ocupă chiar
şi de tratarea erorilor: dacă tasta moartă este urmată de o literă care nu poate avea semnul diacritic respectiv (cum ar fi litera „s"), procedura de
fereastră primeşte două mesaje WM_CHAR - pentru primul parametrul wParam conţine codul ASCII al semnului diacritic, iar pentru al doilea
parametrul wParam conţine codul ASCII al literei „s".

Examinarea mesagelor de la tastatură

Dacă doriţi să vedeţi cum trimite sistemul de operare mesajele de la tastatură către o aplicaţie, programul KEYLOOK (prezentat în figura 5.3) vă
poate ajuta. Acest program afişează în zona client toate informaţiile pe care Windows le trimite procedurii de fereastră pentru opt mesaje diferite
de la tastatură.

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

# KEYLOOK.MAK make file

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

keylook.exe : keylook.obj

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

keylook.obj : keylook.c

     $(CC) $(CFLAGS) keylook.c

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

   KEYLOOK.C -- Displays Keyboard and Character Messages

                (c) Charles Petzold, 1996

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

#include <windows.h>

#include <stdio.h>

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

RECT rect ;

int  cxChar, cyChar ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "KeyLook" ;


     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Keyboard Message Looker",

                          WS_OVERLAPPEDWINDOW,

                          CW_USEDEFAULT, CW_USEDEFAULT,

                          CW_USEDEFAULT, CW_USEDEFAULT,

                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;

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

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

void ShowKey (HWND hwnd, int iType, char *szMessage,

              WPARAM wParam, LPARAM lParam)

     {

     static char *szFormat[2] = { "%-14s %3d    %c %6u %4d %3s %3s %4s %4s",
                                  "%-14s    %3d %c %6u %4d %3s %3s %4s %4s" } ;

     char        szBuffer[80] ;

     HDC         hdc ;

     ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ;

     hdc = GetDC (hwnd) ;

     SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

     TextOut (hdc, cxChar, rect.bottom - cyChar, szBuffer,

              wsprintf (szBuffer, szFormat [iType],

                        szMessage, wParam,

                        (BYTE) (iType ? wParam : ' '),

                        LOWORD (lParam),

                        HIWORD (lParam) & 0xFF,

                        (PSTR) (0x01000000 & lParam ? "Yes"  : "No"),

                        (PSTR) (0x20000000 & lParam ? "Yes"  : "No"),

                        (PSTR) (0x40000000 & lParam ? "Down" : "Up"),

                        (PSTR) (0x80000000 & lParam ? "Up"   : "Down"))) ;

     ReleaseDC (hwnd, hdc) ;

     ValidateRect (hwnd, NULL) ;

     }

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

     {

     static char szTop[] =

               "Message        Key Char Repeat Scan Ext ALT Prev Tran";

     static char szUnd[] =

               "_______        ___ ____ ______ ____ ___ ___ ____ ____";

     HDC         hdc ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;


 

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;

               rect.top = 3 * cyChar / 2 ;

               return 0 ;

          case WM_SIZE :

               rect.right  = LOWORD (lParam) ;

               rect.bottom = HIWORD (lParam) ;

               UpdateWindow (hwnd) ;

               return 0 ;

          case WM_PAINT :

               InvalidateRect (hwnd, NULL, TRUE) ;

               hdc = BeginPaint (hwnd, &ps) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               SetBkMode (hdc, TRANSPARENT) ;

               TextOut (hdc, cxChar, cyChar / 2, szTop, (sizeof szTop) - 1) ;

               TextOut (hdc, cxChar, cyChar / 2, szUnd, (sizeof szUnd) - 1) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_KEYDOWN :

               ShowKey (hwnd, 0, "WM_KEYDOWN", wParam, lParam) ;

               return 0 ;

          case WM_KEYUP :

               ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam) ;

               return 0 ;

          case WM_CHAR :

               ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam) ;

               return 0 ;

 
          case WM_DEADCHAR :

               ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam) ;

               return 0 ;

          case WM_SYSKEYDOWN :

               ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSKEYUP :

               ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSCHAR :

               ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_SYSDEADCHAR :

               ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam) ;

               break ;        // ie, call DefWindowProc

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

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

     }

Figura 5.3. Programul KEYLOOK

Programul KEYLOOK foloseşte ecranul după modelul teleimprimatorului. Atunci când primeşte un mesaj generat de acţionarea unei taste,
programul KEYLOOK apelează funcţia ScrollWindow, care derulează în sus conţinutul întregii zone client cu înălţimea unui caracter. Funcţia TextOut
este folosită pentru afişarea unei noi linii de informaţii, începând afişarea într-un punct aflat la o distanţă egală cu înălţimea unui caracter, faţă de
marginea de jos a ecranului. Este la fel de simplu ca şi în cazul unui teleimprimator. Figura 5-4 prezintă fereastra afişată de programul KEYLOOK
atunci când tastaţi cuvântul „Windows". Prima coloană conţine mesajele de la tastatură; a doua coloană conţine codurile de taste virtuale pentru
mesajele generate de acţionarea fastelor; a treia coloană conţine codul caracterului (şi caracterul); celelalte şase coloane prezintă starea celor şase
câmpuri din parametrul lParam al mesajului.

În cea mai mare parte, programul KEYLOOK foloseşte caracteristici Windows despre care deja am discutat în programele SYSMETS prezentate până
acum, dar sunt folosite şi câteva funcţii noi. Remarcaţi, totuşi, că formatarea coloanelor în programul KEYLOOK ar fi fost destul de dificilă dacă s-ar
fi folosit fontul prestabilit proporţional. Codul pentru afişarea fiecărei linii ar trebui împărţit în nouă secţiuni pentru a păstra coloanele aliniate.
Pentru rezolvarea unei astfel de situaţii o soluţie mai simplă este să folosiţi un font cu dimensiune fixă. Aşa cum am arătat în capitolul anterior,
pentru aceasta este nevoie de două funcţii, pe care le-am combinat într-o singură instrucţiune:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

Programul KEYLOOK apelează aceste funcţii de fiecare dată când obţine un context de dispozitiv. Acest lucru se întâmplă în trei locuri din program:
în funcţia ShowKey, în timpul prelucrării mesajului WM_CREATE din funcţia WndProc şi în timpul prelucrării mesajului WM_PAINT. Funcţia
GetStockObject obţine o variabilă handle a unui obiect grafic de stoc - care în acest caz este fontul de dimensiune fixă folosit în versiunile mai vechi
Windows 3.0. Funcţia SelectObject selectează obiectul în contextul de dispozitiv. După acest apel, orice text va fi afişat cu fontul de dimensiune fixă
selectat. Puteţi să vă întoarceţi la fontul proporţional prestabilit, cu următoarea instrucţiune:

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;


 

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

Funcţia ShowKey apelează funcţia ScrollWindow, care derulează liniile anterioare înaintea afişării unei noi linii. În mod normal, această operaţie ar
duce la invalidarea parţială a ferestrei şi ar generă un mesaj WM_PAINT. Pentru a împiedica acest lucru, înainte de terminare, funcţia ShowKey
apelează funcţia ValidateRect.

Programul KEYLOOK nu salvează mesajele pe care le primeşte, aşa că la primirea unui mesaj WM_PAINT nu poate să re-creeze fereastra. Din acest
motiv, la primirea mesajului WM_PAINT programul KEYLOOK afişează în partea superioară a ferestrei capetele celor nouă coloane. Înaintea apelării
funcţiei BeginPaint, programul KEYLOOK invalidează întreaga fereastră. În acest fel, este ştearsă toată suprafaţa ferestrei, nu numai dreptunghiul
invalid.

(Faptul că programul KEYLOOK nu salvează mesajele pe care le primeşte şi deci la primirea unui mesaj WM_PAINT nu poate să re-creeze fereastra
este un neajuns al programului. Programul TYPER, prezentat puţin mai târziu, corectează această eroare.)

Programul KEYLOOK afişează în partea superioară a zonei client un antet pentru identificarea celor nouă coloane. Deşi este posibilă crearea unui
font subliniat, în acest program am ales o abordare puţin diferită. Am definit două şiruri de caractere, numite szTop (care conţine textul antetului) şi
szUnd (care conţine liniuţele de subliniere) şi le-am afişat în aceeaşi poziţie în partea de sus a ecranului, în timpul prelucrării mesajului WM_PAINT.
În mod normal, Windows afişează textul în modul „opac" (OPAQUE), ceea ce înseamnă că şterge zona de fond din spatele caracterelor afişate. Din
această cauză, al doilea şir de caractere (szUnd) ar şterge primul şir de caractere (szTop). Pentru a împiedica acest lucru, am schimbat modul de
afişare în contextul de dispozitiv din „opac" în „transparent":

SetBkMode (hdc, TRANSPARENT) ;

cursorul DE EDITARE (CARET)

Atunci când introduceţi text într-un program, poziţia în care va apărea următorul caracter este indicată de o liniuţă de subliniere sau de un mic
dreptunghi, în Windows pentru acest semn se foloseşte termenul „cursor de editare".

Funcţii pentru cursorul de editare


Există cinci funcţii principale pentru cursorul de editare:

§  CreateCaret - creează un cursor de editare asociat unei ferestre.

§  SetCaretPos - stabileşte poziţia cursorului de editare în fereastră.

§  ShowCaret - afişează cursorul de editare.


§  HideCaret - maschează cursorul de editare.

§  DestroyCaret - distruge cursorul de editare creat.

Mai există şi alte funcţii pentru obţinerea poziţiei cursorului de editare (GetCaretPos) şi pentru stabilirea şi obţinerea intervalelor de licărire a
acestuia (SetCaretBlinkTime şi GetCaretBlinkTime).

Cursorul de editare este, de obicei, o linie ori un bloc orizontal de dimensiunea unui caracter, sau o linie verticală. Linia verticală este recomandată
în cazul folosirii unui font proportional, cum ar fi fontul sistem prestabilit din Windows. Deoarece caracterele din fonturile proporţionale nu au
aceeaşi lăţime, linia sau blocul orizontal nu poate avea lăţimea exactă a unui caracter.

Cursorul de editare nu poate fi creat pur şi simplu în timpul prelucrării mesajului WM_CREATE şi nici distrus în timpul prelucrării mesajului
WM_DESTROY. El este ceea ce se numeşte o „resursă de sistem". Aceasta înseamnă că în sistem există un singur cursor de editare. De fapt, atunci
când un program trebuie să afişeze un cursor de editare în fereastra proprie, el „împrumută" acest semn de la sistem.

Pare prea restrictiv? Ei bine, în realitate nu este. Gândiţi-vă puţin: afişarea unui cursor de editare într-o fereastră are sens numai dacă fereastra
respectivă deţine cursorul de intrare (input focus). Cursorul de editare indică utilizatorului faptul că poate introduce text în program. La un moment
dat, o singură fereastră poate deţine cursorul de intrare, aşa că existenţa unui singur cursor de editare este logică.

Un program poate determina dacă deţine cursorul de intrare prin prelucrarea mesajelor WM_SETFOCUS şi WM_KILLFOCUS. O procedură de
fereastră recepţionează un mesaj WM_SETFOCUS atunci când primeşte cursorul de intrare şi un mesaj WM_KILLFOCUS atunci când pierde cursorul
de intrare. Aceste mesaje sunt transmise în pereche. O procedură de fereastră primeşte întotdeauna un mesaj WM_SETFOCUS înainte de a primi un
mesaj WM_KILLFOCUS şi întotdeauna va primi un număr egal de mesaje WM_SETFOCUS şi WM_KILLFOCUS pană la distrugerea ferestrei.

Principala regulă de folosire a cursorului de editare este simplă. O procedură de fereastră apelează funcţia CreateCaret în timpul prelucrării
mesajului WM_SETFOCUS şi funcţia DestroyCaret în timpul prelucrării mesajului WM_KILLFOCUS.

Există şi alte câteva reguli: la creare, cursorul de editare nu este afişat. După apelarea funcţiei CreateCaret, programul trebuie să apeleze funcţia
ShowCaret pentru a face vizibil cursorul de editare. În plus, procedura de fereastră trebuie să-l mascheze, apelând funcţia HideCaret, ori de câte ori
desenează ceva pe ecran în timpul prelucrării unui alt mesaj decât WM_PAINT. După terminarea operaţiei de desenare, programul apelează funcţia
ShowCaret ca să afişeze din nou cursorul de editare. Efectul funcţiei HideCaret este aditiv: dacă apelaţi funcţia HideCaret de mai multe ori fără să
apelaţi funcţia ShowCaret, atunci când doriţi ca acest cursor de editare să devină din nou vizibil, trebuie să apelaţi funcţia ShowCaret de tot atâtea
ori de cate ori aţi apelat şi funcţia HideCaret.

Programul TYPER

Programul TYPER, prezentat în Figura 5-5, combină cea mai mare parte a lucrurilor învăţate în acest capitol. Puteţi să vă gândiţi la programul TYPER
ca la un editor de text mai rudimentar. Puteţi să introduceţi text în fereastră, să mutaţi cursorul de editare cu ajutorul tastelor de deplasare şi să
ştergeţi conţinutul ferestrei apăsând tasta Esc. Conţinutul ferestrei este şters, de asemenea, şi atunci când fereastra este redmensionată. Nu există
posibilitatea de derulare, de căutare sau de înlocuire, de salvare a fişierelor sau de verificare ortografică, dar putem spune că este un început!

Pentru a face lucrurile mai uşoare pentru mine, TYPER foloseşte un font cu dimensiune fixă. Aşa cum vă puteţi imagina, scrierea unui editor pentru
un font proportional este mult mai dificilă. Programul obţine un context de dispozitiv în timpul prelucrării mai multor mesaje: WM_CREATE,
WM_KEYDOWN, WM_CHAR si WM_PAINT. De fiecare dată, prin apelarea funcţiilor GetStockObject şi SelectObject, este selectat în contextul de
dispozitiv un font cu dimensiune fixă.

În timpul prelucrării mesajului WM_SIZE, TYPER calculează lăţimea şi înălţimea în caractere a ferestrei şi salvează aceste valori în variabilele cxBuffer
şi cyBuffer. Apoi apelează funcţia malloc pentru alocarea unei zone de memorie suficient de mare pentru a conţine toate caracterele care pot fi
introduse într-o fereastră. Variabilele xCaret şi yCaret sunt folosite pentru stocarea poziţiei cursorului de editare.

În timpul prelucrării mesajului WM_SETFOCUS, TYPER apelează funcţia CreateCaret pentru crearea unui semn caret cu dimensiunile unui caracter,
apoi funcţia SetCaretPos pentru stabilirea poziţiei cursorului de editare şi funcţia ShowCaret, pentru a-l face vizibil. În timpul prelucrării mesajului
WM_KILLFOCUS, TYPER apelează funcţiile HideCaret şi DestroyCaret.

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

# TYPER.MAK make file

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

typer.exe : typer.obj

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

typer.obj : typer.c

     $(CC) $(CFLAGS) typer.c


 

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

   TYPER.C -- Typing Program

              (c) Charles Petzold, 1996

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

#include <windows.h>

#include <stdlib.h>

#define BUFFER(x,y) *(pBuffer + y * cxBuffer + x)

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "Typer" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;

                wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

                wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

     RegisterClassEx (&wndclass) ;

     hwnd = CreateWindow (szAppName, "Typing Program",

                          WS_OVERLAPPEDWINDOW,

                          CW_USEDEFAULT, CW_USEDEFAULT,

                          CW_USEDEFAULT, CW_USEDEFAULT,


                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;

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

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

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

     {

     static char *pBuffer = NULL ;

     static int  cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer,

                 xCaret, yCaret ;

     HDC         hdc ;

     int         x, y, i ;

     PAINTSTRUCT ps ;

     TEXTMETRIC  tm ;

     switch (iMsg)

          {

          case WM_CREATE :

               hdc = GetDC (hwnd) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;

               cxChar = tm.tmAveCharWidth ;

               cyChar = tm.tmHeight ;

               ReleaseDC (hwnd, hdc) ;

               return 0 ;

          case WM_SIZE :

                                   // obtain window size in pixels

               cxClient = LOWORD (lParam) ;


               cyClient = HIWORD (lParam) ;

                                   // calculate window size in characters

               cxBuffer = max (1, cxClient / cxChar) ;

               cyBuffer = max (1, cyClient / cyChar) ;

                                   // allocate memory for buffer and clear it

               if (pBuffer != NULL)

                    free (pBuffer) ;

               if ((pBuffer = (char *) malloc (cxBuffer * cyBuffer)) == NULL)

                    MessageBox (hwnd, "Window too large.  Cannot "

                                      "allocate enough memory.", "Typer",

                                      MB_ICONEXCLAMATION | MB_OK) ;

               else

                    for (y = 0 ; y < cyBuffer ; y++)

                         for (x = 0 ; x < cxBuffer ; x++)

                              BUFFER(x,y) = ' ' ;

                                   // set caret to upper left corner

               xCaret = 0 ;

               yCaret = 0 ;

               if (hwnd == GetFocus ())

                    SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;

          case WM_SETFOCUS :

                                   // create and show the caret

               CreateCaret (hwnd, NULL, cxChar, cyChar) ;

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               ShowCaret (hwnd) ;

               return 0 ;

          case WM_KILLFOCUS :

                                   // hide and destroy the caret


               HideCaret (hwnd) ;

               DestroyCaret () ;

               return 0 ;

          case WM_KEYDOWN :

               switch (wParam)

                    {

                    case VK_HOME :

                         xCaret = 0 ;

                         break ;

                    case VK_END :

                         xCaret = cxBuffer - 1 ;

                         break ;

                    case VK_PRIOR :

                         yCaret = 0 ;

                         break ;

                    case VK_NEXT :

                         yCaret = cyBuffer - 1 ;

                         break ;

                    case VK_LEFT :

                         xCaret = max (xCaret - 1, 0) ;

                         break ;

                    case VK_RIGHT :

                         xCaret = min (xCaret + 1, cxBuffer - 1) ;

                         break ;

                    case VK_UP :

                         yCaret = max (yCaret - 1, 0) ;

                         break ;

                    case VK_DOWN :

                         yCaret = min (yCaret + 1, cyBuffer - 1) ;

                         break ;

                    case VK_DELETE :


                         for (x = xCaret ; x < cxBuffer - 1 ; x++)

                              BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ;

                         BUFFER (cxBuffer - 1, yCaret) = ' ' ;

                         HideCaret (hwnd) ;

                         hdc = GetDC (hwnd) ;

                         SelectObject (hdc,

                              GetStockObject (SYSTEM_FIXED_FONT)) ;

                         TextOut (hdc, xCaret * cxChar, yCaret * cyChar,

                                  & BUFFER (xCaret, yCaret),

                                  cxBuffer - xCaret) ;

                         ShowCaret (hwnd) ;

                         ReleaseDC (hwnd, hdc) ;

                         break ;

                    }

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;

          case WM_CHAR :

               for (i = 0 ; i < (int) LOWORD (lParam) ; i++)

                    {

                    switch (wParam)

                         {

                         case '\b' :                    // backspace

                              if (xCaret > 0)

                                   {

                                   xCaret-- ;

                                   SendMessage (hwnd, WM_KEYDOWN,

                                                VK_DELETE, 1L) ;

                                   }

                              break ;

                         case '\t' :                    // tab

                              do

                                   {
                                   SendMessage (hwnd, WM_CHAR, ' ', 1L) ;

                                   }

                              while (xCaret % 8 != 0) ;

                              break ;

                         case '\n' :                    // line feed

                              if (++yCaret == cyBuffer)

                                   yCaret = 0 ;

                              break ;

                         case '\r' :                    // carriage return

                              xCaret = 0 ;

                              if (++yCaret == cyBuffer)

                                   yCaret = 0 ;

                              break ;

                         case '\x1B' :                  // escape

                              for (y = 0 ; y < cyBuffer ; y++)

                                   for (x = 0 ; x < cxBuffer ; x++)

                                        BUFFER (x, y) = ' ' ;

                              xCaret = 0 ;

                              yCaret = 0 ;

                              InvalidateRect (hwnd, NULL, FALSE) ;

                              break ;

                         default :                       // character codes

                              BUFFER (xCaret, yCaret) = (char) wParam ;

                              HideCaret (hwnd) ;

                              hdc = GetDC (hwnd) ;

                              SelectObject (hdc,

                                   GetStockObject (SYSTEM_FIXED_FONT)) ;

                              TextOut (hdc, xCaret * cxChar, yCaret * cyChar,

                                       & BUFFER (xCaret, yCaret), 1) ;

 
                              ShowCaret (hwnd) ;

                              ReleaseDC (hwnd, hdc) ;

                              if (++xCaret == cxBuffer)

                                   {

                                   xCaret = 0 ;

                                   if (++yCaret == cyBuffer)

                                        yCaret = 0 ;

                                   }

                              break ;

                         }

                    }

               SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;

               return 0 ;

          case WM_PAINT :

               hdc = BeginPaint (hwnd, &ps) ;

               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               for (y = 0 ; y < cyBuffer ; y++)

                    TextOut (hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer) ;

               EndPaint (hwnd, &ps) ;

               return 0 ;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }

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

     }

Figura 5.5. Programul TYPER

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

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

Aşa cum se poate vedea în Figura 5.6, am folosit programul TYPER atunci când am lucrat la discursuri.

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

setul DE CARACTERE windows

Am menţionat anterior că apăsarea unei taste moarte înaintea tastei corespunzătoare unei litere precedată de o tasta moartă generează un mesaj
WM_CHAR pentru care parametrul wParam conţine codul ASCII al caracterului cu semnul diacritic. S-ar putea ca această afirmaţie să nu fie prea
clară, deoarece setul de caractere ASCII nu conţine nici un cod pentru caractere cu semne diacritice. Atunci, ce conţine parametrul wParam? Pentru
a răspunde la această întrebare trebuie să discutăm despre seturile de caractere, un subiect care, deşi la prima vedere pare a-şi avea mai degrabă
locul într-o discuţie despre fonturi, are importanţa lui şi pentru manipularea tastaturii.

Setul de caractere ASCII standard pe 7 biţi defineşte codurile de la 0 la 31 (0x1F) şi codul 127 (0x7F) pentru caracterele de control şi codurile de la
32 (0x20) la 126 (0x7E) pentru caracterele afişabile. Nici unul dintre aceste caractere nu are semne diacritice. Deoarece calculatoarele personale
folosesc octeţi de câte opt biţi, producătorii de calculatoare definesc un set de caractere care conţine 256 de coduri în loc de 128. Codurile
suplimentare pot conţine caractere cu semne diacritice. „Setul de caractere extins" rezultat include şi setul de caractere ASCII cu coduri de la 0 la
127.

Dacă Windows ar accepta un asemenea set de caractere extinse, afişarea caracterelor cu diacritice ar fi foarte simplă, dar Windows nu acceptă un
set de caractere extins, ci două. Din nefericire, însă, prezenţa celor două seturi de caractere nu face lucrurile de două ori mai uşoare.

Setul de caractere OEM

Mai întâi, haideţi să revedem componentele hardware cu care lucrează sistemul de operare Windows - calculatoarele IBM PC şi compatibile. La
începutul anilor '80, dezvoltatorii calculatoarelor IBM PC au decis să extindă setul de caractere ASCII, aşa cum se poate vedea în Figura 5.7. Codurile
de la 0x20 la 0x7E sunt caracterele afişabile din setul de caractere ASCII. Celelalte sunt nestandard - sau cel puţin aşa erau în momentul respectiv.
Acest set de caractere nu poate fi ignorat. Este codificat hardware în cipurile ROM ale plăcilor grafice, imprimantelor şi circuitelor BIOS ale plăcilor
de bază IBM. A fost copiat de numeroşii producători de calculatoare şi periferice compatibile IBM. Acest set de caractere face parte din ceea ce se
numeşte „standardul IBM". Multe programe non-Windows în mod caracter scrise pentru calculatoarele IBM PC au nevoie de acest set de caractere,
deoarece folosesc pentru afişare caracterele de desenare a blocurilor şi a liniilor (codurile de la B0H la DFH).

Singura problemă este faptul că setul extins de caractere definit de IBM nu este potrivit pentru Windows. În primul rând, caracterele de desenare a
blocurilor şi a liniilor folosite de programele în mod text nu sunt necesare sub Windows, deoarece Windows lucrează în mod grafic. Dacă vreţi să
desenaţi o linie orizontală, în Windows este mai uşor să apelaţi o funcţie de desenare decât să afişaţi un şir de caractere 0xC4. În al doilea rând,
alfabetul grecesc şi simbolurile matematice sunt mai puţin importante în Windows decât literele accentuate, folosite de majoritatea limbilor
europene. Un program care trebuie să afişeze simboluri matematice o poate face mai uşor folosind funcţii grafice.

Pe scurt, sistemul de operare Windows acceptă setul de caractere IBM, dar îi acordă o importanţă mai mică - în principal pentru aplicaţii mai vechi,
rulate în ferestre. În mod normal, aplicaţiile Windows nu folosesc setul de caractere IBM. În documentaţia Windows, setul de caractere IBM este
numit „setul de caractere OEM". Setul de caractere OEM este mai precis definit ca setul de caractere nativ pe calculatorul pe care rulează sistemul
de operare Windows.

Asigurarea suportului internaţional sub MS-DOS

Există mai multe variante ale setului de caractere IBM PC, variante numite „pagini de cod ". Varianta folosită în Statele Unite şi în majoritatea ţărilor
europene este pagina 437. Calculatoarele vândute în Norvegia, Danemarca, Portugalia şi alte câteva ţări europene folosesc alte pagini, care conţin
multe dintre caracterele speciale folosite în ţările respective. În ultimul timp, o parte dintre aceste ţări au început să folosescă pagina 850, care
conţine mai puţine caractere grafice şi mai multe litere cu accent şi alte caractere speciale.

Windows asigură suportul pentru paginile de coduri prin instalarea fonturilor OEM (folosite pentru rularea aplicaţiilor MS-DOS în ferestre şi pentru
afişarea memoriei clipboard) corespunzătoare paginii de coduri a sistemului şi prin instalarea tabelelor de conversie corespunzătoare pentru
funcţiile CharToOem şi OemToChar (despre care vom discuta ceva mai târziu).

Programul de instalarea Windows Setup va selecta paginile de cod corespunzătoare, în funcţie de atributele regionale ale configuraţiei curente a
sistemului.

Setul de caractere ANSI

Setul extins de caractere folosit de obicei de Windows şi de programele Windows se numeşte „setul de caractere ANSI" şi este, de fapt, un standard
ISO. Atunci când programul dumneavoastră primeşte un mesaj WM_CHAR, parametrul wParam conţine codul ANSI al caracterului. Setul de
caractere ANSI este prezentat în Figura 5.8. Aşa cum se poate vedea, codurile de la 0x20 la 0x7E reprezintă aceleaşi caractere ca şi în setul de
caractere OEM şi în setul de caractere ASCII. Caracterele afişate sub forma unor blocuri nu sunt definite. Acestea pot apărea diferit pentru
Figura 5.7. Setul IBM de caractere extinse, aranjate după codul caracterelor.
Figura 5.8. Setul ANSI de caractere extinse, aranjate după codul caracterelor.

alte dispozitive de ieşire (cum ar fi imprimanta). Fonturile TrueType definesc unele caractere suplimentare pentru codurile ANSI de la 0x80 la 0x9F.

OEM, ANSI şi fonturile

Windows conţine diferite fonturi pentru afişarea seturilor de caractere ANSI şi OEM. Atunci când obţineţi pentru prima dată o variabilă handle a
unui context de dispozitiv, unul dintre atributele acestuia este fontul selectat, în mod prestabilit, acesta este SYSTEM_FONT („fontul sistem") care
foloseşte setul de caractere ANSI. Dacă vreţi să afişaţi caractere din setul OEM, puteţi să selectaţi fontul OEM_FIXED_FONT (numit şi „font de
terminal") în contextul de dispozitiv, folosind următoarea instrucţiune:

SelectObject (hdc, GetStockObject (OEM_FIXED_FONT)) ;

PROBLEME LEGATE DE INTERNAŢIONALIZARE


Am văzut că atunci când un utilizator Windows cu o tastatură diferită de cea definită pentru Statele Unite introduce un caracter cu semn diacritic,
parametrul wPavam al mesajului WM_CHAR conţine codul ANSI al caracterului respectiv.

Ca urmare, dacă trebuie să afişaţi pe ecran caracterul respectiv, este bine să folosiţi un font pentru setul de caractere ANSI (cum ar fi SYSTEM_FONT
sau SYSTEM_FIXED_FONT). Dacă folosiţi fontul OEM_FIXED_FONT, caracterul afişat pe ecran va fi incorect şi îl va surprinde pe utilizator. Alte câteva
reguli simple vă vor ajuta să păstraţi nemodificat codul de manipulare a tastaturii atunci când scrieţi un program pentru piaţa europeană.
Folosirea seturilor de caractere
Atunci când primiţi un mesaj WM_CHAR, ţineţi seama de faptul că parametrul wParam poate avea şi valori mai mari de 128. Nu porniţi de la ideea
că orice cod mai mare de 128 reprezintă un caracter invalid.

S-ar putea să aveţi uneori nevoie de un caracter scris cu majusculă. Nu folosiţi un algoritm propriu, cum ar fi:

if (ch = 'a' && ch <= 'z') ch -=32 ;                  // Greşit!!!

Aceasta este o metodă greşită în programarea sub Windows. Dar nu folosiţi nici funcţiile standard de conversie din C:

ch = toupper (ch) ;          // Greşit!!!

Rezultatele acestor funcţii sunt corecte numai pentru prima jumătate a setului de caractere. Funcţiile standard din C nu vor converti codul 0xE0 în
0xC0.

În schimb, puteţi să folosiţi funcţiile Windows CharUpper şi CharLower. Dacă pString este un şir de caractere terminat cu zero, puteţi să îl convertiţi
în litere mari folosind funcţia CharUpper:

CharUpper (pString) ;

Dacă şirul de caractere nu este terminat cu zero folosiţi funcţia CharUpperBuff:

CharUpperBuff (pString, nLength) ;

Puteţi să folosiţi funcţia CharUpper şi pentru un singur caracter, dar sunt necesare unele conversii forţate de tip, deoarece cuvântul mai semnificativ
al parametrului trebuie să aibă valoarea 0:

ch = CharUpper ((PSTR) (LONG) (BYTE) ch) ;

Dacă ch este definit ca un caracter fără semn (unsigned character) prima conversie forţată (BYTE) nu este necesară. Windows include şi funcţiile
CharLower şi CharLowerBuff pentru convertirea şirurilor de caractere în litere mici.

Dacă vă gândiţi cu seriozitate la scrierea unor programe Windows care să poată fi modificate cu uşurinţă pentru alte limbi, ar trebui să examinaţi şi
funcţiile CharNext şi CharPrev. Aceste funcţii vă ajută să manipulaţi seturile de caractere pe mai mulţi octeţi, deseori folosite în ţările orientale.
Aceste seturi conţin mai mult de 256 de caractere, dintre care unele folosesc doi octeţi. Dacă vă bazaţi pe aritmetica de pointeri normală din C
pentru parcurgerea unui şir de caractere (de exemplu pentru căutarea unui caracter backslash într-o cale de acces la un director) s-ar putea să
credeţi că aţi găsit caracterul căutat şi, de fapt, să folosiţi al doilea caracter al unui cod. Funcţiile CharNext şi CharPrev primesc ca parametru un
pointer de tip far la un şir de caractere şi returnează un pointer de tip far corect incrementat sau decrementat pentru codurile de caractere formate
din doi octeţi.

Comunicarea cu MS-DOS

Dacă Windows ar fi singurul sistem de operare rulat pe calculator, aţi putea să uitaţi de setul de caractere OEM şi să folosiţi numai setul de
caractere ANSI. Dar utilizatorii pot să creeze fişiere în MS-DOS şi să le folosească în Windows, sau să creeze fişiere în Windows şi să le folosească în
MS-DOS. Din păcate, sistemul de operare MS-DOS foloseşte setul de caractere OEM.

Iată o posibilă problemă de comunicare. Să presupunem că utilizatorul unui calculator personal, vorbitor de limbă germană, creează un fişier numit
UBUNGEN.TXT („exerciţii practice") într-un program MS-DOS, cum ar fi EDIT. Pe un calculator IBM PC caracterul U face parte din setul de caractere
IBM (adică OEM) şi are codul 154 sau 0x9A. (Folosind tastatura de tip american sub MS-DOS pe un calculator IBM PC puteţi să creaţi această literă
introducând codul Alt+154 folosind cifrele din blocul de taste numerice.) MS-DOS foloseşte acest cod de caracter pentru numele fişierului din
director.

Dacă un program Windows apelează o funcţie MS-DOS ca să obţină fişierele dintr-un director şi afişează numele fişierelor pe ecran folosind un font
pentru setul de caractere ANSi, prima literă din numele fişierului - UBUNGEN.TXT va fi afişată ca un bloc negru, deoarece codul 154 este unul dintre
caracterele nedefinite din setul de caractere ANSI. Programul Windows trebuie să convertească codul extins IBM 154 (0x9A) în codul ANSI 220
(0xDC), care reprezintă litera U în setul de caractere ANSI. Acest lucru este făcut de funcţia Windows OemToChar. Funcţia OemToChar primeşte ca
parametri doi pointeri de tip far către două şiruri de caractere. Caracterele OEM din primul şir sunt convertite în caractere ANSI şi sunt stocate în cel
de-al doilea şir:

OemToChar (lpszOemStr, lpszAnsiStr) ;

Acum să vedem acelaşi exemplu, dar în sens invers. Vorbitorul de limbă germană doreşte ca programul Windows să creeze un fişier numit
UBUNGEN.TXT. Primul caracter al numelui de fişier introdus de utilizator are codul 220 (0xDC). Dacă folosiţi o funcţie MS-DOS pentru deschiderea
fişierului, MS-DOS foloseşte caracterul respectiv în numele fişierului. Dacă ulterior utilizatorul vrea să vadă numele fişierului sub MS-DOS, primul
caracter va fi afişat sub forma unui bloc. Înaintea apelării funcţiei MS-DOS trebuie să convertiţi numele fişierului în caractere OEM:

CharToOem (IpszAnsiStr, IpszOemStr) ;

Funcţia CharToOem converteşte caracterul 220 (0xDC) în caracterul 154 (0x9A). În Windows există două funcţii cu acelaşi rezultat, CharToOemBuff şi
OemToCharBuff, pentru care, însă, şirurile de caractere nu trebuie să se termine cu 0.
Conversiile de mai sus sunt făcute şi de funcţia Windows OpenFile. Dacă folosiţi funcţia OpenFile nu apelaţi şi funcţia CharToOem. Dacă folosiţi
funcţii MS-DOS ca să obţineţi lista de fişiere (aşa cum face programul Windows File Manager), atunci numele fişierelor trebuie convertite cu funcţia
OemToChar, înainte de a fi afişate.

Convertirea conţinutului fişierelor este o altă problemă care apare atunci când fişierele sunt folosite atât sub Windows, cât şi sub MS-DOS. Dacă
programul Windows foloseşte fişiere despre care ştiţi cu siguranţă că au fost create într-un program MS-DOS, atunci conţinutul acestora ar trebui
să fie convertit cu ajutorul funcţiei OemToChar. La fel, dacă programul dumneavoastră creează fişiere care vor fi folosite de un program MS-DOS,
trebuie să folosiţi funcţia CharToOem ca să convertiţi conţinutul acestora.

Funcţiile OemToChar şi CharToOem sunt stocate în driverul de tastatură şi conţin tabele de căutare foarte simple. Funcţia OemToChar converteşte
codurile OEM de Ia 0x80 la 0xFF într-un cod ANSI reprezentând caracterul care seamănă cel mai mult cu caracterul OEM corespunzător, în unele
cazuri această conversie este aproximativă. De exemplu, majoritatea caracterelor de desenare din setul de caractere IBM sunt convertite în semne
plus, liniuţe de despărţire şi linii verticale. Majoritatea codurilor OEM de la 0x00 la 0x1F nu sunt convertite în coduri ANSI.

Funcţia CharToOem converteşte codurile ANSI de Ia 0xA0 la 0xFF în coduri din setul OEM. Literele accentuate din setul de caractere ANSI care nu
apar în setul de caractere OEM sunt convertite în caracterul ASCII corespunzător, fără semnele diacritice.

Folosirea blocului de taste numerice


După cum probabil ştiţi, tastatura IBM PC şi sistemul BIOS vă permit să introduceţi coduri pentru caracterele din setul extins IBM; astfel, apăsaţi
tasta Alt, introduceţi cu ajutorul tastelor numerice codul din trei cifre al caracterului OEM, apoi eliberaţi tasta Alt. În Windows, această posibilitate
este dublată în două feluri.

În primul rând, atunci când introduceţi codul Alt+[cod OEM] de la tastele numerice, Windows returnează (prin parametrul wParam al mesajului
WM_CHAR) codul caracterului ANSI care seamănă cel mai mult cu caracterul al cărui cod OEM l-aţi introdus. Aceasta înseamnă că Windows trece
codul prin funcţia OemToChar înainte de generarea mesajului WM_CHAR. Această operaţie se face în folosul utilizatorului. Dacă nu aveţi o tastatură
pentru limba străină pe care doriţi să o folosiţi şi sunteţi obişnuit să introduceţi caracterul U folosind combinaţia de taste Alt+154, puteţi să faceţi
acelaşi lucru şi în Windows. Nu este nevoie să învăţaţi şi codurile ANSI.

În al doilea rând, dacă vreţi să generaţi un cod ANSI extins folosind tastatura de tip american, introduceţi codul Alt+0[cod ANSI] de la blocul de
taste numerice. Parametrul wParam al mesajului WM_CHAR va conţine codul ANSI respectiv. Ca urmare, Alt+0220 este tot U. Puteţi să încercaţi
acest lucru în programele KEYLOCK sauTYPER.

Soluţia Unicode pentru Windows NT

Dezvoltatorii de programe implicaţi în crearea unor aplicaţii pentru piaţa internaţională au fost nevoiţi să inventeze diferite soluţii pentru rezolvarea
deficienţelor codului ASCII pe 7 biţi, cum ar fi paginile de coduri sau seturile de caractere pe doi octeţi. Este nevoie de o soluţie mai bună, şi aceasta
ar putea fi Unicode.

Unicode este un standard de codificare a caracterelor, care foloseşte un cod uniform pe 16 biţi pentru fiecare caracter. Acest cod permite
reprezentarea tuturor caracterelor, din toate limbajele scrise, care pot fi folosite în comunicaţiile prin calculator, inclusiv simbolurile chinezeşti,
japoneze şi coreene. Unicode a fost dezvoltat de un consorţiu de companii din industria calculatoarelor (inclusiv cele mai mari dintre acestea).

Din nefericire, sistemul de operare Windows 95 asigură doar un suport rudimentar pentru standardul Unicode şi nu poate furniza caractere
Unicode prin driverul de tastatură. Aceasta este una dintre diferenţele faţă de Windows NT, care include suportul pentru standardul Unicode.

Evident, adaptarea programelor (şi a modului de gândire al programatorilor) la ideea folosirii codurilor pe 16 biţi este o sarcină laborioasă, dar
merită dacă în acest fel vom putea afişa pe ecran şi vom putea tipări la imprimantă texte în toate limbajele scrise existente.

Last modified: Wednesday, 6 May 2020, 3:43 PM

◄ Elemente de grafică Jump to... Totul despre mouse ►

You are logged in as Mihail Curchi (Log out)


PWINDOWS
Data retention summary
Get the mobile app

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