Documente Academic
Documente Profesional
Documente Cultură
Î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 ;
BN_CLICKED 0
BN_PAINT 1
BN_HILITE 2
BN_UNHILITE 3
BN_DISABLE 4
BN_DOUBLECLICKED 5
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.
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) ;
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) ;
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) ;
Î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):
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:
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.
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:
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) ;
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)) ;
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ă
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))) ;
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] ;
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.
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).
Parametru Descriere
LOWORD (wParam) Identificatorul ferestrei descendent
HIWORD (wParam) Codul de instiintare
IParam Variabila handle a ferestrei descendent
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.
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)
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.
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.
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.
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.
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).
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.
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:
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.
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