Sunteți pe pagina 1din 33

Controale de tip fereastră descendent

În Capitolul 6 au fost prezentate programele din seria CHECKER, care afişează o grilă de dreptunghiuri.
Atunci când executaţi clic într-unul dintre dreptunghiuri, programul afişează un X. Dacă executaţi din
nou clic în dreptunghiul respectiv, X-ul dispare. Deşi versiunile CHECKER1 şi CHECKER2 ale acestui
program folosesc numai fereastra principală, versiunea CHECKER3 foloseşte o fereastră descendent
pentru fiecare dreptunghi. Reactualizarea fiecărui dreptunghi este făcută de o procedură separată a
ferestrei, numită CmdWndProc.
Dacă dorim, putem să adăugăm la procedura CmdWndProc posibilitatea de transmitere a unui mesaj
către procedura corespondentă a ferestrei părinte (WndProc) de fiecare dată când dreptunghiul respectiv
este marcat sau demarcat. Iată cum facem acest lucru: procedura ferestrei descendent poate determina
variabila handle a ferestrei părinte, prin apelarea procedurii GetParent:
hwndParent = GetParent (hwnd) ;
unde hwnd este variabila handle a ferestrei descendent. Procedura poate apoi să trimită un mesaj către
procedura ferestrei părinte:
SendMessage (hwndParent, iMsg, wParam, lParam) ;

Ce valoare va avea parametrul iMsg? Ei bine, orice valoare doriţi dumneavoastră, atâta timp cât valoarea
numerică se încadrează în intervalul de la WM_USER la 0x7FFF. Aceste numere reprezintă mesajele care
nu intră în conflict cu mesajele WM_predefinite. Probabil că, pentru aceste mesaje, fereastra descendent ar
putea să dea parametrului wParam o valoare egală cu identificatorul ferestrei descendent. Parametrul
lParatm ar putea avea valoarea 1 dacă fereastra descendent a fost validată şi valoarea 0 dacă nu a fost
validată. Aceasta este una dintre posibilităţi.
Efectul acestor operaţii va fi crearea unui „control de tip fereastră descendent". Fereastra descendent
prelucrează mesajele primite de la mouse şi de la tastatură şi înştiinţează fereastra părinte atunci când
starea proprie se modifică. În acest fel, fereastra descendent devine un dispozitiv de introducere a
datelor pentru fereastra părinte. Fereastra descendent încapsulează funcţionalităţi specifice legate de
modul de afişare pe ecran, răspunde de datele introduse de utilizator şi metodele de înştiinţare a unei
alte ferestre în momentul producerii unui eveniment important.
Deşi puteţi să creaţi propriile controale de tip fereastră descendent, puteţi să beneficiaţi şi de
avantajele oferite de un set de clase de fereastră (şi proceduri specifice) predefinite, clase pe care programele
pot să le folosească pentru crearea unor controale de tip fereastră descendent standard, pe care cu siguranţă
le-aţi mai văzut şi în alte programe pentru Windows. Aceste controale apar sub forma butoanelor, casetelor
de validare, casetelor de editare, casetelor listă, casetelor combinate, a şirurilor de caractere şi a barelor
de derulare. De exemplu, dacă doriţi să puneţi un buton cu eticheta „Recalculare" într-un colţ al unui
program de calcul tabelar, puteţi să îl creaţi printr-un simplu apel al procedurii CreateWindow. Nu trebuie
să vă faceţi griji în privinţa logicii de interpretare a clicurilor de mouse, a redesenării butonului sau a
modului în care butonul se mişcă atunci când este apăsat. Tot ce trebuie să faceţi este să interceptaţi mesajul
WM_COMMAND - acesta este modul în care butonul informează procedura ferestrei despre
declanşarea unui eveniment.
Este chiar atât de simplu? Ei bine, aproape.
Controalele de tip fereastră descendent sunt folosite de cele mai multe ori în casetele de dialog. Aşa
cum veţi vedea în Capitolul 11, poziţia şi dimensiunea controalelor de tip fereastră descendent sunt
definite într-un şablon al casetei de dialog, conţinut în fişierul de resurse al programului. De asemenea,
puteţi să folosiţi pe suprafaţa client a unei ferestre normale controale de tip fereastră descendent
predefinite. Creaţi fiecare fereastră descendent printr-un apel al funcţiei CreateWindow şi îi modificaţi poziţia
şi dimensiunile apelând funcţia MoveWindow. Procedura ferestrei părinte trimite mesaje către controlul de
tip fereastră descendent, iar acesta trimite mesaje către procedura ferestrei părinte.
Aşa cum am făcut începând din Capitolul 2, pentru crearea unei ferestre normale a aplicaţiei, definiţi
mai întâi clasa ferestrei şi o înregistraţi în Windows cu ajutorul funcţiei RegisterClassEx. Pe baza acestei
clase creaţi apoi fereastra cu ajutorul funcţiei CreateWindow. Totuşi, atunci când folosiţi unul dintre
controalele predefinite, nu trebuie să înregistraţi o clasă pentru fereastra descendent. Clasa există deja în
Windows şi are unul dintre aceste nume: „button", „static", „scrollbar", „edit", „listbox" sau
„combobox". Nu trebuie decât să folosiţi unul dintre aceste nume ca parametru al funcţiei
CreateWindow. Parametrul de stil (style) al funcţiei CreateWindow defineşte mai precis aspectul şi
funcţionalitatea controlului de tip fereastră descendent. Sistemul de operare Windows conţine deja

1
procedurile de fereastră pentru prelucrarea mesajelor către fereastra descendent pe baza acestor clase.
Folosirea controalelor de tip fereastră descendent direct pe suprafaţa ferestrei implică executarea unor
operaţii de nivel mai scăzut decât în cazul folosirii controalelor de tip fereastră descendent în casetele de
dialog, deoarece gestionarul casetei de dialog inserează un nivel de izolare între program şi controale.
Controalele de tip fereastră descendent pe care le creaţi pe suprafaţa ferestrei nu vă oferă însă posibilitatea
de deplasare a cursorului de intrare de la un control la altul cu ajutorul tastei Tab sau al tastelor de
deplasare. Un control de tip fereastră descendent poate obţine cursorul de intrare, dar, după ce obţine acest
cursor, nu îl va mai ceda ferestrei părinte. Vom mai vorbi despre această problemă pe parcursul capitolului
de faţă.

CLASA BUTTON

Vom începe explorarea clasei de fereastră de tip buton cu un program numit BTNLOOK, prezentat în
Figura 8-1. BTNLOOK creează zece controale de tip fereastră descendent, câte unul pentru fiecare stil
standard de buton.
BTNLOOK.C
/*----------------------------------------
BTNLOOK.C – Program de creare a controalelor de tip fereastra (c) Charles Petzold, 1996
----------------------------------------*/
#include <windows.h>
struct
{
long style;
char *text;
}
button[] = {
BS_PUSHBUTTON, "PUSHBUTTON",
BS_DEFPUSHBUTTON, "DEFPUSHBUTTON",
BS_CHECKBOX, "CHECKBOX",
BS_AUTOCHECKBOX, "AUTOCHECKBOX",
BS_RADIOBUTTON, "RADIOBUTTON",
BS_3STATE, "3STATE",
BS_AUTO3STATE, "AUTO3STATE",
BS_GROUPBOX, "GROUPBOX",
BS_AUTORADIOBUTTON, "AUTORADIO",
BS_OWNERDRAW, "OWNERDRAW"
};
#define NUM(sizeof button / sizeof button[0])
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "BtnLook";
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, "Button Look", 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 szTop[] = "iMsg wParam lParam",
szUnd[] = "____ ______ ______",
szFormat[] = "%-16s%04X-%04X %04X-%04X",
szBuffer[50];
static HWND hwndButton[NUM];
static RECT rect;

2
static int cxChar, cyChar;
HDC hdc;
PAINTSTRUCT ps;
int i;
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 + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
for(i = 0; i < NUM; i++)
hwndButton[i] = CreateWindow("button", button[i].text, WS_CHILD|WS_VISIBLE|
button[i].style, cxChar, cyChar *(1 + 2 * i),
20 * cxChar, 7 * cyChar / 4, hwnd,(HMENU) i,
((LPCREATESTRUCT) lParam) -> hInstance, NULL);
return 0;

case WM_SIZE :
rect.left = 24 * cxChar;
rect.top = 2 * cyChar;
rect.right = LOWORD(lParam);
rect.bottom = HIWORD(lParam);
return 0;

case WM_PAINT :
InvalidateRect(hwnd, &rect, TRUE);
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
SetBkMode(hdc, TRANSPARENT);
TextOut(hdc, 24*cxChar, cyChar, szTop, sizeof(szTop)-1);
TextOut(hdc, 24*cxChar, cyChar, szUnd, sizeof(szUnd)-1);
EndPaint(hwnd, &ps);
return 0;

case WM_DRAWITEM :
case WM_COMMAND :
ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
TextOut(hdc, 24 * cxChar, cyChar *(rect.bottom / cyChar - 1), szBuffer,
wsprintf(szBuffer, szFormat, iMsg == WM_DRAWITEM ? "WM_DRAWITEM" : "WM_COMMAND",
HIWORD(wParam), LOWORD(wParam), HIWORD(lParam), LOWORD(lParam)));
ReleaseDC(hwnd, hdc);
ValidateRect(hwnd, &rect);
break;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Fig. 8.1 Programul BTNLOOK

Atunci când executaţi clic pe unul dintre butoane, acesta trimite mesajul WM_COMMAND către
procedura ferestrei părinte, WndProc, pe care deja o cunoaştem. Procedura WndProc a programului
BTNLOOK afişează parametrii wParam şi lParam ai mesajului în jumătatea dreaptă a zonei client, aşa cum
se poate vedea în Figura 8-2.

3
Butonul cu stilul BS_OWNERDRAW este afişat în această fereastră numai sub forma unui fond gri,
deoarece acesta este un stil de buton de a cărui afişare este responsabil programul. Butonul indică acest
lucru afişând mesajul WM DRAWITEM, care conţine un parametru de mesaj lParam - un pointer la o
structură de tip DRAWITEMSTRUCT. Aceste mesaje sunt afişate şi de programul BTNLOOK. (Vom discuta
în detaliu despre butoanele desenate de proprietar mai târziu, în acest capitol.)
Crearea ferestrelor descendent
Programul BTNLOOK defineşte o structură numită button care conţine stilurile de fereastră ale
butoanelor şi şiruri de caractere descriptive pentru fiecare dintre cele 10 tipuri de butoane. Stilurile de
fereastră ale butoanelor încep cu literele BS_ care sunt o prescurtare de la „button style".
Cele 10 ferestre descendent sunt create într-un ciclu for în cadrul procedurii WndProc, în timpul
prelucrării mesajului WM_CREATE. Apelul funcţiei CreateWindow foloseşte următorii parametri:
Class name (numele clasei) "button"
Window text (textul ferestrei) button[i].text
Window style (stilul ferestrei) WS_CHILD|WS_VISIBLE|button[i].style
x position (poziţia x) cxChar
y position (poziţia y) cyChar * (1 + 2 * i)
Width (lăţime) 20 * cxChar
Height (înălţime) 7*cyChar/4
Parent window (fereastra părinte) hwnd
Child window ID (identificatorul ferestrei descendent) (HMENU) i
Instance handle (variabila handle a instanţei) ((LPCREATESTRUCT) lParam) -> hlnstance
Extra parameters (alţi parametri) NULL
Parametrul care reprezintă numele clasei este numele predefinit. Pentru stilul ferestrei sunt
folosite valorile WS_CHILD, WS_VISIBLE sau unul dintre cele zece stiluri de butoane
(BS_PUSHBUTTON, BS_DEFPUSHBUTTON şi aşa mai departe), definite în structura button. Textul
ferestrei (care pentru o fereastră normală apare în bara de titlu) este textul care va fi afişat pe fiecare
buton. În acest exemplu am folosit textul care identifică stilul butonului.
Parametrii x şi y indică poziţia colţului din stânga-sus al ferestrei descendent, raportată la colţul
din stânga-sus al zonei client a ferestrei părinte. Parametrii width şi height specifică lăţimea şi înălţimea
fiecărei ferestre descendent.
Identificatorul ferestrei descendent (ID) trebuie să fie unic pentru fiecare fereastră descendent.
Acest identificator permite procedurii ferestrei părinte să identifice fereastra descendent atunci când
prelucrează mesajele WM_COMMAND primite de la aceasta. Remarcaţi faptul că identificatorul
ferestrei descendent este transmis prin parametrul funcţiei CreateWindow folosit în mod normal pentru
specificarea meniului în program, aşa că trebuie să fie convertit la o valoare de tip HMENU.
Parametrul variabilei handle a instanţei din apelul funcţiei CreateWindow pare puţin ciudat, dar am
profitat de faptul că în mesajul WM_CREATE lParam este de fapt un pointer la o structură de tipul
CREATESTRUCT („structură de creare") care are un membru hlnstance. Ca urmare, am convertit
parametrul lParam la un pointer la o structură CREATESTRUCT şi am obţinut variabila membru
hlnstance.

(Unele programe Windows folosesc o variabilă globală numit hInst pentru a permite procedurii
ferestrei accesul la variabila handle a instanţei din WinMain. În WinMain trebuie să introduceţi
instrucţiunea:
hlnst = hlnstance ;

înainte de crearea ferestrei principale. în programul CHECKER3 prezentat în Capitolul 6 am folosit


funcţia GetWindowLong ca să obţinem variabila handle a instanţei:
GetWindowLong (hwnd, GWL_HINSTANCE)

Oricare dintre aceste metode este bună.)


După apelarea funcţiei CreateWindow nu mai trebuie să facem nimic în legătură cu aceste ferestre
descendent. Procedura ferestrei buton din Windows execută întreţinerea acestor butoane şi toate
operaţiile de redesenare. (Singura excepţie este butonul de tipul BS_OWNERDRAW; aşa cum vom
arăta mai târziu, sarcina de desenare a butoanelor de acest tip revine programului.) La terminarea
programului, Windows distruge toate ferestrele descendent odată cu distrugerea ferestrei părinte.

Descendenţii vorbesc cu părinţii


Atunci când rulaţi programul BTNLOOK în partea stângă a zonei client, sunt afişate diferite tipuri de
butoane. Aşa cum am menţionat anterior, atunci când executaţi clic pe un buton, controlul de tip fereastră
4
descendent trimite mesajul WM_COMMAND către fereastra părinte. Programul BTNLOOK
interceptează mesajul WM_COMMAND şi afişează valorile lParam şi wParam. Iată ce semnificaţie au
acestea:
LOWORD (wParam) Identificatorul ferestrei descendent
HIWORD (wParam) Codul de înştiinţare
lParam Variabila handle a ferestrei descendent
Dacă faceţi conversia unor programe scrise pentru versiunile pe 16 biţi ale sistemului de operare
Windows, reţineţi că aceşti parametri au fost modificaţi în vederea adaptării la dimensiunea pointerilor
pe 32 de biţi.
Identificatorul ferestrei descendent este valoarea transmisă funcţiei CreateWindow la crearea ferestrei
descendent. În programul BTNLOOK, aceşti identificatori au valori de la 0 la 9, corespunzătoare celor
10 butoane afişate în zona client. Variabila handle a ferestrei descendent este valoarea returnată de
funcţia CreateWindow.
Codul de înştiinţare este un cod de submesaj pe care fereastra descendent îl foloseşte pentru a
transmite ferestrei părinte mai multe informaţii despre semnificaţia mesajului. Valorile pe care le pot
avea aceste coduri sunt definite în fişierele antet definite în Windows:

Identificatorul codului de înştiinţare al butonului Valoare

BN_CLICKED 0
BN_PAINT 1
BN_HILITE 2
BN_UNHILITE 3
BN_DISABLE 4
BN_DOUBLECLICKED 5

Codurile de înştiinţare de la 1 la 5 sunt păstrate pentru compatibilitatea cu un stil învechit de


butoane, numit BS_SERBUTTON, aşa că nu veţi întâlni decât codul BN_CLICKED.
Veţi observa că atunci când executaţi clic pe un buton, textul acestuia este înconjurat de o linie
punctată. Această linie indică faptul că butonul respectiv a obţinut cursorul de intrare. Toate
informaţiile introduse de la tastatură sunt tratate de către fereastra descendent a butonului, nu de către
fereastra principală. Dar atunci când un control de tip buton deţine cursorul de intrare, toate tastele sunt
ignorate, cu excepţia barei de spaţiu, care are aceeaşi semnificaţie ca şi un clic de mouse.

Părinţii vorbesc cu descendenţii


Deşi programul BTNLOOK nu ilustrează acest lucru, chiar şi o procedură a ferestrei principale poate
trimite mesaje către controalele de tip fereastră descendent. În fişierele antet din Windows sunt
definite cinci mesaje specifice butoanelor; toate aceste mesaje încep cu literele „BM" - o prescurtare de
la „button message". Iată care sunt aceste mesaje:
BM_GETCHECK BM_SETCHECK BM GETSTATE BM_SETSTATE BM_SETSTYLE
Mesajele BM_GETCHECK şi BM_SETCHECK sunt trimise de fereastra părinte unui control de tip
fereastră descendent pentru a obţine sau pentru a stabili starea casetelor de validare şi a butoanelor radio.
Mesajele BM_GETSTATE şi BM_SETSTATE se referă la starea „normal" sau „apăsat" a unei ferestre,
atunci când executaţi clic pe aceasta sau apăsaţi bara de spaţiu. (Vom vedea cum funcţionează aceste
mesaje atunci când vom discuta despre fiecare tip de buton.) Mesajul BM_SETSTYLE vă permite să
schimbaţi stilul unui buton după crearea acestuia.
Fiecare fereastră descendent are o variabilă handle şi un identificator, unic pentru fiecare fereastră.
Cunoscând unul dintre aceste elemente puteţi să îl aflaţi pe celălalt. Dacă ştiţi variabila handle a unei
ferestre, puteţi să îi determinaţi identificatorul, astfel:
id = GetWindowLong (hwndChild, GWLID) ;

Programul CHECKER3 din Capitolul 6 a demonstrat faptul că o fereastră poate păstra informaţii într-o
zonă specială, rezervată în momentul în care este înregistrată clasa ferestrei. Zona în care este păstrat
identificatorul ferestrei descendent este rezervată de Windows la crearea ferestrei descendent.
Pentru obţinerea identificatorului puteţi să folosiţi şi instrucţiunea:
id = GetDlgCtrlID (hwndChild) ;

Deşi caracterele „Dlg" din numele funcţiei se referă la casetele de dialog, funcţia este de uz general.
Cunoscând identificatorul, puteţi să obţineţi variabila handle a ferestrei descendent:
5
hwnChild = GetDlgItem (hwndParent, id) ;

Butoane de apăsare
Primele două butoane afişate de programul BTNLOOK sunt butoane „de apăsare". Un buton de
apăsare este un dreptunghi care încadrează un text, specificat prin parametrul de text al ferestrei la
apelarea funcţiei CreateWindow. Dreptunghiul se încadrează în dimensiunile precizate pentru înălţime
şi lăţime la apelarea funcţiei CreateWindow sau MoveWindow. Textul este centrat în cadrul
dreptunghiului.
Butoanele de apăsare sunt folosite, de cele mai multe ori, pentru declanşarea imediată a unor
acţiuni care nu necesită păstrarea unor informaţii de tip activat/dezactivat. Celor două tipuri de
butoane de apăsare le corespund stilurile BS_PUSHBUTTON şi BS_DEFPUSHBUTTON. Caracterele
„DEF" din BS_DEFPUSHBUTTON sunt o prescurtare de la „default" (prestabilit). Atunci când sunt
folosite pentru proiectarea casetelor de dialog, butoanele BS_PUSHBUTTON şi
BS_DEFPUSHBUTTON au funcţii diferite. Atunci când au rolul de controale de tip fereastră
descendent, cele două tipuri de butoane de apăsare funcţionează în acelaşi fel, deşi butonul de tip
BS_DEFPUSHBUTTON are un chenar mai îngroşat.
Butoanele de apăsare arată cel mai bine atunci când au înălţimea egală cu 7/4 din înălţimea unui
caractere SYSTEM_FONT, ca în cazul programului BTNLOOK. Lăţimea butonului de apăsare trebuie să
fie cel puţin egală cu lungimea textului conţinut, plus încă două caractere.
Atunci când indicatorul mouse-ului se află în cadrul unui buton de apăsare, apăsarea butonului
mouse-ului determină redesenarea butonului folosind umbre tridimensionale, astfel încât butonul să
apară ca şi cum ar fi apăsat. Eliberarea butonului mouse-ului determină revenirea butonului la starea
normală şi trimite către fereastra părinte mesajul WM_COMMAND cu codul de înştiinţare
BN_CLICKED. Ca şi în cazul altor tipuri de butoane, atunci când un buton de apăsare deţine cursorul de
intrare, textul butonului este înconjurat de o linie punctată, iar apăsarea barei de spaţiu are acelaşi
efect ca şi apăsarea şi eliberarea butonului mouse-ului.
Puteţi să simulaţi apăsarea unui buton trimiţând ferestrei un mesaj BM_SETSTATE.
Instrucţiunea următoare are ca efect apăsarea butonului:
SendMessage (hwndButton, BM_SETSTATE, 1, 0) ;
Apelul următor determină revenirea la normal a butonului:
SendMessage (hwndButton, BM_SETSTATE, 0, 0) ;
Variabila hwndButton este valoarea returnată la apelarea funcţiei CreateWindow.
De asemenea, puteţi să trimiteţi unui buton de apăsare şi mesajul BM_GETSTATE. Controlul de tip
fereastră descendent returnează starea butonului – TRUE dacă butonul este apăsat şi FALSE (sau 0)
dacă acesta nu este apăsat. Totuşi, majoritatea aplicaţiilor nu au nevoie de această informaţie.
Deoarece butoanele de apăsare nu păstrează informaţii de tip activat/dezactivat, mesajele
BM_SETCHECK şi BM_GETCHECK nu sunt folosite.

Casete de validare
Casetele de validare sunt dreptunghiuri etichetate cu un text; de obicei, textul apare în partea dreaptă a
casetei de validare. (Dacă la crearea butonului folosiţi stilul BS_LEFTTEXT, textul apare în stânga
casetei de validare.) De obicei, casetele de validare sunt incluse în aplicaţii pentru a permite
utilizatorilor să selecteze diferite opţiuni. Casetele de validare funcţionează ca un comutator: executarea
unui clic într-o casetă de validare determină apariţia unui marcaj de validare; un al doilea clic face ca
marcajul de validare să dispară.
Cele mai cunoscute stiluri de casete de validare sunt BS_CHECKBOX şi BS_AUTOCHECKBOX.
Atunci când folosiţi stilul BS_CHECKBOX, vă revine dumneavoastră sarcina de aplicare a marcajului
de validare, prin trimiterea unui mesaj BS_SETCHECK. Parametrul wParam trebuie să aibă valoarea 1
pentru afişarea marcajului de validare şi valoarea 0 pentru ştergerea acestuia. Puteţi să folosiţi următoarea
secvenţă de cod pentru inversarea marcajului de validare la prelucrarea mesajului WM_COMMAND
primit de la control:
SendMessage ((HWND) 1Param, BM_SETCHECK, (WPARAM) SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0), 0) ;

Remarcaţi folosirea operatorului .' înaintea celui de-al doilea apel al funcţiei SendMessage. Valoarea
lParam este variabila handle a ferestrei descendent, transmisă către procedura ferestrei prin mesajul
WM_COMMAND. Dacă ulterior doriţi să aflaţi starea butonului, trimiteţi un nou mesaj
BM_GETCHECK. Sau puteţi să stocaţi starea curentă a casetei de validare într-o variabilă statică din
procedura ferestrei.
Puteţi să iniţializaţi o casetă de validare de tip BS_CHECKBOX cu un X, trimiţând mesajul
BM_SETCHECK:
SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;
6
În cazul folosirii stilului BS_AUTOCHECKBOX, controlul este cel care actualizează afişarea
marcajului de validare. Procedura ferestrei părinte poate să ignore mesajele WM_COMMAND.
Atunci când vreţi să aflaţi starea butonului, trimiteţi către acesta mesajul BM_GETCHECK:
iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0) ;

iCheck are valoarea TRUE (sau o valoare diferită de 0) dacă butonul este validat şi valoarea FALSE
(sau 0) în caz contrar.

Celelalte două stiluri de casete de validare sunt BS_3STATE şi BS_AUTO3STATE. Aşa cum indică şi
numele lor, aceste stiluri permit afişarea unei stări terţe - culoarea gri din caseta de validare - în cazul
în care trimiteţi către control un mesaj WM_SETCHECK cu parametrul wParam egal cu 2. Culoarea
gri indică utilizatorului că opţiunea respectivă este nedeterminată sau irelevantă. În acest caz, caseta nu
poate fi validată - cu alte cuvinte, este dezactivată. Totuşi, caseta de validare continuă să trimită
mesaje către fereastra părinte atunci când se execută clic. Vom descrie puţin mai târziu metode mai
bune de dezactivare a unei casete de validare.
Caseta de validare este aliniată la partea stângă a dreptunghiului şi este centrată între marginile de
sus şi de jos ale acestuia, conform dimensiunilor specificate la apelarea funcţiei CreateWindow.
Executarea unui clic oriunde în cadrul dreptunghiului determină trimiterea unui mesaj
WM_COMMAND către fereastra părinte, înălţimea minimă a casetei de validare este egală cu înălţimea
unui caracter. Lăţimea minimă este egală cu numărul de caractere din text, plus două.

Butoane radio
Butoanele radio seamănă cu casetele de validare, cu excepţia faptului că au formă circulară, nu
dreptunghiulară. Un punct mare în interiorul cercului indică butonul radio care a fost selectat. Stilurile
de fereastră pentru butoanele radio sunt BS_RADIOBUTTON şi BS_AUTORADIOBUTTON, dar cel
de-al doilea este folosit numai în casetele de dialog. În casetele de dialog, grupurile de butoane radio
sunt folosite de obicei pentru indicarea opţiunilor care se exclud reciproc. Spre deosebire de casetele de
validare, butoanele radio nu funcţionează precum comutatoarele - aceasta înseamnă că atunci când
executaţi un al doilea clic pe un buton radio, starea acestuia rămâne neschimbată.
Atunci când recepţionaţi un mesaj WM_COMMAND de la un buton radio, trebuie să afişaţi marcajul
de validare al acestuia, trimiţând mesajul BM_SETCHECK cu parametrul wParam egal cu 1:
SendMessage (hwndButton, BM_SETCHECK, 1, 0) ;

Pentru toate celelalte butoane radio din acelaşi grup puteţi să ştergeţi marcajul de validare trimiţându-le
mesajul BM_SETCHECK cu parametrul wParam egal cu 0:
SendMessage (hwndButton, BM_SETCHECK, 0, 0) ;

Casetele de grup
Casetele de grup, care au ca stil de fereastră BS_GROUPBOX, sunt excepţia din clasa butoanelor. Acestea
nici nu interpretează intrările de la mouse sau de la tastatură, nici nu trimit mesaje WM_COMMAND către
fereastra părinte. Casetele de grup sunt nişte chenare dreptunghiulare care afişează în partea de sus textul
ferestrei. De cele mai multe ori sunt folosite pentru încadrarea altor controale.

Modificarea textului unui buton


Puteţi să modificaţi textul unui buton (sau al unei alte ferestre) prin apelarea funcţiei SetWindowText:
SetWindowText (hwnd, pszString) ;

unde hwnd este variabila handle a ferestrei al cărei text urmează să fie înlocuit, iar pszString este un
pointer la un şir de caractere terminat cu caracterul nul. Pentru o fereastră normală, acesta este textul
care va fi afişat în bara de titlu. Pentru un buton, acesta este textul afişat pe buton.
De asemenea, puteţi să obţineţi textul ferestrei curente:
iLength = GetWindowText (hwnd, pszBuffer, iMaxLength) ;

Parametrul iMaxLength specifică numărul maxim de caractere care va fi copiat în bufferul adresat de
pointerul pszBuffer. Funcţia returnează lungimea şirului de caractere copiat. Puteţi să pregătiţi
programul pentru un text de o anumită lungime apelând mai întâi funcţia GetWindowTextLength:
iLength = GetWindowTextLength (hwnd) ;

Butoane vizibile şi butoane activate


Pentru recepţionarea intrărilor de la mouse şi de la tastatură, o fereastră descendent trebuie să fie

7
vizibilă (afişată), dar şi activată. Atunci când o fereastră este vizibilă, dar nu este activată, textul acesteia
este afişat cu gri, în loc de negru.
Dacă nu includeţi parametrul WS_VISIBLE în clasa ferestrei la crearea ferestrei descendent, aceasta
nu va fi afişată până când nu apelaţi funcţia ShowWindow:
ShowWindow (hwndChild, SW_SHOWNORMAL) ;

Dacă includeţi parametrul WS_VISIBLE în clasa ferestrei, nu este nevoie să apelaţi funcţia
ShowWindow. Totuşi, prin apelarea funcţiei ShowWindow puteţi să mascaţi o fereastră descendent
afişată:
ShowWindow (hwndChild, SW_HIDE) ;

Puteţi să determinaţi dacă o fereastră descendent este afişată:


IsWindowVisible (hwndChild) ;

De asemenea, puteţi să activaţi sau să dezactivaţi o fereastră descendent. În mod prestabilit,


ferestrele descendent sunt activate. Iată cum puteţi să le dezactivaţi:
EnableWindow (hwndChild, FALSE) ;

Pentru controalele de tip buton, această acţiune are ca efect afişarea cu gri a textului din buton. Butonul
nu mai răspunde la intrările de la mouse sau de la tastatură. Aceasta este cea mai bună metodă de a
indica faptul că opţiunea reprezentată de un anumit buton nu este disponibilă.
Puteţi să reactivaţi o fereastră descendent prin următorul apel:
EnableWindow (hwndChild, TRUE) ;

Puteţi să determinaţi dacă o fereastră descendent este activă sau nu prin următorul apel:
IsWindowEnabled (hwndChild) ;

Butoane şi cursorul de intrare


Aşa cum am menţionat mai devreme în acest capitol, butoanele de apăsare, butoanele radio şi butoanele
desenate de proprietar primesc cursorul de intrare atunci când se execută clic pe acestea. Controlul
indică faptul că deţine cursorul de intrare printr-o linie punctată care înconjoară textul. Atunci când o
fereastră descendent primeşte cursorul de intrare, fereastra părinte îl pierde; toate intrările de la
tastatură sunt direcţionate către control, nu către fereastra părinte. Totuşi, controalele de tip fereastră
descendent răspund numai la apăsarea barei de spaţiu, care funcţionează în acest caz ca şi butonul
mouse-ului. Situaţia prezintă o problemă serioasă: programul a pierdut controlul asupra tastaturii.
Haideţi să vedem ce putem să facem în acest caz.
Aşa cum am discutat în Capitolul 5, atunci când sistemul de operare Windows mută cursorul de
intrare de la o fereastră (cum ar fi fereastra părinte) la o altă fereastră (cum ar fi controlul de tip fereastră
descendent), trimite mai întâi un mesaj WM_KILLFOCUS către fereastra care pierde cursorul de intrare.
Parametrul wParam al mesajului este variabila handle a ferestrei care primeşte cursorul de intrare.
Windows trimite apoi un mesaj WM_SETFOCUS către fereastra care primeşte cursorul de intrare.
Parametrul wParam al mesajului este variabila de manipulare a ferestrei care pierde cursorul de intrare. (În
ambele cazuri, wParam poate avea valoarea NULL, ceea ce arată că nici o fereastră nu primeşte sau nu
pierde cursorul de intrare.)
O fereastră părinte poate împiedica un control de tip fereastră descendent să primească cursorul de
intrare prelucrând mesajul WM_KILLFOCUS. Să presupunem că matricea hwndCmd conţine variabilele
handle ale tuturor ferestrelor descendent. (Aceste variabile au fost salvate în matrice în timpul apelării
funcţiei CreateWindow pentru crearea ferestrelor descendent.) NUM este numărul ferestrelor
descendent.
case WM_KILLFOCUS :
for (i = 0 ; i < NUM ; 1++)
if (hwndChild[i] == (HWND) wParam
SetFocus (hwnd) ;
break ;
return 0 ;

În această secvenţă de cod, atunci când fereastra părinte este avertizată că urmează să piardă cursorul
de intrare în favoarea uneia dintre ferestrele descendent, apelează funcţia SetFocus ca să recâştige
cursorul de intrare.
Iată o cale mai simplă (dar mai puţin evidentă) de a face acelaşi lucru:
case WM_KILLFOCUS ;
~ if (hwnd == GetParent ((HWND) wParam))
SetFocus (hwnd) ;
return 0 ;

8
Totuşi, ambele metode prezentate au un neajuns: împiedică butoanele să răspundă la apăsarea barei de
spaţiu, deoarece butoanele nu obţin niciodată cursorul de intrare. O soluţie mai bună ar fi să permiteţi
butoanelor să obţină cursorul de intrare, dar, în acelaşi timp, să permiteţi utilizatorului să treacă de la un
buton la altul cu ajutorul tastei Tab. Pare imposibil, dar vă voi arăta cum se poate face totuşi acest lucru
printr-o tehnică numită „subclasarea ferestrelor", în programul COLORS1, prezentat ceva mai târziu în
acest capitol.

CONTROALE ŞI CULORI
Aşa cum puteţi vedea în Figura 8-2, aspectul unor butoane nu este cel mai potrivit. Butoanele de
apăsare arată bine, dar altele sunt desenate pe un fond gri, care pur şi simplu nu are ce căuta acolo.
Motivul este faptul că butoanele sunt proiectate pentru a fi folosite în casetele de dialog, iar în Windows
95 casetele de dialog au suprafaţa gri. Fereastra noastră are suprafaţa albă, deoarece aşa a fost definită
în structura WNDCLASS:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

Am făcut acest lucru deoarece de foarte multe ori afişăm text în zona client, iar interfaţa GDI
foloseşte culorile pentru text şi pentru fond, definite în contextul prestabilit al dispozitivului, iar
acestea sunt întotdeauna negru şi alb. Pentru a îmbunătăţi aspectul acestor butoane trebuie ori să
schimbăm culoarea, zonei client aşa încât aceasta să se asorteze cu fondul butoanelor, ori să schimbăm
culoarea folosită pentru fondul butoanelor.
Primul pas pentru realizarea aceastei operaţii este să înţelegem modul în care Windows foloseşte
„culorile de sistem".

Culorile de sistem
Windows păstrează 25 de culori de sistem pentru afişarea diferitelor părţi ale ecra nului. Puteţi să
obţineţi şi să stabiliţi aceste culori cu ajutorul funcţiilor GetSysColor şi SetSysColors. Identificatorii
definiţi în fişierele antet din Windows specifică culorile de sistem. Stabilirea unei culori de sistem cu
ajutorul funcţiei SetSysColors afectează culoarea respectivă numai pe durata sesiunii Windows curente.
Puteţi să schimbaţi o parte dintre culorile de sistem folosind secţiunea Display a Panoului de control
din Windows (Control Panel) sau modificând secţiunea [colors] din fişierul WIN.INI. Secţiunea
[colors] foloseşte pentru cele 25 de culori de sistem cuvinte cheie (altele decât identificatorii folosiţi de
funcţiile GetSysColor şi SetSysColors) urmaţi de valori pentru culorile roşu, verde şi albastru. Aceste
valori sunt cuprinse în intervalul 0 - 255. Tabelul următor prezintă modul în care sunt identificate cele 25
de culori de sistem, cu ajutorul identificatorilor folosiţi de funcţiile GetSysColor şi SetSysColors şi al
cuvintelor cheie folosite în fişierul WIN.INI. Tabelul este ordonat crescător, în funcţie de valoarea
constantelor COLOR_ (de la 0 la 24):

GetSysColor şi SetSysColors WIN.INI


COLOR_SCROLLBAR Scrollbar
COLOR_BACKGROUND Background
COLOR_ACTIVECAPTION ActiveTitle
COLOR_NACTIVECAPTION InactiveTitle
COLOR_MENU Menu
COLOR_WINDOW Window
COLOR_WINDOWFRAME WindowFrame
COLOR_MENUTEXT MenuText
COLOR_WINDOWTEXT WindowText
COLOR_CAPTIONTEXT TitleText
COLOR_ACTIVEBORDER ActiveBorder
COLOR_NACTIVEBORDER InactiveBorder
COLOR_APPWORKSPACE AppWorkspace
COLOR_HIGHLIGHT Hilight
COLOR_HIGHLIGHTTEXT HilightText
COLOR_BTNFACE ButtonFace
COLOR_BTNSHADOW ButtonShadow
COLOR_GRAYTEXT GrayText
COLOR_BTNTEXT ButtonText
COLOR_NACTIVECAPTIONTEXT InactiveTitleText
COLOR_BTNHIGHLIGHT ButtonHilight
COLOR_3DDKSHADOW ButtonDkShadow
COLOR_3DLIGHT ButtonLight
COLOR_NFOTEXT InfoText
9
COLOR_INFOBK InfoWindow

Valorile prestabilite pentru aceste culori sunt furnizate de driverul de afişare. Sistemul de operare
Windows foloseşte valorile prestabilite, dacă acestea nu sunt suprascrise de valorile din secţiunea
[colors] a fişierului WIN.INI, care poate fi modificată din Panoul de control.
Şi acum urmează partea proastă. Deşi semnificaţia multora dintre aceste culori este uşor de înţeles (de
exemplu, COLOR_BACKGROUND este culoarea zonei din spaţiul de lucru aflată în spatele tuturor
ferestrelor), folosirea culorilor în Windows 95 a devenit destul de haotică. La început, sistemul de
operare Windows era mult mai simplu din punctul de vedere al aspectului. Înainte de apariţia versiunii
Windows 3.0 nu erau definite decât primele 13 culori de sistem. Pe măsură ce sunt tot mai mult
folosite controalele complexe, cu aspect tridimensional, sunt necesare tot mai multe culori de sistem.

Culorile butoanelor
Această problemă este pusă în evidenţă în cazul butoanelor: COLOR_BTNFACE este folosită pentru
culoarea suprafeţei principale a butoanelor de apăsare şi pentru fondul celorlalte butoane. (De asemenea,
aceasta este culoarea de sistem folosită pentru casete de dialog şi casete de mesaje.)
COLOR_BTNSHADOW este folosită pentru sugerarea unei umbre la marginile din dreapta şi de jos
ale butoanelor de apăsare, precum şi în interiorul pătratelor din casetele de validare şi a cercurilor din
butoanele radio. În cazul butoanelor de apăsare, pentru culoarea textului este folosită constanta
COLOR_BTNTEXT; pentru celelalte butoane este folosită constanta COLOR_WINDOWTEXT. Pentru
celelalte părţi ale butoanelor mai sunt folosite alte câteva culori de sistem.

Aşadar, dacă vreţi să afişaţi butoane pe suprafaţa zonei client, una dintre metodele de evitare a
conflictelor între culori este folosirea culorilor de sistem. Pentru început, folosiţi COLOR_BTNFACE la
definirea clasei de fereastră pentru fondul zonei client:
wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;
Puteţi să încercaţi acest lucru în programul BTNLOOK. Atunci când variabila hbrBackground din
structura WNDCLASSEX are o valoare, Windows înţelege că aceasta se referă la culoarea de
sistem, nu la o variabilă handle. Atunci când sunt specificate în câmpul hbrBackground al structurii
WNDCLASSEX, la aceste valori trebuie adăugat 1, pentru a se evita folosirea valorii NULL. Dacă în
timpul rulării programului culoarea de sistem se schimbă, suprafaţa zonei client va fi invalidată şi
Windows va folosi noua valoare pentru COLOR_BTNFACE.
Acum am cauzat însă o altă problemă. Atunci când afişaţi text cu ajutorul funcţiei TextOut, sistemul
de operare Windows foloseşte valorile definite în contextul de dispozitiv pentru culoarea fondului
(care şterge fondul din spatele textului) şi pentru culoarea textului. Valorile prestabilite sunt alb (fond)
şi negru (text), indiferent de valoarea culorilor de sistem şi de conţinutul câmpului hbrBackground din
structura de clasă a ferestrei. Ca urmare, trebuie să folosiţi funcţiile SetTextColor şi SetBkColor, astfel încât
culorile pentru fond şi pentru text să fie aceleaşi cu culorile de sistem. Acest lucru poate fi făcut după
ce obţineţi o variabilă handle a dispozitivului:
SetBkColor (hdc, GetSysColor (COLORBTNFACE)) ; SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;

Acum, fondul zonei client, al textului şi culoarea textului se potrivesc cu culorile butoanelor.

Mesajul WM_CTLCOLORBTN
Am văzut cum putem să modificăm culoarea zonei client şi culoarea textului astfel încât să se
potrivească cu culoarea de fond a butoanelor. Putem să modificăm şi culoarea butoanelor în program?
Ei bine, doar teoretic, nu şi în practică.
Ceea ce probabil nu vreţi să faceţi este să folosiţi funcţia SetSysColors ca să modificaţi aspectul
butoanelor. Aceasta va afecta toate programele care rulează în momentul respectiv sub Windows - o
mişcare pe care utilizatorii nu o vor aprecia prea mult.
O abordare mai potrivită (din nou, teoretic) este prelucrarea mesajului WM_CTLCOLORBTN.
Acesta este un mesaj pe care controalele de tip buton îl trimit către procedura ferestrei părinte atunci
când fereastra descendent este pe cale de a executa o redesenare. Fereastra părinte poate să folosească
această ocazie ca să modifice culorile pe care procedura ferestrei descendent urmează să le
folosească pentru desenare. (In versiunile pe 16 biţi ale sistemului de operare Windows, un mesaj numit
WM_CTLCOLOR era folosit pentru toate controalele. Acesta a fost înlocuit cu mesaje separate pentru
fiecare tip standard de control.)
Atunci când procedura părinte recepţionează mesajul WM_CTLCOLORBTN, parametrul wParam
conţine variabila handle a contextului de dispozitiv al butonului, iar IParatn este variabila handle a

10
ferestrei butonului. Atunci când fereastra părinte primeşte acest mesaj, controlul de tip buton a obţinut
deja contextul de dispozitiv. În timpul prelucrării mesajului WM_CTLCOLORBTN:

■ Stabiliţi (opţional) o culoare de text cu ajutorul funcţiei SetTextColor.


■ Stabiliţi (opţional) o culoare de fond cu ajutorul funcţiei SetBkColor.
■ Returnaţi către fereastra descendent o variabilă de manipulare a pensulei
cu care se va desena.

Teoretic, fereastra descendent foloseşte pensula respectivă pentru colorarea fondului. Este sarcina
dumneavoastră să distrugeţi pensula atunci când aceasta nu mai este necesară.
Totuşi, în legătură cu mesajul WM_CTLCOLORBTN este o problemă: acesta nu este trimis decât de
butoanele de apăsare şi de butoanele desenate de proprietar; de asemenea, numai butoanele desenate
de proprietar răspund la prelucrarea făcută mesajului de către fereastra părinte, folosind pensula
trimisă pentru colorarea fondului. Oricum, acest lucru este inutil, deoarece fereastra părinte este
răspunzătoare de desenarea acestor butoane.
Mai târziu în acest capitol vom examina situaţii în care mesaje asemănătoare cu
WM_CTLCOLORBTN, dar aplicate altor tipuri de controale, sunt mult mai utile.

Butoane desenate de proprietar


Dacă doriţi să aveţi controlul complet asupra aspectului vizual al butoanelor, dar nu vreţi să vă pierdeţi
timpul cu logica de tratare a intrărilor de la mouse şi de la tastatură, creaţi un buton cu stilul
BS_OWNERDRAW. Acest lucru este ilustrat în programul OWNERDRW, prezentat în Figura 8-3.

OWNERDRW.MAK
#------------------------
# Fişierul de construcţie OWNERDRW.MAK
#------------------------
ownerdrw.exe : ownerdrw.obj
$(LINKER) $(GUIFLAGS) -OUT:ownerdrw.exe ownerdrw.obj $(GUILIBS)
ownerdrw.obj : ownerdrw.c
$(CC) $(CFLAGS) ownerdrw.c
OWNERDRW.C
/*----------------------------------------------
OWNERDRW.C – Program demonstrative de desenare a butoanelor de către proprietar
(c) Charles Petzold, 1996
----------------------------------------------*/
#include <windows.h>
#define IDC_SMALLER 1
#define IDC_LARGER 2
#define BTN_WIDTH (8 * cxChar)
#define BTN_HEIGHT (4 * cyChar)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "OwnerDrw";
MSG msg;
HWND hwnd;
WNDCLASSEX wndclass;
hInst = hInstance;
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 = szAppName;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(szAppName, "Owner-Draw Button Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
11
DispatchMessage(&msg);
}
return msg.wParam;
}
void Triangle(HDC hdc, POINT pt[])
{
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Polygon(hdc, pt, 3);
SelectObject(hdc, GetStockObject(WHITE_BRUSH));
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hwndSmaller, hwndLarger;
static int cxClient, cyClient, cxChar, cyChar;
int cx, cy;
LPDRAWITEMSTRUCT pdis;
POINT pt[3];
RECT rc;
switch(iMsg)
{
case WM_CREATE :
cxChar = LOWORD(GetDialogBaseUnits());
cyChar = HIWORD(GetDialogBaseUnits());
// Crează butoanele de apăsare desenate de proprietar
hwndSmaller = CreateWindow("button", "",
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
0, 0, BTN_WIDTH, BTN_HEIGHT,
hwnd,(HMENU) IDC_SMALLER, hInst, NULL);
hwndLarger = CreateWindow("button", "",
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
0, 0, BTN_WIDTH, BTN_HEIGHT,
hwnd,(HMENU) IDC_LARGER, hInst, NULL);
return 0;
case WM_SIZE :
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
// Mută butoanele în noul centru
MoveWindow(hwndSmaller, cxClient / 2 - 3 * BTN_WIDTH / 2,
cyClient / 2 - BTN_HEIGHT / 2,
BTN_WIDTH, BTN_HEIGHT, TRUE);
MoveWindow(hwndLarger, cxClient / 2 + BTN_WIDTH / 2,
cyClient / 2 - BTN_HEIGHT / 2,
BTN_WIDTH, BTN_HEIGHT, TRUE);
return 0;
case WM_COMMAND :
GetWindowRect(hwnd, &rc);
// Micşorează sau măreşte dimensiunile ferestrei cu 10%
switch(wParam)
{
case IDC_SMALLER :
rc.left += cxClient / 20;
rc.right -= cxClient / 20;
rc.top += cyClient / 20;
rc.bottom -= cyClient / 20;
break;
case IDC_LARGER :
rc.left -= cxClient / 20;
rc.right += cxClient / 20;
rc.top -= cyClient / 20;
rc.bottom += cyClient / 20;
break;
}
MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, TRUE);
return 0;
case WM_DRAWITEM :
pdis =(LPDRAWITEMSTRUCT) lParam;
// Colorează zona cu alb şi o încadrează cu negru
FillRect(pdis->hDC, &pdis->rcItem,
(HBRUSH) GetStockObject(WHITE_BRUSH));
FrameRect(pdis->hDC, &pdis->rcItem,
(HBRUSH) GetStockObject(BLACK_BRUSH));
// Desenează triunghiuri negre cu vîrfurile orientate spre interior şi spre exterior
cx = pdis->rcItem.right - pdis->rcItem.left;
cy = pdis->rcItem.bottom - pdis->rcItem.top;
switch(pdis->CtlID)
{
case IDC_SMALLER :
pt[0].x = 3 * cx / 8; pt[0].y = 1 * cy / 8;
pt[1].x = 5 * cx / 8; pt[1].y = 1 * cy / 8;
pt[2].x = 4 * cx / 8; pt[2].y = 3 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 7 * cx / 8; pt[0].y = 3 * cy / 8;
pt[1].x = 7 * cx / 8; pt[1].y = 5 * cy / 8;
12
pt[2].x = 5 * cx / 8; pt[2].y = 4 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 5 * cx / 8; pt[0].y = 7 * cy / 8;
pt[1].x = 3 * cx / 8; pt[1].y = 7 * cy / 8;
pt[2].x = 4 * cx / 8; pt[2].y = 5 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 1 * cx / 8; pt[0].y = 5 * cy / 8;
pt[1].x = 1 * cx / 8; pt[1].y = 3 * cy / 8;
pt[2].x = 3 * cx / 8; pt[2].y = 4 * cy / 8;
Triangle(pdis->hDC, pt);
break;
case IDC_LARGER :
pt[0].x = 5 * cx / 8; pt[0].y = 3 * cy / 8;
pt[1].x = 3 * cx / 8; pt[1].y = 3 * cy / 8;
pt[2].x = 4 * cx / 8; pt[2].y = 1 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 5 * cx / 8; pt[0].y = 5 * cy / 8;
pt[1].x = 5 * cx / 8; pt[1].y = 3 * cy / 8;
pt[2].x = 7 * cx / 8; pt[2].y = 4 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 3 * cx / 8; pt[0].y = 5 * cy / 8;
pt[1].x = 5 * cx / 8; pt[1].y = 5 * cy / 8;
pt[2].x = 4 * cx / 8; pt[2].y = 7 * cy / 8;
Triangle(pdis->hDC, pt);
pt[0].x = 3 * cx / 8; pt[0].y = 3 * cy / 8;
pt[1].x = 3 * cx / 8; pt[1].y = 5 * cy / 8;
pt[2].x = 1 * cx / 8; pt[2].y = 4 * cy / 8;
Triangle(pdis->hDC, pt);
break;
}
// Întoarce dreptrunghiul, dacă este selectat butonul
if(pdis->itemState & ODS_SELECTED)
InvertRect(pdis->hDC, &pdis->rcItem);
// Desenează un dreptunghi luminos dacă butonul are cursorul de intrare
if(pdis->itemState & ODS_FOCUS)
{
pdis->rcItem.left += cx / 16;
pdis->rcItem.top += cy / 16;
pdis->rcItem.right -= cx / 16;
pdis->rcItem.bottom -= cy / 16;
DrawFocusRect(pdis->hDC, &pdis->rcItem);
}
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Fig. 8.3 Programul OWNERDRW

Acest program contine doua butoane in centrul zonei client, asa cum se poate vedea in Figura 8-4.
Pe butonul din partea stanga sunt desenate patru triunghiuri cu varful indreptat catre interior.
Executarea unui clic pe acest buton reduce dimen-siunea f erestrei cu 10%. Pe butonul din partea
dreapta sunt desenate patru triunghiuri cu varful indreptat catre exterior. Executarea unui clic pe acest
buton creste di-mensiunea ferestrei cu 10%.

13
Majoritatea programelor care folosesc stilul BS_OWNERDRAW pentru desenarea butoanelor de
proprietar folosesc mici imagini (bitmap) pentru identificarea acestor butoane. Programul OWNERDRW
desenează cele patru triunghiuri pe suprafaţa butoanelor.
În timpul prelucrării mesajului WM_CREATE, programul OWNERDRW obţine lăţimea şi înălţimea
medii ale fontului de sistem prin apelarea funcţiei GetDia-logBasellnits. Această funcţie este deseori foarte
utilă pentru obţinerea informaţiilor necesare. OWNERDRW creează apoi două butoane cu stilul BS_
OWNERDRAW; butoanele au de opt ori lăţimea fontului sistem şi de patru ori înălţimea acestuia.
(Atunci când folosiţi imagini predefinite pentru desenarea butoanelor, este bine să ştiţi că aceste
dimensiuni creează butoane de 64x64 pixeli pe un monitor VGA.) Butoanele nu sunt încă poziţionate. în
timpul prelucrării mesajului WM_CREATE, OWNERDRW poziţionează butoanele în centrul zonei client,
prin apelarea funcţiei MoveWindow.
Executarea unui clic pe aceste butoane determină generarea mesajelor WM_COM-MAND. Pentru
prelucrarea mesajului WM_COMMAND, OWNERDRW apelează funcţia GetWindcrwRect ca să stocheze
poziţia şi dimensiunea întregii ferestre (nu numai a zonei client) într-o structură de tip RECT (dreptunghi).
Această poziţie este definită faţă de ecran. OWNERDRW modifică apoi câmpurile structurii în funcţie de
butonul pe care s-a executat clic. Programul reooziţionează şi redimensionează fereastra prin apelarea funcţiei
MoveWindow. Aceasta generează un nou mesaj WM_SIZE şi butoanele sunt repoziţionate în centrul zonei
client.
Dacă nu ar mai face nimic altceva, programul ar fi complet funcţional, dar butoanele nu ar fi vizibile.
Un buton creat cu stilul BS_OWNERDRAW trimite ferestrei părinte un mesaj WM_DRAWITEM de
fiecare dată când butonul trebuie să fie redesenat. Aceasta se întâmplă la crearea butonului, de fiecare
dată când este apăsat sau eliberat, când obţine sau pierde cursorul de intrare şi în orice altă situaţie când
trebuie să fie redesenat.
în mesajul WMJDRAWITEM, parametrul IParam este un pointer către o structură de tip
DRAWITEMSTRUCT. Programul OWNERDRW stochează acest pointer într-o variabilă numită pdis.
Această structură conţine informaţiile necesare programului pentru redesenarea butonului. (Aceeaşi
structură este folosită şi pentru casetele listă şi articolele de meniu desenate de proprietar.) Câmpurile din
structură importante pentru lucrul cu butoane sunt hDC (contextul de dispozitiv al butonului), rcltem (o
structură RECT în care este stocată dimensiunea butonului), CtllD (identificatorul ferestrei controlului) şi
UemState (care indică dacă butonul este apăsat sau dacă deţine cursorul de intrare).
Programul OWNERDRW începe prelucrarea mesajului WM_DRAWITEM prin apelarea funcţiei
FillRect, pentru a şterge suprafaţa butonului cu o pensulă de culoare albă. Apoi este apelată funcţia
FrameRed, care trasează un chenar negru în jurul butonului. în continuare, OWNERDRW desenează cele
patru triunghiuri negre pe buton, prin apelarea funcţiei Potygon. Aceasta este situaţia normală.

Dacă butonul este apăsat, unul dintre biţii câmpului UemState din structura DRAWITEMSTRUCT
are valoarea 1. Puteţi să testaţi acest bit folosind constanta ODS_SELECTED. Dacă bitul are valoarea 1,
OWNERDRW inversează culorile butonului, apelând funcţia InvertRect. Dacă butonul deţine cursorul
de intrare, atunci bitul ODS_FOCUS din câmpul UemState are valoarea 1. în acest caz, programul
OWNERDRW desenează o linie punctată în jurul textului afişat de buton, apelând funcţia
DrawFocusRect.
Un avertisment legat de folosirea butoanelor desenate de proprietar: Windows obţine contextul de
dispozitiv şi îl include în structura DRAWITEMSTRUCT. Lăsaţi contextul de dispozitiv în aceeaşi stare
în care l-aţi găsit. Orice obiect GDI selectat în contextul de dispozitiv trebuie să fie deselectat. De
asemenea, aveţi grijă să nu desenaţi în afara dreptunghiului care defineşte limitele butonului.

CLASA STATIC
Puteţi să creaţi un control static de tip fereastră descendent folosind clasa „static" în funcţia
CreateWindozu. Aceste ferestre sunt un tip destul de „inofensiv" - nu acceptă intrări de la mouse sau de
la tastatură şi nu trimit mesaje WM_COMMAND către fereastra părinte. (Atunci când executaţi clic pe o
fereastră descendent statică, aceasta interceptează mesajul WM_NCHITTEST şi returnează o valoare
HTTRANSPARENT către Windows. Ca rezultat, Windows trimite acelaşi mesaj WM_NCHITTEST
către fereastra aflată dedesubt, care de obicei este fereastra părinte. în general, fereastra părinte
transmite mesajul primit către procedura DefWindowProc, unde acesta este convertit într-un mesaj de
mouse din zona client.)
Primele şase stiluri de ferestre descendent statice nu fac decât să deseneze un dreptunghi sau un
cadru în zona client a ferestrei descendent. în tabelul următor stilurile statice „RECT" (coloana din
stânga) sunt dreptunghiuri pline, iar cele trei stiluri „FRAME" (coloana din dreapta) sunt chenare
dreptunghiulare necolorate:
SS_BLACKRECT SS_BLACKFRAME SS_GRAYRECT SS_GRAYFRAME SS_WHITERECT
SS_WHITEFRAME

14
„BLACK", „GRAY" şi „WHITE" nu sunt, de fapt, culprile dreptunghiurilor res pective. Culorile
reale folosite se bazează pe culori de sistem, aşa cum se poate vedea în tabelul următor:

Control static Culoare de sistem

BLACK COLOR_3DDKSHADOW
GRAY COLOR_BTNSHADOW
WHITE COLOR_BTNHIGHLIGHT

Parametrul pentru textul ferestrei din apelul funcţiei CreateWindow este ignorat în cazul acestor stiluri
de fereastră. Colţul din stânga-sus al dreptunghiului începe la poziţia indicată de parametrii x şi y,
relativ la fereastra părinte. (Puteţi să folosiţi şi stilurile SS_ETCHEDHORZ, SSJBTCHEDVERT sau
SS_ETCHEDFRAME ca să creaţi chenare umbrite cu culorile „gray" şi „white".)
Clasele statice includ şi trei stiluri de text: SS_LEFT, SS_RIGHT şi SS_CENTER. Acestea creează
rubrici de text aliniate la stânga, la dreapta sau centrate. Textul este furnizat ca parametru al funcţiei
CreateWindow şi poate fi modificat prin apelarea

funcţiei SetWindowText. Atunci când procedura ferestrei pentru controalele statice afişează acest text,
foloseşte funcţia DrawText cu parametrii DT_WORDBREAK, DT_NOCLIP şi DTEXPANDTABS.
Textul este încadrat în dreptunghiul ferestrei descendent.
Fondul ferestrelor descendent de tip text este, în mod normal, COLOR_BTNFACE, iar textul este scris
cu culoarea dată de COLOR_WINDOWTEXT. Puteţi să interceptaţi mesajele WM_CTLCOLORSTATIC
dacă doriţi să modificaţi culoarea textului prin apelarea funcţiei SetTextColor sau a fondului prin apelarea
funcţiei SetBkColor şi prin returnarea unei variabile handle a pensulei pentru fond. Acest lucru va fi
ilustrat în programul COLORS1, prezentat puţin mai jos.
In sfârşit, clasa „static" include stilurile de fereastră SSJCON şi SSJUSERITEM. Acestea nu au însă
nici o semnificaţie atunci când sunt folosite pe post de controale de tip fereastră descendent. Le vom
prezenta pe larg atunci când vom discuta despre casetele de dialog.

CLASA SCROLLBAR
Atunci când am prezentat pentru prima dată termenul de bare de derulare, în Capitolul 3, la
proiectarea seriei de programe SYSMETS, am discutat despre câteva diferenţe dintre „barele de
derulare pentru ferestre" şi „controalele de tip bară de derulare". Programele SYSMETS foloseau bare
de derulare pentru ferestre, care apar în partea dreaptă sau în partea de jos a ferestrelor. Puteţi să adăugaţi
bare de derulare la o fereastră prin includerea identificatorului WS_VSCROLL, WSJHSCROLL sau
amândouă, în stilul ferestrei, la crearea acesteia. Acum suntem gata să creăm câteva controale de tip
bară de derulare, care sunt ferestre descendent ce pot să apară oriunde în zona client a ferestrei părinte.
Puteţi să creaţi controale de tip bară de derulare folosind clasa predefinită de fereastră „scrollbar" şi
unul dintre cele două stiluri de bare de derulare, SBSJVERT şi SBS_HORZ.
Spre deosebire de butoane (ca şi de controalele de editare şi casetele listă, despre care vom discuta
mai târziu), barele de derulare nu trimit mesaje WM_COMMAND către fereastra părinte. în schimb,
trimit mesaje WM_VSCROLL şi WM_HSCROLL, ca şi barele de derulare ale ferestrelor. Atunci când
prelucraţi mesajele primite de la barele de derulare, puteţi să faceţi diferenţa între barele de control ale
ferestrelor şi controalele de tip bare de derulare cu ajutorul parametrului IParatn. Acesta va fi 0 pentru
barele de derulare ale ferestrelor sau o variabilă handle în cazul controalelor de tip bară de derulare.
Cele două cuvinte care formează parametrul wParam au aceeaşi semnificaţie atât pentru barele de
derulare ale ferestrelor, cât şi pentru controalele de tip bară de derulare.
Deşi barele de derulare ale ferestrelor au lăţime fixă, în cazul controalelor de tip bare de derulare
Windows foloseşte dimensiunile dreptunghiului specificate la apelarea funcţiei CreateWindow sau a
funcţiei MoveWindow. Puteţi să creaţi bare de derulare lungi şi subţiri sau bare de derulare scurte şi
late.
Dacă vreţi să creaţi controale de tip bare de derulare cu aceleaşi dimensiuni ca şi barele de derulare
ale ferestrelor, puteţi să folosiţi funcţia GetSystemMetrics ca să obţineţi înălţimea unei bare de
derulare orizontale:
GetSystemMetrics (SH_CYHSCROLL) ;

sau lăţimea unei bare de derulare verticale:


GetSystemMetrics (SM_CXVSCROLL) ;

(Conform documentaţiei, identificatorii SBS_LEFTALIGN, SBS_RIGHTALIGN, SBSJTOPALIGN şi


SBS_BOTTOMALIGN pentru barele de derulare ale ferestrelor stabilesc dimensiuni standard pentru
barele de derulare. Totuşi, acest lucru este valabil numai pentru casetele de dialog.)
15
Puteţi să stabiliţi intervalele şi poziţia unui control de tip bară de derulare cu aceleaşi apeluri pe
care le folosiţi şi pentru barele de derulare ale ferestrelor:
SetScrollRange (hwndScroll , SB CTL, iMin, iMax, bRedraw) ; SetScrollPos (hwndScroll , SB CTL, iPos, bRedraw) ;
SetScrollInfo (hwndScroll , SB_CTL, &si, bRedraw) ;

Diferenţa constă în faptul că barele de derulare ale ferestrelor folosesc o variabilă către fereastra
principală ca prim parametru şi SB_VERT sau SB_HORZ ca al doilea parametru.
Destul de surprinzător, culoarea de sistem COLOR_SCROLLBAR nu mai este folosită pentru
controalele de tip bară de derulare. Butoanele de la capătul barei şi caseta de derulare de pe bară
folosesc culorile COLOR_BTNFACE, COLOR_BTN-HIGHUGHT, COLOR_BTNSHADOW,
COLOR_BTNTEXT (pentru săgeţi) şi COLOR J3TNLIGHT. Spaţiul mai mare dintre cele' două
butoane de la capete este o combinaţie a culorilor COLOR_BTNFACE şi COLOR_BTNHIGHLIGHT.
Dacă interceptaţi mesajul WM_CTLCOLORSCROLLBAR, puteţi să returnaţi o pensulă pentru
suprascrierea culorilor folosite pentru această zonă. Haideţi să facem acest lucru.

Programul COLORS1
Pentru a vedea câteva moduri de folosire a barelor de derulare şi a ferestrelor des cendent statice - ca
şi pentru a explora mai în adâncime modul de folosire a culorilor - vom folosi programul COLORS1,
prezentat în Figura 8-5. COLORS1 afişează trei bare de derulare, etichetate cu „Red", „Green" şi „Blue" în
partea stângă a zonei client. Pe măsură ce deplasaţi casetele de pe barele de derulare în jumătatea dreaptă
a zonei client, este compusă culoarea indicată de amestecul celor trei culori primare. Valorile numerice
corespunzătoare celor trei culori primare sunt afişate sub barele de derulare.
COLORS1.MAK
#-----------------------
# Fişierul de construcţie COLORS1.MAK make file
#-----------------------
colors1.exe : colors1.obj
$(LINKER) $(GUIFLAGS) -OUT:colors1.exe colors1.obj $(GUILIBS)
colors1.obj : colors1.c
$(CC) $(CFLAGS) colors1.c
COLORS1.C
/*----------------------------------------
COLORS1.C – Programul demonstrative de utilizare a barelor de derulare pentru folosirea culorilor
(c) Charles Petzold, 1996
----------------------------------------*/
#include <windows.h>
#include <stdlib.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ScrollProc(HWND, UINT, WPARAM, LPARAM);
WNDPROC fnOldScr[3];
HWND hwndScrol[3], hwndLabel[3], hwndValue[3], hwndRect;
int color[3], iFocus;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "Colors1";
static char *szColorLabel[] = { "Red", "Green", "Blue" };
HWND hwnd;
int i;
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 = CreateSolidBrush(0L);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(szAppName, "Color Scroll",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
hwndRect = CreateWindow("static", NULL,
WS_CHILD | WS_VISIBLE | SS_WHITERECT,
0, 0, 0, 0,
hwnd,(HMENU) 9, hInstance, NULL);
for(i = 0; i < 3; i++)

16
{
hwndScrol[i] = CreateWindow("scrollbar", NULL,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_VERT,
0, 0, 0, 0,
hwnd,(HMENU) i, hInstance, NULL);
hwndLabel[i] = CreateWindow("static", szColorLabel[i],
WS_CHILD | WS_VISIBLE | SS_CENTER,
0, 0, 0, 0,
hwnd,(HMENU)(i + 3), hInstance, NULL);
hwndValue[i] = CreateWindow("static", "0",
WS_CHILD | WS_VISIBLE | SS_CENTER,
0, 0, 0, 0,
hwnd,(HMENU)(i + 6), hInstance, NULL);
fnOldScr[i] =(WNDPROC) SetWindowLong(hwndScrol[i], GWL_WNDPROC,
(LONG) ScrollProc);
SetScrollRange(hwndScrol[i], SB_CTL, 0, 255, FALSE);
SetScrollPos (hwndScrol[i], SB_CTL, 0, FALSE);
}
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 COLORREF crPrim[3] = { RGB(255, 0, 0), RGB(0, 255, 0),
RGB(0, 0, 255) };
static HBRUSH hBrush[3], hBrushStatic;
static int cyChar;
static RECT rcColor;
char szbuffer[10];
int i, cxClient, cyClient;
switch(iMsg)
{
case WM_CREATE :
for(i = 0; i < 3; i++)
hBrush[i] = CreateSolidBrush(crPrim[i]);
hBrushStatic = CreateSolidBrush(
GetSysColor(COLOR_BTNHIGHLIGHT));
cyChar = HIWORD(GetDialogBaseUnits());
return 0;
case WM_SIZE :
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
SetRect(&rcColor, cxClient / 2, 0, cxClient, cyClient);
MoveWindow(hwndRect, 0, 0, cxClient / 2, cyClient, TRUE);
for(i = 0; i < 3; i++)
{
MoveWindow(hwndScrol[i],
(2 * i + 1) * cxClient / 14, 2 * cyChar,
cxClient / 14, cyClient - 4 * cyChar, TRUE);
MoveWindow(hwndLabel[i],
(4 * i + 1) * cxClient / 28, cyChar / 2,
cxClient / 7, cyChar, TRUE);
MoveWindow(hwndValue[i],
(4 * i + 1) * cxClient / 28, cyClient - 3 * cyChar / 2,
cxClient / 7, cyChar, TRUE);
}
SetFocus(hwnd);
return 0;
case WM_SETFOCUS :
SetFocus(hwndScrol[iFocus]);
return 0;
case WM_VSCROLL :
i = GetWindowLong((HWND) lParam, GWL_ID);
switch(LOWORD(wParam))
{
case SB_PAGEDOWN :
color[i] += 15;
// fall through
case SB_LINEDOWN :
color[i] = min(255, color[i] + 1);
break;
case SB_PAGEUP :
color[i] -= 15;
// fall through
case SB_LINEUP :
color[i] = max(0, color[i] - 1);
break;
case SB_TOP :
17
color[i] = 0;
break;
case SB_BOTTOM :
color[i] = 255;
break;
case SB_THUMBPOSITION :
case SB_THUMBTRACK :
color[i] = HIWORD(wParam);
break;
default :
break;
}
SetScrollPos (hwndScrol[i], SB_CTL, color[i], TRUE);
SetWindowText(hwndValue[i], itoa(color[i], szbuffer, 10));
DeleteObject((HBRUSH)
SetClassLong(hwnd, GCL_HBRBACKGROUND,
(LONG) CreateSolidBrush(
RGB(color[0], color[1], color[2]))));
InvalidateRect(hwnd, &rcColor, TRUE);
return 0;
case WM_CTLCOLORSCROLLBAR :
i = GetWindowLong((HWND) lParam, GWL_ID);
return(LRESULT) hBrush[i];
case WM_CTLCOLORSTATIC :
i = GetWindowLong((HWND) lParam, GWL_ID);
if(i >= 3 && i <= 8) // controale de text statice
{
SetTextColor((HDC) wParam, crPrim[i % 3]);
SetBkColor((HDC) wParam, GetSysColor(COLOR_BTNHIGHLIGHT));
return(LRESULT) hBrushStatic;
}
break;
case WM_SYSCOLORCHANGE :
DeleteObject(hBrushStatic);
hBrushStatic = CreateSolidBrush(
GetSysColor(COLOR_BTNHIGHLIGHT));
return 0;
case WM_DESTROY :
DeleteObject((HBRUSH)
SetClassLong(hwnd, GCL_HBRBACKGROUND,
(LONG) GetStockObject(WHITE_BRUSH)));
for(i = 0; i < 3; DeleteObject(hBrush[i++]));
DeleteObject(hBrushStatic);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
LRESULT CALLBACK ScrollProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
int i = GetWindowLong(hwnd, GWL_ID);
switch(iMsg)
{
case WM_KEYDOWN :
if(wParam == VK_TAB)
SetFocus(hwndScrol[(i +
(GetKeyState(VK_SHIFT) < 0 ? 2 : 1)) % 3]);
break;
case WM_SETFOCUS :
iFocus = i;
break;
}
return CallWindowProc(fnOldScr[i], hwnd, iMsg, wParam, lParam);
}
Fig. 8.5 Programul COLORS1
Programul COLORS1 îşi „pune copiii la muncă". Programul foloseşte 10 controale de tip fereastră
descendent: 3 bare de derulare, 6 ferestre de text static, 1 dreptunghi static. COLORS1 interceptează
mesajele WM_CTLCOLORSCROLLBAR pentru colorarea secţiunilor interioare ale celor trei bare de
derulare cu roşu, verde şi albastru şi mesajele WM_CTLCOLORSTATIC pentru colorarea textului static.
Puteţi să deplasaţi casetele de pe barele de derulare cu mouse-ul sau de la tastatură. Puteţi să folosiţi
programul COLORS1 ca un instrument de dezvoltare pentru experimentele cu culori sau pentru
alegerea unor culori atractive (sau, dacă doriţi, a unor culori urâte) pentru programele dumneavoastră.
Fereastra afişată de programul COLORS1, din nefericire redusă la tonuri de gri pe pagina tipărită, este
prezentată în Figura 8-6.

18
Figura 8-6. Fereastra afişată de programul COLORSl.

Programul COLORSl nu prelucrează mesajele WM_PA,INT. Teoretic, toate operaţiile din programul
COLORSl sunt executate de ferestrele descendent.
Culoarea afişată în partea dreaptă a zonei client este de fapt culoarea de fond a ferestrei. O fereastră
descendent cu stilul SS_WHITERECT blochează partea stângă a zonei client. Celei trei bare de derulare
sunt controale de tip fereastră descendent cu stilul SBS_VERT. Aceste bare de derulare sunt poziţionate
în partea de sus a ferestrei descendent SS_WHITERECT. Şase alte ferestre descendent statice cu stilul
SS_CENTER (text centrat) asigură afişarea etichetelor şi a valorilor pentru fiecare culoare. COLORSl
creează fereastra principală şi cele zece ferestre descendent în cadrul funcţiei WinMain prin apelarea
funcţiei CreateWindow. Ferestrele statice SSJWHITERECT şi SS_CENTER folosesc clasa de ferestre
„static"; cele trei bare de derulare folosesc clasa de ferestre „scrollbar".
Coordonatele x şi y, precum şi înălţimea şi lăţimea folosite ca parametri la apelarea funcţiei
CreateWindow au iniţial valoarea 0, deoarece poziţia şi dimensiunile ferestrei descendent depind de
dimensiunea zonei client, care nu este cunoscută încă. Procedura ferestrei principale a programului
COLORSl redimensionează toate cele zece ferestre descendent cu ajutorul funcţiei MoveWindow atunci
când primeşte mesajul WM_SIZE. Ca urmare, de fiecare dată când redimensionaţi fereastra
programului COLORSl, dimensiunea barelor de derulare se schimbă proporţional.
Atunci când procedura WndProc primeşte mesajul WM_VSCROLL, cuvântul mai semnificativ al
parametrului IParam reprezintă variabila handle a ferestrei descendent. Putem să folosim funcţia
GetWindowLong ca să obţinem identificatorul ferestrei descendent:
i - GetWindowLong (IParam, GWW_ID) ;

Pentru cele trei bare de derulare am stabilit ca identificatori numerele 0,1 şi 2, aşa că procedura WndProc
poate să determine care dintre aceste controale a generat mesajul. Deoarece variabilele handle ale
ferestrelor descendent au fost salvate în matrice la crearea ferestrelor, WndProc poate să prelucreze
mesajele primite de la barele de derulare şi să stabilească noua valoare pentru bara de derulare
corespunzătoare, cu ajutorul funcţiei SetScrollPos:
SetScrollPos (hwndScrol[i], SB_CTL, color[i], TRUE) ;

De asemenea, WndProc modifică şi textul afişat de fereastra descendent de sub bara de derulare:
SetWindowText (hwndValue[i], itoa (color[i], szbuffer, 10)) ;

Interfaţa automatizată cu tastatura


Controalele de tip bară de derulare pot interpreta şi apăsările de taste, dar numai atunci când deţin
cursorul de intrare. Tabelul următor prezintă modul în care apăsarea tastelor de deplasare este
transformată în mesaje:

Tasta de deplasare Valoarea wParam din mesajul barei de derulare

Home , SB_TOP
End SB_BOTTOM
Page Up SB_PAGEUP
19
Page Down SB_PAGEDOWN
Left sau Up SB_LINEUP
Right sau Down SB_LINEDOWN

De fapt, mesajele SB_TOP şi SB_BOTTOM nu pot fi generate decât prin folosirea tastaturii. Dacă doriţi
ca un control de tip bară de derulare să obţină cursorul de intrare atunci când se execută clic pe aceasta,
trebuie să includeţi identificatorul WS_TAB-STOP în parametrul de stil al ferestrei la apelarea funcţiei
CreateWindow. Atunci când o bară de derulare deţine cursorul de intrare, pe caseta de derulare de pe ea este
afişat un bloc gri care clipeşte.
Totuşi, pentru a furniza barelor de derulare o interfaţă completă cu tastatura, este necesară ceva mai
multă muncă. Mai întâi, procedura WndProc trebuie să acorde explicit barei de derulare cursorul de
intrare. Acest lucru se face prin prelucrarea mesajului WM_SETFOCUS, pe care fereastra părinte îl
primeşte atunci când primeşte cursorul de intrare. WndProc cedează pur şi simplu cursorul de intrare
uneia dintre barele de derulare:
SetFocus (hwndScrol[iFocus]) ;
De asemenea, aveţi nevoie de o metodă de trecere de la o bară de derulare la alta cu ajutorul tastaturii,
de preferat folosind tasta Tab. Un lucru ceva mai dificil, deoarece atunci când o bară de derulare deţine
cursorul de intrare, aceasta prelucrează toate apăsările de taste. Dar barele de derulare interpretează
numai tastele de deplasare, tasta Tab fiind ignorată. Calea de ieşire din această dilemă este oferită de o
tehnică

numita ,,subdasarea ferestrelor". O vom folosi pentru a adauga la programul COLORS1


posibilitatea de a trece de la o bara de derulare la alta, cu ajutorul tastei Tab.

Subclasarea ferestrelor
Procedure de fereastra pentru controalele de tip bara de derulare se afla undeva in sistemul de operare
Windows. Totusi, puteti sa obtineti adresa acestei proceduri de fereastra prin apelarea functiei
GetWindowLong, folosind identificatorul GWL-_WNDPROC ca parametru. Mai mult, puteti sa stabilifi o
noua procedura de fereastra prin apelarea functiei SetWindowLong. Aceasta tehnica, numita ,,subclasarea
ferestrelor", este foarte puternica. Va permite sa ,,agajafi" procedura de fereastra, sa prelucrafi unele
mesaje in cadrul programului propriu si apoi sa transmiteji celelalte mesaje catre vechea procedura de
fereastra.
Procedura de fereastra care face prelucrarea preliminara a mesajelor de la barele de derulare in
programul COLORS1 se numeste ScrollProc si se afla catre sfarsitul listingului COLORS1.C.
Deoarece ScrollProc este o functie din cadrul programului COLORS1 care poate fi apelata de Windows,
trebuie sa fie definita cu cuvantul cheie CALLBACK.
Pentru fiecare dintre cele trei bare de derulare, COLORS1 foloseste functia SetWindowLong ca sa
stabileasca adresa noii proceduri de fereastra si objine adresa procedurii de fereastra existenta:
fn01dScr[i] = (WNDPROC) SetWindowLong (hwndScroi[i], GWL_WNDPROC, (LONG) ScrollProc)) ;

Acum functia ScrollProc primeste toate mesajele pe care sistemul de operare Windows le trimite
catre procedurile de fereastra ale tuturor barelor de derulare din programul COLORS1 (dar, desigur, nu
si ale barelor de derulare din alte programe). Procedura ScrollProc nu face decat sa cedeze cursorul de
intrare barei de derulare urmatoare (sau anterioare) atunci cand este apasata tasta Tab (respectiv
Shift+Tab), apoi apeleaza vechea procedura de fereastra cu ajutorul functiei CallWindorvProc.

Colorarea fondului
Atunci cand defineste clasa de fereastra, programul COLORS1 stabileste pentru fondul zonei client o
pensula neagra plina:
wndclass.hbrBackground • CreateSolidBrush (OL) ;

Atunci cand modificafi valorile barelor de derulare, programul trebuie sa creeze o noua pensula si sa
puna variabila handle a acesteia in structura de clasa a ferestrei. Asa cum avem posibilitatea sa
obtinem si sa stabilim procedura barelor de derulare cu ajutorul funcfiilor GetWindowLong si
SetWindowLong, putem sa obtinem si sa stabUim si variabila handle a acestei pensule cu ajutorul
functiilor GetClassWord si SetClassWord.
Puteji sa aea^i o noua pensula, sa inserati variabila handle a acesteia in structura de dasa a ferestrei,
apoi sa stergefi vechea pensula:
OeleteObject ((HBRUSH)
SetClassLong (hwnd, GCL HBRBACKGROUND, (LONG)

20
CreateSolidBrush TRGB (color[0], color[l], color[2])))) ;

La urmatoarea recoloraxe a fondului ferestrei, Windows va folosi noua pensula. Pentru a forja
stergerea fondului, invalidam partea dreapta a zonei client:
InvalidateRect (hwnd, ircColor, TRUE) ;

Valoarea TRUE (diferita de 0) folosita ca eel de-al treilea parametru indica faptul ca dorim ca fondul
sa fie sters Inainte de a fi redesenat.
Functia InvalidateRect forteaza sistemul de operare Windows sa puna un mesaj WM_PAINT in
coada de mesaje a ferestrei. Deoarece are prioritate scazuta, mesajul WMJPAINT nu va fi prelucrat
imediat daca inch mai miscati caseta de pe bara de derulare cu ajutorul mouse-ului sau al tastaturii.
Daca vreti ca fereastra sa fie actualizata imediat dupa modificarea culorii, puteti sa adaugati
instructiunea:
UpdateWindow (hwnd) ;
dupa apelarea functiei InvalidateRect. Dar aceasta instructiune va incetini prelucrarea mesajelor de la
mouse sau de la tastatura.
Functia WndProc din programul COLORS1 nu prelucreaza mesajele WM_PAINT, ci le trimite
functiei DefWindowProc. Codul prestabilit de prelucrare a mesajului WM_PAINT consta numai in
apelarea functiilor BeginPaint si EndPaint pentru validarea ferestrei. Deoarece am specificat in apelul
functiei InvalidateRect ca fondul ferestrei trebuie sters, functia BeginPaint forfeaza sistemul de
operare Windows sS genereze mesajul WM_ERASEBKGND (stergere fond). Functia WndProc ignora
acest mesaj. Mesajul este prelucrat de Windows, care sterge fondul zonei client, folosind pensula
specificata in clasa ferestrei.
Intotdeauna este bine sa faceti curatenie inainte de terminarea programului, asa că in timpul
prelucrarii mesajului WM_DESTROY este apelata din nou funcţia DeleteObject:
DeleteObject ((HBRUSH) SetClassLong (hwnd, GCL_HBRBACKGROUND,
(LONG) GetStockObject (WHITE_BRUSH))) ;

Colorarea barelor de derulare si a textului static


In programul COLORS1, interiorul celor trei bare de derulare si textul din cele sase rubrici de text
sunt colorate cu rosu, verde si albastru. Colorarea barelor de derulare se realizeaza prin prelucrarea
mesajelor WM_CTLCOLORSCROLLBAR.
In procedura WndProc definim o matrice statica ce contine trei variabile handle ale pensulelor:
static HBRUSH hBrush[3] ;
Cele trei pensule sunt create in timpul prelucrarii mesajului WM_CREATE:
for (i = 0 ; i < 3 ; i++)
hBrush[i] • CreateSolidBrush (crPrim[i]) ;

unde matricea crPrim contine valorile RGB pentru cele trei culori primare. In timpul prelucrarii
mesajului WMlCTLCOLORSCROLLBAR, procedura ferestrei returneaza una dintre cele trei pensule:
case WM_CHX0LORSCR0LLBAR : i = GetWindowLong ((HWND) lParam, GWWJD) ; return (LR ESULT ) hBrush[i] ;

Aceste pensule trebuie distruse in timpul prelucrarii mesajului WM_DESTROY:


for (i = 0 ; i < 3 ; DeleteObject (hBrush[i++])) ;
Textul din rubricile de text static este colorat mtr-un mod similar prin prelucrarea mesajului
WM_CTLCOLORSTATIC si prin apelarea functiei SetTextColor. Fondul textului este stabilit cu ajutorul
functiei SetBkColor, folosind culoarea de sistem COLOR_BTNHIGHLIGHT. In acest fel fondul
textului are aceeasi culoare ca si dreptunghiul static din spatele barelor de derulare si al textului. In
cazul controalelor de tip text static, culoarea de fond se aplica numai pentru dreptunghiul din spatele
caracterelor care formeaza textul, nu pentru tot spatiul ocupat de fereastra controlului. Pentru aceasta,
procedura ferestrei trebuie sa returneze o variabila handle la o pensula de culoarea
COLOR_BTNHIGHLIGHT. Aceasta pensula se numeste hBrushStatic, este creata in timpul prelucrarii
mesajului WM_CREATE si este distrusa in timpul prelucrarii mesajului WMDESTROY.
Prin crearea unei pensule bazate pe culoarea COLOR_BTNHIGHLIGHT In timpul prelucrarii
mesajului WM_CREATE si folosirea acesteia pe toata durata programului, ne-am expus la aparitia unei
mici probleme. Daca in timpul rularii programului culoarea COLOR_BTNHIGHLIGHT este
modificata, culoarea dreptunghiului static se va modifica, la fel si culoarea de fond a textului, dar culoarea
de fond a controalelor de tip text va ramane tot vechea culoare COLOR_BTNHIGHLIGHT.
Pentru rezolvarea acestei probleme, programul COLORS1 prelucreaza si mesajul
WM_SYSCOLORCHANGE, prin simpla recreare a pensulei hBrushStatic cu noua culoare.

21
C LASAEDIT
Clasa de editare este, din unele puncte de vedere, cea mai simpla clasa de fereastra predefinita, iar din
alte puncte de vedere este cea mai complicate. Atunci cand creati o fereastra descendent folosind numele
de clasa ,,edit", definiti un dreptunghi pe baza coordonatelor x si y si a parametrilorinaltime si latime -
specificati la apelarea functiei CreateWindow. Acest dreptunghi contine text care poate fi editat. Atunci cand
fereastra descendent detine cursorul de intrare, puteti sa introduced text de la tastatura, sa deplasati
cursorul, sa selectati portiuni din text cu ajutorul mouse-ului sau al tastei Shift si al tastelor de
deplasare, sa stergeti textul selectat si sa 11 pastrati in memoria temporara (Clipboard) apasand tastele
Ctrl+X, sa il copiati cu ajutorul tastelor Ctrl+C sau sa inserati in controlul de editare textul din memoria
temporara apasand tastele Ctrl+V.
Unul dintre cele mai simple moduri de folosire a controalelor de editare este crearea rubricilor de
intrare cu o singura linie. Dar controalele de editare nu sunt limitate la o singura linie - lucru pe care il
vom demonsrra in programul POPPAD1, prezentat in Figura 8-7. Pe masura ce vom discuta despre noi
elemente, programul POPPAD va fi imbogatit cu meniuri, casete de dialog (pentru incarcarea si
salvarea fisierelor si posibilitati de tiparire. Versiunea finala va fi un editor de texte simplu dar complet,
cu o crestere surprinzator de mica a codului fata de versiunea initials.
POPPAD1.MAK
#-----------------------
# Fişierul de construcţie POPPAD1.MAK make file
#-----------------------
poppad1.exe : poppad1.obj
$(LINKER) $(GUIFLAGS) -OUT:poppad1.exe poppad1.obj $(GUILIBS)
poppad1.obj : poppad1.c
$(CC) $(CFLAGS) poppad1.c
POPPAD1.C
/*-------------------------------------------------------
POPPAD1.C – Editor Popup care utilizează casetele de editare ale ferestrelor descendent
(c) Charles Petzold, 1996
-------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
char szAppName[] = "PopPad1";
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
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, szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hwndEdit;
switch(iMsg)
{
case WM_CREATE :
hwndEdit = CreateWindow("edit", NULL,
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
WS_BORDER | ES_LEFT | ES_MULTILINE |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0,
22
hwnd,(HMENU) 1,
((LPCREATESTRUCT) lParam) -> hInstance, NULL);
return 0;
case WM_SETFOCUS :
SetFocus(hwndEdit);
return 0;
case WM_SIZE :
MoveWindow(hwndEdit, 0, 0, LOWORD(lParam),
HIWORD(lParam), TRUE);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == 1)
if(HIWORD(wParam) == EN_ERRSPACE ||
HIWORD(wParam) == EN_MAXTEXT)
MessageBox(hwnd, "Edit control out of space.",
szAppName, MB_OK | MB_ICONSTOP);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Fig. 8.7 Programul POPPAD1
POPPAD1 este un editor multilinie (încă fără posibilităţi de lucru cu fişiere) realizat cu mai puţin de
100 de linii de cod C. (Totuşi, un dezavantaj este faptul că controlul de editare multilinie prestabilit nu
permite introducerea a mai mult de 32 kilooctefi de text.) Asa cum se poate vedea, programul
POPPAD1 in sine nu face mare lucru. Controlul predefinit de editare executa cea mai mare parte a
operajiilor. In aceasta forma, programul va permite sa vedefi ce pot face controalele de editare fara
nici un ajutor din partea programului.

Stilurile clasei edit


Asa cum am menjionat anterior, pute^i sa creaji un control de editare folosind clasa de ferestre ,,edit" la
apelarea funcjiei CreateWindow - stilul ferestrei este WS_CHILD, plus cateva opfiuni. Ca si in cazul
controalelor statice de tip fereastra descendent, textul din controalele de editare poate fi aliniat la dreapta,
aliniat la stanga sau centrat. Modul de aliniere este specificat prin stilurile de fereastra ES_LEFT,
ES_RIGHT si ES_CENTER.
In mod prestabilit, un control de editare are o singura linie. Putefi sa creafi controale de editare
multilinie folosind stilul de fereastra ES_MULTILINE. In cazul unui control de editare cu o singura
linie puteti, In mod normal, sa introduce text numai pana la sfarsitul dreptunghiului care limiteaza
controlul de editare. Pentru crearea unui control care face automat derularea orizontala a textului,
folosifi stilul ES_AUTOHSCROLL. In cazul controalelor de derulare multilinie, textul este trecut
automat pe o linie noua, exceptand situatiile in care folositi stilul de fereastra ES_AUTOHSCROLL,
caz in care trebuie sa apasafi tasta Enter ca sa treceti la o linie noua. De asemenea, puteti sa included
posibilitatea de derulare verticals automata a textului intr-un control de editare multilinie, folosind stilul
ES_AUTOVSCROLL.
Atunci cand include^ intr-un control de editare multilinie stiluri de derulare a textului, puteti sa
adaugafi si bare de derulare. Acest lucru se face cu ajutorul acelorasi identificatori ca si in cazul ferestrelor
principale: WS_HSCROLL si WS_VSCROLL.'
In mod prestabilit, controalele de editare nu sunt incadrate de un chenar. Putefi sa adaugafi un
chenar folosind stilul WS_BORDER.
Atunci cand selectafi un bloc de text intr-un control de editare, textul respectiv este afisat in mod video
invers. Totusi, atunci cand controlul pierde cursorul de intrare, textul selectat nu mai este marcat. Daca
vreti ca textul selectat sa fie marcat chiar si dupa ce controlul nu mai define cursorul de intrare, puteti
sa folosifi stilul ES_NO-HIDESEL.
Atunci cand programul POPAD1 creeaza controlul de editare, stilul folosit la apelarea funcfiei
CreateWindow este specificat astfel:
WS CHILD ! WSJISIBLE | WS_HSCROLL | WS VSCROLL | WS BORDER ! ES_LEFT | ES HULTILINE~| ES~AUTOHSCROLL |
ES_AUTOVSCROLL
In POPPAD1, dimensiunile controlului de editare sunt stabilite ulterior, la apelarea funcfiei
MoveWindow, dupa ce funcfia WndProc primeste mesajul WMJ5IZE. Dimen-siunea controlului de editare
este stability initial la dimensiunile ferestrei principale:
MoveWindow (hwndEdit, 0, 0, LOWORD (IParam),
HIWORD (IParam), TRUE) ;

Pentru controalele de editare pe o singura linie, inalfimea controlului trebuie sa corespunda inalfimii
unui caracter. In cazul in care controlul de editare are un chenar
23
(si majoritatea au), folosifi ca dimensiune de 1,5 ori inalfimea unui caracter (indusiv spatiul extern).

Instiintarea controalelor de editare


Controalele de editare trimit mesaje WM_COMMAND catre fereastra parinte. Sem-nificafia variabilelor
wParam si IParatn este aceeasi ca si in cazul controalelor de tip buton:

Parametru Descriere
LOWORD (wParam) Identificatorul ferestrei descendent
HIWORD (wParam) Codul de instiintare
IParam Variabila handle a ferestrei descendent

Codurile de Instiintare sunt prezentate mai jos:


EN_SETFOCUS Controlul de editare a obfinut cursorul de intrare
EN_KILLFOCUS Controlul de editare a pierdut cursorul de intrare
EN_CHANGE Confinutul controlului de editare se va modifica
EN_UPDATE Confinutul controlului de editare s-a modificat
EN_ERRSPACE Controlul de editare nu mai are spafiu
EN_MAXTEXT Controlul de editare nu mai are spajiu pentru inserare
EN_HSCROLL S-a executat clic pe bara de derulare orizontala a controlului de editare
EN_VSCROLL S-a executat clic pe bara de derulare verticala a controlului de editare
Programul POPPAD1 intercepteaza numai codurile de instiinf are EN_ERRSPACE si EN_MAXTEXT si
afiseaza o caseta de mesaje.
Controalele de editare stocheaza textul in memoria programului ferestrei pa rinte. Asa cum am
menjionat mai sus, confinutul unui control de editare este limitat la 32 kilooctefi.

Folosirea controalelor de editare


Daca folosifi mai multe controale de editare cu o singura linie pe suprafafa ferestrei principale, trebuie
sa folosifi subclasarea ferestrelor pentru mutarea cursorului de intrare de la un control la altul. Putefi
sa face|i acest lucru ca si in programul COLORS1, prin interceptarea tastelor Tab si Shift+Tab. (Un alt
exemplu de subclasare a ferestrelor este prezentat in programul HEAD, mai tarziu in acest capitol.)
Modul de interpretare a tastei Enter este la latitudinea dumneavoastra. Pute^i sa o folosifi in acelasi mod
ca si tasta Tab sau ca pe un semnal ca toate rubricile de editare sunt completate.
Daca vreji sa insera^i text intr-un control de editare, puteji sa folositi funcfia SetWindowText.
Obtinerea textului con^inut de un control de editare se face cu ajutorul funcjiilor GetWindowTextLength si
GetWindawText. Vom vedea exemple de folosire a acestor func^ii in versiunile ulterioare ale programului
POPPAD.

Mesaje cat re un control de editare


Nu vom discuta aid despre toate mesajele pe care puteji sa le trimiteti catre un control de editare, deoarece
sunt destul de multe, iar unele dintre acestea vor fi folosite In versiunile urmatoare ale programului
POPPAD. Aici vom face doar o prezentare generala.
Aceste mesaje va permit sa decupafi, sa copiati sau sa stergeti selecjia curenta. Utilizatorul
selecteaza textul asupra caruia se va actiona cu ajutorul mouse-ului sau al tastei Shift si al tastelor de
deplasare, marcand textul selectat din controlul de editare:
SendMessage (hwndEdit, WM_CUT, 0, 0) ; SendMessage (hwndEdit, WM_C0PY, 0, 0) ; SendMessage (hwndEdit, WM_CLEAR,
0, 0) ;

WM_CUT sterge selectia curenta din controlul de editare si o trimite In memoria temporara
(clipboard). WM_COPY copiaza selectia In memoria temporara, dar lasa intact continutul controlului de
editare. WM_CLEAR sterge selectia din controlul de editare, dar nu o copiaza in memoria temporara.
De asemenea, putefi sa inseraji textul din memoria temporara in controlul de editare, incepand de
la pozitia curenta a cursorului:
SendMesage (hwndEdit, WM_PASTE, 0, 0) ;
Puteti sa obfineti pozitiile de inceput si de sfarsit ale selectiei curente:
SendMessage (hwndEdit, EM_GETSEL, (WPARAM) MStart,
(LPARAM) &iEnd) ;

Pozitia de sfarsit este, de fapt, pozitia ultimului caracter selectat, plus 1. Puteti sa selectati text:
SendMessage (hwndEdit, EM_SETSEL, iStart, iEnd) ; Puteti sa inlocuiti selectia curenta cu un alt text:
SendMessage (hwndEdit, EM_REPLACESEL, 0, (LPARAM) szString) ; Pentru controalele de editare multilinie

24
puteti sa obtineti numarul de linii:
iCount = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0) ;

Pentru o anumita linie puteti sa obtinefi deplasarea fa^a de Inceputul buffer-ului de editare:
iOffset = SendMessage (hwndEdit, EM_LINEINDEX, iLine, 0) ;

Liniile sunt numerotate incepand de la 0. Daca parametrul iLine are valoarea -1 va fi returnata
deplasarea fata de inceput a liniei pe care se afla cursorul. Puteti sa obtineti lungimea unei linii:
iLenght = SendMessage (hwndEdit, EM_LINELENGTH, iLine, 0) ; si sa copiati linia intr-un buffer:
iLenght = SendMessage (hwndEdit, EM_GETLINE, iLine,
(LPARAM) szBuffer) ;

CLASA LISTBOX
Ultimul control predefinit de tip fereastra descendent despre care vom discuta in acest capital este caseta
lista. O caseta lista este o colecfie de siruri de caractere afisate sub forma unei liste cu posibilitati de
derulare !ntr-un dreptunghi. Un program poate sa adauge sau sa stearga siruri de caractere din lista
trimifand mesaje procedurii de fereastra a casetei lista. Caseta lista trimite mesaje WM_COMMAND
catre fereastra parinte atunci cand este selectat un element din lista. Fereastra parinte poate sa
determine ce element a fost selectat.
Casetele lista sunt de obicei folosite In casete de dialog - de exemplu, in caseta de dialog apelata prin
selectarea optiunii Open din meniul File. Caseta lista afiseaza fisierele din directorul curent si poate
sa afiseze si alte subdirectoare. O caseta lista poate sa permita selectii simple sau selectii multiple. In eel
de-al doilea caz, utilizatorul poate sa selecteze mai multe elemente din lista. Atunci cand define cursorul de
intrare, o caseta lista afiseaza o linie punctata in jurul unui element din lista. Acest cursor nu indica
elementul selectat din lista. Elementul selectat este indicat prin marcare, ceea ce inseamna afisarea
acestuia in mod video invers.
In casetele lista cu selecjie simpla, utilizatorul poate sa selecteze elementul pe care este pozitionat
cursorul, prin apasarea barei de spatiu. Tastele cu sagefi deplaseaza atat cursorul, cat si selectia
curenta si pot determina derularea continutului casetei lista. Tastele Page Up si Page Down determina
derularea casetei lista prin deplasarea cursorului, dar nu muta si selecjia curenta. Apasarea unei litere
muta cursorul si selectia la primul (sau urmatorul) element care incepe cu litera respectiva. Un element
poate fi selectat si cu ajutorul mouse-ului, prin clic sau dublu clic.
In casetele lista cu selecfie multipla, bara de spafiu schimba starea de selectare a elementului pe
care este pozifionat cursorul. (Daca elementul este selectat, bara de spafiu il deselecteaza.) Tastele cu
sageti deselecteaza toate elementele selectate anterior si muta cursorul si selecfia ca si in cazul casetelor
lista cu selecfie simpla. Totusi, combinarea tastelor cu sageji cu tasta Ctrl determina mutarea cursorului
fara mutarea selecfiei. Tasta Shift combinata cu tastele cu sagefi extinde selecfia.
Executarea unui clic sau a unui dublu clic pe un element intr-o caseta lista cu selecjie multipla
deselecteaza toate elementele selectate anterior si selecteaza elementul pe care s-a executat clic.
Executarea unui clic pe un element in timp ce este apasata tasta Shift schimba starea de selectare a
elementului respectiv, fara sa afecteze starea de selectare a altor elemente.

Stilurile casetelor lista


Puteti sa creafi un control de editare de tip caseta lista cu ajutorul funcfiei Create-Window, folosind
clasa de fereastra ,,listbox" si stilul WS_CHILD. Totusi, stilul pre-stabilit de casete lista nu trimite mesaje
WM_COMMAND catre fereastra parinte, ceea ce inseamna ca programul trebuie sa interogheze caseta
lista (prin mesaje trimise catre aceasta) cu privire la selectarea elementelor din lista. Ca urmare, aproape
intotdeauna controalele de tip caseta lista includ identificatorul de stil LBS_NOTIFY, care permite
ferestrei parinte sa primeasca mesaje WM_COMMAND de la caseta lista. Daca vreti ca elementele din
lista sa fie sortate de catre caseta lista, trebuie sa folosifi identificatorul LBS_SORT, un alt stil deseori
intainit.

In mod prestabilit, casetele lista permit numai selectii simple. Daca vreti sa creati casete lista cu
selectii multiple, trebuie sa folositi identificatorul de stil LBS_MUL-TIPLESEL.
In mod normal, casetele lista se actualizeaza automat atunci cand un nou element este adaugat In
lista. Puteti sa impiedicati acest lucru folosind stilul LBS_NORE-DRAW. Este msa putin probabil sa
doriji sa folositi acest stil, deoarece puteti sa impiedicati temporar redesenarea unei casete lista prin
folosirea mesajului WM_SET-REDRAW, despre care vom vorbi putin mai tlrziu.
In mod prestabilit, fereastra casetei lista afiseaza numai lista de elemente, fara nici un chenar in jurul
acestora. Puteti sa adaugati un chenar cu ajutorul identificatorului de stil WS_BORDER. Pentru
adaugarea unei bare de derulare verticale, care poate fi folosita pentru derularea listei cu ajutorul mouse-
ului, folositi identificatorul de stil WS_VSCROLL.
25
Fisierele antet din Windows definesc un stil de caseta lista numit LBS_STAN-DARD care include
cele mai des folosite stiluri. Acesta este definit astfel:
(LBSJOTIFY i LBS_SORT | WS_VSCROLL j WS_B0RDER)

De asemenea, puteti sa folositi identificatorii WS_SIZEBOX si WS_CAPTION. Acestia permit utilizatorului


sa redimensioneze caseta lista si sa o mute oriunde in zona client a ferestrei parinte.
Latimea casetei lista trebuie sa fie egala cu lungimea celui mai mare sir de caractere, plus latimea barei
de derulare. Puteti sa obtinefi latimea barei de derulare verticals cu ajutorul functiei:
GetSystemMetrics (SM_CXVSCROLL) ;

Puteti sa calculati inaltimea casetei lista inmultind inaltimea unui caracter cu numarul de elemente care
vreti sa fie afisate pe ecran. Casetele lista nu folosesc spatiul de separare extern (external leading)
pentru spatierea liniilor de text.

Introducerea sirurilor de caractere in casetele lista


Urmatoarea etapa dupa crearea unei casete lista este introducerea sirurilor de caractere in aceas'ta.
Puteti sa faceti acest lucru trimifand mesaje catre procedura casetei lista prin apelarea functiei
SendMessage. Sirurile de caractere sunt referite, de obicei, printr-un index care incepe de la 0 pentru
elementul aflat pe cea mai de sus pozitie a listei. In exemplele care urmeaza, humdList este variabila
handle a ferestrei descendent a controlului de tip caseta lista, iar Undex este valoarea indexului. In cazul
in care transmiteti functiei SendMessage ca parametru un text, parametrul IParatn este un pointer la un
sir de caractere terminat cu caracterul nul.
In majoritatea acestor exemple, functia SendMessage poate sa returneze o valoare LB_ERRSPACE
(definita ca -2) daca procedura ferestrei nu mai are sufidenta me-morie pentru stocarea continutului
casetei lista. SendMessage returneaza valoarea LB_ERR (-1) daca apare o eroare din alt motiv si
LB_OKAY (0) daca operatia se termina cu succes. Puteti sa testati valoarea reruiyiata de functia SendMessage
pentru detectarea eventualelor erori.
Daca folositi stilul LBS_SORT (sau daca introduceti sirurile in caseta lista in ordinea in care vreti sa
fie afisate), cea mai simpla cale de completare a casetei lista este folosirea mesajului LB_ADDSTRING:
SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAH) szString) ;

Daca nu folosrji stilul LBS_SORT puteti sa inserati sirurile de caractere In caseta lista specificand o
valoare de index si folosind mesajul LBJNSERTSTRING:
SendHessage (hwndList, LBJNSERTSTRING, iIndex, (LPARAM) szString) ;

De exemplu, daca Undex are valoarea 4, szString va deveni noul sir de caractere cu indexul 4 - al
cincilea sir de caractere de la inceputul listei (deoarece numaratoarea incepe de la 0). Orice sir de
caractere cu index mai mare este tmpins mai jos cu o pozifie. Daca Undex are valoarea -1, sirul de
caractere este adaugat pe ultima pozitie din lista. Pute^i sa folositi mesajul LBJNSERTSTRING pentru
casetele de lista cu stilul LBS_SORT, dar continutul listei nu va fi sortat din nou. (De asemenea, putefi
sa inserati siruri de caractere in lista si cu ajutorul mesajului LB_DIR, despre care vom discuta in detaliu
catre sfarsitul acestui capitol.)
Puteti sa stergeji un sir de caractere din lista prin specificarea unui index si a mesajului
LBlDELETESTRING:
SendHessage (hwndList, LB_DELETESTRING, iIndex, 0) ; Puteti sa stergefi tot continutul listei cu ajutorul
mesajului LB_RESETCONTENT:
SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ;

Procedura de fereastra a casetei lista actualizeaza imaginea afisata de fiecare data cand un element
este adaugat sau sters din lista. Daca aveti de adaugat sau de sters mai multe siruri de caractere, puteti
sa inhibaji temporar aceasta operate, dezactivand indicatorui flag de redesenare:
SendMessage (hwndList, WMJETREDRAW, FALSE, 0) ; Dupa ce aji terminat, puteti sa activati din nou indicatorui
flag de redesenare:
SendHessage (hwndList, WMJETREDRAW, TRUE, 0) ;

In cazul casetelor lista create cu stilul LBS_NOREDRAW semaforul de redesenare este initial
dezactivat.

Selectarea si extragerea intrarilor


Apelurile functiei SendMessage care executa sarcinile de mai jos returneaza, de obicei, o valoare. Daca
apare o eroare, aceasta valoare este LB_ERROR (definita ca -1).
Dupa ce afi introdus intr-o lista cateva elemente, putefi sa aflati cate elemente confine lista:
iCount = SendHessage (hwndList, LB_GETCOUNT, 0, 0) ;

26
O parte dintre celelalte apeluri sunt diferite pentru listele cu selecfie simpla si listele cu selectie
multipla. Vom discuta mai intai despre listele cu selectie simpla.
In mod normal, permiteti utilizatorilor sa selecteze un element din lista, dar daca vreti sa marcaji o
selecjie prestabilita, putefi sa folositi apelul:
SendHe'ssage (hwndList, LB_SETCURSEL, iIndex, 0) ;

Daca in apelul de mai sus Undex are valoarea -1, toate elementele din lista sunt deselectate.

Putefi sa selectati un element si pe baza primelor caractere:


iIndex = SendHessage (hwndList, LB_SELECTSTRING, iIndex, (LPARAM) szSearchString) ;

Valoarea ilndex folosita ca parametru IParam la apelarea functiei SendMessage este indexul de la care
incepe cautarea unui element ale carui caractere de inceput se potrivesc cu sirul de caractere
szSearchString. Daca ilndex are valoarea -1, cautarea se face de la Inceputul listei. Functia SendMessage
returneaza indexul elementului selectat sau LB_ERR daca nici un element nu corespunde sirului de
caractere szSearchString.
Atunci cand primifi un mesaj WM_COMMAND de la caseta lista (sau in orice alt moment) putefi sa
determinati indexul selectiei curente cu ajutorul mesajului LB_GETCURSEL:
ilndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0 ) ;

Valoarea ilndex returnata de acest apel este LB_ERR daca nu a fost selectat nici un element.
Putefi sa determinati lungimea oricarui sir de caractere din caseta lista:
iLenght = SendMessage (hwndList, LB_GETTEXTLEN, ilndex, 0) ; si sa copiati elementul respectiv intr-un buffer
de text:
iLenght = SendMessage (hwndList, LB_GETTEXT, ilndex, (LPARAM) szBuffer) ;

In ambele cazuri, valoarea (Length returnata de funcfie este lungimea sirului. Matricea szBuffer trebuie sa
fie destul de mare pentru toata lungimea sirului de caractere plus un caracter NULL pentru incheiere.
Puteti sa folosifi mai intai mesajul LB_GET-TEXTLEN ca sa alocati memoria necesara pentru stocarea
sirului de caractere.
In cazul unei casete lista cu selectie multipla, nu puteti sa f olositi mesajele LB_SET-CURSEL,
LB_GETCURSEL si LB_SELECTSTRING. In schimb, puteti sa folosiţi mesajul LB_SETSEL ca sa selectati
un anumit element fara sa afectati starea de selectare a celorlalte elemente:
SendMessage (hwndList, LB_SETSEL, wParam, ilndex) ;

Parametrul wParam este diferit de zero pentru selectarea si marcarea elementului, si zero pentru
deselectarea acestuia. Daca parametrul IParam este -1, toate elementele din lista sunt fie selectate, fie
deselectate. De asemenea, puteti sa determinati starea de selectare a unui anumit element:
iSelect = SendMessage (hwndList, LB_GETSEL, ilndex, 0) ;

unde iSelect are o valoare diferita de 0 daca elementul indicat de ilndex este selectat, si 0 daca acesta nu
este selectat.

Receptionarea mesajelor de la casetele lista


Atunci cand un utilizator executa clic pe o caseta lista, aceasta primeste cursorul de intrare. Fereastra
parinte poate ceda cursorul de intrare unei casete lista folosind functia SetFocus:
SetFocus (hwndList) ;

Atund cand o caseta lista detine cursorul de intrare, pentru selectarea elementelor din lista pot fi
folosite tastele de deplasare, literele si bara de spatiu.
Casetele lista trimit catre ferestrele parinte mesaje WM_COMMAND. Semnificatia variabilelor
wParam si IParam este aceeasi ca si In cazul butoanelor si controalelor de editare:
LOWORD (wParam) Identificatorul ferestrei descendent
HIWORD (wParam) Codul de instiintare
IParam Variabila handle a ferestrei descendent
i care sunt codurile de instiintare si valorile acestora:
LBN_ERRSPACE -2
LBN_SELCHANGE 1
LBN_DBLCLK 2
LBN_SELCANCEL 3
LBN_SETFOCUS 4
LBN KILLFOCUS 5

27
Caseta lista trimite ferestrei parinte codurile LBN_SELCHANGE si LBN_DBLCLK numai daca in stilul
ferestrei este inclus si identificatorul LBS_NOTIFY.
Codul LBN_ERRSPACE indica faptul ca in lista nu mai este spatiu. Codul LBN_SELCHANGE
indica faptul ca selectia curenta s-a modificat; aceste mesaje apar atunci cand utilizatorul muta marcajul
in caseta lista, schimba starea de selectare a unui element cu ajutorul barei de spatiu sau executa clic
pe un element. Codul LBN_DBLCLK indica faptul ca s-a executat dublu clic pe un element din lista.
(Valorile codurilor de instiintare LBN_SELCHANGE si LBN_DBLCLK se refera la numarul de clicuri
executate cu mouse-ul.)
In functie de aplicatia pe care o scrieti, puteti sa folositi oricare dintre mesajele LBN_SELCHANGE
si LBNJDBLCLK, sau chiar'pe amandoua. Programul va primi mai multe mesaje LBN_SELCHANGE,
dar mesajul LBN_DBLCLK va fi transmis numai atunci cand utilizatorul executa dublu clic pe un
element din lista. Daca programul interpreteaza acest dublu clic, trebuie sa asigurati si o interfata cu
tastatura pentru mesajul LBN_DBLCLK (o tasta sau o combinatie de taste care sa aiba acelasi efect ca si
un dublu clic).

O aplicatie simpla cu casete lista


Acum stiti cum sa creati o caseta lista, sa o completati cu siruri de caractere, sa receptionati mesajele
trimise de caseta lista si sa extrageti siruri de caractere, asa ca este timpul sa scrieti o aplicatie.
Programul ENVIRON, prezentat in Figura 8-8, foloseste o caseta lista. in zona client pentru afisarea
numelor variabilelor de mediu MS-DOS curente (cum ar fi PATH, COMSPEC si PROMPT). Pe masura ce
selectati o variabilaj numele acesteia si sirul de caractere folosit de mediu sunt afisate in partea de sus a
zonei client.
ENVIRON.MAK
#-----------------------
# Fişierul de construcţie ENVIRON.MAK make file
#-----------------------
environ.exe : environ.obj
$(LINKER) $(GUIFLAGS) -OUT:environ.exe environ.obj $(GUILIBS)
environ.obj : environ.c
$(CC) $(CFLAGS) environ.c
ENVIRON.C
/*----------------------------------------
ENVIRON.C – caseta lista a mediului
(c) Charles Petzold, 1996
----------------------------------------*/
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#define MAXENV 4096
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "Environ";
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)(COLOR_WINDOW + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(szAppName, "Environment List Box",
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);
}
287
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

28
{
static char szBuffer[MAXENV + 1];
static HWND hwndList, hwndText;
HDC hdc;
int i;
TEXTMETRIC tm;
switch(iMsg)
{
case WM_CREATE :
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwnd, hdc);
hwndList = CreateWindow("listbox", NULL,
WS_CHILD | WS_VISIBLE | LBS_STANDARD,
tm.tmAveCharWidth, tm.tmHeight * 3,
tm.tmAveCharWidth * 16 +
GetSystemMetrics(SM_CXVSCROLL),
tm.tmHeight * 5,
hwnd,(HMENU) 1,
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
hwndText = CreateWindow("static", NULL,
WS_CHILD | WS_VISIBLE | SS_LEFT,
tm.tmAveCharWidth, tm.tmHeight,
tm.tmAveCharWidth * MAXENV, tm.tmHeight,
hwnd,(HMENU) 2,
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
for(i = 0; environ[i]; i++)
{
if(strlen(environ [i]) > MAXENV)
continue;
*strchr(strcpy(szBuffer, environ [i]), '=') = '\0';
SendMessage(hwndList, LB_ADDSTRING, 0,(LPARAM) szBuffer);
}
return 0;
case WM_SETFOCUS :
SetFocus(hwndList);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == 1 && HIWORD(wParam) == LBN_SELCHANGE)
{
i = SendMessage(hwndList, LB_GETCURSEL, 0, 0);
i = SendMessage(hwndList, LB_GETTEXT, i,
(LPARAM) szBuffer);
strcpy(szBuffer + i + 1, getenv(szBuffer));
*(szBuffer + i) = '=';
SetWindowText(hwndText, szBuffer);
}
return 0;
case WM_DESTROY :
288
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Fig. 8.8 Programul ENVIRON

Programul ENVIRON creează două ferestre descendent: o casetă listă cu stilul cu stilul SS_LEFT (text
aliniat la stânga) ENVIRON foloseşte variabila environ (declarată extern în STDLIB.H) ca să obţină lista
şirurilor de caractere de mediu şi mesajul LB_ADDSTRING ca să introducă în lista terestrei descendent
fiecare şir de caractere.
După ce lansaţi în execuţie programul ENVIRON, puteţi să selectaţi o variabilă de mediu cu ajutorul
mouse-ului sau al tastaturii. De fiecare dată când selectaţi un alt element, caseta listă trimite un mesaj
WM.COMMAND către procedura ferestrei părinte, WndProc. Atunci când recepţionează mesajul
WM.COMMAND, WndProc verifică dacă cuvântul mai puţin semnificativ al variabilei wParam are
valoarea 1 identificatorul casetei listă) şi dacă cuvântul mai semnificativ al variabilei wParam (codul de
înştiinţare) are valoarea LBN.SELCHANGE. în caz afirmativ, obţine indexul elementului selectat, cu
ajutorul mesajului LB_GETCURSEL, si elementul respectiv- numele variabilei de mediu - cu ajutorul
mesajului LB_GETTEXT Programul ENVIRON foloseşte funcţia C getenv ca să obţină şirul de
caractere corespunzător variabilei respective şi funcţia SetWindowText ca să trimită şirul de caractere
respectiv ferestrei statice, care afişează textul.

Remarcaţi faptul că programul ENVIRON nu poate să folosească indexul returnat de mesajul


LB_GETCURSEL ca să indexeze variabila environ şi să obţină şirul de caractere corespunzător variabilei
29
de mediu. Deoarece caseta listă are stilul LBS_SORT (inclus în stilul LBS_STANDARD), indicii nu mai
corespund (variabilele de mediu conţinute de environ nu sunt ordonate).

Listarea fişierelor
Am păstrat pentru sfârşit ce era mai interesant: LB_DIR - cel mai puternic mesaj pentru casetele listă.
Acest mesaj completează caseta listă cu lista fişierelor dintr-un director, opţional incluzând şi
subdirectoarele şi discurile valide:
SendMessage (hwndList, LB_DIR, iAttr, (LPARAM) szFileSpec) ;
Folosirea codurilor pentru atributele fişierelor
Parametrul iAttr reprezintă codul pentru atributele fişierului. Octetul cel mai puţin semnificativ
reprezintă codul normal pentru atributele de fişiere în cazul unui apel MS-DOS:

iAttr Valoare Atribut

DDL_READWRITE 0x0000 Fişier normal


DDL_READONLY 0x0001 Fişier cu acces numai pentru citire
DDL_HIDDEN 0x0002 Fişier ascuns
DDL.SYSTEM 0x0004 Fişier de sistem
DDL_DIRECTORY 0x0010 Subdirector
DDL_ARCfflVE 0x0020 Fişier marcat pentru arhivare

Următorul octet furnizează alte posibilităţi de control asupra elementelor dorite:

iAttr Valoare Opţiune

DDL_DRIVES 0x4000 Include literele de discuri Numai căutare


DDL_EXCLUSIVE 0x8000 exclusivă

Atunci când valoarea iAttr a mesajului LB_DIR este DDLJREADWRITE, caseta listă conţine
fişierele normale, fişierele cu acces numai pentru citire (read-only) şi fişierele marcate pentru
arhivare. Aceasta corespunde logicii folosite de MS-DOS pentru căutarea fişierelor. Atunci când iAttr
are valoarea DDL_DIRECTORY, în afara fişierelor menţionate mai sus lista include şi subdirectoarele
din directorul curent, numele directoarelor fiind încadrate între paranteze pătrate. Dacă iAttr are
valoarea DDL_l5RIVES! DDL_DIRECTORY, lista conţine şi toate discurile valide, literele co-
respunzătoare acestora fiind încadrate între liniuţe de despărţire.
Dacă cel mai semnificativ bit al valorii iAttr are valoarea 1, vor fi listate toate fişierele care corespund
criteriului selectat, fiind excluse fişierele normale. De exemplu, pentru un program de salvare, puteţi lista
numai fişierele care au fost modificate de la ultima salvare. Pentru aceste fişiere bitul de arhivare are valoarea
1, aşa că trebuie să folosiţi codul DDL_EXCLUSIVE' | DDL_ARCHIVE.
Ordonarea listei de fişiere
Parametrul IParam este un pointer la un şir de caractere pentru specificarea fişierelor, cum ar fi „*.*". Această
specificare nu afectează subdirectoarele incluse în caseta listă. Dacă folosiţi stilul LBS_SORT pentru casetele
în care sunt listate fişierele, vor fi afişate mai întâi fişierele care corespund specificaţiei făcute, apoi
(opţional) unităţile de disc valide, sub forma:
[-A-]
şi (tot opţional) numele subdirectoarelor. Primul subdirector din listă va avea forma:Directorul „două
puncte" permite utilizatorului să urce un nivel în arborele de directoare, către directorul rădăcină.
(Această intrare nu apare în listă dacă listaţi fişierele din directorul rădăcină.) în sfârşit, numele specifice
ale directoarelor sunt afişate sub forma:
[SUBDIR]

Dacă nu folosiţi stilul LBS_SORT, numele fişierelor şi ale directoarelor vor fi amestecate, iar literele
unităţilor de disc apar în partea de jos a listei.

Un program ffead pentru Windows


Un utilitar UNIX foarte cunoscut, numit head, afişează liniile de început ale unui fişier. Haideţi să folosim o
casetă listă ca să scriem un program asemănător sub Windows. Programul HEAD, prezentat în Figura 8-9,
afişează într-o casetă listă toate fişierele şi subdirectoarele. Puteţi să alegeţi un fişier pentru afişare
30
executând dublu clic pe numele acestuia sau apăsând tasta Enter arunci când numele fişierului este
selectat. Cu oricare dintre aceste metode puteţi să treceţi într-un subdirector. Programul afişează cel
mult 8 KB de la începutul fişierului în partea dreaptă a zonei client a ferestrei programului HEAD.
HEAD.MAK
#--------------------
# Fişierul de construcţie HEAD.MAK make file
#--------------------
head.exe : head.obj
$(LINKER) $(GUIFLAGS) -OUT:head.exe head.obj $(GUILIBS)
head.obj : head.c
$(CC) $(CFLAGS) head.c
HEAD.C
/*---------------------------------------------
HEAD.C – Program care afişează începutul fişierului
(c) Charles Petzold, 1996
---------------------------------------------*/
#include <windows.h>
#include <string.h>
#include <direct.h>
#define MAXPATH 256
#define MAXREAD 8192
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ListProc(HWND, UINT, WPARAM, LPARAM);
WNDPROC fnOldList;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "Head";
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)(COLOR_BTNFACE + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(szAppName, "File Head",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static BOOL bValidFile;
static char sReadBuffer[MAXREAD], szFile[MAXPATH];
static HWND hwndList, hwndText;
static OFSTRUCT ofs;
static RECT rect;
char szBuffer[MAXPATH + 1];
HDC hdc;
int iHandle, i;
PAINTSTRUCT ps;
TEXTMETRIC tm;
switch(iMsg)
{
case WM_CREATE :
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
GetTextMetrics(hdc, &tm);
ReleaseDC(hwnd, hdc);
rect.left = 20 * tm.tmAveCharWidth;
rect.top = 3 * tm.tmHeight;
hwndList = CreateWindow("listbox", NULL,
WS_CHILDWINDOW | WS_VISIBLE | LBS_STANDARD,
tm.tmAveCharWidth, tm.tmHeight * 3,
tm.tmAveCharWidth * 13 +
31
GetSystemMetrics(SM_CXVSCROLL),
tm.tmHeight * 10,
hwnd,(HMENU) 1,
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
hwndText = CreateWindow("static", getcwd(szBuffer, MAXPATH),
WS_CHILDWINDOW | WS_VISIBLE | SS_LEFT,
tm.tmAveCharWidth, tm.tmHeight,
tm.tmAveCharWidth * MAXPATH, tm.tmHeight,
hwnd,(HMENU) 2,
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
fnOldList =(WNDPROC) SetWindowLong(hwndList, GWL_WNDPROC,
(LPARAM) ListProc);
SendMessage(hwndList, LB_DIR, 0x37,(LPARAM) "*.*");
return 0;
case WM_SIZE :
rect.right = LOWORD(lParam);
rect.bottom = HIWORD(lParam);
return 0;
case WM_SETFOCUS :
SetFocus(hwndList);
return 0;
case WM_COMMAND :
if(LOWORD(wParam) == 1 && HIWORD(wParam) == LBN_DBLCLK)
{
if(LB_ERR ==(i = SendMessage(hwndList,
LB_GETCURSEL, 0, 0L)))
break;
SendMessage(hwndList, LB_GETTEXT, i,(LPARAM) szBuffer);
if(-1 != OpenFile(szBuffer, &ofs, OF_EXIST | OF_READ))
{
bValidFile = TRUE;
strcpy(szFile, szBuffer);
getcwd(szBuffer, MAXPATH);
if(szBuffer [strlen(szBuffer) - 1] != '\\')
strcat(szBuffer, "\\");
SetWindowText(hwndText, strcat(szBuffer, szFile));
}
else
{
bValidFile = FALSE;
szBuffer [strlen(szBuffer) - 1] = '\0';
chdir(szBuffer + 1);
getcwd(szBuffer, MAXPATH);
SetWindowText(hwndText, szBuffer);
SendMessage(hwndList, LB_RESETCONTENT, 0, 0L);
SendMessage(hwndList, LB_DIR, 0x37,(LONG) "*.*");
}
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
case WM_PAINT :
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
SetBkColor (hdc, GetSysColor(COLOR_BTNFACE));
if(bValidFile && -1 !=(iHandle =
OpenFile(szFile, &ofs, OF_REOPEN | OF_READ)))
{
i = _lread(iHandle, sReadBuffer, MAXREAD);
_lclose(iHandle);
DrawText(hdc, sReadBuffer, i, &rect, DT_WORDBREAK |
DT_EXPANDTABS | DT_NOCLIP | DT_NOPREFIX);
}
else
bValidFile = FALSE;
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
LRESULT CALLBACK ListProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
if(iMsg == WM_KEYDOWN && wParam == VK_RETURN)
SendMessage(GetParent(hwnd), WM_COMMAND, 1,
MAKELONG(hwnd, LBN_DBLCLK));
return CallWindowProc(fnOldList, hwnd, iMsg, wParam, lParam);
}
Fig. 8.9 Programul HEAD

32
În programul ENVIRON, atunci când selectaţi o variabilă de mediu - cu un clic de mouse sau de la
tastatură - programul afişa şirul de caractere corespunzător variabilei respective. Dacă am fi optat pentru
aceeaşi soluţie şi în cazul programului HEAD, acesta ar fi fost prea lent, deoarece ar fi trebuit să deschidă
şi să închidă continuu fişiere, pe măsură ce mutaţi selecţia în caseta listă. De aceea, în programul HEAD
este necesar să executaţi dublu clic pe numele fişierului sau al subdirectorului. Aceasta creează o mică
problemă, deoarece controalele de tip casetă listă nu au o interfaţă automatizată cu tastatura care să
corespundă unui dublu clic. După cum ştiţi, în măsura în care este posibil, este bine să asigurăm şi o
interfaţă cu tastatura.
Soluţia? Subclasarea ferestrelor, desigur. Funcţia de subclasare din programul HEAD se numeşte
ListProc. Aceasta interceptează mesajele WM_KEYDOWN având parametrul wParam egal cu
VK_RETURN şi trimite către fereastra părinte un mesaj WM_COMMAND cu codul de înştiinţare
LBNJDBLCLK. în timpul prelucrării mesajului WM_COMMAND, procedura WndProc foloseşte
funcţia Windows OpenFile ca să verifice elementul selectat din listă. Dacă funcţia OpenFile returnează
o eroare, înseamnă că elementul selectat nu este un fişier, aşa că, probabil, este un director. Ca urmare,
programul HEAD foloseşte funcţia chdir ca să treacă în subdirectorul respectiv, apoi trimite către caseta
listă un mesaj LB_RESETCONTENT ca să şteargă conţinutul, şi un mesaj LB DIR ca să completeze
caseta listă cu fişierele din noul subdirector.
În timpul prelucrării mesajului WM_PAINT, procedura WndProc deschide fişierul selectat cu ajutorul
funcţiei Windows OpenFile. Aceasta returnează o variabilă handle MS-DOS pentru fişierul respectiv,
variabilă care poate fi transmisă funcţiilor Windows Jread şi Jclose. Conţinutul fişierului este afişat cu
ajutorul funcţiei DrawText.

33

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