O simpla fereastra

La nivel de API crearea unei simple ferestre precum urmatoare

chiar daca nu are nici o functionalitate nu este o treaba simpla.

Codul care o creaza este dat in continuare

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

// Pasul 4: Procedura ferestrei
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Pasul 1: Inregistrarea clasei ferestrei
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Inregistrarea ferestrei a esuat!", "Eroare!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Pasul 2: Crearea ferestrei
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Titlul ferestrei mele",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Inregistrarea ferestrei a esuat!", "Eroare!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Pasul 3: Bucla de mesaje
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Dupa cum se observa chiar si crearea unei simple ferestre necesita mai mult de 70 de linii de cod. Pentru crearea unei ferestre la nivel API sunt necesari urmatorii pasi:

Pasul 1. Inregistrarea clasei ferestrei

    O clasa fereastra stocheaza informatia referitoare la un anume tip de fereastra, inclusiv "Procedura ferestrei" care asigura controlul ferestrei, icoanele mari si mici ale ferestrei precum si culoarea de fundal (background) a ferestrei. In acest mod se poate inregistra o clasa o singura data iar apoi se pot crea atatea ferestre cate se doresc din ea, fara a fi necesara specificarea acelor atribute mereu si mereu. Majoritatea atributelor pot fi schimbate pentru fiecare fereastra in parte si atunci cand se doreste acest lucru.

O clasa fereastra nu are NIMIC in comun cu clasele C++. 

const char g_szClassName[] = "myWindowClass";

Variabila de mai sus stocheaza mai intai numele clasei fereastra, pe care-l vom folosi pentru a inregistra aceasta fereastra in sistemul Windows. Codul folosit in WinMain pentru aceasta este:

WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Inregistrarea ferestrei a esuat!", "Eroare!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

Structura wc, de tip WNDCLASSEXT, este initializata iar apoi este apelata functia API RegisterClassEx().

Membrii structurii afecteaza "clasa fereastra" dupa cum urmeaza:

cbSize
Dimensiunea structurii.
style
Class Styles (CS_*), a nu fi confundate cu Window Styles (WS_*) Se face de regula  0.
lpfnWndProc
Un pointer catre "Procedura ferestrei" a acestei clase.
cbClsExtra
Dimensiunea datelor suplimentare alocate pentru aceasta clasa in memorie. De regula se ia 0.
cbWndExtra
Dimensiunea datelor suplimentare alocate pentru fiecare fereastra creata din aceasta clase. De regula se ia 0.
hInstance
Un handle catre instanta acestei aplicatii (acela pe care-l primim ca prim parametru al functiei WinMain()).
hIcon
Icoana mare (32x32) aratata cand utilizatorul foloseste  Alt+Tab.
hCursor
Cursorul care va fi afisat in fereastra noastra.
hbrBackground
Pensula (Brush) folosita pentru desenarea fundalului ferestrei noastre.
lpszMenuName
Numele unui meniu ce va fi folosit cu aceasta clasa fereastra.
lpszClassName
Numele de identificare al clasei ferestrei.
hIconSm
Icoana mica (16x16) ce apare be bara de task-uri si in coltul din stanga sus al ferestrei.

Pasul 2. Crearea ferestrei

Odata inregistrata clasa ferestri se poate crea o fereastra din aceasta clasa folosind functia CreateWindowEx.

HWND hwnd;

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Titlul ferestrei mele",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

Primul parametru (WS_EX_CLIENTEDGE) este extended windows style, in acest caz a fost aleasa o aparenta de interio adancit . Se poate schimba cu valoarea 0 (sau cu alte valori) si se observa diferentele. 

Urmatorul este numele clasei (g_szClassName), acesta spune sistemului ce fel de fereastra sa creeze. Deoarece dorim sa cream o fereastra din clasa pe care tocmai am inregistrat-o folosim numele acestei clase.

Urmatorul parametru este titlul ferestrei (adica textul ce va apare pe bara de sus a feretrei).

Parametrul folosit aici: WS_OVERLAPPEDWINDOW este parametrul de stil al ferestrei. Exista cateva astfel de stiluri si este necesar ca acestea sa fie incercate pentru a le intelege (vom reveni ulterior).

Urmatorii 4 parametrii (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) sunt coordonatele  X si Y ale coltului stanga-sus al ferestrei si respectiv latimea si inaltimea ferestrei. Valorile X si Y au fost setate la CW_USEDEFAULT pentru a permite sistemului windows sa aleaga unde s-o plaseze pe ecran. De reamintit ca valori 0,0 pentru X si Y inseamna coltul din stanga sus si ca unitatile sunt pixeli.

Urmatorile valori (NULL, NULL, g_hInst, NULL) corespund respectiv la handle-rul ferestrei parinte, al menuului, al instantei aplicatiei precum si un pointer datele de creare a ferestrei. In sistemul windows, ferestrele sunt aranjate intr-o ierarhie de tip parinte-copili. Un buton intr-o fereastra este copilul ferestrei care-l contine. In acest exemplu, handlerul parental este NULL deoarec aceasta fereastra este orfana (este o fereastra de nivelul cel mai de sus in ierarhia aplicatiei). Handlerul de menu este de asemenea NULL deoarece nu folosim inca un menu. Handlerul de instanta este valoarea transmisa prin primul parametru al functiei WinMain. Handlerul catre Datele de creare (care pot fi utilizate pentru a transmite date aditionale, dar care sunt arareori folosite) este de asemenea setat la NULL. 

Daca va intrebati ce face aces magic NULL, el este definit ca 0 (zero). Mai precis, in C este definit ca ((void*)0), deoarece se foloseste pentru a fi utilizat cu pointeri. Cateodata compilatorul C sa genereaza mesaje de avertizare (warnings) daca se foloseste NULL pentru valori intregi (de retinut ca mesajele de avertizare receptionate depind de nivelul de avertizare cu care a fost configurat compilatorul). Aceste mesaje se pot ignora sau se poate folosi valoarea 0 pentru a le ivita.

O regula din categoria "Bune obiceiuri" este aceea de a se verifica intotdeauna valorile returnate de functii. Se evita astfel timpul irosit pentru aflarea cauzelor pentru care un cod nu functioneaza asa cum era de asteptat. In cazul de aici, foarte adesea functia CreateWindow() poate esua intr-un punct oarecare datorita multiplelor posibilitati de a gresi. De aceea si aici se aplica regula anterioara: Verifica totdeauna valorile returnate! Aici nu se face nimic altceva decat se opreste programul in caz de aparitie a unei erori.

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Inregistrarea ferestrei a esuat!", "Eroare!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

Acum a venit momentul sa aratam fereastra si s-o actualizam pentru a ne asugura ca toate componentele sunt afisate corespunzator.

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

Parametrul nCmdShow este optional, putem simplu sa tracem in SW_SHOWNORMAL mereu si sa terminam cu asta. Totusi, folosind parametrul receptionat de WinMain() da posibilitatea programului sa precizeze in ce stare doreste sa porneasca fereastra: visibila, maximizata, minimizata, etc.

Pasul 3. Bucla de mesaje

Aceasta este sufletul intregului program deoarece practic aproape tot ce face un program trebuie sa treaca prin acest punct de control.

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;

 
Procedura GetMessage() extrage un mesaj din coada de mesaje a aplicatiei. De fiecare data cand utilizatorul misca mouse-ul, apasa o cheie a tastaturii, face clic pe un item de menu sau orice altceva, sunt generate mesaje de catre sistem si adaugate la coada de mesaje a aplicatiei. Apelare functiei GetMessage() permite extragere urmatorului mesaj din coada de asteptare (si implicit stergerea lui) in vederea procesarii lui. Daca nu exista mesaj functia GetMessage() se blocheaza. Termenul de blocare inseamna ca nu se revine din aceasta rutina pana cand nu se receptioneaza un mesaj. 

Functia TranslateMessage()efectueaza o procesare suplimentara asupra evenimentelor generate de tastatura generand mesaje WM_CHAR impreuna cu mesaje WM_KEYDOWN.

Si in sfarsit, functia DispatchMessage() trimite mai departe mesaje catre fereastra careia ii sunt destinate. Aceasta poate fi fereastra principala a aplicatiei, o alta, sau un control si in anumite cazuri unei ferestre create "in apatele cortinei" de catre sistem sau un alt program. Este treaba sistemului ca mesajele sa ajunga la fereastra care trebuie.

Pasul 4. Procedura ferestrei

Daca bucla de mesaje este inima aplicatiei, procedura ferestrei este creierul aplicatiei.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Procedura ferestrei este chemata pentru fiecare mesaj, parametrul HWND este handlerul ferestrei curente, adica aceleia careia ii este destinat mesajul. Aceasta este important deoarece pot exista doua sau mai multe ferestre create din aceeasi clase si care vor utiliza aceeasi procedura de fereastra (aici WndProc()). Deosebirea apare prin aceea ca parametrul hwnd va fi diferit pentru ferestre diferite. Spre exemplu atunci cand se primeste un mesajul WM_CLOSE aici se distruge fereastra. Deoarece este folosita valoarea primita prin primul parametru, nici o alta fereastra nu va fi afectata, ci numai aceea pentru careia ii era destinat.

Mesajul WM_CLOSE este trimis atunci cand utilizatorul apasa buonul Close sau introduce de la tastatura Alt-F4. Aceasta face ca fereastra sa fie distrusa oricum de catre sistemul windows, dar daca dorim sa efectuam curatenia dupa aplicatia noastra este bine ca aici sa fie stersele toate obiectele dependente. Tot aici, este momentul sa intrebam utilizatorul daca doreste sa salveze anumite fisier etc, etc.

Atunci cand se apeleaza functia DestroyWindow() sistemul trimite mesajul WM_DESTROY catre fereastra ce trebuie distrusa, in acest cas unica noastra fereastra, iar apoi distruge orice descendenti (copii) ai acestei ferestre inainte de a distruge definitiv fereastra. Deoarece exista o singura fereastra in aplicatia noastra s-a efectuat tot ce trebuia si acum trebuie ca programul sa se termine, de aceea este apelata functia  PostQuitMessage().

Aceasta adauga la coada de mesaje a aplicatie mesajul WM_QUIT. Acest mesaj nu va ajunge niciodata sa fie prelucrat deoarece face ca functia GetMessage() sa intoarca o valoare FALSE si, dupa cum se observa in bucla de mesaje, aici se indeplineste conditia de terminare a buclei prin returnarea codul ultimului mesaj, adica parametrul wParam al mesajului WM_QUIT care se intampla sa fie valoarea transmisa functiei PostQuitMessage().

Valoarea retunata de aplicatie este de un real folos numai atunci cand aplicatia este apelata de un alt program caruia dorim sa-i transmitem o valoare particulara.

Cornel Mironel Niculae, 2003-2004

08-Mar-2004