Az elözö fejezetben megmutattam, hogyan kell csinálni egy üres ablakot. Most megmutatom hogyan lehet csinálni nem üres ablakot. Mindenki tükön ülve, körmeit rágva várja, hogy vajon hogy lehet olyat csinálni...
Bár ennek a tutoriálnak a célja a programászati oldalról való bemutatása a winapinak, kitérek egy kicsit a resource file-okra is és azt is megmutatom, hogyan lehet owner-drawn controlokat csinálni.
You are in control
Az elözö fejezetbeli RegisterClassEx() alapján a controlok osztályait is be kell regisztrálni, kivéve a standard controlokat. Ezeket
létre lehet hozni simán a CreateWindow() függvénnyel, mert a winapi olyan rendes, hogy beregisztrálja öket.
A bonyolultabb control-okhoz már kelleni fog a ComCtl32.lib
és neked kell ezt megtenni az InitCommonControlsEx() függvénnyel.
Tehát érdemes úgy elindulni, hogy ez már a kódban van, és ha kell valamilyen típus, akkor egyszerüen hozzáadod a megfelelö flag-et.
CODE
#pragma comment(lib, "comctl32.lib")
#include <windows.h>
#include <commctrl.h>
// ...
void CreateControls()
{
INITCOMMONCONTROLSEX iccs;
// alap control-ok és a különféle bar-ok (pl. trackbar)
iccs.dwICC = ICC_STANDARD_CLASSES|ICC_BAR_CLASSES;
iccs.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCommonControlsEx(&iccs);
// ...
}
Gondolom senki nem lepödik meg ezen; teljesen világos, hogy ha több controlt akarsz, akkor control szexet kell csinálni. A control-ok üzeneteit ugyanúgy a föablak WndProc metódusában kapjuk meg, a WM_COMMAND üzenettel. A paraméterek között lehet a control azonosítója és/vagy a control ablakleírójára mutató pointer. A control-ok tulajdonságait késöbb is lehet állitgatni a SendMessage() függvénnyel.
Menü
A menüvel az a helyzet, hogy nem nagyon lehet beleszólni a kinézetébe, tehát hasonlóan az ablakhoz ez is olyan stílusú lesz amilyen be van állítva (Xp, Vista stb.). Ami nem is olyan nagy baj. A föablak létrehozása után
az alábbi módon lehet egy menüt hozzácsatolni:
CODE
// ezek lesznek az azonosítók
#define IDM_OPEN_ITEM 1001
#define IDM_SAVE_ITEM 1002
#define IDM_SAVEAS_ITEM 1003
#define IDM_EXIT_ITEM 1004
#define IDM_ABOUT_ITEM 1005
HMENU menu = CreateMenu();
HMENU submenu1 = CreatePopupMenu();
HMENU submenu2 = CreatePopupMenu();
AppendMenuA(submenu1, MF_STRING, IDM_OPEN_ITEM, "&Open");
AppendMenuA(submenu1, MF_STRING, IDM_SAVE_ITEM, "&Save");
AppendMenuA(submenu1, MF_STRING, IDM_SAVEAS_ITEM, "Save &As");
AppendMenuA(submenu1, MF_SEPARATOR, 0, 0);
AppendMenuA(submenu1, MF_STRING, IDM_EXIT_ITEM, "&Exit");
AppendMenuA(submenu2, MF_STRING, IDM_ABOUT_ITEM, "&About");
AppendMenuA(menu, MF_STRING|MF_POPUP, (UINT)submenu1, "&File");
AppendMenuA(menu, MF_STRING|MF_POPUP, (UINT)submenu2, "&Help");
SetMenu(hwnd, menu);
Az elvétve fellelhetö & jelek accelerator-ok (értsd: shortcut), ami akkor fog aktiválódni, ha lenyomod az alt-ot. Ekkor a megjelölt betüt lenyomva megnyílik a hozzátartozó menü. Billentyüzet partizánoknak ajánlott. Fontos, hogy nem lehet összevissza kavargatni ezeket a függvényhívásokat, tehát érdemes úgy kialakítani a rendszeredet, hogy ebben a sorrendben hívódjanak meg. Hasonlóan a menüelemek sorrendjét sem az azonosítóik határozzák meg, hanem a hívási sorrend. Az eredmény valami ilyesmi: Az egyes menüelemekhez tartozó akciókat a WndProc -ban lehet megadni. Például az About menüponthoz az alábbit kell csinálni:
CODE
case WM_COMMAND:
switch( LOWORD(wParam) )
{
case IDM_ABOUT_ITEM:
MessageBoxA(hwnd, "WinAPI tutorial 2", "About", MB_OK);
break;
// ...
default:
break;
}
Menükröl további info található itt: Menus
Gomb
Más nyelvekkel ellentétben a gombok közé tartozik minden ami gombhoz hasonló, tehát a checkbox, a radiobutton és a groupbox is. Mi az, hogy nem is hasonlít? Dehogyisnem:
Na ugye. Itt most a pushbutton-t fogom megmutatni, a többi hasonlóan kezelhetö. A létrehozás a CreateWindow() függvénnyel történik, például így:
CODE
#define IDC_BUTTON1 1020
button1 = CreateWindow(
"BUTTON", "Click Me", WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,
300, starty, 120, 40, hwnd, (HMENU)IDC_BUTTON1, hinst, 0);
Ennyi. Nem kell hozzáadni az ablakhoz mint a menüt, automatikusan létrejön és felszabadul. Az egyetlen szépséghibája, hogy a Windows 95-ös idöket idézi, de majd késöbb megmutatom hogyan lehet átvariálni XP vagy Vista stílusúvá. A lenyomás kezelése teljesen hasonló a menüéhez:
CODE
case WM_COMMAND:
switch( LOWORD(wParam) )
{
case IDC_BUTTON1:
MessageBoxA(hwnd, "LE nyomtad!", "A gombot", MB_OK);
break;
// ...
}
Ha lusták lennétek azonosítót csinálni, akkor az lParam-ban ott a gomb HWND-je.
Label, picturebox
Ilyenek önmagukban nincsenek, ezekhez a static típusú control-t kell használni. Ami még rosszabb, hogy nem méretezödnek automatikusan (a szövegüktöl függöen),
így elég sok mindent neked kell hozzá megirni.
Alapvetöen tök ugyanugy kell létrehozni, mint a gombot, csak a "STATIC" osztálynévvel és nyilván az ehhez tartozó stílus flag-ekkel. Ki is használom a lehetöséget, hogy egy ilyen static
controlba forgómorgót rajzoljak, de elöbb:
Trackbar
Ez nem standard control, tehát be kell regisztrálni (ICC_BAR_CLASSES), illetve a CreateWindowEx() függvénnyel kell létrehozni.
CODE
#define IDC_TRACKBAR 1022
trackbar = CreateWindowEx(
0, TRACKBAR_CLASS, "trackbar1", WS_CHILD|WS_VISIBLE|TBS_AUTOTICKS,
80, starty + 2, 200, 30, hwnd, (HMENU)IDC_TRACKBAR, hinst, NULL);
Mivel a winapi olyan nagyon következetes, a trackbar üzijeit nem a WM_COMMAND-on keresztül kapod meg, hanem a WM_VSCROLL és WM_HSCROLL-on keresztül. Söt itt már ID-t se kapsz, mert válság van meg minden. Az lParam-ból kell kiszedni a HWND-t.
CODE
case WM_VSCROLL:
case WM_HSCROLL:
if( (HWND)lParam == trackbar )
{
switch( LOWORD(wParam) )
{
case TB_LINEUP:
case TB_LINEDOWN:
case TB_PAGEUP:
case TB_PAGEDOWN:
// page up/down vagy nyilak
break;
case TB_THUMBTRACK:
// egér
break;
}
}
A thumb pozíciójának lekérését majd késöbb látni fogjátok. Bövebben ld. Trackbar
Resource fájlok
Tudom, hogy sokkal jobban szeretitek a kódból való programozást, de amikor a 734-edik dialogot programozod le, akkor nálad is kiborul a bili. Föleg ha ezt pure winapiban teszed (ne rontsuk már el
mindenféle OOP bugyutasággal). Resource fájlt úgy lehet hozzáadni a projekthez (visual studio), hogy jobb klikk és Add -> Resource. És akkor meg is nyit...valamit, amit aztán nemtud bezárni, de ha
sikerül valahogy megnyitni a kódját, akkor valami ilyen zagyvaság fogad:
CODE
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE 9, 1
#pragma code_page(1252)
// ...
Bármennyire is viszket töle a tenyered, NE töröld ki, mert nem fog müködni. Keress egy neked tetszö helyet, és oda kapard a saját dolgaidat. Például egy dialogot így kell csinálni:
CODE
// resource.h-ba
#define IDD_DIALOG1 100
#define IDC_RADIO1 10
#define IDC_RADIO2 11
#define IDC_RADIO3 12
#define IDC_GROUP1 13
// .rc fájlba
IDD_DIALOG1 DIALOGEX 0, 0, 176, 102
STYLE DS_SETFONT|DS_MODALFRAME|DS_FIXEDSYS|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Change indicator"
FONT 8, "MS Shell Dlg", 0, 0
BEGIN
DEFPUSHBUTTON "OK", IDOK, 64, 80, 50, 16
DEFPUSHBUTTON "Cancel", IDCANCEL, 120, 80, 50, 16
CONTROL "Type", IDC_GROUP1, "Button", BS_GROUPBOX, 6, 6, 100, 56
CONTROL "Circle", IDC_RADIO1, "Button", BS_AUTORADIOBUTTON, 20, 20, 38, 10
CONTROL "Square", IDC_RADIO2, "Button", BS_AUTORADIOBUTTON, 20, 32, 38, 10
CONTROL "Gear", IDC_RADIO3, "Button", BS_AUTORADIOBUTTON, 20, 44, 38, 10
END
Ezek az értékek nem pixelben értendöek. hanem logikai koordinátákban (így akkor is jól fog kinézni a cucc ha mondjuk kiprinteled). Tipikusan próba szerencse alapon kell belöni öket (amúgymeg vannak ilyen függvények, hogy DPtoLP meg fordítva). A DEFPUSHBUTTON az olyan, hogy nem kell külön definiálni az ID-ját, mert egy beépitettet adhatsz meg neki, például IDOK. Ez azért van így, mert általában minden dialognak van OK és Cancel gombja. A dialog megnyitása így néz ki:
CODE
INT_PTR WINAPI DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch( msg )
{
case WM_CLOSE:
EndDialog(hWnd, 1);
break;
case WM_COMMAND:
switch( LOWORD(wParam) )
{
case IDOK:
// ...
return TRUE;
case IDCANCEL:
SendMessage(hWnd, WM_CLOSE, 0, 0);
return TRUE;
}
default:
break;
}
return FALSE;
}
// valahol a kódban
DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), hWnd, &DialogProc);
Bármennyire is kívánkozik a DestroyWindow() meghívása, ne tedd, mert legyilkolja a föablakot is.
Forgómorgó
Szintén nincs. Illetve van homokóra, de nem sikerült még életre keltenem. Viszont van egy ilyen dolog amit úgy hívnak, hogy GDI+, és meglepö módon objektum orientált!
Ennek segitségével lehet rajzolni animált gif-et. Elöször is egy hasznos oldal, amit nem szeretnék elfelejteni: Chimply.
Itt lehet mindenféle forgómorgót csinálni, mindenféle színnel és méretben. Már csak le kéne programozni.
CODE
#include <windows.h>
#include <gdiplus.h>
class AnimatedGIF : public Gdiplus::Image
{
protected:
Gdiplus::Graphics* gfx;
IStream* stream;
HWND container;
UINT numframes;
UINT currentframe;
// ...
};
Elég fontos, hogy a GDI+ objektumokat nem szerencsés automatikus változóként létrehozni, mert szanaszéjjel szállhat a program. Ezért mivel úgyis leszármazok belöle, egy kötelezöen meghívandó Dispose() metódussal szabadítom fel, amiben viszont el kell végezni az ösosztály destruktorának dolgait, mert szintén szanaszét fog szállni:
CODE
void AnimatedGIF::Dispose()
{
delete gfx;
stream->Release();
if( nativeImage )
{
Gdiplus::DllExports::GdipDisposeImage(nativeImage);
nativeImage = 0;
}
}
Kitérö: akkor mi a francért hozom létre automatikusan? Nem hozom. De esetleg te majd igen, és nem fogod érteni, hogy mi a bánatért száll el ;) Maga a GIF betöltés nem egyértelmü, de mindenki leszedi a tutorial kódját és megnézi. Amire oda kell még figyelni, hogy rajzoláskor elöbb egy bitmapba rajzolj (és rögtön méretezd is át a képet) és utána rakd ki a controlba, különben villogni fog. Maga az animálás úgy történik, hogy egy timer-re rákötöd az alábbi metódust:
CODE
void AnimatedGIF::NextFrame()
{
if( numframes > 0 )
{
currentframe = (currentframe + 1) % numframes;
if (nativeImage)
SelectActiveFrame(&Gdiplus::FrameDimensionTime, currentframe);
}
}
Ilyen timert hozzá lehet csapni az ablakhoz a SetTimer() függvénnyel, aminek vagy megadsz egy függvénypointert, vagy a WM_TIMER üzenetben hajtod végre amit szeretnél. Egy létezö timert úgy lehet módosítani, hogy úgy csinálsz, mintha nem is létezne. Például a trackbar-hoz így lehet hozzákötni:
CODE
case TB_THUMBTRACK: {
unsigned short pos = HIWORD(wParam);
SetTimer(hWnd, IDC_TIMER, (6 - pos) * 20, 0);
} break;
Ugyanezt el lehet játszani a többi üzenetnél is. Maga a rajzolás pedig a WM_DRAWITEM üzenetben történik. Ne felejtsd el felinicializálni a GDI+-t a GdiplusStartup() függvénnyel, különben nem rosszabb helyen fog elszállni a program, mint minden new hívásnál.
Owner-drawn combo box
Elég sok fejfájást okozott, hogy a comboboxban nem lehet letiltani elemet. Végül hosszas guglizás és hajtépés után sikerült
valamit összeszenvednem, ami elfogadhatónak mondható.
CODE
void CustomCombo::RenderItem(LPDRAWITEMSTRUCT ds)
{
TCHAR itemtext[256];
COLORREF fg = 0, bg = 0;
// ...
SendMessage(ds->hwndItem, CB_GETLBTEXT, ds->itemID, (LPARAM)itemtext);
textlen = strlen(itemtext);
if( itemtext[0] == '@' )
{
fg = SetTextColor(ds->hDC, GetSysColor(COLOR_GRAYTEXT));
--textlen;
}
else
{
bg = SetBkColor(
ds->hDC, GetSysColor((ds->itemState & ODS_SELECTED) ?
COLOR_HIGHLIGHT : COLOR_WINDOW));
fg = SetTextColor(
ds->hDC, GetSysColor((ds->itemState & ODS_SELECTED) ?
COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
}
ExtTextOut(
ds->hDC, 2 * x, y, ETO_CLIPPED|ETO_OPAQUE, &ds->rcItem,
&itemtext[i], (UINT)textlen, NULL);
// ...
}
CODE
LRESULT CustomCombo::ProcessCommands(WPARAM wparam, LPARAM lparam)
{
HWND ctrl = (HWND)lparam;
WORD msg = HIWORD(wparam);
if( msg == CBN_SELCHANGE )
{
TCHAR itemtext[256];
DWORD ind = SendMessage(ctrl, CB_GETCURSEL, 0, 0);
SendMessage(ctrl, CB_GETLBTEXT, ind, (LPARAM)itemtext);
if( itemtext[0] == '@' )
SendMessage(ctrl, CB_SETCURSEL, cursel, 0);
else
cursel = ind;
}
return 0;
}
Nagyjából ennyi, a többi már csak körítés. Amit sehogyan nem sikerült megcsinálnom, hogy Vista stílusú legyen, ugyanis ezt a winapisok "elfelejtették" leimplementálni. Azaz csak úgy tudtam volna megcsinálni, ha teljesen én rajzolom az egész controlt, amihez már nem volt kedvem. Egyébként ezt az uxtheme nevü csodát kellene használni.
XP és Vista stílusok
A módszer részletesen le van írva az alábbi linken: Enabling visual styles. Neked elég
az alábbi direktívát hozzáadni a kódhoz (mindenféle #include elött):
CODE
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls'
version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
Ezzel a XP-s control library-t linkeli hozzá a kódhoz, így ha a célgépen megtalálható az a DLL, akkor azt fogja használni. Ezen kivül a control-okhoz tartozó szövegek köverségét ki lehet kapcsolni az alábbi módon:
CODE
HFONT font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
SendMessage(button1, WM_SETFONT, (WPARAM)font, MAKELPARAM(TRUE, 0));
Saját fontot létrehozhatsz a CreateFont() függvénnyel.
Summarum
Nem túl részletesen megmutattam hogyan lehet a WinAPI-val szórakozni. Ha használható szintre akarod hozni ezt az egész rendszert, akkor érdemes valami OOP interfészt ráhúzni.
Erre lehet találni példát a DummyFramework-ben (föoldal), vagy a Win32++ libraryban (ugyanaz mint az MFC, és amúgy elég hasznos).
Höfö:
|