Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

Jednorázové kreslení do okna. Kontext klientské oblasti.

7.1.2002

Minulý článek jsme zakončili ukázkou výpisu textu do okna s možností volby barvy písma, barvy pozadí textu a volby průhlednosti. Na závěr jsem také položil "kontrolní otázku", jak v uvedeném případě lze zabránit problikávání textu při roztahování okna za okraj? Řešením je nastavení stylu třídy okna. Ukázali jsem si, že pokud chceme mít text (vypisovaný funkcí DrawText) stále vycentrovaný, musíme zajistit překreslování okna při každé změně jeho rozměru. K tomu slouží příznaky CS_HREDRAW a CS_VREDRAW v prvku style struktury WNDCLASSEX. Když tyto odstraníme, text se nebude překreslovat během roztahování. Je samozřejmě vždy třeba uvážit, co je v konkrétním případě výhodnější. V tomto pokračování si ukážeme, jak kreslit do okna jednorázově, tj. nezávisle na systémově řízeném překreslování. Naučíme se také, jak získat kontext zařízení celého okna, když budeme chtít kreslit do neklientské oblasti, třeba do titulkového pruhu okna. Řekněme že budeme chtít, aby při stisknutí levého tlačítka myši byly v daném místě okna vypsány souřadnice místa, ke bylo tlačítko stisknuto. Navíc při současném držení klávesy <Control> bude text v červené barvě. Tento text pak při dalším překreslení okna zmizí. Vytvoříme si tedy v proceduře okna handler zprávy WM_LBUTTONDOWN. Když se podíváme do dokumentace, zjistíme že zpráva WM_LBUTTONDOWN má ve svém parametru lParam souřadnice místa, kde k události došlo. Tyto souřadnice jsou relativné ke klientské oblasti okna, tedy bod (0,0) je v levém horním rohu klientské oblasti. Další parametr této zprávy - wParam - pak určuje, zda některá z tzv. virtuálních kláves je současně stlačena. Touto klávesou může být <Control>, <Shift> nebo některé z dalších tlačítek myši, včetně tzv. x-buttonů, což jsou další tlačítka na některých myších (např. Microsoft IntelliMouse), standardně s funkcí "zpět" a "vpřed". Možné hodnoty tedy jsou:

  • MK_CONTROL - stlačena klávesa <Ctrl>
  • MK_SHIFT - stlačena klávesa <Shift>
  • MK_LBUTTON - stlačeno levé tlačítko myši
  • MK_RBUTTON - stlačeno pravé tlačítko myši
  • MK_MBUTTON - stlačeno prostřední tlačítko myši
  • MK_XBUTTON1 - stlačeno 1. doplňkové tlačítko (pouze ve Windows 2000/XP)
  • MK_XBUTTON2 - stlačeno 2. doplňkové tlačítko (pouze ve Windows 2000/XP)

Přidáme si tedy vlastní funkci, které z procedury okna předáme zmíněné 2 parametry. Podívejme se na výsledek a její kompletní tvar a vysvětleme podrobnosti:

win-api-7-1

void VlastniKresleni(WPARAM wParam, LPARAM lParam)
{
  POINTS ptCursor;
  HDC hdc;
  TCHAR chText[30];
  ptCursor = MAKEPOINTS(lParam);
  _stprintf(chText, "x=%d, y=%d", ptCursor.x, ptCursor.y);
  hdc = GetDC(g_hwndMain);
  if ( wParam & MK_CONTROL )
    SetTextColor(hdc, 0x000000FF);
  TextOut(hdc, ptCursor.x, ptCursor.y, chText, lstrlen(chText));
  ReleaseDC(g_hwndMain, hdc);
}

K získání souřadnic z parametru lParam zprávy WM_LBUTTONDOWN použijeme makro MAKEPOINTS, které nám vrátí strukturu POINTS obsahující 2 souřadnice.

typedef struct tagPOINTS { 
  SHORT x; 
  SHORT y; 
} POINTS, *PPOINTS;

Samozřejmě, toto makro bychom nemuseli použít, neboť souřadnice jsou uloženy ve 32 ti bitovém parametru tak, že x-ová souřadnice je v dolním WORDu a y-ová v horním. Člověk je však tvor chybující (a někdy i líný) a snadno zamění x-ovou a y-ovou souřadnici. A právě makro MAKEPOINTS nám zabrání udělat tuto chybu. Dále si sestavíme textový řetězec pomocí funkce _stprintf. Tato funkce z hlediska použití Unicode "univerzální" varianta funkce sprintf. Je deklarována v hlavičkovém souboru <tchar.h>, kde v závislosti na existenci hodnoty _UNICODE je buď definována jako sprintf (bez Unicode) nebo swprintf (použití Unicode). Kromě souboru <tchar.h> musíme ještě vložit soubor <stdio.h>. Pokud by se stalo, že používáte nějaké vývojové prostředí, kde bude chybět soubor <tchar.h>, můžete "ručně" nahradit v našem kódu funkci _stprintf funkcí sprintf. Nyní se dostáváme k tomu nejdůležitějšímu z naší funkce, a to získání kontextu zařízení v případě, že nekreslíme jako odpověď na zprávu WM_PAINT. V tomto případě můžeme kdykoli "na požádání" získat handle kontextu zařízení pomocí finkce GetDC:

HDC GetDC(
  HWND hWnd   // handle okna
);

Takto získaný kontext zařízení musíme po jeho použití opět uvolnit funkcí ReleaseDC:

int ReleaseDC(
  HWND hWnd,  // handle okna
  HDC hDC     // handle kontextu zařízení
);

Tento kontext zařízení zahrnuje, stejně jako v případě jeho získání funkcí BeginPaint, pouze klientskou oblast okna. Pokud bychom chtěli mít přístup i do neklientské oblasti, museli bychom použít funkci GetWindowDC:

HDC GetWindowDC(
  HWND hWnd   // handle to window
);

Ukažme si použití této funkce na modifikovaném příkladě, kde budeme informace o souřadnicích kliknutí vypisovat do titulkového pruhu. Samozřejmě jednodušší a rychlejší (pro systém) by bylo tento text nastavit jako text okna, který se zobrazuje v titulkovém pruhu. Tento text je však zobrazen systémově nastaveným písmem, které nemůžeme nijak ovlivnit, pokud tedy nepoužijeme vlastní kreslení. Funkce, která bude uvedené realizovat, bude podobná funkci VlastníKreslení. Rozdíl bude v získání handle na kontext zařízení a dále v souřadnicích vypisovaného textu, které v tomto případě "ne zcela profesionálně" zkusmo nastavíme tak, aby text nepřekrýval text titulku okna a byl "někde uprostřed". Až se budeme později věnovat uživatelskému kreslení neklientské oblasti okna, ukážeme si samozřejmě, jak vypočítat souřadnice třeba tak, aby byl text vertikálně vycentrován při jakémkoli uživatelském nastavení výšky titulkového pruhu. Funkce tedy může vypadat takto:

void KresleniDoTitulku(WPARAM wParam, LPARAM lParam)
{
  POINTS ptCursor;
  HDC hdc;
  TCHAR chText[30];
  ptCursor = MAKEPOINTS(lParam);
  _stprintf(chText, "x=%d, y=%d", ptCursor.x, ptCursor.y);
  hdc = GetWindowDC(g_hwndMain);
  if ( wParam & MK_CONTROL )
    SetTextColor(hdc, 0x000000FF);
  TextOut(hdc, 150, 8, chText, lstrlen(chText));
  ReleaseDC(g_hwndMain, hdc);
}

Na závěr si ještě vytvoříme funkci, kterou budeme volat při pohybu myši v klientské oblasti (zpráva WM_MOUSEMOVE) a která bude souřadnice myši "vypisovat" nastavením získaného textu jako textu okna, který se u standardních oken vypisuje v titulkovém pruhu.

void OnWM_MOUSEMOVE(WPARAM wParam, LPARAM lParam)
{
  POINTS ptCursor;
  TCHAR chText[30];
  ptCursor = MAKEPOINTS(lParam);
  _stprintf(chText, "x=%d, y=%d", ptCursor.x, ptCursor.y);
  SetWindowText(g_hwndMain, chText);
}

Nyní si do proceduru okna upravíme tak, že funkci OnWM_MOUSEMOVE budeme volat při pohybu myši a funkci KresleniDoTitulku při stlačení levého tlačítka:

LRESULT CALLBACK WindowProcMain(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch ( message )
  {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    case WM_MOUSEMOVE:
      OnWM_MOUSEMOVE(wParam, lParam);
      break;
    case WM_LBUTTONDOWN:
      KresleniDoTitulku(wParam, lParam);
      break;
    case WM_PAINT:
      OnWM_PAINT();
      break;
  }
  return DefWindowProc(hwnd, message, wParam, lParam);
}

Podívejme se na výsledek:

win-api-7-2

Když však program spustíte, zjistíte že po kliknutí myší se text vypsaný funkcí KresleniDoTitulku ztratí jakmile myší pohneme o jediný pixel. Je to způsobeno tím, že změna textu okna, který má být u tohoto typu okna vypsán v titulkovém pruhu vyvolá překreslení celého titulkového pruhu, nejen tedy obdélníka tvořícího text v titulku. Samozřejmě že je možné okno kreslit zcela ve vlastní režii, tedy včetně korektního překreslování neklientské blasti. To však již patří mezi pokročilejší techniky, ke kterým se v tomto seriálu také dostaneme. Pro posílení motivace ke studiu Win API se můžete podívat na okno, které je celé kreslené "uživatelsky". Jedná se o jednoduchý příklad, který jsem před časem použil jako doprovod k svému článku. Nicméně tlačítko "Zavřít" a check-box "možno zavřít" jsou opravdu funkční, i když to samozřejmě nejsou samostatná okna, ale jsou simulována jako grafické objekty umístěné v titulkovém pruhu okna. Pokud tedy vytrváte, bude pro vás něco podobného a samozřejmě graficky mnohem lépe zpracovaného, hračkou.

win-api-7-3

Doprovodný projekt (Visual C++ 6) je ke stažení zde: win_api_7.zip