Radek Chalupa - vývoj software, konzultace a školení programování
Radek Chalupa

Radek Chalupa

· Vývoj software

· Školení a konzultace programování

· Software ke stažení


Buffering v GDI

Odpověď na mnoho různých dotazů (zejména na programátorských fórech) týkajících se kreslení a překreslování okna (pomocí standardního GDI) směřuje k použití paměťového kontextu zařízení. Ukážeme si velice jednoduchý příklad, jak na to.

Řekněme že chceme kreslit myší (při stisknutém levém tlačítku) do okna jednoduchou čáru. Výsledný program musí splňovat 2 podmínky:

  • čára (chcete-li) stopa po myši se musí objevit v okně hned po akci uživatele (samozřejmě zanedbáváme zpoždění v řádu zlomků sekund) způsobené fyzickou rychlostí hardware počítače:-))
  • veškerý nahreslený obsah musí v okně zůstat i po jeho částečném nebo úplném překreslení vyvolaném například jeho minimalizací a následnou obnovou

Ukázkový projekt je pro urychlení vygenerován jako Win32 aplikace ve Visual Studiu .NET 2003. První úpravou (a dalším zjednodušením poblému:-)) je změna stylu okna z WS_OVERLAPED na kombinaci stylů WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX, čímž zabráníme změně rozměrů okna a zjednodušíme si náš úkol (jak uvidíme dále) - jde zde o vysvětlení nejjednodušího principu, další "sofistikaci" již nechám na čtenáři popřípadě na další článek.

hWnd = CreateWindow(szWindowClass,
szTitle,
WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX,
200, 180,
640, 480,
NULL, NULL, hInstance, NULL);

Dále si vytvoříme dvě globální proměnné: pro "paměťový" kontext zařízení a "paměťovou" bitmapu:

// Globální proměnné pro buffering
HDC g_hdcMem = NULL;
HBITMAP g_hBitmapMem = NULL;

Při vytvoření okna, tj. v obsluze zprávy WM_CREATE tyto proměnné inicializujeme. Obslužná funkce zprávy WM_CREATE bude vypadat takto:

// Obsluha zprávy WM_CREATE
void OnCreate(HWND hWnd)
{
RECT rect;
HDC hdcScreen = GetDC(NULL);
GetClientRect(hWnd, &rect);
g_hdcMem = CreateCompatibleDC(hdcScreen);
g_hBitmapMem = CreateCompatibleBitmap(hdcScreen, rect.right, rect.bottom);
SelectObject(g_hdcMem, g_hBitmapMem);
FillRect(g_hdcMem, &rect, GetSysColorBrush(COLOR_WINDOW));
ReleaseDC(NULL, hdcScreen);
}

O co zde jde? Nejprve musíme vytvořit tzv. "paměťový" kontext zařízení (HDC). K tomu použijeme funkci CreateCompatibleDC, která jako parametr vyžaduje handle nějakého existujícího kontextu, na základě jehož vlastností je nový kontext zařízení vytvořen. Proto si "vypůjčíme" například kontext celé obrazovky, který získáme pomocí funkce GetDC s parametrem NULL. Stejně tak bychom samozřejmě mohli zadat handle existujícího okna. Dále potřebujeme vytvořit bitmapu (HBITMAP) kterou vybereme do našeho kontextu. Tuto bitmapu vytvoříme pomocí funkce CreateCompatibleBitmap, která ve svých parametrech vyžaduje kromě patného handle kontextu zařízení rozměry této bitmapy. Z pochopitelných důvodů vytvoříme tuto bitmapu o velikosti odpovídající klientské (a tedy námi překreslované) oblasti okna. Tu získáme pomocí funkce GetClientRect. Po vybrání bitmapy do kontextu zařízení už můžeme do kontextu začít kreslit. Můžeme si jej například vyplnit nějakou výchozí barvou - v našem případě jednoduše použijeme barvu pozadí okna - máme k disposici odpovídající systémový stětec který získáme pomocí funkce GetSysColorBrush. Když máme vytvořený paměťový kontext zařízení, bude dalším krokem vytvoření obsluhy zprávy WM_PAINT, která přijde při požadavku na překreslení okna nebo jeho části. Vzhledem k tomu že (jak uvidíme dále) budeme veškeré naše kreslení do okna realizovat kreslením do naeeho paměťového kontextu zařízení, budeme v obsluze zprávy WM_PAINT pouze kopírovat náš paměťový konetxt do kontextu zařízení okna. Kód obslužné funkce bude tedy jednoduchý:

// Obsluha zprávy WM_PAINT
void OnPaint(HWND hWnd)
{
HDC hDC;
RECT rect;
PAINTSTRUCT ps;
GetClientRect(hWnd, &rect); hDC = BeginPaint(hWnd, &ps); BitBlt(hDC, 0,0, rect.right, rect.bottom, g_hdcMem, 0,0, SRCCOPY); EndPaint(hWnd, &ps); }
Nyní se podíváme na vlastní "kreslení" do okna. Budeme ho relizovat tím nejjednodušším způsobem. Při detekci pohybu myši (zachycení zprávy WM_MOUSEMOVE budeme s využitím parametru wParam testovat, zda je stisknuté levé tlačíto myši a v tom případě nakrelíme "čáru" do nového cílového bodu, zjištěného z parametru lParam zprávy WM_MOUSEMOVE. Poku není levé tlačítko stisknuté, přemístíme do stejného bodu aktuální pozici v kontextu zařízení (která pak bude výchozím bodem pro další kreslení). Funkce vypadá následovně:
// Obsluha zprávy WM_MOUSEMOVE
void OnMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
if ( wParam & MK_LBUTTON )
LineTo(g_hdcMem, LOWORD(lParam), HIWORD(lParam));
else
MoveToEx(g_hdcMem, LOWORD(lParam), HIWORD(lParam), NULL);
RedrawWindow(hWnd, NULL, NULL, RDW_UPDATENOW | RDW_INVALIDATE);
}

A to je skoro vše. Zbývá ještě jeden krok, který sice není nutný, ale v mnohých případech výrazně zlepší překreslování okna. Pokud totiž v oblsuze WM_PAINT překreslujeme celé okno (přesněji řečeno jeho klientskou oblast) je zcela zbytečné nechávat před tím systém tuto oblast "překreslit" štětcem pozadí, jak todělá výchozí obsluha zprávy WM_ERASEBKGND. Zamezíme tím nežádoucímu problikávání, které byv našem případě nebylo tak výrazné, neboť naše okno nelze roztahovat a tudíž nedochází k jeho rychle se opakujícímu překreslování při roztahování, ale není na škodu vždy tuto "optimalizaci" provést. Existují 2 používané způsoby. Jedním z nich je zabránit výchozímu (DefWindowProc) zpracování zprávy WM_ERASEBKGND. Druhou možností je nastavit na začátku handle štětce okna na hodnotu NULL. V našem příkladě je použit první způsob. Do procedury okna přidáme následující kód:

// přepínač na hodnotu identifikátoru zprávy
switch (message)
{
case WM_ERASEBKGND:
return 0;
break;
// obsluhy dalších zpráv ...
}

Copyright © 2017 Radek Chalupa