Radek Chalupa Software na zakázkuŠkolení a konzultaceFreewareČlánkyMapa stránekKontakt
Software na zakázku Školení a konzultace Freeware Články Mapa stránek Kontakt

Základní stavební kameny Win32 programu

V minulém článku jsme skončili tím, že jsme si řekli o hlavní funkci programu, tedy WinMain a řekli něco o významu jejích parametrů. Abychom dostali ten nejjednodušší kompletní program ve Windows, musíme si říci o 3 základních stavebních kamenech takového programu:
  • smyčka zpráv
  • registrace třídy okna a vytvoření okna
  • procedura okna a zpracování zpráv
Jak hned uvidíte, "všechno souvisí se vším", takže nelze dost dobře vysvětlit a pochopit jeden ze zmíněných bodů bez toho, abychom něco věděli o dalších. Začněme nejlépe tou smyčkou zpráv, která bývá běžně umístěna přímo uvnitř těla funkce WinMain.

Smyčka zpráv

Rozšiřme si do náš kód tak, jak vidíte na následujícím výpisu:
MSG msg;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShow)
{
  while ( GetMessage(&msg, NULL, 0, 0) )
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return 0;
}
To co jsme právě přidali, je ona smyčka zpráv, ve které běží celý náš program. Funkce GetMessage vezme zprávu z fronty zpráv aplikace, a odešle jí k dalšímu zpracování, s jedinou výjimkou, kterou je zpráva WM_QUIT. Při přijetí této zprávy funkce GetMessage vrátí FALSE a smyčka zpráv je tak ukončena a posléze dojde k ukončení celé funkce WinMain, a tedy celé aplikace. V prvním parametru funkce GetMessage je adresa struktury MSG, obsahující informace o zprávě vyjmuté z fronty zpráv aplikace:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG;
Prvek hwnd je handle okna, kterému je zpráva směrována. V této smyčce zpráv se totiž soustřeďují zprávy určené všem oknům naší aplikace. Prvek message je identifikátor zprávy, například minule zmíněný WM_LBUTTONDOWN. Dále následují již zmíněné parametry wParam a lParam, time je pak čas, kdy byla zpráva vložena do fronty a v pt je pozice kurzoru v absolutních souřadnicích obrazovky. Druhým parametrem funkce GetMessage je handle okna jehož zprávy má tato funkce vyjmout z fronty. V případě použití této funkce ve smyčce zpráv aplikace zde zadáme hodnotu NULL znamenající, že mají být vybírány zprávy všem oknům aplikace. Další dva parametry funkce GetMessage mohou představovat filtr zpráv. Můžeme tak specifikovat, že chceme vybírat pouze zprávy v určitém rozsahu identifikátorů. Ve smyčce zpráv opět zadáme tyto parametry nulové, což znamená, že chceme vybírat všechny zprávy Ve smyčce zpráv dále vidíte funkci TranslateMessage. Tato funkce "překládá" virtuální kódy zpráv klávesnice a generuje další "znakové" zprávy. Podrobněji se samozřejmě budeme zprávám klávesnice věnovat později, nyní není pro nás podrobné vysvětlení důležité. Následuje funkce DispatchMessage, která zprávu odešle do procedury příslušného okna, o které si řekneme později. Další základní věcí Win32 programu je registrace třídy okna a vytvoření okna této třídy. Třídy definují určité vlastnosti, které jsou společné všem oknům, které k této třídě náleží. Některé z těchto vlastností můžeme později u individuálního okna změnit, buď hned při jeho vytvoření nebo později za běhu programu. Existují některé systémové třídy, které nemusíme (a ani nemůžeme) v aplikaci registrovat, a můžeme ihned vytvořit okno patřící k takové systémové třídě. Možná vás již napadlo, že těmito třídami jsou například standardní prvky Windows, jako button, list-box, edit-box apod. Každá třída, jak systémová tak registrovaná aplikací má nějaké jméno ("řetězcová" hodnota), na které se odkazujeme při vytvoření okna patřícího této třídě.

Registrace třídy okna

Jak tedy zaregistrovat třídu okna? K registraci třídy okna použijeme funkci RegisterClassEx, jejímž parametrem je adresa struktury WNDCLASSEX obsahující požadované vlastnosti registrované třídy.
ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx) // data třídy
typedef struct _WNDCLASSEX
{
  UINT cbSize;
  UINT style;
  WNDPROC lpfnWndProc;
  int cbClsExtra;
  int cbWndExtra;
  HINSTANCE hInstance;
  HICON hIcon;
  HCURSOR hCursor;
  HBRUSH hbrBackground;
  LPCTSTR lpszMenuName;
  LPCTSTR lpszClassName;
  HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
Nemělo by nyní asi smyl podrobně rozebírat význam jednotlivých parametrů. Řekneme si o těch základních a ukážeme si nejjednodušší příklad jejich naplnění. cbSize - je velikost této struktury. Musíme ji naplnit hodnotou sizeof(WNDCLASSEX) style - můžeme uvést kombinaci stylů třídy. Kompletní seznam možných parametrů naleznete v dokumentaci. Běžně se používá kombinace hodnot CS_VREDRAW (obsah celého okna se překreslí, pokud dojde ke změně výšky okna, tedy k jeho roztažení) a CS_HREDRAW (totéž pouze s tím, že jde o změnu šířky). lpfnWndProc - adresa procedury okna. O proceduře okna si řekneme později, zatím tolik, že zde jednoduše uvedeme název procedury okna kterou musíme mít v programu vytvořenu. cbClsExtra - zde můžeme v některých speciálních případech uvést počet bytů, které chceme alokovat spolu s třídou pro použití v některých speciálních případech. Běžně zadáme 0. cbWndExtra - obdobně můžeme alokovat dalších počet bytů spolu s instancí programu. Používá se opět v některých speciálních případech. hInstance - handle instance, které nám systém přidělí v 1. parametru (typu HINSTANCE) funkce WinMain. hIcon - handle ikony příslušné třídy okna, běžně získané pomocí funkce LoadIcon či LoadImage. V tom nejjednodušším případě můžeme použít některou ze systémových ikon, nejlépe tu symbolizující aplikaci. Získáme ji takto: LoadIcon(NULL, IDI_APPLICATION) hCursor - handle kurzoru, který bude zobrazen, pokud se ukazatel myši přesune do oblasti okna patřícího k této třídě. Systémový kurzor "šipka" získáme takto: LoadCursor(NULL, ICD_ARROW) hbrBackground - štětec typu HBRUSH definující pozadí, které bude vyplňovat okno této třídy. V nejjednodušším případě můžeme získat handle stětce definováním příslušné systémové barvy, například (HBRUSH)(COLOR_WINDOW+1) nám dá štětec v barvě nastavené jako barva pozadí oken. lpszMenuName - hlavní menu okna. Můžeme zatím nechat NULL, později si ukážeme, jak používat menu. lpszClassName - jméno identifikující třídu. Musí být jednoznačné v celém systému. hIconSm - handle malé ikony (obvykle 16x16) reprezentující třídu. Pokud uvedeme NULL, systém použije "zmenšenou" ikonu specifikovanou v prvku hIcon. Nyní si dejme vše dohromady a ukažme si registraci třídy. V naší aplikaci si nejprve vytvoříme funkci InitApp typu BOOL, do které budeme postupně přidávat jednotlivé kroky, prováděné při inicializaci aplikace, počínaje nyní registrací třídy. Před tím si nadefinujeme jméno třídy, které budeme potřebovat na více místech, proměnnou pro uložení handle instance, který budeme často v kódu potřebovat, dále handle hlavního okna aplikace:
#define _MainClassName "TohleExistovatNebude"
HINSTANCE g_hInstance
HWND g_hwndMain
Ten prefix g_ jsem zvyklý dávat u "globálních" proměnných, které pak na první pohled odliším od těch použitím uvnitř funkcí. Podobně hodnoty definované pomocí "#define", popř. konstanty označuji s podtržítkem na začátku, takže je na první pohled odliším od proměnných. Vlastní "rukopis" si samozřejmě každý vytvoří sám. A nyní již můžeme zaregistrovat třídu
BOOL InitApp()
{
  WNDCLASSEX wc;
  wc.cbSize = sizeof(WNDCLASSEX);
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hbrBackground =  (HBRUSH)(COLOR_WINDOW +  1);
  wc.hCursor = LoadCursor(NULL,  IDC_ARROW);
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wc.hInstance = g_hInstance;
  wc.lpfnWndProc = WindowProcMain;
  wc.lpszClassName = _MainClassName;
  wc.lpszMenuName = NULL;
  wc.style = CS_HREDRAW | CS_VREDRAW;
  if ( (!RegisterClassEx(&wc))
    return FALSE;
}

Vytvoření okna

Nyní máme zaregistrovanou třídu, takže můžeme vytvořit hlavní (a zatím jediné) okno patřící této třídě. K tomu použijeme funkci CreateWindowEx.
// Toto bude ve funkci InitApp
g_hwndMain = CreateWindowEx(0, // rozšířený styl okna
  _MainClassName, // jméno třídy
  "Moje první nealkoholické okno", // text okna
  WS_OVERLAPPEDWINDOW, // styl okna
  100, 100, // souřadnice na obraziovce
  450, 350, // rozměry - šířka a výška
  (HWND)NULL, // okna předka
  (HMENU)NULL, // handle hlavní nabídky
  g_hInstance, // handle instance
  NULL); // další "uživatelská" data
  if (g_hwndMain == NULL)
   return FALSE;
Ještě dodám, že proměnnou g_hInstance si nastavíme hned na začátku funkce WinMain na hodnotu prvního parametru (hInstance) této funkce. Příště si podrobněji vysvětlíme význam jednotlivých parametrů funkce CreateWindowEx, vytvoříme proceduru okna a konečně budeme moci naši první Win API aplikaci spustit.

Copyright © Radek Chalupa, 2018