Radek ChalupaČlánkyUkázky kóduTipy a trikyAktualityŠkolení a konzultaceVývoj softwareFreewareKontakt

Rozšiřujeme znalost GDI.  15.1.2002

V tomto pokračování na čas opustíme dialogové prvky a podíváme se trochu více na GDI, tedy grafické rozhraní. Zatím jsme se naučili pouze vypisovat text a měnit jeho základní vlastnosti, tedy barvu písma a pozadí. Samozřejmě výraz vlastnosti textu zde není z programátorského pohledu přesný, neboť uvedené vlastnosti se nastavují kontextu zařízení (HDC) a nikoli samotnému textu v kreslící funkci.

Dnešní doprovodný projekt jsem založil pomocí wizarda s tím, že jsem si nechal vygenerovat kostru aplikace (je to defaultní nastavení, když zakládáte nový Win32 projekt - ve Visual Studiu NET). Jednak si ušetříme psaní či kopírování kódu, který už umíme (procedura okna, registrace třídy apod) a hlavně si řekneme o některých "úskalích" editoru zdrojů. Takto vygenerované zdroje mají totiž nastavený jazyk na angličtinu. Když přidáváme nové zdroje, jsou již nastaveny na češtinu (samozřejmě v české verzi Windows). Je proto třeba zdrojům s angličtinou nastavit (ve vlastnostech příslušného zdroje) jazyk na češtinu, a to ještě před tím, než do tohoto zdroje budeme vkládat nějaké texty obsahující diakritiku.

Ale nyní již ke GDI. Ukážeme si nejprve kreslení čar. Budeme například chtít, aby při stisknutím levého tlačítka myši se nakreslila čára spojující tento bod s předchozím bodem kliknutí. Postupně si tuto funkci rozšíříme o různé typy čar. K vykreslení úsečky složí funkce LineTo:

BOOL LineTo(
  HDC hdc,    // handle kontextu zařízení
  int nXEnd,  // x-souřadnice koncového bodu
  int nYEnd   // y-souřadnice koncového bodu
);

Tato funkce vykreslí čáru z aktuální pozice do bodu určeného souřadnicemi nXEnd a nYEnd a současně nastaví aktuální bod na tyto souřadnice. Při získání kontextu zařízení (třeba pomocí GetDC) je aktuální bod nastaven na souřadnice (0,0). Pokud chceme změnit aktuální bod bez kreslení čáry, máme k dispozici funkci MoveToEx:

BOOL MoveToEx(
  HDC hdc,          // handle kontextu zařízení
  int X,            // x-souřadnice nového "aktuálního bodu"
  int Y,            // y-souřadnice
  LPPOINT lpPoint   // původní "aktuální bod"
);

Jak je zřejmé, tato funkce kromě nastavení aktuálního bodu nám vrátí v parametry lpPoint souřadnice původního bodu. Pokud nás tyto původní souřadnice nezajímají, můžeme jako parametr lpPoint uvést NULL.

Vzhledem k tomu, že na stisknutí myši budeme vždy znova získávat kontext zařízení (GetDC), budeme si koncový bod předchozí čáry ukládata do proměnné ptDraw, kterou si na počátku vynulujeme:

POINT ptDraw = {0,0};

Nyní se již můžeme podívat, jak bude vypadat ta nejjednodušší varianta kreslící funkce, kterou budeme volat z procedury okna při přijetí zprávy WM_LBUTTONDOWN s tím, že si předáme parametry, které budeme (již nyní i později) potřebovat:

void MyDrawLine(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  POINTS point = MAKEPOINTS(lParam);
  HDC hdc = GetDC(hWnd);
  MoveToEx(hdc, ptDraw.x, ptDraw.y, NULL);
  LineTo(hdc, point.x, point.y);
  ptDraw.x = point.x;
  ptDraw.y = point.y;
  ReleaseDC(hWnd, hdc);
}

Výsledkem nyní bude posloupnost navazujících úseček černé barvy a tloušťce 1 pixelu. Pokud budeme chtít měnit barvu, tloušťku popřípadě další vlastnosti čáry, musíme se seznámit s objektem GDI, zvaným pero (Pen). Tento objekt definující vlastnosti čáry, musíme nejprve vytvořit, a získaný handle objektu (typ HPEN) pak nastavíme kontextu zařízení. K vytvoření pera máme k dispozici nejjednodušší funkci CreatePen:

HPEN CreatePen(
  int fnPenStyle,    // styl pera
  int nWidth,        // šířka pera
  COLORREF crColor   // barva pera
);

Parametr fnPenStyle v určuje, zda se jedná o běžnou spojitou čáru či některou z čar přerušovaných, popř. další vlastnost, o které se dozvíme později. Pro spojitou čáru použijeme hodnotu PS_SOLID. Další možnosti definují čáru tečkovanou, čárkovanou nebo kombinace těchto vlastností. Nebudu zde opisovat celý přehled, najdete jej v dokumentaci u popisu této funkce, zde si kromě spojité čáry vyzkoušíme čáru tečkovanou.

Parametr nWidth určuje šířku pera. V nejjednodušším případě v pixelech. Co jsou to logické jednotky (uvedené v dokumentaci) si řekneme někdy později.

Barvu pera pak učuje parametr crColor nám již známého typu COLORREF.

Takto vytvořené pero musíme po jeho použití odstranit fukcí DeleteObject:

BOOL DeleteObject(
  HGDIOBJ hObject   // handle to graphic object
);

Možná se zeptáte, proč zde zmiňuji zrušení pera, když jsme ho ještě nepoužili. Je totiž možná dobrý zvyk (který samozřejmě nikomu nevnucuji, ale sám se ho držím) v podobných případech (dalším jsou třeba funkce GetDC a ReleaseDC) si nejprve napsat "začátek a konec" a mezi tyto řády pak psát kód tento objekt používající. Snížím tak pravděpodobnost, že po napsání zejména nějakého delšího kódu, zapomenu vytvořený objekt (spíše více objektů) na konci zrušit. Takže nyní k tomu "uprostřed". Pokud chceme vytvořené pero použít, musíme ho vybrat do příslušného kontextu zařízení funkcí SelectObject:

HGDIOBJ SelectObject(
  HDC hdc,          // handle kontextu zařízení
  HGDIOBJ hgdiobj   // handle grafického objektu
);

Tuto funkci budeme používat velmi často. Slouží totiž, jak napovídá obecný typ handle grafického objektu (HGDIOBJ) nejen pro výběr pera, ale i pro ostatní grafické objekty, ke kterým se postupně dostaneme. Upravme si tedy na závěr náš program tak, aby pokud při kliknutí myši držíme klávesu <Ctrl> se čára kreslila modrá s tloušťkou 4 pixely.

void MyDrawLine(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  POINTS point = MAKEPOINTS(lParam);
  HDC hdc = GetDC(hWnd);
  HPEN hPen = CreatePen(PS_SOLID, 4, 0x00FF0000);
  if ( wParam & MK_CONTROL )
    SelectObject(hdc, hPen);
  MoveToEx(hdc, ptDraw.x, ptDraw.y, NULL);
  LineTo(hdc, point.x, point.y);
  ptDraw.x = point.x;
  ptDraw.y = point.y;
  ReleaseDC(hWnd, hdc);
  DeleteObject(hPen);
}

Jeden z možných výsledků vidíte na obrázku:

win-api-15

Zjištění zda je v okamžiku stlačení tlačítka myši stisknuta některá klávesa, popřípadě jiné tlačítko myši, provedeme otestováním parametru wParam na přítomnost jedné z možných hodnot. Jejich popis i symbolické konstanty naleznete v dokumentaci v popisu zprávy WM_LBUTTONDOWN. Stejně je tomu i u dalších "myších zpráv", jako třeba WM_RBUTTONUP apod.

Samozřejmě celá ukázka v žádném případě není ani zárodkem nějakého kreslícího programu. Nakreslené čáry zůstanou v okně pouze do doby, než dojde k jeho překreslení, neboť toto kreslení nemá s handlerem zprávy WM_PAINT žádnou vazbu.

Doprovodný projekt je ke stažení zde: win_api_15.zip


Copyright © 2019 - Radek Chalupa