PROGRAMOWANIE – PRZYKŁADY
IDE C++ WinBGI C# Wpadki

WinAPI C++

Pierwszy program w Windows API ANSI i Unicode Pierwszy program okienkowy w C++ Typy i funkcje WinAPI Funkcja główna programu Procedura okna Malowanie okna Poprzedni przykład Następny przykład Kontakt

Pierwszy program w Windows API

Klasyczna technika tworzenia oprogramowania dla Windows w języku C++ polega na wykorzy­staniu biblio­teki Windows API (ang. Application Programming Interface, interfejs programo­wania aplikacji). Biblioteka ta, zwana również WinAPI, jest przeo­gromna i niejedno­krotnie trzeba się przez nią przedzierać z wielką ostrożno­ścią jak przez dżunglę. Jak wspomniano we wstępnie do niniejszej witryny, o rozmiarze problemu świadczy najlepszy podrę­cznik Charlsa Petzolda pt. Progra­mowanie Windows liczący niemal 1300 stron, a można go traktować jako obszerne wprowa­dzenie do sztuki progra­mowania w WinAPI. Chociaż nieporówny­walnie łatwiej jest pisać programy w środo­wiskach wizu­alnych, programo­wanie w WinAPI ma kilka istotnych zalet. Po pierwsze, pozwala zrozumieć wewnętrzną budowę i funkcjo­nowanie Windows. Po drugie, powstałe programy wykonawcze są względnie małe i nie wymagają dodatko­wego oprogramo­wania. Po trzecie wreszcie, poznanie klasy­cznej techniki programo­wania w Windows staje się prędzej czy później przydatne w innych środowiskach i językach programo­wania.

Zazwyczaj pierwszy program prezentowany na kursach nowego języka programo­wania wypisuje na ekranie tekst powitalny. W językach C i C++ jego kod mógłby wyglądać następu­jąco:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Cześć! Witamy w Windows!\n");
    return 0;
}

W Windows jest to oczywiście bardzo prosta aplikacja konsolowa. Jej odpowie­dnikiem wyświetla­jącym ten sam tekst w oknie byłaby aplikacja okienkowa, której kod różni się znacznie od poprze­dniego:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)
{
    MessageBox(NULL, "Cześć! Witamy w Windows!", "Powitanie", MB_ICONINFORMATION);
    return 0;
}

Mianowicie do kodu żródłowego włączony został plik nagłówkowy windows.h zamiast stdio.h, nazwą funkcji głównej jest WinMain zamiast main, pojawiły się nowe typy danych (WINAPI, HINSTANCEPSTR), a zamiast funkcji printf wypisu­jącej tekst wywołana została funkcja WinAPI o nazwie MessageBox pokazu­jąca proste okno komuni­katu z ozdobną ikoną i przyci­skiem OK (rys.). Rzecz jasna zaprojekto­wanie i wyświe­tlenie własnego okna aplikacji, a zwłaszcza zapewnienie komuni­kacji tego okna z systemem opera­cyjnym, innymi oknami i urządze­niami wejścia-wyjścia wymaga od progra­misty znacznie większego nakładu pracy. Okna komuni­kują się z otocze­niem za pomocą komuni­katów wysyłanych do nich w celu poinformo­wania o zaistnieniu sytuacji, na które mają zarea­gować.

ANSI i Unicode

Biblioteka WinAPI oprócz tradycyjnego systemu ANSI kodowania znaków (strona kodowa Windows-1250 w polskiej wersji Windows) wspiera kodowanie w systemie Unicode (odmiana UTF-16). Odpowie­dnikiem 1-bajtowego w ANSI typu char jest 2-bajtowy w Unicode typ wchar_t. Stałe znakowe i łańcu­chowe w standar­dzie Unicode zapisuje się podobnie jak zwykłe stałe tekstowe, poprze­dzając je literą L, np.:

wchar_t szerokiZnak = L'Ż';
wchar_t palindrom[] = L"Kobyła ma mały bok";

Niestety, kod napisany dla ANSI nie kompiluje się w proje­kcie skonfigu­rowanym dla Unicode i odwrotnie. Na szczęście zgodność znaków i łańcu­chów można łatwo osiągnąć przy użyciu dostępnego w WinAPI macra TEXT. Co więcej, makrami są również nazwy wielu funkcji tej biblio­teki. Na przykład wywo­łanie postaci

MessageBox(NULL, TEXT("Cześć! Witamy w Windows!"), TEXT("Powitanie"), MB_ICONINFORMATION);

jest poprawne w obu przypadkach. W rzeczywistości MessageBox jest makrem określa­jącym nazwę MessageBoxA w konfigu­racji dla ANSI i MessageBoxW dla Unicode. W biblio­tece WinAPI zadbano również o zależne od ustawio­nego w opcjach projektu kodowania nazewni­ctwo typów. Na przykład typ TCHAR oznacza w ANSI typ CHAR, a w Unicode typ WCHAR (pierwszy jest odpowie­dnikiem char, drugi wchar_t).

Kompilatory Borland C++ 5.5 i MinGW C++ 5.1 domyślnie przyjmują, że kompilo­wany program obsługuje znaki i łańcuchy kodowane w ANSI (Windows-1250 w polskiej wersji systemu). Aby ustawić kodowanie Unicode, należy za pomocą dyrektywy #define zdefi­niować symbole UNICODE_UNICODE, a nadto w ustawie­niach drugiego kompila­tora dodać opcję -municode. W Visual Studio C++ 2017 domyślnym ustawie­niem jest kodowanie Unicode. Aby go zmienić na natywne, należy we właściwo­ściach projektu wybrać zakładkę Ogólne, a nastę­pnie rozwinąć listę Zestaw znaków i wybrać z niej pozycję Nie ustawiono.

Wszystkie prezentowane na niniejszej witrynie programy w WinAPI C++ nie wymagają Unicode, dlatego są pisane dla wersji ANSI, przez co ich kod źródłowy jest nieco prostszy i bardziej zrozu­miały. Zatem w Visual Studio przesta­wiamy opcję Zestaw znaków we właści­wościach ich projektów, a ponieważ żaden z nich nie używa plików stdafx.h, stdafx.cpptargetver.h, rezy­gnujemy z prekompi­lowanego nagłówka.

Pierwszy program okienkowy w C++

Zadaniem pierwszego programu okienkowego w C++, który nazwiemy Pierwszy, jest utworzenie typowego w Windows okna z paskiem tytułowym, naryso­wanie elipsy i wyświe­tlenie tekstu powital­nego. Podobnie jak w przy­padku programu konsolo­wego, jego budowę najwygo­dniej rozpocząć od urucho­mienia kreatora nowego projektu. Zatem urucha­miamy kreatora i w jego oknie doko­nujemy następu­jących ustawień:

Wygenerowany przez środowisko programistyczne projekt programu może zawierać oprócz pliku Pierwszy.cpp (lub main.cpp w MinGW C++) inne pliki (*.h, *.cpp, *.res, *.ico). Są one w rozpatry­wanym przypadku zbędne, toteż należy je wszystkie usunąć z projektu, a zawartość wspomnia­nego pliku źródłowego dosto­sować do przedsta­wionego poniżej szablonu aplikacji okien­kowej. Należy pamiętać, aby w Visual Studio przestawić projekt na kodowanie ANSI i zrezy­gnować z prekompilo­wanego nagłówka, jeśli został włączony.

Okno jest najważ­niejszym elementem GUI (ang. Graphics User Interface, graficzny interfejs użytko­wnika) zajmującym prosto­kątny fragment ekranu i komuni­kującym się z oto­czeniem za pomocą komuni­katów. Komunikat jest formą danych niosących informacje o zaistniałym zdarzeniu, np. naciśnięciu klawisza klawia­tury lub przycisku myszy, przesu­waniu jej po blacie biurka lub wybraniu pole­cenia menu. Gdy program rozpo­czyna działanie, Windows tworzy dla niego kolejkę komuni­katów (ang. message queue), tworzy też okno główne aplikacji w oparciu o tzw. klasę okna (ang. window class), która określa m.in. procedurę okna (ang. window procedure) obsługu­jącą komuni­katy kierowane do okna. Windows wysyła komunikat do okna, wywołując procedurę okna, która wykonuje stosowne operacje i zwraca sterowanie z powrotem do Windows.

Oprócz procedury okna obsługującej komunikaty program okienkowy, jak już wspomniano wcześniej, zawiera funkcję WinMain, która jest jego punktem startowym podobnie jak funkcja main w programie konsolowym. Zadania, jakie spełnia funkcja WinMain, są niemal takie same dla większości programów, dlatego przy jej budowie można posłużyć się pewnym ogólnie przyjętym wzorcem. Na początku wymagane jest wypeł­nienie struktury definiu­jącej klasę okna, co nie jest możliwe bez procedury okna. Dopiero potem można zareje­strować w systemie klasę okna, utworzyć okno tej klasy i pokazać go na ekranie. Ostatnim fragmentem wzorco­wego kodu funkcji WinMain jest pętla komuni­katów (ang. message loop), w której komunikaty są pobierane z kolejki i przesy­łane do wykonania przez procedurę okna.

Pełny kod źródłowy pierwszego programu okienkowego w Windows API C++ zawarty w poje­dynczym pliku o nazwie Pierwszy.cpp (lub main.cpp w MinGW C++) jest przedsta­wiony na poniższym listingu. Kod ten można traktować jako szablon do wykorzy­stania przy tworzeniu innych programów okien­kowych konfiguro­wanych dla ANSI. Obie funkcje występu­jące w programie, funkcja główna WinMain i proce­dura okna WndProc, są szcze­gółowo omówione w dalszej części niniejszej strony.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)
{
    static char szClassName[] = "Pierwszy";
    HWND hWnd;
    MSG msg;
    WNDCLASS wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInst;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClassName;

    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, "Ten program wymaga Win32!", szClassName, MB_ICONERROR);
        return 0;
    }
    hWnd = CreateWindow(szClassName, "Pierwszy program w WinAPI", WS_OVERLAPPEDWINDOW,
                 CW_USEDEFAULT, CW_USEDEFAULT, 450, 350, NULL, NULL, hInst, 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 message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rect);
            InflateRect(&rect, -rect.right / 10, -rect.bottom / 10);
            Ellipse(hdc, rect.left, rect.top, rect.right, rect.bottom);
            DrawText(hdc, "Cześć! Witamy w Windows API!", -1, &rect,
                     DT_SINGLELINE | DT_CENTER | DT_VCENTER);
            EndPaint(hWnd, &ps);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

Utworzone i wyświetlone przez program okno (rys.) jest w pełni funkcjo­nalne. Można go przesunąć w inne miejsce ekranu, zmienić jego rozmiar, zwinąć, zmaksymali­zować i zamknąć. Za każdym razem, gdy okno zmienia rozmiar, jego ograniczony ramką i paskiem tytułowym obszar roboczy (ang. client area) jest przemalo­wywany. Okno ma menu systemowe, które pojawia się, gdy klikniemy lewym przyciskiem myszy na ikonie usytuo­wanej po lewej stronie paska tytuło­wego.

Typy i funkcje WinAPI

W powyższym kodzie źródłowym C++ występuje wiele typów danych, które są charaktery­styczne dla programów okienko­wych w Windows i raczej nie są używane w progra­mach konso­lowych. Nowe typy pojawiają się już w nagłówku funkcji WinMain:

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)

Typ WINAPI określa konwencję wywoływania funkcji przyjętą w systemie Windows. Gdy program jest uruchamiany, funkcja WinMain otrzymuje od systemu cztery argumenty. Pierwszy typu HINSTANCE jest uchwytem instancji (reali­zacji, kopii) programu. Uchwyty w Windows są wartościami przypomi­nającymi wskaźniki – służą do wskazy­wania różnych obiektów (nie w sensie programo­wania obiekto­wego). Częstymi typami uchwytów są:

Drugi argument funkcji WinMain, również typu HINSTANCE, ma zawsze wartość NULL (w 16-bitowych wersjach Windows umożliwiał dostęp do poprze­dniej instancji programu). Z kolei trzeci argument typu LPSTR jest łańcuchem zawiera­jącym wiersz polecenia uruchamia­jącego program, zaś czwarty liczbą całko­witą określa­jącą, w jakiej postaci okno pojawi się na ekranie (normalne, zmaksymali­zowane, zwinięte).

W nagłówku funkcji WndProc obsługującej komunikaty kiero­wane do okna, którą w przeci­wieństwie do WinMain można nazwać inaczej, występują jeszcze inne typy danych:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

Wartość LRESULT jest zdefiniowana jako LONG, a ta w C++ jako long, natomiast CALLBACK określa podobnie jak WINAPI konwencję wywoły­wania funkcji w Windows. Pierwszy argument funkcji WndProc jest uchwytem okna, do którego jest kiero­wany komunikat, drugi numerem tego komuni­katu. Wyjaśnijmy przy okazji, że komunikaty są identyfi­kowane przez 32-bitowe liczby całko­wite typu UINT zdefinio­wanego jako unsigned int. Trzeci i czwarty argument funkcji dostar­czają dodatko­wych infor­macji o komuni­kacie, a ich znaczenie zależy od rodzaju komunikatu. Typ WPARAM jest zdefinio­wany jako UINT, a LPARAM jako LONG.

Funkcje WinMain i WndProc używają łącznie czterech najczęściej występu­jących w progra­mach okienko­wych struktur danych (struct w C/C++):

Nie są to bynajmniej wszystkie typy, jakie można spotkać w progra­mach okienko­wych WinAPI C++. Na przykład BOOL oznacza wartości FALSETRUE typu int zdefinio­wane jako 0 i 1, DWORD – liczby unsigned int, BYTEunsigned char, APIENTRY – ten sam typ co WINAPI, a PASCAL – ten sam co CALLBACK.

Klasyczny sposób programowania w Windows polega na wykorzy­staniu funkcji biblio­teki API. W pierw­szej wersji Windows biblio­teka liczyła mniej niż 450 funkcji, ale rozrastała się wraz z rozwojem systemu. Obecnie zawiera ich tysiące. W przyto­czonym wyżej programie używanych jest 19 funkcji API. Oto ich lista w kolej­ności występo­wania:

Funkcja główna programu

Powróćmy do funkcji WinMain w programach okienkowych będącej odpowie­dnikiem funkcji main w progra­mach konsolo­wych. Od niej rozpo­czyna się wykonanie programu, a pierwszą czynnością, jaką zazwyczaj wykonuje, jest inicjali­zacja struktury typu WNDCLASS potrzebnej do zarejestro­wania klasy okna. W rozpatry­wanym programie ten fragment kodu wygląda następu­jąco:

static char szClassName[] = "Pierwszy";
WNDCLASS wc;

wc.style         = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   = WndProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hInst;
wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wc.lpszMenuName  = NULL;
wc.lpszClassName = szClassName;

Styl okna ustalany jest za pomocą dwóch znaczników bitowych CS_HREDRAWCS_VREDRAW oznacza­jących, że okno ma być odświe­żane (przemalo­wywane) za każdym razem, gdy zmieni się jego szerokość lub wysokość. Drugiemu polu struktury zostaje przypi­sany wskaźnik na procedurę WndProc przetwarza­jącą komuni­katy, które Windows kieruje do okna. Kolejne dwa pola służą do rezerwacji dodatkowej pamięci dla własnych celów, ale program nie korzysta z tej możliwości, więc je zeruje. W nastę­pnych czterech polach zapamię­tane zostają uchwyty do instancji programu, predefinio­wanej w Windows ikony i kursora myszy oraz jasno­szarego pędzla używanego do wypeł­niania obszaru roboczego okna. Ikona i kursor są ładowane z zasobów Windows, o czym świadczy wartość NULL pierwszego argu­mentu funkcji LoadIconLoadCursor. Drugi argument jest identyfi­katorem ładowa­nego obiektu (IDI_APPLICATION oznacza standar­dową ikonę aplikacji, a IDC_ARROW kursor w kształcie strzałki). Funkcja GetStock­Object służy do pobierania standar­dowych obiektów grafi­cznych, które ma Windows, takich jak pędzle, pióra i czcionki. Jej wartością jest uchwyt ogólnego typu HGDIOBJ wskazu­jący na pobrany obiekt, dlatego należało dokonać jej konwersji na wartość typu HBRUSH. Dwa ostatnie pola struktury są łańcu­chami określa­jącymi nazwę menu i klasy okna. Polu menu przypisany zostaje wskaźnik NULL, bo okno nie ma menu, a polu nazwy klasy – łańcuch szClassName.

Po wypełnieniu wszystkich dziesięciu pól struktury można ją użyć do zarejestro­wania klasy okna, wywo­łując funkcję RegisterClass. Gdy ta operacja zawiedzie, funkcja MessageBox wyświetla okno komunikatu informu­jące o problemie. Po zamknięciu okna komuni­katu działanie programu zostaje zakończone (powrót z funkcji WinMain):

if (!RegisterClass(&wc))
{
    MessageBox(NULL, "Ten program wymaga Win32!", szClassName, MB_ICONERROR);
    return 0;
}

Pierwszym argumentem funkcji MessageBox jest uchwyt okna nadrzę­dnego względem okna komuni­katu lub wskaźnik NULL, gdy okna nadrzę­dnego nie ma. Drugim argu­mentem jest tekst wyświe­tlany wewnątrz okna komuni­katu, trzecim napis na pasku tytułowym, a czwartym stała lub suma logiczna stałych określa­jących ikonę symboli­zującą rodzaj komuni­katu i przyciski służące do zamknięcia okna (domyślnie OK, gdy nie określono przycisków).

Gdy klasa okna została zarejestrowana, okno jest tworzone za pomocą funkcji Create­Window. Część spośród jej jedenastu argu­mentów zawiera podobne infor­macje jak pola struktury WNDCLASS użytej do rejestracji okna:

HWND hWnd;

hWnd = CreateWindow(szClassName, "Pierwszy program w WinAPI", WS_OVERLAPPEDWINDOW,
             CW_USEDEFAULT, CW_USEDEFAULT, 450, 350, NULL, NULL, hInst, NULL);

W pierwszym argumencie podana jest nazwa klasy tworzo­nego okna, w drugim napis na pasku tytułowym, a w trzecim styl określany jako kombinacja kilku znaczników (stałych bitowych). Wartość WS_OVERLAPPED­WINDOW oznacza zwykłe okno, które można nakładać na inne okna pojawia­jące się na ekranie. Okno ma pasek tytułowy, ikonę, menu systemowe, ramkę umożliwia­jącą zmianę rozmiaru oraz przyciski zwijania, maksymali­zacji i zamknięcia. W kolej­nych czterech argu­mentach podane są współ­rzędne ekranowe lewego górnego narożnika oraz szerokość i wysokość okna. Stała CW_USEDEFAULT oznacza, że Windows ma przyjąć wartość domyślną. Następne trzy argu­menty są uchwytami okna nadrzę­dnego, menu i instancji programu. Tworzone okno nie podlega żadnemu oknu ani nie ma menu, toteż dwa z tych argu­mentów mają wartość NULL, natomiast uchwyt instancji programu jest wartością przekazaną przez Windows do funkcji WinMain w jej pierwszym argu­mencie. Ostatni argument wywo­łania funkcji Create­Window jest wskaźni­kiem na dodatkowe dane, których tu nie ma, więc jest równy NULL.

Funkcja CreateWindow zwraca uchwyt do utworzonego okna. Jest on potrzebny do odwoły­wania się do okna w trakcie dalszego działania programu, dlatego zostaje zapamię­tany w zmiennej hWnd typu HWND. Aby okno pojawiło się na ekranie, należy wywołać dwie funkcje API:

ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);

Pierwsza wyświetla okno na ekranie, druga wysyła bezpo­średnio do procedury okna komunikat WM_PAINT informu­jący, że obszar roboczy okna wymaga odświe­żenia. Drugi argument funkcji ShowWindow jest liczbą przeka­zaną przez Windows w ostatnim argu­mencie funkcji WinMain określa­jącą, w jakiej postaci okno ma się ukazać na ekranie:

Po utworzeniu i wyświetleniu okna funkcja główna programu WinMain obsługuje komuni­katy, wykonując następu­jacą pętlę komuni­katów:

MSG msg;

while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Funkcja GetMessage pobiera komunikat z kolejki komuni­katów do struktury typu MSG podanej w pierwszym argu­mencie. Wartości NULL lub 0 pozostałych argu­mentów wskazują, że program odbiera wszystkie komuni­katy wysyłane do jego okien. Wartość GetMessage jest niezerowa dla każdego komuni­katu pobranego z kolejki z wyjątkiem komuni­katu WM_QUIT. Oznacza to, że pętla komuni­katów jest wykony­wana dopóty, dopóki funkcja GetMessage nie napotka komuni­katu WM_QUIT, który spowoduje jej przerwanie, a tym samym zakoń­czenie wykonania programu. Pętla komuni­katów będąca sercem każdego programu okienko­wego nigdy nie zosta­łaby zakoń­czona, gdyby nie otrzymała komuni­katu WM_QUIT.

Wewnątrz pętli komunikatów wywoływane są dwie funkcje API: Translate­MessageDispatch­Message. Pierwsza przekształca pochodzące od klawia­tury komuni­katy klawi­szowe na bardziej wygodne do analizy komuni­katy znakowe i odsyła je do Windows celem umieszczenia ich w kolejce komuni­katów, druga przeka­zuje pobrane z kolejki komuni­katy do Windows celem ich przetwo­rzenia przez proce­durę okna.

Procedura okna

Procedura okna jest wywoływana przez Windows, od którego otrzymuje w argu­mentach uchwyt okna typu HWND, numer identyfi­kacyjny komuni­katu typu UINT oraz dwa parametry typu WPARAMLPARAM dostarcza­jące dodatko­wych infor­macji o komuni­kacie. Do proce­dury okna dociera wiele różnych komuni­katów, które mogą być przez nią przetwa­rzane lub nie. Generalnie większość z nich jest przekazy­wana do obróbki do proce­dury domyślnej DefWindowProc, a tylko niektóre wymagają zdefinio­wania własnej obsługi. Zazwyczaj dotyczy to następu­jących komuni­katów:

Dobrze napisana procedura okna zwraca po obsłużeniu komuni­katu wartość 0, a wszystkie, których nie przetwarza, pozostawia do obsługi proce­durze DefWindowProc. Najwygo­dniejszą instrukcją pozwala­jącą wybierać komuni­katy do przetwo­rzenia jest switch. W omawianym programie procedura WndProc przetwarza tylko trzy komuni­katy, pozostałe zostawia proce­durze domyślnej:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_PAINT:
            ...         // Malowanie obszaru roboczego okna
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

Obsługę komunikatu WM_CREATE można by w rozpatry­wanym przypadku pominąć, pozosta­wiając go do przetwo­rzenia proce­durze domyślnej. Intencją zamieszczenia banalnego kodu było jedynie wskazanie miejsca, w którym można wykonać dodatkowe czynności inicjali­zacyjne.

Komunikat WM_PAINT dociera do procedury okna, gdy jest ono po raz pierwszy wyświe­tlane na ekranie, a potem za każdym razem, gdy obszar roboczy okna zostanie uniewa­żniony. Wyjaśnijmy, że obszar roboczy staje się nieważny, gdy zostanie zasło­nięty przez inne okno i potem odsło­nięty, bądź gdy rozmiar okna ulegnie zmianie. Unieważ­nienie obszaru roboczego może nastąpić również w wyniku wywołania funkcji InvalidateRect. Gdy procedura okna otrzyma komunikat WM_PAINT, powinna odtworzyć obszar roboczy, przemalo­wując jego zawartość.

Szczególnie ważnym komunikatem, który należy zawsze obsłużyć, jest WM_DESTROY. Pojawia się on w chwili, gdy okno po usunięciu z ekranu jest niszczone. Obsługa tego komuni­katu polega na wywołaniu funkcji PostQuitMessage, która wstawia do kolejki komuni­katów programu komunikat WM_QUIT. Gdy w funkcji WinMain funkcja GetMessage pobierze go z kolejki, zwróci wartość 0, przez co pętla komuni­katów zostanie zakoń­czona. Gdyby komunikat WM_DESTROY nie został obsłużony, okno zostałoby zniszczone, ale program pozosta­wałby w pamięci, oczekując nadare­mnie na komuni­katy.

Malowanie okna

Malowanie okna, a dokładniej obszaru roboczego okna, umożliwiają funkcje API należące do grupy GDI (ang. Graphics Device Interface, interfejs urzą­dzenia grafi­cznego). Aby program mógł malować na ekranie, musi uzyskać uchwyt do tzw. kontekstu urzą­dzenia (ang. device context) – struktury związanej z ekranem monitora (kontekst może odnosić się także do innego urzą­dzenia grafi­cznego, np. drukarki, plotera czy nawet urzą­dzenia pamięcio­wego istnieją­cego tylko w pamięci komputera). Na początku przetwa­rzania komuni­katu WM_PAINT uchwyt kontekstu urzą­dzenia uzyskuje się, a po zakoń­czeniu malowania zwalnia go, wywołując funkcje BeginPaintEndPaint. Funkcja BeginPaint dostarcza również pewnych infor­macji o malowaniu w strukturze typu PAINSTRUCT, a ponadto przygoto­wuje obszar do malowania, czyszcząc jego tło za pomocą pędzla określo­nego w klasie okna. Zasygnali­zowana powyżej obsługa komuni­katu malowania obszaru roboczego okna została w rozpatry­wanym przypadku zaprogra­mowana następu­jąco:

case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    GetClientRect(hWnd, &rect);
    InflateRect(&rect, -rect.right / 10, -rect.bottom / 10);
    Ellipse(hdc, rect.left, rect.top, rect.right, rect.bottom);
    DrawText(hdc, "Cześć! Witamy w Windows API!", -1, &rect,
             DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    EndPaint(hWnd, &ps);
    return 0;

Funkcja GetClientRect, której argumentami są uchwyt okna i wskaźnik do struktury RECT zawiera­jącej pola left, top, rightbottom, udostę­pnia rozmiary prosto­kąta obszaru roboczego okna (lewy górny i prawy dolny narożnik). Pola lefttop są ustawiane na 0, toteż rightbottom określają szerokość i wysokość prosto­kąta. Funkcja InflateRect została użyta do zmniej­szenia jego wielkości o 1/10 z każdej z czterech stron. Z kolei funkcja Ellipse rysuje czarnym piórem elipsę dopaso­waną do pomniej­szonego prosto­kąta i zamalo­wuje jej wnętrze białym pędzlem (kolory domyślne). Na koniec funkcja DrawText rysuje wewnątrz tego samego prosto­kąta napis. Wartość jej trzeciego argu­mentu jest długością rysowa­nego łańcucha; gdy jest równa –1, długość tę oblicza Windows. Stała DT_SINGLELINE oznacza, że powrót karetki ('\r') i wysuw wiersza ('\n') nie mają być interpre­towane jako znaki sterujące, a stałe DT_CENTERDT_VCENTER, że tekst ma być wyśrodko­wany wewnątrz prosto­kąta w poziomie i pionie.


Opracowanie przykładu: czerwiec 2020