10. Standard vezérlők (Standard Controls: Button, Edit, List Box)
2014.06.08. 14:55
Példa: ctl_one
Ugyan már használtunk gombokat, de azért itt is megemlítjük őket, hogy minden egy helyen legyen. Már említettük, de frissítsük fel, hogy minden vezérlő tulajdonképpen egy-egy ablak, tehát mindent amit az ablakokkal lehet, azt a vezérlőkkel is lehet. Mint azt már korábbról tudjuk, van egy üzenet hurok. Az ablakunk ezen keresztül üzenetekkel kommunikál. Elküldük a vezérlőnek hogy csináljon valamit, és ha esemény következik be, akkor a vezérlő visszaküld egy másik üzenetet. A hagyományos vezérlőknél ez az üzenet a WM_COMMAND lesz, amint azt a gomboknál és a menüknél már láttuk. Van még olyan hogy WM_NOTIFY, erről később lesz szó.
Sokféle üzenetet küldözgetünk, és minden vezérlőnek megvan a saját üzenet készlete. Általában egy üzenetet csak az fogad, akinek szánjuk, de létezik olyan eset is, amikor egy üzetetet több vezérlő is fogad.
Üzenetet küldhetünk a SendMessage() API segítségével, és használhatjuk a GetDlgItem()-et hogy megkapjuk a vezérlő handlerét. Vagy használhatjuk a SendDlgItemMessage()-t, ami mindkettőt elvégzi egyszerre. A két módszer eredménye azonos.
A Windows környezetben az egyik leggyakoribb vezérlő elem a szerkesztő (edit). Ezt alkalmazzuk arra, hogy a felhasználó adatokat adhasson meg, módosíthassa, másolhassa azokat. Lényegében a notepad.exe is csak egy rendes ablak, benne egy nagy szerkesztő vezérlővel.
Ebben a példában a szerkesztés vezérlőt ezzel a kóddal hozzuk elő:
SetDlgItemText(hwnd, IDC_TEXT, "This is a string");
És ennyi! Ez máris működik!
Hogy szöveget fogadjunk a vezérlőtől az se sokkal bonyolultabb, bár kicsit több munka van vele, mint a beállítással:
int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT)); if(len > 0) { int i; char* buf; buf = (char*)GlobalAlloc(GPTR, len + 1); GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1); //... do stuff with text ... GlobalFree((HANDLE)buf); }
Először is le kell foglalnunk a sztring számára a memóriát. Ehhez tudnunk kell, hogy mennyi memórára van szükségünk. Nincs olyan hogy GetDlgItemTextLength(), de van helyette GetWindowTextLength(). Csak arra van szükség, hogy átadjuk a GetDlgItem() handlerét neki.
Most hogy már megvan a hossza, le tudjuk foglalni a szükséges memóriát. Ellenőrízzük, hogy van-e valami szöveg, és csak ha a hossz nagyobb mint nulla, akkor folytatjuk. A GlobalAlloc() foglalja le a memóriát, és nullákkal tölti fel. Ez ugyanaz mint a DOS/UNIX C-ben a calloc(). Kapunk egy memória mutatót. A kért memória mennyiség egyel nagyobb, mint a szöveg hossza volt! Miért? Mert a GetWindowTextLength() a szöveg hosszába nem számolja bele a lezáró nullát.
Figyelem! Néhány API kéri a lezáró nullát, mások pedig nem! Ennek minden esetben utána kell nézni, ellenkező esetben memória túlcsordulás, és más durva dolgok történhetnek!!!
Most már hívhatjuk a GetDlgItemText()-et, ami a buf pufferbe beleírja a tárolt szöveget. Figyeljük meg, hogy itt is +1 a szöveg hossza! Fogunk még ezzel szívni..
Miután ezzel megvagyunk, fel kell szabadítani a lefoglalt memórát. Ezt oly módon kell megtenni, hogy ne szivárogjon, mert akkor lecsöpög, és és processzor áramköreit rövidre zárja, amitől tönkremegy a számítógép :D
After we're all done using the text (which we'll get to in a moment), we need to free up the memory that we allocated so that it doesn't leak out and drip down onto the CPU and short circuit your computer. To accomplish this, we simply call GlobalFree() and pass in our pointer.
Szóval csak hívjuk meg a GlobalFree()-t, és megmenekül a világ.
Ha már van sok szép szövegünk, előbb utóbb szembejön a probléma, hogy mi van amikor a felhasználó számokat akar bevinni? Szerencsére van erre API, ami lefoglalja a szükséges memóriát, és elvégzi a szöveg->szám átalakítást.
BOOL bSuccess; int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);
Hasonlóan működik mint a GetDlgItemText(), de nem pufferbe másolja a sztringet, hanem a szám értékét adja vissza. A harmadik paraméter opcionális. Ha hiba van az átalakításkor, a függvény nulla értékkel tér vissza. Ez nem szerencsés, mert a felhasználó is írhat nullát. Nem tudjuk megkülönböztetni. Ha nem baj hogy nullát ad vissza hiba esetén, akkor nem kell megadni. TODO: Ennek azért még utána kell nézni, nem biztos hogy jól értettem.
A szerkesztés vezérlőnek megadhatunk egy ES_NUMBER stílust. Ez kizárólag a 0-9 számjegyeket engedi begépelni. Hasznos, ha csak pozitív egész értéket kell megadni.
Másik kedvenc vezérlőnk a ListBox(). Az első amit tenni akarunk vele, hogy elemet adunk hozzá:
int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)"Hi there!");
Ez nagyon egyszerű volt. Ha a listadoboz rendelkezik LBS_SORT stílussal, akkor abc sorrendben fogja megjeleníteni, egyébként a sor végére biggyeszti az új elemet.
SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index, (LPARAM)nTimes);
Ha jól értem, akkor az indexhez hozzárendeli az értéket, tehát a "Hi there!" szöveghez az nTimes értékét. TODO!
Van, mikor nem érdekel minket, hogy mikor változik meg a listbox értéke, egyszerűen a kiválasztás ténye érdekel (ez a gyakoribb). Máskor viszont már az is számít, ha a felhasználó éppen kiválasztja. Például a kiválasztás eredményétől függ a többi választások lehetősége. Ennek figyelésére szolgál az LBN_SELCHANGE üzenet, ami a WM_COMMAND-on keresztül érkezik. Ellentétben a gombok, vagy a menü WM_COMMAND kezelőjével ahol csak a kattintás tényét kapjuk meg, itt több más paramétert is kapunk. Emiatt egy második ellenőrzésre is szükség van, ahol megvizsgáljuk a wParam értékét.
case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_LIST: // It's our listbox, check the notification code switch(HIWORD(wParam)) { case LBN_SELCHANGE: // Selection changed, do stuff here. break; } break; // ... other controls } break;
Most hogy tudjuk, megváltozott a kiválasztás, olvassuk is ki hogy mit kért a user! Ebben a példában többszörös kiválasztásra adtunk lehetőséget, ami kicsit komplikálja a helyzetet. Ha egyszerű lista lenne, akkor elég csak az LB_GETCURSEL használata, és megkapjuk a kiválasztott tétel indexét.
Először is szükséges tudnunk, hogy hány kiválasztott elem van, mert eszerint foglaljuk a memóriát az indexek tárolására.
HWND hList = GetDlgItem(hwnd, IDC_LIST); int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);
int *buf = GlobalAlloc(GPTR, sizeof(int) * count); SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf); // ... Do stuff with indexes GlobalFree(buf);
Lefoglalunk annyi memóriát amennyit kell, és az LB_GETSELITEMS feltölti nekünk a kiválasztott indexekkel. Tehát buf[0] az első index, és buf[count-t] az utolsó.
Nyilván ez arra kell nekünk, hogy lekérdezzük a kiválasztáshoz tartozó adatokat. Ez sem bonyolult, csak egy üzenetet kell küldenünk:
int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);
Nem csak int, hanem bármilyen egyszerű típusba is lehet kérni például HBITMAP az int helyett:
HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);
Ugyan már volt szó róla, de emlékezzünk meg az ún statikus vezérlőkről is! Azért statikus, mert nem csinál semmit, nem változik. Tipikusan ilyen lehet egy szöveg megjelenítése. Ekkor az IDC_STATIC azonosítót kapja (-1), ami azt jelenti, hogy nincs azonosítója.
Példánkban megjelenítjük az adatot a kiválasztásban. Feltételezzük, hogy csak egy elem lehet kiválasztva. TODO!
SetDlgItemInt(hwnd, IDC_SHOWCOUNT, data, FALSE);
9. Modális párbeszéd ablakok (Modeless dialogs)
2014.06.07. 22:45
Példa: dlg_two
A CreateDialog() nagyban hasonlít a DialogBox()-hoz, de van egy lényeges különbség közöttük. A DialogBox() mint azt az előző fejezetben láttuk saját üzenethurokkal rendelkezik, és nem is tér vissza, amíg az ablakát be nem zárjuk. A CreateDialog() ezzel szemben azonnal visszatér, és a fő ablak üzeneteit eszi. Létrehozhatunk ilyet erőforrásként mint ahogyan azt az előző példában tettük, így készíthetünk "eszköztárat".
IDD_TOOLBAR DIALOGEX 0, 0, 98, 52 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION EXSTYLE WS_EX_TOOLWINDOW CAPTION "My Dialog Toolbar" FONT 8, "MS Sans Serif" BEGIN PUSHBUTTON "&Press This Button",IDC_PRESS,7,7,84,14 PUSHBUTTON "&Or This One",IDC_OTHER,7,31,84,14 END
Létre kell hozzunk egy globális változót, amiben a CreateDialog() által visszaadott handlert fogjuk tárolni. Erre azért van szükség, hogy később tudjunk rá hivatkozni. A DialogBox() nem ad vissza handlert, mert az az ablak bezárásával semmisül meg.
HWND g_hToolbar = NULL;
case WM_CREATE: g_hToolbar = CreateDialog(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_TOOLBAR), hwnd, ToolDlgProc); if(g_hToolbar != NULL) { ShowWindow(g_hToolbar, SW_SHOW); } else { MessageBox(hwnd, "CreateDialog returned NULL", "Warning!", MB_OK | MB_ICONINFORMATION); } break;
Mindig ellenőrízzük a visszatérési értéket, és ha az nem NULL, akkor az ablakunkat a ShowWindow() segítségével meg tudjuk jeleníteni. A DialogBox() nem kéri a ShowWindow() hívását, mert a rendszer ezt automatikusan megteszi helyettünk.
A toolbar-unknak szüksége van egy kezelő eljárásra:
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { switch(Message) { case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_PRESS: MessageBox(hwnd, "Hi!", "This is a message", MB_OK | MB_ICONEXCLAMATION); break; case IDC_OTHER: MessageBox(hwnd, "Bye!", "This is also a message", MB_OK | MB_ICONEXCLAMATION); break; } break; default: return FALSE; } return TRUE; }
Ugyanazok az üzenetkezelési szabályok igazak a CreateDialog()-ra, mint a DialogBox() esetén. Nem hívjuk meg a DefWindowProc() eljárást. Ha eldobjuk az üzenetet, akkor FALSE a visszatérési érték, ha viszont feldolgozzuk, akkor TRUE.
Eltérés, hogy nem hívjuk meg az EndDialog() eljárást, helyette a DestroyWindow() használható. Ebben az esetben a párbeszéd ablak a fő ablakkal együtt záródik.
A fő ablak WndProc() részlete:
case WM_DESTROY: DestroyWindow(g_hToolbar); PostQuitMessage(0); break;
Szeretnénk olyat, hogy gombnyomásra megjelenjen, és eltűnjön az eszköztárunk:
case WM_COMMAND: switch(LOWORD(wParam)) { case ID_DIALOG_SHOW: ShowWindow(g_hToolbar, SW_SHOW); break; case ID_DIALOG_HIDE: ShowWindow(g_hToolbar, SW_HIDE); break; //... other command handlers } break;
Elvileg most már egyszerre van egy párbeszéd ablakunk, és egy fő ablakunk is. Majdnem jó, de hiába nyomkodjuk az Alt+O, Alt+P billentyűket, semmi sem történik. A DialogBox() tudja a saját üzeneteit alapból kezelni, de a CreateDialog() sajnos nem. Szerencsére meg tudjuk oldani magunk a dolgot, ha az IsDialogMessage()-t hívjuk az alkalmazás fő eseményhurkában.
while(GetMessage(&Msg, NULL, 0, 0)) { if(!IsDialogMessage(g_hToolbar, &Msg)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } }
Az IsDialogMessage() kapja meg először az üzenetet (az hogy neki szól-e, az első paraméterben kapott handlertől tudja). Eldönti hogy feldolgozza-e. Ha igen, akkor TRUE értékkel tér vissza. Ebben az esetben már nem foglalkozunk vele tovább, hiszen az üzenetet már feldolgozták.
8. Párbeszéd ablakok (Dialog boxes)
2014.06.07. 22:36
Példa: dlg_one
Párbeszéd ablakok, a grafikus felület (GUI) fejlesztőinek barátja
Nehezen találunk olyan windózos programot, ami ne alkalmazna párbeszéd paneleket. Elég megkeresni kedvenc szövegszerkesztőnkben a Fájl menü Megnyitás parancsát, és máris ott van előttünk egy ilyen. Nem csak a fájl megnyitás lehet párbeszéd ablak! Nagy előnye, hogy gyorsan össze lehet dobni egy felhasználói felületet (GUI azaz grafikus felhasználói felület). Ezek az alapértelmezett műveleteket el is tudják végezni, nekünk csak a lényegi dolgokkal kell foglalkoznunk. A párbeszéd panelek pontosan azt tudják mint a hagyományos ablakok, de lényegi eltérés, hogy kiegészülnek pár alapvető funkcióval. Ilyen a vezérlők létrehozása és kezelése. Szinte az összes API használható ablakon, és párbeszéd panelen egyaránt!
Első lépésként létrehozzuk a párbeszéd panel erőforrást. Ez természetesen függ a fordítótól, mi most a szöveges változatot írjuk meg.
IDD_ABOUT DIALOG DISCARDABLE 0, 0, 239, 66 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "My About Box" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "&OK",IDOK,174,18,50,14 PUSHBUTTON "&Cancel",IDCANCEL,174,35,50,14 GROUPBOX "About this program...",IDC_STATIC,7,7,225,52 CTEXT "An example program showing how to use Dialog Boxes\r\n\r\nby theForger", IDC_STATIC,16,18,144,33 END
Az első sorban az IDD_ABOUT az erőforrás azonosítója, DIALOG az erőforrás típusa, a számok pedig az X, Y, Szélesség, Magasság dimenziói. Ezek nem pixelben értendőek! Ez függ a szöveg méretétől (ami pedig a felhasználó beállításaitól). Nagyobb betűméret esetén a párbeszéd ablak is nagyobb lesz, kissebb esetén meg kissebb. Így biztosított, hogy a felhasználó minden esetben el tudja olvasni a szöveget. Azért van befolyásunk a méretre, mégpedig a MapDialogRect() segítségével. Ez már pixel alapú. A DISCARDABLE azt jelenti, hogy ha akarja, akkor a windóz swappelheti az erőforrásunkat.
A következő sor a STYLE-lal kezdődik, és azt követi a létrehozandó párbeszéd panel stílusa. Ahhoz, hogy az előredefiniált konstansokat használni tudjuk, szükséges include-olni a window.h fejléc állományt az .rc fájlunkban, vagy ha VC++-t használunk, akkor winres.h-ban, vagy afxres.h-ban. A szerkesztő lehet ezt automatikusan meg is teszi.
A CAPTION sor egy magyarázó szöveg.
A FONT sor határozza meg az alkalmazott font nevét és méretét. Ettől még nem fog minden számítógépen, minden felhasználónál ugyanúgy megjelenni. Általában ez nem okoz gondot.
Jöhetnek a párbeszéd ablak vezérlői.
DEFPUSHBUTTON "&OK",IDOK,174,18,50,14
Ez egy "OK" gomb lesz. Az idézőjelben az '&' jel az azt követő karaktert aláhúzza. Ez azt eredményezi, hogy ha megnyomjuk az Alt+O kombinációt, az ugyanaz, mintha megnyomtuk volna a gombot. Nem feltétlenül van így szükség egérre. Az IDOK az azonosítója. Ezt nem kell külön definiálnunk, mert már előre definiált. A számok a sor végén az X,Y pozíciók, és a szélesség, magasság értékek.
Két helyen is szerepel az ID_STATIC azonosító (ami -1 értéket képvisel). Ezzel azt jelezzük, hogy mi nem akarjuk használni, nincs is szükségünk azonosítóra. A fordító magától is csinálhat ilyet.
A "\r\n" a windózos újsor jelölés, ez gondolom senkinek sem új dolog.
Most, hogy ez mind bekerült az .rc fájlba, itt az ideje hogy elkészüljön a párbeszéd eljárás is. Nem kell aggódni, kvázi ugyanaz mint a WindProc().
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { switch(Message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: EndDialog(hwnd, IDOK); break; case IDCANCEL: EndDialog(hwnd, IDCANCEL); break; } break; default: return FALSE; } return TRUE; }
Van pár fontos eltérés a párbeszéd eljárás, és a window eljárás között. Nem hívjuk meg a DefWindowProc() eljárást! Ha mégis megtesszük, akkor érdekes dolgok történhetnek.
Ezt nem értem, valaki lefordíthatná nekem:
"Secondly, in general you return FALSE for messages you don't process, and TRUE for messages you do process, UNLESS the message specifies you return something else. Note that this is what we do above, the default is to do nothing and return FALSE, while messages we do handle break the switch() and return TRUE."
Harmadszor, nem hívjuk meg a DestroyWindow() eljárást hogy bezárjuk a párbeszéd ablakot, helyette az EndDialog()-ot használjuk. Második paramétere az az érték, amit a DialogBox() adott.
Végezetül anélkül hogy a WM_CREATE-et kezelnénk, a WM_INITDIALOG amivel foglalkozunk, hogy végrehajtsuk a párbeszéd ablak megnyitása előtt szükséges dolgokat. Ezután TRUE-val visszatérünk, a billentyűzet fókuszt pedig megkapja az alapértelmezett vezérlő. Valójában kezelhetjük a WM_CREATE üzenetet is, de ez még azelőtt megjelenik, mielőtt a vezérlők létrejönnének, ezért azokhoz nem férünk hozzá. A WM_INITDIALOG érkezésekor már létrejöttek a vezérlők is.
case WM_COMMAND: switch(LOWORD(wParam)) { case ID_HELP_ABOUT: { int ret = DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc); if(ret == IDOK){ MessageBox(hwnd, "Dialog exited with IDOK.", "Notice", MB_OK | MB_ICONINFORMATION); } else if(ret == IDCANCEL){ MessageBox(hwnd, "Dialog exited with IDCANCEL.", "Notice", MB_OK | MB_ICONINFORMATION); } else if(ret == -1){ MessageBox(hwnd, "Dialog failed!", "Error", MB_OK | MB_ICONINFORMATION); } } break; // Other menu commands... } break;
Ezzel a kóddal hozzuk létre az "about box"-ot, amit beleteszünk a WM_COMMAND() kezelőbe. Ha nem tiszta teljesen, akkor a menüvel foglalkozó részben le volt írva részletesen. Az ID_HELP_ABOUT azonosító a Help menü About menüpontjára hivatkozik. Mivel ezt a menüt a fő ablakban szeretnénk látni, ezért a kódot nem a párbeszéd ablakban, hanem a fő ablak WndProc() eljárásába kell tenni.
Most hogy eltároltuk a DialogBox() visszatérési értékét, láthatjuk annak a hatását, ha megnyomunk valamit (Esc, Enter stb).
DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
IDD_ABOUT a párbeszéd ablak erőforrás azonosítója, hwnd a szülőablak handlere. Az AboutDlgProc() a párbeszéd ablak vezérlőit kezeli.
Felmerül a kérdés, hogy ha a DialogBox() még nem tért vissza, akkor hogyan tudjuk kezelni az üzeneteit. A DialogBox() is rendelkezik saját üzenethurokkal. Ez gondoskodik arról, hogy ha Tab billentyűt nyomunk, akkor a billentyű fókusz vándoroljon vezérlőről vezérlőre.
A DialogBox() használatának másik hatása, hogy amíg be nem zárjuk, addig a fő ablak inaktív lesz. Van hogy ez jó nekünk, és van hogy nem (például lebegő eszköztárnak használnánk). A következőkben erre helyezzük a hangsúlyt.
7. Menük, és ikonok (Menus and icons)
2014.06.05. 20:44
Példa: menu_one
Általában a menüket a csillivilli szerkesztővel hozzuk létre, ami elkészít egy .rc fájlt, amit végül belefordít a .exe-be. Ez ugye elkészítésileg egyszerű, de a belseje meg bonyolult. Most kézzel fogjuk ezt megtenni. Ebben a példában a simple_window projektet vesszük alapul, ezt fogjuk tovább bővíteni.
Először a resource.h fájl:
#define IDR_MYMENU 101 #define IDI_MYICON 201 #define ID_FILE_EXIT 9001 #define ID_STUFF_GO 9002
Nem sok minden van benne, mert most egy egyszerű menüt készítünk. A neveket természetesen tetszőlegesen választhatjuk. Jöhet az .rc fájl.
#include "resource.h" IDR_MYMENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Stuff" BEGIN MENUITEM "&Go", ID_STUFF_GO MENUITEM "G&o somewhere else", 0, GRAYED END END IDI_MYICON ICON "menu_one.ico"
Az .rc fájlt hozzáadjuk a projekthez (vagy a makefile-hoz ha azt használunk). Bele include-oljuk a resource.h állományt a forrásfájlba, így most már abból elérhetők lesznek a parancsok azonosítói, valamint a menü erőforrás. A menü, és az ikon erőforrások ablakhoz rendelése legegyszerűbben úgy lehetséges, ha regisztráljuk az ablak osztályt így:
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU);wc.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);
Próbáljuk ki, mit alkottunk! Az ablakunknak most már van egy menüje, ami tartalmaz egy File, és egy Stuff menüpontot. Feltételezzük, hogy az .rc fájl megfelelően össze lett állítva, és becsatoltuk az .exe programunkba (lásd. fordítóval kapcsolatos jegyzet).
Az ablak bal felső sarkában, valamint a taskbar-on most már látnunk kell a kicsi ikont, amit megadtunk. Ha Alt+Tab-ot nyomunk, akkor ugyanez megjelenik nagyban is a program listán. Az egyszerűség kedvéért a LoadIcon()-t használtuk, ami jó is, ha az alapértelmezett 32x32 méretű ikont akarjuk tölteni. Ha kissebb kép áll rendelkezésre, akkor használhatjuk a LoadImage()-t. Ne feledjük, hogy minden egyes fájl amit felveszünk erőforrásnak, növeli a végleges programunk méretét!
Második példa: menu_two
A menü erőforrás alternatívája, amikor menet közben építjük fel. Ez több munkával jár, de rugalmasabb, és néha csak így lehet. Megtehetjük, hogy az ikonokat nem erőforrásként építjük be, hanem külön fájlként futásidőben töltjük be. Így akár a felhasználó is kiválaszthatja, hogy melyik legyen.
Kezdjük újra a simple-window példát, de most anélkül, hogy hozzáadnánk a .h, és .rc fájlokat! Most a WM_CREATE üzenetet fogjuk használni, hogy a menüt az ablakhoz adjuk.
#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
Ezt a két azonosítót hozzáadjuk a .c fájlhoz, majd jöhet a WM_CREATE:
case WM_CREATE: { HMENU hMenu, hSubMenu; HICON hIcon, hIconSm; hMenu = CreateMenu(); hSubMenu = CreatePopupMenu(); AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit"); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File"); hSubMenu = CreatePopupMenu(); AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go"); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff"); SetMenu(hwnd, hMenu); hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE); if(hIcon) SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); else MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR); hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); if(hIconSm) SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); else MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR); } break;
Ez létrehozza a menüt, és hozzácsatolja az ablakhoz, pont mintha erőforrásból csináltuk volna. A program befejezésével a menü automatikusan megszűnik, így nekünk nincs több gondunk vele. Viszont kipróbáltuk a GetMenu(), és a DestroyMenu()-t.
Az ikonokat gyorsan lerendezzük. Meghívjuk kétszer a LoadImage()-t, egyszer a 16x16-os, egyszer pedig a 32x32-es ikonhoz. Itt nem használhatjuk a LoadIcon()-t, mert az csak erőforráshoz alkalmas. NULL-t adunk meg a példány handlernek, mert nem töltünk erőforrást a modulból, hanem az azonosítót adjuk meg, amelyik ikont be akarjuk tölteni. Végül az LR_LOADFROMFILE flag fogja jelezni, hogy amit megadtunk az a fájl neve, és nem erőforrásból lesz.
Ha minden sikeres volt, akkor hozzárendeljük az ikon handlerjét az ablakhoz a WM_SETICON üzenettel. Ellenkező esetben dialógus ablak figyelmeztet arra, hogy hiba történt.
Megjegyzés: A LoadIcon() hibával tér vissza, ha az ikon nem a programmal megegyező könyvtárban van. Ha VC++ alatt dolgozunk, és a fejlesztő környezetből futtatjuk a programot, akkor a munkakönyvtár az lesz, ahol a projektfájl is található. Ha viszont Debug, vagy Release módban futtatod például explorerrel, vagy parancssorból, akkor át kell másolni az ikon fájlt, hogy a program megtalálja. Esetleg használhatunk teljes elérési utat is (ez nyilván nem túl praktikus).
Rendben hogy van menünk, de csináljon is valamit! Ehhez csak az kell, hogy kezelje a WM_COMMAND üzenetet. Tehát ellenőríznünk kell az üzenetet amit kaptunk. A WndProc() most így néz ki:
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { switch(Message) { case WM_CREATE: { HMENU hMenu, hSubMenu; hMenu = CreateMenu(); hSubMenu = CreatePopupMenu(); AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit"); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File"); hSubMenu = CreatePopupMenu(); AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go"); AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff"); SetMenu(hwnd, hMenu); hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE); if(hIcon) SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); else MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR); hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); if(hIconSm) SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); else MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR); } break; case WM_COMMAND: switch(LOWORD(wParam)) { case ID_FILE_EXIT: break; case ID_STUFF_GO: break; } break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, Message, wParam, lParam); } return 0; }
Alakul. Kezd már jó kövér lenni :)
Láthatjuk, hogy felvettük a WM_COMMAND üzenetet is, de van belül mégegy switch(). Ez a wParam alapján dönti el, hogy mit kell csinálnia. Természetesen az Exit-re jó lenne kilépni, ezt így tudjuk megtenni:
PostMessage(hwnd, WM_CLOSE, 0, 0);
A WM_COMMAND kezelőnk most már ilyen lett:
case WM_COMMAND: switch(LOWORD(wParam)) { case ID_FILE_EXIT: PostMessage(hwnd, WM_CLOSE, 0, 0); break; case ID_STUFF_GO: break; } break;
Az ID_STUFF_GO már a fantáziánkra van bízva. Megfigyelhetjük, hogy a menu_one.exe a saját ikonjával látszik, míg a menu_two.exe nem. Előbbi erőforrást használt, utóbbi pedig menet közben töltötte be az ikont. Amikor az explorer megjeleníti a programokat, akkor megkeresi az erőforrások között az első ikont, és ezt fogja felhasználni program ikonként. Ha azt szeretnénk, hogy mindenkor a saját ikonunkat lássuk, csak annyi a dolgunk, hogy felvesszük erőforrásként az ikont, és ID-nek valami nagyon alacsony számot adunk neki (például 1). Még használnunk sem kell, a windows mégis ezt fogja a program ikonjának mutatni.
6. Erőforrások használata (Using resources)
2014.06.04. 22:15
Bevezetés: Olvassunk függeléket az erőforrásokról :)
Ez megint inkább elméleti dolog lesz, nem kell hozzá fordítgatni.
Az erőforrások a futtatható állományban (.exe) tárolt bináris adatok. Létrehozhatunk ún. erőforrás-scriptet. Ezt az elterjedt vizuál stílusú fordítók el is készítik nekünk anélkül hogy hozzá kellene nyúlnunk. Ha az általunk használt fordító mégsem képes erre, akkor nincs más, kézzel kell szerkesztenünk. Sajnos az egyes fordítók eltérően kezelik az erőforrásokat. Megpróbálunk a közös vonások alapján haladni.
Az erőforrás szerkesztők (ide értve a MSVC++ is) nagyon nehézzé teszik az erőforrás fájl kézi szerkesztését, mert kikényszerítik egy saján formátum használatát. Pedig olykor nagyon hasznos lehet, ha magunk is bele tudunk nyúlni. Másik kellemetlenség, hogy az MSVC++ mindeképpen resource.h névvel illeti, még akkor is, ha másképpen akarnánk. Az egyszerűség kedvéért ezt fogjuk használni, de a függelékben láthatjuk majd, hogy mi kell változtatni.
Előszőr is egy egyszerű belépő: Ikon.
#include "resource.h" IDI_MYICON ICON "my_icon.ico"
Ennyi az egész fájl. Az IDI_MYICON egy azonosító, az ICON a fájl típusa, az idézőjeles pedig maga az ikon elérési útja. Ennek minden fordítóval működnie kell.
Most akkor mi van ezzel az #include-dal? Programunknak szüksége van arra, hogy azonosítsa az ikont, ennek legegyszerűbb módja egy egyedi azonosító (IDI_MYICON) bevezetése. Ezt megtehetjük ha létrehozzuk a resource.h fájlt, és beinclude-oljuk az erőforrás scriptbe, és a forrásfájlba is.
#define IDI_MYICON 101
Mint látható, az IDI_MYICON-hoz hozzárendeltük a 101 értéket. Ahol tehát hivatkozunk az IDI_MYICON-ra, az ugyanaz, mintha beírnánk a 101-et. Mégis, sokkal könnyebb megjegyezni egy beszédes nevet mint a számot. Ha pedig valamiért később módosítani kell az értékét, akkor elég egyetlen helyen megtenni, nem kell a programban keresgélni.
Most mondjuk hogy adjunk hozzá egy MENU erőforrást!
#include "resource.h" IDI_MYICON ICON "my_icon.ico" IDR_MYMENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END END
Az IDI_MYMENU a menü neve, a MENU pedig a típusa. Vegyük észre, hogy szerepel két BEGIN - END páros. Néhány fordító '{' jelet használ a BEGIN, és '}' jelet az END helyett. Olyan is van, hogy mindkettővel működik. Ezt ki kell tapasztalni, és aszerint használni. Természetesen ha mindkettőt tudja, akkor szabadon választható, hogy melyik legyen.
Használtunk egy új konstanst, ID_FILE_EXIT néven. Ilyen még nincs, ezért létre kell hozni.
#define IDI_MYICON 101 #define ID_FILE_EXIT 4001
Nagyobb projekteknél már jó macerás ezt mind kézzel csinálni, ezért legtöbben rábízzák ezt a szerkesztőjükre. Mégis, ennek ellenére előfordul hogy valami összeugrik, és akkor lehet kézzel kotorászni a bugyraiban. De legalább lehet.
Most nézzünk egy példát arra, hogy hogyan használjuk az erőforrásunkat.
HICON hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
A LoadIcon() első paramétere a példány-handler (hInstance), ami a WinMain()-tól érkezik, és a GetModuleHandler() segítségével használható amint azt már néztük. A második paraméter az erőforrás azonosítója. De mi a retek az a MAKEINTRESOURCE?!? Egyik módja az azonosításnak az "ID" használata, ahogyan azt most tettük, ekkor a MAKEINTRESOURCE csinál LPCTSTR-t az UINT-ből. Többnyire ehelyett az alábbi formát szokás használni, itt közvetlenül a sztring azonosít.
HICON hMyIcon = LoadIcon(hInstance, "MYICON");
A LoadIcon(), és más erőforrás töltő API-k meg tudják különböztetni az INT típust a sztringre mutató pointertől. Megnézi az érték felső szavát (HI WORD), és ha ez nulla, akkor ID-ről van szó (ez azt is jelenti, hogy 65536 ID-t lehet használni). Ha nem nulla, akkor egy sztringet címző mutató.
Hát ezen is túl vagyunk :)
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.
4. Most már az ablakunk is csináljon valamit (handling_messages)!
2014.06.02. 20:29
Példa: window_click
Az rendben, hogy van egy ablakunk, de a default dolgokon kívül (átméretezés, bezárás, stb) semmit sem csinál. Nagyon izgi. :(
Ebben a példában elővesszük az előző programunkat (vagy megnyitjuk a window_click projektet), és ezen példán keresztül megnézzük, hogy miként működik az üzenetkezelés. Programunk megmondja, hogy mi a neve. Haszna semmi, de közben ismerkedünk a WndProc()-cal.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; }
Ez ugye ismerős? Tud bezárni, visszajelezni, és továbbadni az üzenetet. Tegyük fel, hogy szeretnénk egerészni is!
Ha megnyomjuk az egér bal gombját, akkor történjen valami. Ehhez hozzá kell adnunk az eseménykezelőhöz a WM_LBUTTONDOWN üzenetet (vagy WM_RBUTTONDOWN, WM_MBUTTONDOWN értelemszerűen a jobb, illetve középső gombhoz).
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_LBUTTONDOWN: // <- // <- ide jön a te cuccod break; // <- case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; }
Általában mindegy az üzenetek sorrendje, csak azt ne felejtsük el, hogy tegyük ki mindig a break kulcsszót! Ellenkező esetben összetorlódnak, mi meg kereshetjük a hibát! Láthatjuk, hogy hozzáadtunk egy új sort a switch() szerkezethez! Most belepakoljuk azt a kódot, ami megmutatja a program nevét.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_LBUTTONDOWN: // BEGIN NEW CODE { char szFileName[MAX_PATH]; HINSTANCE hInstance = GetModuleHandle(NULL); GetModuleFileName(hInstance, szFileName, MAX_PATH); MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION); } // END NEW CODE break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; }
A GetModuleHandle() mondja meg, hogy melyik programról kérjük az információt, és a GetModuleFileName() adja meg végülis a választ. TODO: Ezt a GetModuleHandle() dolgot nem teljesen értem.
A GetModuleFileName() második paramétere LPTSTR (vagy LPSTR a régi hivatkozások szerint), ami nem más, mint char *, amit karakter tömbként definiálunk:
char szFileName[MAX_PATH];
MAX_PATH egy makró a window.h fejléc állományból, ami definiálja a fájlnév tárolásához szükséges puffer méretét a Win32 alatt.
Harmadik paraméter a MAX_PATH (újra előkerült). A GetModuleFileName() számára közli a pufferméretet.
A GetModuleFileName() meghívását követően szFileName feltöltődik egy nulla lezárású sztringgel, ami a .exe fájl nevét tartalmazza. Ezt egyszerűen átadjuk a MessageBox() függvénynek. Ez a felhasználóval való kapcsolattartás legegyszerűbb módja.
3. Csináljunk ablakot, és örüljünk (simple_window)!
2014.06.01. 22:27
Példa: simple_window
Az ablak kirakása nem túl nehéz, csak előbb sok mindent meg kell csinálni. Szóval nem nehéz, csak macerás. Ha jól látom, akkor a win32-re ez végig igaz lesz! Gépelünk, gépelünk..
Jöjjön előbb egy példa, amin keresztül boncolhatjuk azt a bizonyos orvosi lovat!
#include <windows.h> const char g_szClassName[] = "myWindowClass"; // Step 4: the Window Procedure LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hwnd; MSG Msg; //Step 1: Registering the Window Class wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } // Step 2: Creating the Window hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL); if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Step 3: The Message Loop while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; }
Akár kis is próbálhatjuk hogy mit alkottunk! Töltsük be a példaprogramok közül, és futtassuk le!
Megjelent egy hagyományos ablak, amiben ugyan nincs semmi, de átméretezhető, és a sarkában ott vannak a vezérlő gombok. És még fejléce is van neki! Nahát! És mindez alig nyolcvan sorba került! ;o)
Nézzük meg, hogy mi a retek ez a sok programsor!
Első lépés:
Elöljáróban annyit, hogy a Window osztálynak semmi köze nincs a C++ osztályokhoz! Ez tárolja az ablakhoz felhasznált ikont, a vezérlőket, és a háttérszínt. Ezeket a tulajdonságokat nem kell minden ablakhoz külön külön beállítani, elég csak az ablakokat létrehozni. Persze ablakonként is állíthatók ha kell.
const char g_szClassName[] = "myWindowClass";
Ez a változó tárolja az ablak osztályának a nevét, ezzel fogja a rendszer regisztrálni az ablakunkat.
A következőkben létrehozunk egy WNDCLASSEX típusú változót (aminek van pár beállítható értéke), majd a RegisterClassEx() függvénnyel regisztráljuk.
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }
Akkor tételesen az egyes mezők jelentéséről:
cbSize: Jelzi a struktúra méretét
style:Egyszerűen legyen 0
lpfnWndProc:Mutató a window eljárásra
cbClsExtra: Lefoglalt memória mennyisége az extra adatok számára. Legyen 0!
cbWndExtra: Ugyanaz, csak ablakonként. Szintén 0.
hInstance: Az alkalmazás példány kezelője (ez a WinMain() első paramétere).
hIcon: Nagy (32x32) ikon, amit az Alt+Tab nyomásakor látunk
hCursor: Az ablak felett látható kurzor
hbrBackground: Háttér ecset, ami megadja az ablak színét
lpszMenuName: A menü erőforrás neve
lpszClassName: Azonosító név
hIconSm: Kicsi (16x16) ikon, amit a taskbar-on és az ablak bal felső sarkában látunk
Ezután meghívjuk a RegisterClassEx() eljárást, ami sikertelenség esetén dob egy üzenetet.
Második lépés:
Ha már regisztráltunk, akkor létrehozhatjuk az ablak példányunkat.
HWND hwnd;hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL);
Az első paraméter egy szebb dizájnt ad az ablak keretének (3D). Ha látni szeretnéd a különbséget, akkor állítsd próbából 0-ra az értékét!
Következőként az osztály nevét adjuk meg.
A WS_OVERLAPPEDWINDOW egy ablak stílus paraméter. Sok ilyen van, ki kell próbálni, hogy melyik mit csinál.
A CW_USEDEFAULT, CW_USEDEFAULT, 240, 120 az ablak elhelyezését határozza meg. X,Y,Szélesség,Magasság formátumban. Az értékek pixelben értendőek, a képernyő bal felső sarka a 0,0. Amikor CW_USEDEFAULT érték szerepel, akkor a rendszer automatikusan helyezi el az ablakot a képernyőn.
Most jön a ParentWindowHandle, a MenuHandle, ApplicationInstanceHandle, WindowCreationData négyes.
ParentWindowHandle: Ha egy gombot teszünk az ablakra, akkor a gombot gyermeknek, az ablakot pedig szülőnek nevezzük. Mivel ez az ablak egy legfelső objektum, nincs szülője, ezért ez NULL.
MenuHandle: A menü is NULL, mert nincs olyanunk.
ApplicationInstanceHandler: A WinMain() első paraméterét átadjuk
WindowCreationData: Nem használjuk, NULL
Ténykedésünk eredményét mindig ellenőrízzük le, ezt végzi el az alábbi pár sor:
if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }
Ha minden jó volt, akkor a WinMain() utolsó paraméterének segítségével megjeleníthetjük az ablakunkat
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
Az nCmdShow paraméter opcionális, egyszerűen írhatnánk azt is, hogy SW_SHOWNORMAL. Mégis, ezzel be lehet állítani pár dolgot az ablakon. Ez nem túl tiszta még, majd kiderül.
Azt hiszed végeztünk? Hát nem!
Harmadik lépés:
while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;
Ez a program motorja!
A GetMessage() üzenetet kap az alkalmazások üzenet sorából. Minden alkalommal amikor a felhasználó mozgatja az egeret, gépel a billentyűzeten, kattint a menün, stb, a rendszer generál egy üzenetet a program üzenetsorába. A GetMessage() hívásával kiolvassuk a következő rendelkezésre álló üzenetet, és töröljük a sorból.
A TranslateMessage() további dolgokat tesz a billentyűeseményekkel.
A DispatchMessage() tovább küldi az üzenetet, hátha nem nekünk szólt. A rendszer tudni fogja kinek kell továbbítania.
Negyedik lépés:
Eldöntjük, hogy a kapott üzenetekkel mit kezdjünk.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; }
A HWND paraméter címzi az aktuális ablakot. Például a bezárás gombot megnyomva innen tudjuk, hogy melyik ablakot is kell bezárni. A WM_CLOSE üzenet a rendszer általános parancsa, ha Alt+F4-et nyomunk, vagy az ablak sarkában az 'x'-et. A rendszer automatikusan bezárná az ablakot, de célszerű ezt inkább manuálisan megtenni, mert így a kilépés előtt még megtehetünk dolgokat. Például mentés kilépés előtt, kilépési megerősítés kérése, stb.
Ha meghívjuk a DestroyWindow() eljárást, akkor a rendszer küld egy WM_DESTROY üzenetet, amit még utoljára elcsíphetünk. Ekkor a PostQuitMessage() segítségével küldhetünk egy végeredmény kódot az esetleges hívó program számára.
Egyenlőre ennyi, most örülünk :)
2. Első programunk (getting started)
2014.05.31. 19:26
Első programunk
Találtam egy pár hivatkozást, ami a témával foglalkozik, "természetesen" angolul. Hát tudok én angolul?!?
Na jó, egy kicsit, elvégre az évek alatt ragadt mint a kosz. De ennyi. A programozáshoz azért pont elég szokott lenni. A következőkben többnyire fordítások következnek majd a következő oldalról.
Amennyiben jobb az angolod mint az enyém (ez valószínű:)), és hibát vagy pontatlanságot találsz, kérlek közöld velem, hogy javíthassam a közjó érdekében!
Szóval próbáljuk meg mondjuk ezt itt: http://www.winprog.org/tutorial/start.html
Amikor egy új programot kezdünk írni, akkor pár dolgot be kell állítanunk a fejlesztő környezetnek. Semmi vészes, de itt képekkel illusztrálva lehet puskázni. Első link a konzolos alkalmazásnak, második a windózosnak. Köszönet a szerzőnek érte :)
Azzal kezdi, hogy a példakódokat mindenképpen töltsük le, mert csak a fontos elemeket fogja idézni, és nem közli újra a teljes programot! Megemlíti továbbá, hogy elvár bizonyos ismereteket, tehát akinek nem mond semmit a macro, typedef vagy a switch(), az előbb mélyedjen el jobban a C nyelvben.
A szerző felhívja a figyelmet, hogy a használt nyelv a C, és nem a C++, tehát a fájlok kiterjesztése .C lesz, nem pedig .CPP! A fordítónak gondot okozhat a figyelmetlenség.
#include <windows.h> int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK); return 0; }
Így elsőre nem is tűnik túl bonyolultnak! Azért azt jegyezzük meg, hogy mire ezzel a tutoriallal végzünk, valószínűleg gond nélkül megy majd a tíz ujjas vakon gépelés :)
Ha valamiért nem működne ez a program elsőre, akkor győződjünk meg róla, hogy a projekt beállításkor Win32 alkalmazást állítottunk be, és még véletlenül sem konzolt! Kicsit más világ. Ha még mindig nem jó, akkor gáz van, meg kell keresni a hibát. Ebben a szerző sem tud további segítséget nyújtani.
Nekem amúgy nem ment elsőre. Ha csak indítok egy új win32 projektet, akkor hiába másolom be a fenti pár sort, két warninggal jutalmaz (mondjuk legalább lefordul), és a megjelenő ablakban nem a szöveg, hanem sok kis négyzet jelenik meg. Ha a példák között szereplő test.c-t nyitom meg projektként, akkor bezzeg lefordul, fut, és azt írja ki amit kell. Ki érti ezt?!? Gondolom ez még kiderül később.
Nézzük tételesen!
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
A fenti sor ugyanaz, mint a hagyományos main() függvényünk volt! Ezzel kezdődik a program. Van pár paramétere, most ezeket vegyük szemügyre!
- HINSTANCE hInstance: Programpéldányunk címe, vagy handlere (ez tulajdonképpen maga az .exe)
- HINSTANCE hPrevInstance: Win32 programoknál mindig NULL az értéke.
- LPSTR lpCmdLine: A parancssori paraméterek egyszerű sztringként, a program neve nélkül.
- int nCmdShow: Int típus a ShowWindow() számára. Később bővebben rátérünk.
A WinAPI meghatározza a hívási konvenciókat, ezt _stdcall néven tisztelhetjük. Valószínűleg nem lesz rá szükségünk,
Következő alkalommal egy egyszerű ablakot fogunk a képernyőre varázsolni.
1. Első szárnypróba (Home)
2014.05.31. 14:29
Sziasztok!
Most jöhetne a rizsa meg az életrajz, meg egy csomó senkit sem érdeklő dolog. De nem jön.
Egy kísérletet indítok el. Blogom még nem volt, lehet hogy most majd lesz. Ha beválik, akkor marad.
Egy nagyobb projektbe kezdtem bele, és többször egymás után ráfutottam arra a problémára, hogy ma programot fejleszteni borzasztóan erőforrásigényes dolog. Viszonylag egyszerű dolgot szeretnék megoldani, és rendre hekkelnem kell valamit a fejlesztő környezeten, az elgondolásomon, és egyáltalán mindenen, hogy a szekér nemhogy fusson, de legalább elinduljon. Gyakorlatilag végigpróbálam a Microsoft féle visual-csillivillit, de az összes lassú volt. A dotNet úgy tűnik ezt tudja. Természetesen a guruk tudják hogy miért van így, és mit lehet tenni ellene. Én nem vagyok guru. De emlékszem a jó öreg C64 korszakra. Ott amit írtam, az volt. Szóval ez így nem jól van szerintem.
Megpróbálok visszatérni a gyökerekhez, és megidézem a natív windóz programozás (Win32) szellemét. Ennek a próbának fogok itt megnyilvánulási lehetőséget biztosítani. Hátha még akármi is lehet belőle.
Amit célul tűztem ki, hogy egy EKG diagramot tudjak animálni, a szisztolés csúcsnál pedig hangot lejátszani. Mindezt timerrel megoldva, hogy a főprogram hátterében fusson (vagy külön szálon, mindegy).
Mielőtt elfelejtem, linkek!