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

WinAPI C++

Rysowanie gwiazdki Moduł rysowania gwiazdki Kontrolki edycyjne w oknie dialogowym Praca z kontrolkami okna dialogowego Tworzenie i wybieranie obiektów graficznych Program "Gwiazdka" w WinAPI C++ Poprzedni przykład Następny przykład Kontakt

Rysowanie gwiazdki

Podstawowymi obiektami GDI do tworzenia grafiki w Windows są: pióro (ang. pen) do ryso­wania odcinków, pędzel (ang. brush) do wypeł­niania obszarów zamknię­tych i czcionka (ang. font) do wypisy­wania napisów. Niektóre funkcje WinAPI używające tych obiektów przypomi­nają funkcje biblio­teki WinBGI. Na przykład MoveToEx przesuwa pióro do wska­zanej pozycji, LineTo rysuje odcinek prostej od bieżącej pozycji pióra do pozycji wyszczegól­nionej w argu­mentach tej funkcji, RectangleEllipse rysują prostokąt o zadanych przeciwle­głych wierzchoł­kach i elipsę przylega­jącą do boków danego prostokąta, a TextOut wypisuje łańcuch znaków od podanej pozycji. Dla porównania warto przyto­czyć funkcję rysującą gwiazdkę foremną o n ramionach (rys.), której wierz­chołki leżą naprze­miennie na dwóch koncentry­cznych okręgach o środku (p,q) i promie­niach R, r.

Odpowiednikiem funkcji gwiazdka używanej w programie kreślenia gwiazdki w WinBGI rysującej tę figurę bieżącym pisakiem i wypełnia­jącej ją zgodnie z aktu­alnie ustawionym stylem wypeł­niania obszarów jest następu­jąca funkcja korzysta­jąca z narzędzi dostę­pnych w Windows API:

void gwiazdka(HDC hdc, int n, int p, int q, double R, double r)
{
    double alpha2 = M_PI / n, alpha = 2 * alpha2;
    LOGPEN pen;
    MoveToEx(hdc, R + p, q, NULL);
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha, psi = fi - alpha2;
        LineTo(hdc, r * cos(psi) + p, q - r * sin(psi));
        LineTo(hdc, R * cos(fi) + p, q - R * sin(fi));
    }
    GetObject(GetCurrentObject(hdc, OBJ_PEN), sizeof(pen), &pen);
    FloodFill(hdc, p, q, pen.lopnColor);
}

Łatwo sprawdzić, że podobieństwo między obydwu funkcjami gwiazdka jest duże. Pierwszym argu­mentem MoveToEx, LineToFloodFill jest uchwyt kontekstu urzą­dzenia grafi­cznego takiego jak ekran lub drukarka, a drugim i trzecim współ­rzędne punktu. Ostatnim argu­mentem MoveToEx może być NULL albo wskaźnik na strukturę typu POINT, gdy zachodzi potrzeba zapamię­tania punktu, w którym pióro było ustawione przed przesu­nięciem. Wypeł­nienie gwiazdki za pomocą funkcji FloodFill wymaga uzyskania infor­macji o kolorze naryso­wanego konturu, czyli o kolorze pióra użytego do jego ryso­wania. Uchwyt pióra udostępnia funkcja GetCurrent­Object, a infor­macje o nim funkcja GetObject, która umieszcza je w struk­turze LOGPEN. Pole lopnColor tej struktury określa kolor pióra.

Moduł rysowania gwiazdki

Znacznie lepszy sposób rysowania zapełnionej gwiazdki polega na użyciu funkcji Polygon (wielokąt), która podobnie jak Rectangle, EllipseRoundRect (prostokąt z zaokrą­glonymi rogami) rysuje stosowny kontur i wypełnia jego wnętrze aktualnie wybranym w konte­kście urzą­dzenia pędzlem. Funkcja wymaga podania współrzę­dnych wierz­chołków wielokąta w tablicy struktur POINT. Wypada tu nadmienić, że jest ona reali­zowana na poziomie stero­wnika urzą­dzenia grafi­cznego, przez co działa nieporówny­walnie szybciej niż wywoły­wane w pętli funkcje LineTo i na koniec FloodFill. W nowej wersji funkcji gwiazdka wprowa­dzimy dodatkowy argument określa­jący kąt pomiędzy osią poziomą i promieniem łączącym środek gwiazdki z jej pierwszym wierz­chołkiem (rys.).

Zważywszy na wygodę precyzowania wartości nowego argumentu założymy, że wyraża on liczbę całkowitą stopni, a jego wartością domyślną jest 0. Definicję funkcji gwiazdka umieścimy w odrębnym module, którego plik nagłów­kowy star.h wygląda następu­jąco:

// Funkcja rysowania gwiazdki w Windows API
// ======================================================
// hdc   - kontekst urządzenia graficznego
// n     - liczba ramion
// p, q  - współrzędne środka
// R, r  - promienie okręgów, na których leżą wierzchołki
// omega - nachylenie promienia R wiodącego do pierwszego
//         wierzchołka (stopnie, wartość domyślna = 0)
// ======================================================

#ifndef H_STAR
#define H_STAR

#include <windows.h>
#include <math.h>

void gwiazdka(HDC hdc, int n, int p, int q, double R, double r, int omega = 0);

#endif // H_STAR

W poniższej definicji funkcji gwiazdka stano­wiącej drugą część modułu występuje tablica dynamiczna o elemen­tach typu POINT. Po wyzna­czeniu wartości wszystkich jej elementów (współrzę­dnych wierz­chołków gwiazdki) i wywo­łaniu funkcji Polygon pamięć przydzie­lona tablicy jest zwalniana. Wyjaśnijmy jeszcze, że kąt omega jest na początku przeli­czany na radiany, a ostatnim argu­mentem funkcji Polygon jest liczba wierz­chołków rysowa­nego wielokąta.

void gwiazdka(HDC hdc, int n, int p, int q, double R, double r, int omega)
{
    double alpha2 = M_PI / n;
    double omegar = omega * M_PI / 180;
    POINT *P = new POINT[n = 2 * n];
    for (int k = 0; k < n; k++)
    {
        double fi = k * alpha2 + omegar;
        double ro = (k % 2 == 0) ? R : r;
        P[k].x = int(ro * cos(fi)) + p;
        P[k].y = q - int(ro * sin(fi));
    }
    Polygon(hdc, P, n);
    delete[] P;
}

Kontrolki edycyjne w oknie dialogowym

Program rysowania gwiazdki powinien udostępniać okno dialogowe z kontrol­kami edycyj­nymi pozwala­jącymi na zmianę jej parame­trów. Oczywiście okno parametrów najwygo­dniej jest przywo­ływać za pomocą polecenia menu. Numery ID elementów menu i kontrolek edycyj­nych definiu­jemy w pliku nagłów­kowym resource.h postaci:

#define IDM_PARAM   101
#define IDM_LEWO    102
#define IDM_PRAWO   103
#define IDM_KONIEC  104

#define IDC_N       201
#define IDC_RBIG    202
#define IDC_RLOW    203

Zgodnie z ogólnie przyjętą konwencją nazwy symboliczne numerów ID kontrolek są rozpoczy­nane od przedro­stka IDC_ (ang. control ID, identyfi­kator kontrolki). Menu ma składać się z czterech elementów MENUITEM poleca­jących wyświe­tlić okno parame­trów gwiazdki, obrócić ją w lewo lub w prawo i zakoń­czyć wykonanie programu, zaś okno dialogowe ma zawierać trzy kontrolki EDITTEXT pozwala­jące na wyświe­tlenie i edycję parametrów n, Rr gwiazdki, a także trzy opisujące je kontrolki LTEXT (etykiety) oraz przycisk potwier­dzający i przycisk anulujący edycję. Zasoby te definiu­jemy w następu­jącym pliku skryptowym gwiazdka.rc:

#include <windows.h>
#include "resource.h"

Menu MENU
BEGIN
    MENUITEM "P&arametry", IDM_PARAM
    MENUITEM "&Lewo", IDM_LEWO
    MENUITEM "&Prawo", IDM_PRAWO
    MENUITEM "&Koniec", IDM_KONIEC
END

Param DIALOG 160, 50, 181, 78
STYLE DS_MODALFRAME | WS_POPUP | WS_SYSMENU
CAPTION "Parametry"
FONT 8, "Verdana"
BEGIN
    LTEXT "n:", -1, 17, 15, 16, 11
    LTEXT "R:", -1, 17, 33, 16, 11
    LTEXT "r:", -1, 17, 51, 16, 11
    EDITTEXT IDC_N, 33, 15, 57, 11
    EDITTEXT IDC_RBIG, 33, 33, 57, 11
    EDITTEXT IDC_RLOW, 33, 51, 57, 11
    DEFPUSHBUTTON "&Rysuj", IDOK, 121, 15, 43, 13
    PUSHBUTTON "&Anuluj", IDCANCEL, 121, 51, 43, 13
END

Ikona ICON "Gwiazdka.ico"

Przycisk z napisem Rysuj jest domyślny. Gdy użytkownik zmieni lub wpisze w kontrol­kach edycyj­nych nowe wartości parame­trów i kliknie go lewym przyci­skiem myszy lub naciśnie klawisz Enter, obszar roboczy okna głównego zostanie przemalo­wany – pojawi się w nim gwiazdka o zmienio­nych parame­trach. Przycisk Anuluj jest przyci­skiem zwykłym. Naciśnięcie go spowo­duje jedynie zamknięcie okna dialo­gowego.

Praca z kontrolkami okna dialogowego

Gdy okno dialogowe jest tworzone, jego kontrolki edycyjne powinny zostać zapeł­nione tekstami określa­jącymi wartości parame­trów gwiazdki widniejącej w oknie głównym, a w momencie naciśnięcia przycisku Rysuj nowe wartości parame­trów powinny zostać przesłane z kontrolek okna dialogo­wego do okna głównego celem przemalo­wania jego obszaru robo­czego. Okno dialogowe parame­trów wygodniej jest utworzyć za pomocą funkcji DialogBox­Param niż DialogBox, gdyż zawiera ona piąty argument, którym jest wskaźnik na przekazy­wane dane. Zatem deklaru­jemy na początku kodu źródło­wego programu strukturę parame­trów gwiazdki:

struct PARAM            // Parametry gwiazdki
{
    int n, R, r;
};

Następnie definiujemy i inicjalizujemy w procedurze okna WndProc zmienną staty­czną typu PARAM:

static PARAM param = { 5, 0, 0 };

Wartość 5 określa wstępną liczbę ramion gwiazdki. Długości obu promieni zostaną dostoso­wane do rozmiaru okna głównego podczas obsługi komuni­katu WM_SIZE w proce­durze WndProc. Gdy okno dialogowe parame­trów będzie tworzone, wskaźnik na zmienną param zostanie do niego przeka­zany jako wartość typu LPARAM piątego argu­mentu wywołania DialogBoxParam:

case IDM_PARAM:
    DialogBoxParam(hInstance, "Param", hWnd, (DLGPROC)ParamProc, (LPARAM)&param);
    return 0;

Wskaźnik ten jest dostępny w procedurze ParamProc okna dialogo­wego jako parametr typu LPARAM w komuni­kacie WM_INITDIALOG. Przeka­zane w ten sposób do okna dialogo­wego dane najwygo­dniej jest zapamiętać w dwóch zdefinio­wanych w jego proce­durze zmiennych staty­cznych:

static PARAM dane, *pdane;

Procedura powinna ustawić ich wartości na początku obsługi komuni­katu WM_INITDIALOG, przypi­sując zmiennej pdane wskaźnik na orygi­nalną strukturę parame­trów gwiazdki i kopiując tę strukturę do zmiennej dane:

pdane = (PARAM*)lParam;
dane = *pdane;

Rozwiązanie to jest zgodne z prefe­rowaną przez wielu progra­mistów zasadą, by użycie zmiennych global­nych ograni­czyć do niezbę­dnego minimum. Z pewnością para­metry gwiazdki (liczba ramion i promienie) używane zarówno w proce­durze okna, jak i proce­durze okna dialog­owego, a także uchwyt programu dostępny w funkcji głównej i potrzebny w proce­durze okna, kwalifi­kują się do definicji global­nych. W przy­padku większej liczby okien natłok zmiennych global­nych używanych do komuni­kacji między nimi prowadzi do bałaganu w programie, powinno się więc takich zmiennych unikać, ba, nawet w prostych programach, by nie nabyć złych przyzwy­czajeń.

Udostępnione w zmiennej dane parametry gwiazdki można przesłać do kontrolek edycyj­nych. Aby wstawić tekst do kontrolki, można posłużyć się funkcją SetWindowText, zaś aby pobrać tekst z kontrolki, funkcją GetWindowText. W przy­padku okien dialogo­wych wygodniej­szymi funkcjami są SetDlg­ItemTextGetDlg­ItemText, gdyż wymagają podania numeru ID kontrolki zamiast jej uchwytu, a gdy przesy­łane są liczby całko­wite, lepiej posłużyć się funkcjami SetDlg­ItemIntGetDlg­ItemInt, gdyż dokonują konwersji liczby na tekst i tekstu na liczbę. Obsługę komuni­katu WM_INITDIALOG w proce­durze okna dialogo­wego parame­trów gwiazdki można więc zaprogra­mować następu­jąco:

case WM_INITDIALOG:
    pdane = (PARAM*)lParam;
    dane = *pdane;
    SetDlgItemInt(hDlg, IDC_N, dane.n, FALSE);
    SetDlgItemInt(hDlg, IDC_RBIG, dane.R, FALSE);
    SetDlgItemInt(hDlg, IDC_RLOW, dane.r, FALSE);
    return TRUE;

Argumentami funkcji SetDlgItemInt są: uchwyt okna dialogo­wego, numer ID kontrolki, przesy­łana liczba całko­wita i wartość logiczna wskazu­jąca, czy wartość jest przesy­łana ze znakiem (TRUE), czy bez niego (FALSE). Fokus uzyskuje pierwsza kontrolka edycyjna, toteż użytko­wnik może rozpocząć modyfi­kację parame­trów od określenia liczby ramion gwiazdki. Przesyłanie danych w odwrotnym kierunku, tj. z kontrolek edycyj­nych do orygi­nalnej struktury parame­trów gwiazdki odbywa się, gdy proce­dura okna dialogo­wego otrzyma komunikat WM_COMMAND z parame­trem wParam równym IDOK, czyli gdy naciśnięty zostanie przycisk Rysuj. Obsługa tego komuni­katu jest bardziej złożona, wymagane jest bowiem spraw­dzenie, czy teksty kontrolek reprezen­tują poprawne wartości liczbowe:

case IDOK:
    if ((dane.n = GetDlgItemInt(hDlg, IDC_N, NULL, FALSE)) >= 2)
    if ((dane.R = GetDlgItemInt(hDlg, IDC_RBIG, NULL, FALSE)) > 0)
    if ((dane.r = GetDlgItemInt(hDlg, IDC_RLOW, NULL, FALSE)) > 0)
    {
        *pdane = dane;
        InvalidateRect(GetParent(hDlg), NULL, TRUE);
        return TRUE;
    }
    MessageBox(hDlg, "Nieprawidłowe parametry gwiazdki.", "Błąd danych", MB_ICONERROR);
    return TRUE;

Trzecim argumentem funkcji GetDlgItemInt jest wskaźnik na zmienną typu BOOL, w której funkcja umieszcza wartość TRUE, gdy konwersja tekstu na liczbę całko­witą udała się, a wartość FALSE, gdy nie. Argument ten może być równy NULL, a wówczas nie ma infor­macji o popra­wności wyniku. Wartością funkcji jest otrzymana liczba całko­wita, a w razie błędu zero. W pro­gramie żąda się dodatkowo, by liczba ramion gwiazdki wynosiła co najmniej 2, a pro­mienie okręgów, na których leżą jej wierz­chołki, były dodatnie. Gdy wszystkie trzy uzyskane wartości są poprawne, skomple­towana struktura dane jest kopio­wana do orygi­nalnej struktury param, po czym wymuszone zostaje przemalo­wanie obszaru robo­czego okna głównego przy nowych parame­trach gwiazdki (GetParent udostępnia uchwyt okna nadrzę­dnego). Gdy wartości kontrolek nie są poprawne, wyświe­tlone zostaje okno informu­jące o błędzie. Po naryso­waniu nowej gwiazdki okno dialogowe nie jest zamykane, użytko­wnik może nadal edytować jej parametry. Okno zamyka przycisk Anuluj generu­jący komunikat WM_COMMAND z parame­trem wParam równym IDCANCEL.

Tworzenie i wybieranie obiektów graficznych

Standardowe obiekty graficzne używane przez funkcje rysujące GDI mają ustalone atrybuty domyślne. Miano­wicie standar­dowe pióro ma grubość jednego piksela i rysuje ciągłą linię kolorem czarnym, standar­dowy pędzel jest biały i nie ma żadnych wzorków, standar­dowa czcionka jest czcionką syste­mową, ma kolor czarny i jest pisana na tle określonym w klasie okna przez pędzel używany do czysz­czenia obszaru roboczego.

Jeżeli istnieje potrzeba użycia obiektu graficznego o innych cechach, trzeba go utworzyć, a nastę­pnie wybrać w kontekście urzą­dzenia grafi­cznego, używając funkcji SelectObject. W danej chwili w kontekście urzą­dzenia może być wybrany tylko jeden obiekt każdego rodzaju. Po wybraniu obiektu można się nim posłu­giwać, a po zwolnieniu kontekstu utwo­rzony obiekt należy usunąć, wywołując funkcję DeleteObject. Funkcja SelectObject wybiera obiekt graficzny w kontekście urzą­dzenia i zwraca uchwyt typu HGDIOBJ do poprze­dniego obiektu, który był w tym kontekście wybrany. Można zatem po użyciu utworzo­nego obiektu wrócić do obiektu poprze­dniego za pomocą jednej instrukcji, jeśli oczywiście jego uchwyt został wcześniej zapamię­tany. Oto przykład utworzenia nowego pędzla i wybrania go, a po użyciu powrotu do pędzla poprze­dniego:

hBrush = (HBRUSH)SelectObject(hdc, CreateSolidBrush(RGB(255, 255, 0)));
...
DeleteObject(SelectObject(hdc, hBrush));

Rzutowanie wartości funkcji SelectObject na wartość typu HBRUSH jest niezbędne, ponieważ jest ona ogólnego typu HGDIOBJ. Należy podkre­ślić z naciskiem, że nie wolno usunąć obiektu grafi­cznego aktualnie wybranego w kontekście urządzenia. Wszystkie obiekty utworzone powinny być po użyciu usunięte. Należy pamiętać, że obiektów standar­dowych nie wolno usuwać.

Opisany sposób postępowania ma miejsce podczas rysowania gwiazdki pomarań­czowym piórem o grubości 2 pikseli i wypeł­niania jej żółtym pędzlem:

HPEN hPen;
HBRUSH hBrush;
...
case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    hPen = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 2, RGB(255, 165, 0)));
    hBrush = (HBRUSH)SelectObject(hdc, CreateSolidBrush(RGB(255, 255, 0)));
    gwiazdka(hdc, param.n, xs, ys, param.R, param.r, omega);
    DeleteObject(SelectObject(hdc, hBrush));
    DeleteObject(SelectObject(hdc, hPen));
    EndPaint(hWnd, &ps);
    return 0;

Pióro zostało utworzone za pomocą funkcji CreatePen, której pierwszy argument określa styl pióra (stała PS_SOLID oznacza styl ciągły), drugi jego grubość, trzeci kolor. Pędzel został utworzony za pomocą funkcji CreateSolid­Brush, która wymaga jedynie podania koloru. Efekt wykonania powyż­szego kodu źródło­wego w gotowym programie jest przedsta­wiony na poniższym rysunku.

Wyjaśnijmy jeszcze, że współrzędne środka gwiazdki i długości dwóch promieni będące argu­mentami dostępnej w zaprezen­towanym powyżej module Star funkcji gwiazdka są wyzna­czane w proce­durze WndProc, gdy dotrze do niej komunikat WM_SIZE:

static PARAM param = { 5, 0, 0 };
static int xs, ys, omega = 0;
...
case WM_SIZE:
    xs = LOWORD(lParam) / 2;
    ys = HIWORD(lParam) / 2;
    param.r = (param.R = max(9 * min(xs, ys) / 10, 3)) / 3;
    return 0;

Pierwszy komunikat WM_SIZE jest wysyłany do procedury okna, gdy funkcja WinMain wywołuje ShowWindow, a potem za każdym razem, gdy rozmiar okna zostanie zmieniony. W mniej znaczącym słowie parametru lParam przekazy­wana jest szerokość obszaru robo­czego okna, a w bar­dziej znaczącym wysokość tego obszaru. Dostęp do tych wartości umożli­wiają makrode­finicje (makra) LOWORDHIWORD. Procedura okna wykorzy­stuje je do obliczenia współrzę­dnych środka obszaru robo­czego okna, które zapamię­tuje w zmiennych staty­cznych xsys. Następnie wyznacza wartości pól R i r struktury param. Gdy zatem do procedury okna dotrze komunikat WM_PAINT, wszystkie parametry gwiazdki są znane i można ją narysować.

Program "Gwiazdka" w WinAPI C++

Kod źródłowy programu rysowania gwiazdki jest pokazany na poniższym listingu. W przeci­wieństwie do poprze­dnich dwóch programów przykła­dowych (PierwszyFraktale) polu hbrBackground struktury używanej do reje­stracji okna w funkcji WinMain zamiast uchwytu pędzla pobiera­nego z zaso­bnika Windows za pomocą funkcji GetStockObject przypisano stałą COLOR_WINDOW określa­jącą jeden ze standar­dowych kolorów syste­mowych (kolor tła okna takiej klasy powinien zmienić się, gdy użytko­wnik zmieni schemat kolorów na swoim kompu­terze). Ponadto do proce­dury okna dialogo­wego wprowa­dzono uspra­wnienie polegające na wskazaniu kontrolki, która jest niewypeł­niona lub zawiera nieprawi­dłowy tekst. Gdy okno komuni­katu informu­jącego o błędzie zostanie zamknięte, kontrolka taka otrzymuje fokus. Argu­mentem funkcji SetFocus jest uchwyt kontrolki, która ma przejąć fokus. Udostę­pnia go funkcja GetDlgItem, która z kolei wymaga podania uchwytu okna i numeru ID kontrolki.

#include <windows.h>
#include "resource.h"
#include <star.h>

struct PARAM            // Parametry gwiazdki
{
    int n, R, r;
};

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

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)
{
    static char szClassName[] = "Gwiazdka";
    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(hInst, "Ikona");
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszMenuName  = "Menu";
    wc.lpszClassName = szClassName;

    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, "Ten program wymaga Win32!", szClassName, MB_ICONERROR);
        return 0;
    }
    hwnd = CreateWindow(szClassName, szClassName, WS_OVERLAPPEDWINDOW,
                 CW_USEDEFAULT, CW_USEDEFAULT, 520, 400, 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)
{
    static HINSTANCE hInstance;
    static PARAM param = { 5, 0, 0 };
    static int xs, ys, omega = 0;
    HDC hdc;
    PAINTSTRUCT ps;
    HPEN hPen;
    HBRUSH hBrush;

    switch (message)
    {
        case WM_CREATE:
            hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
            return 0;

        case WM_SIZE:
            xs = LOWORD(lParam) / 2;
            ys = HIWORD(lParam) / 2;
            if (param.R <= 0) param.r = (param.R = max(9 * min(xs, ys) / 10, 3)) / 3;
            return 0;

        case WM_COMMAND:
            switch (wParam)
            {
                case IDM_PARAM:
                    DialogBoxParam(hInstance, "Param", hWnd, (DLGPROC)ParamProc,
                                   (LPARAM)&param);
                    return 0;

                case IDM_LEWO:
                    if ((omega += 3) >= 360) omega -= 360;
                    InvalidateRect(hWnd, NULL, TRUE);
                    return 0;

                case IDM_PRAWO:
                    if ((omega -= 3) < 0) omega += 360;
                    InvalidateRect(hWnd, NULL, TRUE);
                    return 0;

                case IDM_KONIEC:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);
                    return 0;
            }
            break;

        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            hPen = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 2, RGB(255, 165, 0)));
            hBrush = (HBRUSH)SelectObject(hdc, CreateSolidBrush(RGB(255, 255, 0)));
            gwiazdka(hdc, param.n, xs, ys, param.R, param.r, omega);
            DeleteObject(SelectObject(hdc, hBrush));
            DeleteObject(SelectObject(hdc, hPen));
            EndPaint(hWnd, &ps);
            return 0;

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

BOOL CALLBACK ParamProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static PARAM dane, *pdane;
    int idc;

    switch (message)
    {
        case WM_INITDIALOG:
            pdane = (PARAM*)lParam;
            dane = *pdane;
            SetDlgItemInt(hDlg, IDC_N, dane.n, FALSE);
            SetDlgItemInt(hDlg, IDC_RBIG, dane.R, FALSE);
            SetDlgItemInt(hDlg, IDC_RLOW, dane.r, FALSE);
            return TRUE;

        case WM_COMMAND:
            switch (wParam)
            {
                case IDOK:
                    if ((dane.n = GetDlgItemInt(hDlg, idc = IDC_N, NULL, FALSE)) > 2)
                    if ((dane.R = GetDlgItemInt(hDlg, idc = IDC_RBIG, NULL, FALSE)) > 0)
                    if ((dane.r = GetDlgItemInt(hDlg, idc = IDC_RLOW, NULL, FALSE)) > 0)
                    {
                        *pdane = dane;
                        InvalidateRect(GetParent(hDlg), NULL, TRUE);
                        return TRUE;
                    }
                    MessageBox(hDlg, "Nieprawidłowe parametry gwiazdki.", "Błąd danych",
                               MB_ICONERROR);
                    SetFocus(GetDlgItem(hDlg, idc));
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hDlg, wParam);
                    return TRUE;
            }
    }
    return FALSE;
}

Procedura okna po otrzymaniu komunikatu WM_COMMAND sprawdza wartość parametru wParam w celu przechwy­cenia komuni­katów wygenero­wanych przez pole­cenia menu. Gdy wybrane zostanie pole­cenie Parametry, wywołuje funkcję DialogBox­Param, która tworzy modalne okno dialogowe parame­trów gwiazdki. Gdy użytko­wnik zmieni ich wartości i naciśnie przycisk Rysuj (rys.), proce­dura okna dialogo­wego wymusza wymalo­wanie gwiazdki dla nowych parame­trów, wywołując funkcję InvalidateRect z uchwytem okna głównego. Gdy w menu zostanie wybrane pole­cenie Koniec, procedura okna głównego wysyła do siebie komunikat WM_CLOSE zamykający okno i kończący działanie programu.

Funkcja InvalidateRect jest również wywoływana w proce­durze okna, gdy w menu zostaje wybrane polecenie Lewo lub Prawo. Zanim jednak obszar roboczy okna zostanie uniewa­żniony, wartość zmiennej omega jest odpowie­dnio zwiększana lub zmniej­szana o 3, co oznacza, że gwiazdka ma być obrócona w lewo lub w prawo o 3 stopnie. Nowa wartość jest w razie potrzeby korygo­wana, by mieściła się w zakresie od 0 do 359 stopni.

Uwaga. Według dokumentacji Microsoft wartość koloru systemo­wego przypisy­wana polu hbrBackground struktury używanej do reje­stracji klasy okna powinna być zwiększona o 1, czego odzwiercie­dleniem w powyż­szym programie powinna być instrukcja:

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

Wymaganie to budzi wiele wątpliwości i nieporozumień na forach interne­towych. W rozpatry­wanym przypadku uzyskane tło okna jest odpowiednie, konse­kwencją użycia zmodyfi­kowanej stałej byłoby czarne tło okna.


Opracowanie przykładu: lipiec 2020