Uploaded by ceban.00

8.1

advertisement
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;
static int cxChar, cyChar;
2
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
4
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ă
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
BN_PAINT
BN_HILITE
BN_UNHILITE
BN_DISABLE
BN_DOUBLECLICKED
0
1
2
3
4
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
5
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:
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
6
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) ;
Î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.
7
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
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 mouseului. 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.)
8
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 ;
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 ecranului. 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
COLOR_BACKGROUND
COLOR_ACTIVECAPTION
COLOR_NACTIVECAPTION
Scrollbar
Background
ActiveTitle
InactiveTitle
9
COLOR_MENU
COLOR_WINDOW
COLOR_WINDOWFRAME
COLOR_MENUTEXT
COLOR_WINDOWTEXT
COLOR_CAPTIONTEXT
COLOR_ACTIVEBORDER
COLOR_NACTIVEBORDER
COLOR_APPWORKSPACE
COLOR_HIGHLIGHT
COLOR_HIGHLIGHTTEXT
COLOR_BTNFACE
COLOR_BTNSHADOW
COLOR_GRAYTEXT
COLOR_BTNTEXT
COLOR_NACTIVECAPTIONTEXT
COLOR_BTNHIGHLIGHT
COLOR_3DDKSHADOW
COLOR_3DLIGHT
COLOR_NFOTEXT
COLOR_INFOBK
Menu
Window
WindowFrame
MenuText
WindowText
TitleText
ActiveBorder
InactiveBorder
AppWorkspace
Hilight
HilightText
ButtonFace
ButtonShadow
GrayText
ButtonText
InactiveTitleText
ButtonHilight
ButtonDkShadow
ButtonLight
InfoText
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.
10
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
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;
11
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);
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;
}
12
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;
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
14
î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
„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
GRAY
WHITE
COLOR_3DDKSHADOW
COLOR_BTNSHADOW
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
15
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.)
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 descendent 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;
16
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++)
{
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);
17
}
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 :
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;
}
18
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.
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:
19
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
Home
,
End
Page Up
Page Down
Left sau Up
Right sau Down
Valoarea wParam din mesajul barei de derulare
SB_TOP
SB_BOTTOM
SB_PAGEUP
SB_PAGEDOWN
SB_LINEUP
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] = (W NDPROC) SetW indowLong (hwndScroi[i], GW L_W NDPROC, (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.
20
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)
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) ; ret urn (LRES U LT ) hBrus h[i ] ;
Aceste pensule trebuie distruse in timpul prelucrarii mesajului WM_DESTROY:
for (i = 0 ; i < 3 ; DeleteObject (hBrush[i++])) ;
21
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.
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);
22
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,
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
23
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 intrun 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
(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)
HIWORD (wParam)
IParam
Identificatorul ferestrei descendent
Codul de instiintare
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
24
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
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
25
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.
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
26
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) ;
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 ) ;
27
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)
HIWORD (wParam)
Identificatorul ferestrei descendent
Codul de instiintare
IParam
Variabila handle a ferestrei descendent
i care sunt codurile de instiintare si valorile acestora:
LBN_ERRSPACE
LBN_SELCHANGE
LBN_DBLCLK
LBN_SELCANCEL
LBN_SETFOCUS
LBN KILLFOCUS
-2
1
2
3
4
5
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
28
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)
{
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,
29
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 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) ;
30
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
DDL_READWRITE
DDL_READONLY
DDL_HIDDEN
DDL.SYSTEM
DDL_DIRECTORY
DDL_ARCfflVE
Valoare
Atribut
Fişier normal
Fişier cu acces numai pentru citire
Fişier ascuns
Fişier de sistem
Subdirector
Fişier marcat pentru arhivare
0x0000
0x0001
0x0002
0x0004
0x0010
0x0020
Următorul octet furnizează alte posibilităţi de control asupra elementelor dorite:
iAttr
Valoare
Opţiune
DDL_DRIVES
DDL_EXCLUSIVE
0x4000
0x8000
Include literele de discuri
Numai căutare 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 corespunză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 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
#--------------------
31
# 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 +
GetSystemMetrics(SM_CXVSCROLL),
32
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));
33
return CallWindowProc(fnOldList, hwnd, iMsg, wParam, lParam);
}
Fig. 8.9 Programul HEAD
Î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.
34
Download