5. Az üzenetek mibenléte (The Message Loop)
2014.06.04. 22:15
Az üzenetek megértésével fogunk kicsit foglalkozni. Ennek ismerete nélkül is lehet ollózni kissebb programokat, de célszerű jobban beleásni magunkat, hogy később már ne érjenek kellemetlen meglepetések. Alább megpróbálom nagyjából lefordítani az eredeti szöveget.
Mi az az üzenet?
Az üzenet egy integer, vagyis egés érték. Ha belenézünk a header fájlokba, akkor péládul ezt látjuk:
#define WM_INITDIALOG 0x0110 #define WM_COMMAND 0x0111 #define WM_LBUTTONDOWN 0x0201
.. és így tovább. A Windows üzeneteket használ a kommunikációra az egyes részei között. Ha van egy ablakunk, vagy vezérlőnk (ami tulajdonképpen egy speciális ablak), akkor az üzenetet küld, ha történik valami. Ha egy másik ablakot szeretne hogy tegyen valamit, akkor küld egy üzenetet. Ha esemény következik be (pl. a felhasználó mozgatja az egeret, vagy gépel), akkor ez az üzenet a rendszerre tartozik. Ha van egy ilyen ablakunk, akkor mi kezeljük az üzenetet, és ennek megfelelően járunk el.
Minden windózos üzenet két paraméterből áll, ezek a wParam, és lParam. Eredetileg a wParam csak 16 bites volt, de a Win32-ben már mindkettő 32 bites. Nem minden üzenet használja mindkét paramétert. Például a WM_Close egyiket sem használja, a WM_COMMAND viszont mindkettőt. lParam a HWND (window handle) ha vezérlőtől jön, és NULL ha nem.
Üzenet küldéséhez használhatjuk a PostMessage(), és a SendMessage() eljárásokat. A PostMessage() elküldi az üzenetet a sorba, és rögtön visszatér. Ez azt jelenti, hogy mikor a PostMessage() már végzett, az üzenet még nem biztos hogy fel van dolgozva. A SendMessage() közvetlenül az ablaknak küldi az üzenetet, és amíg az fel nem dolgozza, addig nem is tér vissza. Az ablak bezárásához használhattuk a WM_CLOSE üzenetet így:
PostMessage(hwnd, WM_CLOSE, 0, 0);
Ez ugyanaz, mintha megböktük volna az (x)-et. Vegyük észre, hogy wParam, és lParam értéke is 0! Már tudjuk, hogy a WM_CLOSE nem használja ezeket.
Amikor elkezdünk párbeszédablakokat használni, akkor szükséges lesz üzeneteket küldeni, hogy a párbeszédet kontrollálni tudjuk. Először a GetDlgItem() használatára lesz szükség, hogy megkapjuk a handlert az ID használatával, majd hívjuk a SendMessage()-t. Másik módszer a SendDlgItemMessage(), ami a kettő kombinációja. A SendDlgItemMessage(), és más egyszerű API-k (mint például a GetDlgItemText() ), minden ablaknál működnek, nem csak a párbeszédablaknál.
Az üzenet-sor
Tegyük fel, hogy a következő eset áll elő: Éppen elfoglaltak vagyunk, mondjuk a WM_PAINT végrehatjásával, amikor a felhasználó vadul elkezd valamit gépelni. Na most mi legyen? Hagyjuk félbe a rajzolást amíg feldolgozzuk a billentyűket? Vagy hagyjuk a billentyűket és ne foglalkozzunk velük? Egyik sem jó! Ezért van az üzenet-sor, ami addig tárolja az üzeneteket, amíg fel nem dolgozzuk, és el nem távolítjuk őket. Így semmi sem fog elveszni.
Az üzenet-hurok
while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); }
Az üzenet hurok meghívja a GetMessage()-t, ami belenéz az üzenet-sorba. Ha a sor üres, akkor várakozik addig, amíg valami nem érkezik (addig a programot blokkolja). Ha egy esemény történik, vagyis egy üzenet kerül az üzenet-sorba (például a rendszer jelzi hogy egér kattintás volt), akkor a GetMessage() egy pozitív egész értékkel tér vissza. Ez egy üzenet amit fel kell dolgozni, az Msg struktúra mezői ki vannak töltve. Nullával tér vissza ha WM_QUIT volt, és negatívval, ha hiba. Fogjuk az üzenetet (ami az Msg változóban van), és odaadjuk a TranslateMessage() számára. Ez kicsit matat még vele, átalakítja a virtuális gombokat karakterekké. Ez a lépés opcionális, de érdemes, ellenkező esetben bizonyos dolgok nem működnek. Ezután az Msg-t megkapja a DispatchMessage(), ami ellenőrzi hogy melyik ablaknak szól, aztán megkeresi az ablak Window eljárását. Meghívja azt, és átadja neki az ablak handlerét, az üzenetet, wParam, lParam értékeit. A Window eljárásban ellenőrízhetjük az üzenetet, a paramétereket, és azt csinálunk velük, amit akarunk! Ha a konkrét üzenetet nem kezeljük, akkor meghívjuk a DefWindowProc() eljárást, ami az alapértelmezett műveleteket elvégzi helyettünk (ami sokszor azt jelenti, hogy nem csinál semmit). Miután végeztünk az üzenet feldolgozással, a Window eljárásunk véget ér, a DispatchMessage() véget ér, és kezdjük a hurkot elölről.
Ez egy nagyon fontos dolog a windózos programoknál! A window eljárásunkat nem mágikusan hívja meg a rendszer, hanem közvetve mi magunk hívjuk meg a DispatchMessage() eljárással. Ha szükséges, akkor használhatjuk a GetWindowLong() eljárást is az ablakunk közvetlen hívásához.
while(GetMessage(&Msg, NULL, 0, 0) > 0) { WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC); fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam); }
A fenti kód működik, ámbár vannak nyitva hagyott kérdések. Ilyen például a Unicode/ANSI fordítás, a timer visszahívás stb. Szóval kipróbálhatjuk, de azért az éles programban már ne alkalmazzuk!
Miért nem hívjuk közvetlenül a WndProc()-ot? Az üzenet-hurok felel az összes ablakért ami a programunk része (gombok, lista dobozok, stb). Ebből már sejthető, hogy nem mindegy melyik ablak kapja meg a vezérlést. Az első paraméter (a handler) mutatja meg, hogy az üzenetet kinek szánták.
Mint az látható, az alkalmazásunk az idő nagy részében körbe-körbe kering, és arra vár hogy üzenetet küldhessen a boldog ablakoknak, akik alig várják hogy feldolgozhassák azokat. De mi van, ha ki kellene lépni? Mivel egy while() cliklust használunk, ha a GetMessage() hamis értékkel (vagyis nullával) tér vissza, akkor véget ér a ciklus, és vele együtt a programunk (a WinMain() ) is. Pontosan ezt váltja ki a PostQuitMessage()! Elhelyez egy WM_QUIT üzenetet a sorba, és pozitív értékkel tér vissza. A GetMessage() kitölti az Msg-t és nullával tér vissza. Ezen a ponton az Msg wParam mezője tartalmazza azt az értéket, amit a PostQuitMessage() számára átadtunk. Ezt figyelmen kívül is hagyhatjuk, vagy visszatérhetünk a WinMain()-ból (befejezve a folyamatot), kilépési kódnak használva az üzenetet.
Fontos!
A GetMessage() hiba esetén -1 értékkel tér vissza. Ez figyelni kell, vagy pedig elkapni (try-catch)!
Elvileg ezt az alábbi módon is megtehetnénk.
while(GetMessage(&Msg, NULL, 0, 0))while(GetMessage(&Msg, NULL, 0, 0) != 0)while(GetMessage(&Msg, NULL, 0, 0) == TRUE)
A fentiek mindegyike ROSSZ!
while(GetMessage(&Msg, NULL, 0, 0) > 0)
Ez már a jó megvalósítás! A fentiek is elvileg működnek egy darabig, de egyszercsak előfordulhat olyan eset, amikor mégsem. Ilyenkor fejvakarás. Használjuk az utolsó módszert, az a biztos!
Ha még mindig nem tiszta ezzel kapcsolatban valami, nem kell aggódni, később minden a helyére kerül.
A bejegyzés trackback címe:
Kommentek:
A hozzászólások a vonatkozó jogszabályok értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a Felhasználási feltételekben és az adatvédelmi tájékoztatóban.