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