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

Seznámení s Unicode. Začínáne s GDI - výpis textu do okna.

5.1.2002

V minulém díle jsme vytvořili tu nejjednodušší Win32 aplikaci, která má hlavní okno se všemi základními náležitostmi. Nejprve si řekněme alespoň to základní o používání Unicode. I když tato problematika nepatří mezi nezbytně nutné základy Win API, zmíním se o ní z toho důvodu, že jsem v kódu použil makro TEXT a také jsme již narazili na typ TCHAR, přesněji řečeno na ukazatel na tento typ - LPTSTR. V další části článku se pak již budeme zcela věnovat úvodu do GDI - tedy kreslení (mezi které z hlediska GDI patří i výpis textů) na plochu okna.

Pár slov o UNICODE

Unicode jednoduše řečeno používá znaky o rozměru 2 byte. Do takové znakové tabulky se pak vejde 256 * 256 znaků, tedy 65536 různých znaků, takže jedna taková znaková tabulka může obsahovat nejrůznější národní sady znaků. Při použití Unicode pak musíme pro znak použít typ WCHAR, tedy onen 16-ti bitový (2 byte) znak. Vzhledem k tomu, že v praxi často chceme, abychom měli univerzální zdrojový kód, z kterého pak nějakým jednoduchým nastavením dostaneme verzi programu pro  Unicode, máme k dispozici typ TCHAR. Tento typ je definován v závislosti na tom, zda je definována hodnota UNICODE. Pokud je hodnota UNICODE definována, je (pomocí typedef) TCHAR definován jako WCHAR, v opačném případě jako CHAR. Typ CHAR je opět pomocí typedef ve Windows definovaný jako standardní céčkovský typ char. V následujícím výpisu vidíte "proškrtanou" část souboru <WinNT.h>, která je sám, spolu s mnoha dalšímu hlavičkovými soubory, vložený do souboru <windows.h>:

typedef char CHAR;
typedef wchar_t WCHAR;
#ifdef UNICODE
typedef WCHAR TCHAR, *PTCHAR;
#else
typedef char TCHAR, *PTCHAR;
#endif
Pokud jde o použití konstantních řetězců, k jejich správné interpretaci podle nastavení Unicode nám slouží makro TEXT
TEXT(
  LPTSTR string  // ANSI or Unicode string
);
Toto makro identifikuje text jako Unicode, pokud je definována hodnota UNICODE, v opačném případě jako ANSI řetězec. Chceme-li tedy náš kód mít bez problémů přeložitelný i v případě definovaného Unicode, musíme toto makro důsledně používat. Příkladem v našem kódu je definice
#define _AppName TEXT("Učíme se WinAPI")
Tolik prozatím tento velmi stručný úvod do Unicode a nyní se již dáme do kreslení.

Začínáme kreslit - úvod do GDI

Aplikace ve Windows nemají běžným způsobem možnost přímého přístupu ke grafickému hardware. Proto zde existuje jakási mezivrstva, která bezpečným způsobem zprostředkovává tuto komunikaci s grafickým zařízením. Samozřejmě kromě GDI již existují další specializovaná rozhraní, jako je převážně "herní" DirectX nebo OpenGL. Ale jak asi všichni víte, aplikace používající některé z těchto rozhraní se více či méně odlišují od běžných aplikací ve Windows. Například tím, že hlavně hry většinou běží ve výhradním, celoobrazovkovém režimu, což samozřejmě není pro běžnou aplikaci vhodné. Na druhou stranu, tyto rozhraní poskytují mnohem větší rychlost, která je v případě takových aplikací většinou jedním z klíčových faktorů. My však zůstaneme u GDI, na kterém je zatím stále velká většina programů pro Windows. I když v současné době je již asi nutné se zmínit o GDI+, což je nové rozhraní, již objektové (tedy založené na třídách C++), které bylo uvedeno v souvislosti s nástupem Windows XP, v jejichž instalaci je již runtimová podpora těchto funkcí. Ale i pro starší verze Windows je možné zdarma získat tuto runtimovou podporu, takže aplikace používající GDI+ mohou pak běžet třeba pod Windows 98. Myslím ale že je žádoucí zvládnout nejdříve GDI, již proto, že pro použití GDI+ je třeba mít instalovanou jednu z nejnovějších verzí Windows SDK (Software Development Kit), což pro velkou část programátorů, kteří se k Internetu připojují přes telefon, zůstává prakticky nedostižným snem. Takže nyní již dále jen o GDI. Cenou za zmíněné zprostředkování přístupu ke grafickému hardware je samozřejmě rychlost. A výhody? Především bezpečnost a univerzálnost. Bezpečností mám na mysli to, že se nám nemůže stát, že bychom nějakou chybou pomocí GDI funkcí "sáhli někam, kam nesmíme". Je to dáno tím, že nemáme přímý přístup do paměti grafického adaptéru, jako tomu bylo v DOSu, což bylo velice nebezpečné, stačila malá chyba a .... Další výhodou je univerzálnost. Znamená to asi tolik, že tytéž funkce, které použijeme pro kreslení na obrazovku, budeme používat i při výstupu na tiskárnu nebo i jiné zařízení. Základním pojmem v GDI je tzv. kontext zařízení (device context).Kontext zařízení je jakási struktura, obsahující informace o konkrétním zařízením (jako je například barevná hloubka). Toto obhospodařuje GDI. Aplikace musí pouze pomocí příslušných funkcí získat handle tohoto kontextu zařízení a tento handle pak použít ve všech kreslících funkcích. Po jeho použití je nutné ho opět uvolnit, neboť jeho alokace je poměrně náročná na systémové zdroje a je proto žádoucí mít kontext zařízení "přivlastněn" na dobu minimálně nutnou. Tento handle nám samozřejmě kromě kreslení umožňuje získat nejrůznější informace o kontextu zařízení, a především na něj aplikovat různé vlastnosti, jako je barva textu, pozadí, barvy čar, písmo apod. Slovo vlastnosti není vždy zcela přesné (použil jsem ho zde pro zjednodušení), neboť některé z těchto "vlastností" jsou realizovány výběrem příslušných grafických objektů (nejde o objekty ve smyslu tříd C++), jako jsou pera, štětce apod. Ale k tomu se dostaneme postupně. Nyní se ale již vrhneme na kreslení do našeho okna. V té souvislosti si musíme říci o zprávě WM_PAINT a způsobu, jak je ve Windows realizováno překreslování oken. Vzhledem k tomu, že ve Windows dochází často k tomu, že jednotlivá okna jsou překryta zcela nebo zčásti jiným oknem a následně opět "zviditelněna", je samozřejmě třeba při tomto zviditelnění okna nebo jeho části toto okno ihned překreslit. O tom, že nastala potřeba překreslení okna informuje příslušnou aplikaci systém Windows tím, že pošle zprávu WM_PAINT do procedury příslušného okna. Ještě přesněji řečeno, před zprávou WM_PAINT je poslána ještě zpráva WM_ERASEBKGND, která je oknu poslána, když má být překresleno jeho pozadí. O výplň pozadí se nám postará systém (pokud mu to nezakážeme) tím způsobem, že pozadí okna vyplní štětcem, přiřazeným dané třídě oken, jak jsme si naznačili v 3. díle seriálu. Co je to přesně štětec si řekneme později. Na nás tedy je zachytit zprávu WM_PAINT a reagovat na ní překreslením okna nebo jeho části tak jak chceme, aby v daném okamžiku vypadalo. V tomto díle si ukážeme, jak realizovat výpis textu do okna. V proceduře okna si zachytíme zprávu WM_PAINT a zavoláme si naši funkci, ve které budeme realizovat vlastní kreslení. Podívejme se nejprve na hotový kód a pak si jej vysvětleme:
void OnWM_PAINT()
{
  PAINTSTRUCT ps;
  HDC hdc;
  TCHAR chText[] = TEXT("Ahoj Win API !");
  hdc = BeginPaint(g_hwndMain, &ps);
  TextOut(hdc, 10, 10, chText, lstrlen(chText));
  EndPaint(g_hwndMain, &ps);
}
LRESULT CALLBACK WindowProcMain(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch ( message )
  {
    case WM_PAINT:
      OnWM_PAINT();
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
  }
  return DefWindowProc(hwnd, message, wParam, lParam);
}
a výsledek po spuštění:

win api 5

V případě že kreslíme v reakci na zprávu WM_PAINT, používáme k získání kontextu zařízení funkci BeginPaint a k jeho uvolnění pak EndPaint. Funkce BeginPaint má jako první parametr handle okna, jehož kontext zařízení (přesněji nikoli celého okna, ale pouze jeho klientské oblasti) chceme získat. Druhým parametrem je adresa struktury PAINTSTRUCT, o které zatím jen tolik, že obsahuje informace, které můžeme využít v některých speciálních případech. Obvykle ji můžeme ignorovat, jako je tomu v našem případě. Když máme handle kontextu zařízení, můžeme použít tu asi nejjednodušší GDI funkci, kterou je TextOut.
BOOL TextOut(
  HDC hdc,           // handle kontextu zařízení
  int nXStart,       // x-ová souřadnice
  int nYStart,       // y-ová souřadnice
  LPCTSTR lpString,  // textový řetězec
  int cbString       // počet znaků
);
Tato funkce vypíše na místo určené souřadnicemi nXStart a nYStart text lpString, resp. tolik znaků. kolik určíme parametrem cbString. Délku celého textu určíme funkcí lstrlen, což je z hlediska Unicode "univerzální" varianta funkce strlen. Doprovodný projekt (Visual C++ 6) je ke stažení zde: win_api_5.zip