Recommended Books and References

 

Books

If you expect anyone online to treat you with respect while you are learning, you NEED to get a good book to learn from. We're here to provide direction and explain things that need explaining, not to be your librarian or teach you step by step.

You can find more recommended books and links to buy at the #Winprog Store.

Programming Windows
by Charles Petzold. The book to get on Win32 API. If you want to write programs using just the API (which is what this tutorial covers), you need this book.
Programming Windows with MFC
by Jeff Prosise. If you want to venture into MFC (AFTER becoming fully accustomed to using the Win32 API), this is the book for you. If you don't like MFC but intend on getting a job doing windows developement, get this anyway, it's better to know than not.
Programming Applications for Windows
by Jeffrey Richter. Not for newbies, if you want to be up on managing processes and threads, dlls, windows memory management, exception handling, and hooking into the system, then this is the book for you.
Visual C++ Windows Shell Programming
by Dino Esposito. For anyone interested in the visual and user-friendly aspects of windows, this book covers writing extentions to the windows shell, working efficiently with files and drag and drop, customizing the taskbar and windows explorer, and numerous other tricks. Well worthwhile for anyone writing GUI apps in windows.
Network Programming for Microsoft Windows
Up to date information on network programming, including NetBIOS, mailslots and pipes, and of course the ever important windows sockets, complete with winsock2 and raw sockets. Also contains specific information on the various windows platforms including 2000 and CE.

Links

MSDN Online
This site has references for all imaginable Microsoft technologies, including full Win32 API and MFC documentation. If this didn't come with your compiler (ie. VC++) then the completely free online site will provide you with the required information. People will get really pissed off if you ask questions you could answer by doing a simple search on MSDN.
#winprog homepage
See FAQ and Store

Példa: font_one

Fontok betöltése[images/font_one.gif]

 

Ha valaki esetleg nem tudná a font az a betűtípust jelenti. Beleértve a külalakot, hogy dőlt vagy félkövér, a színét, és méretét is. Most jó sok magyarázat fog következni, ezért a fordítás hitelessége erősen kérdéses. Kérlek fenntartással olvasd! - ford. -

A Win32 GDI figyelemre méltó hozadéka a sok betűtípus, stílus, nyelv, és karakterkészlet. Ez azzal jár, hogy a CreateFont() összesen 14 paramétert vár (magasság, stílus, család, stb)! Ijesztő!

Szerencsére ez annyira nem is fáj, mert sokszor használhatjuk az alapértelmezett értékeket, ami egyszerűen 0, vagy NULL. Ilyenkor a rendszerszintű beállítást veszi alapul.

A CreateFont() egy HFONT handlert hoz létre. A GetObject() egy LOGFONT struktúrát tölt fel, pont úgy, ahogyan a HBITMAP-ból egy BITMAP struktúrát.

A LOGFONT mezői megegyeznek a CreateFont() paramétereivel, és egy létező LOGFONT struktúra segítségével közvetlenül létrehozhatunk fontot. Ehhez a CreateFontIndirect()-et használhatjuk. Ez akkor hasznos, ha egy létező font handleréből akarunk új fontot létrehozni, hogy csak pár dologban térjen el az eredetitől. A GetObject() használatával kitöltjük a LOGFONT-ot, megváltoztatjuk amit akarunk, majd létrehozzuk az új fontot a CreateFontIndirect() segítségével.

    HFONT hf;
    HDC hdc;
    long lfHeight;
    
    hdc = GetDC(NULL);
    lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
    ReleaseDC(NULL, hdc);

    hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times New Roman");

    if(hf)
    {
        DeleteObject(g_hfFont);
        g_hfFont = hf;
    }
    else
    {
        MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
    }                   

Ezzel a kóddal létrehozzuk a példa képen látható fontot. Ez a Times New Roman, 12 pont magas, dőlt típus. A dőlt flag a CreateFont() hatodik paramétere, amit TRUE -ra állítottunk. A használandó font neve az utolsó paraméter.

Van egy kis trükk a dologban. Amikor az emberek betűtípussal dolgoznak, akkor képpontban akarják megadni a méretét. 10-es méret, 12-es méret stb. Na ez az, amit a CreateFont() nem fogad el! Ő ugyanis logikai egységet vár, ami eltér a képernyőnk a nyomtatónk között, de két nyomtató és két képernyő között is eltérő lehet. Az egyes eszközök felbontása ugyanis nagyon nagy szórást mutathat. A nyomtatók tudnak 600, 1200 képpontot inch-enként, míg a monitor jó ha 200-at. Ha ugyanazt a betűméretet használnánk nyomtatóhoz és monitorhoz is, valószínűleg ki sem tudnánk olvasni az egyes betűket!

Mindössze annyi dolgunk van, hogy a pont méretből konvertálunk a készülég logikai egységébe. Jelen esetben a készülék a képernyő, így elkérjük a képernyő HDC-jét. A GetDeviceCaps() adja meg a logikai képpontok számát inch-enként. A MulDiv() szoroz, oszt, és máris megvan a helyes logikai egység, amit a CreateFont() is elfogad.

 Alap betűkészlet

 

Amikor először hívjuk a GetDC()-t hogy megkapjuk az ablakunk HDC-jét, akkor a rendszer alap betűtípusa van kiválasztva, ami nem túl esztétikus. A legegyszerűbb út egy működő fonthoz, ha a GetStockObject()-et meghívjuk, és kiválasztjuk a DEFAULT_GUI_FONT-ot. Így elkerüljük a CreateFont() maceráit. Ez egy rendszer objektum, és tetszőleges számban meghívható, nem kell memória szivárgástól tartani. Meghívhatjuk a DeleteObject()-et ami nem csinál semmit, nem kell követni a CreateFont() vagy GetStockObject()-et mielőtt megpróbáljuk felszabadítani.

Szöveg rajzolása

 

Most hogy van egy jó kis fontunk, hogyan tesszük ki a szöveget a képernyőre? Ez feltételezi hogy nem Edit vagy Stactic vezérlőre akarunk írni.

Alap lehetőségek a TextOut(), vagy DrawText(). A TextOut() egyszerűbb, de kevesebbet is tud. Nem csinál szövegtörést és igazítást.

    char szSize[100];
    char szTitle[] = "These are the dimensions of your client area:";
    HFONT hfOld = SelectObject(hdc, hf);

    SetBkColor(hdc, g_rgbBackground);
    SetTextColor(hdc, g_rgbText);

    if(g_bOpaque)
    {
        SetBkMode(hdc, OPAQUE);
    }
    else
    {
        SetBkMode(hdc, TRANSPARENT);
    }

    DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);

    wsprintf(szSize, "{%d, %d, %d, %d}", prc->left, prc->top, prc->right, prc->bottom);
    DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

    SelectObject(hdc, hfOld);

Első dolog, hogy a SelectObject() használatával vesszük a fontot amit használni fogunk, a rajzolásra kész HDC-hez. Minden jövőbeni szöveggel kapcsolatos művelet ugyanezt a fontot fogja hasznélni, kivéve persze ha megváltoztatjuk.

Ezután beállítjuk a szöveg és háttérszínt. A háttérszín beállítása nem jelenti azt, hogy minden szöveghez látszódni is fog. Ez csak bizonyos műveleteknél lesz így. Ez függ az aktuális háttérszín módtól is (Background Mode). Ha ez OPAQUE (alapból ez van), akkor a rajzolt szöveg ki lesz töltve a háttérszínnel. Ha viszont ez a beállítás TRANSPARENT, akkor a szöveg háttérszín nélkül lesz megrajzolva, tehát ekkor a háttérszín opció hatástalan.

Most megrajzoljuk a szöveget a DrawText() használatával, átadjuk a használni kívánt HDC-t, és a sztringet. A harmadik paraméter a sztring hossza, de ez lehet -1, mert a DrawText() elég okos hozzá hogy tudja ezt magától is. A negyedik paraméter a prc, ez egy mutató az ügyfél RECT-re. A DrawText() ezen a téglalapon belül rajzol az általunk megadott további flag-ek alapján.

Első híváskor megadtunk egy DT_WORDBREAK paramétert, ez a bal felső sarokból indulva úgy igazítja a sortörést, hogy a téglalaphoz illeszkedjen. A második hívásnál sortörés nélkül nyomtatjuk a sort, függőlegesen és vízszintesen is középre igazítva.

Ügyfél újrarajzolás

 

Megjegyzés a példaprogramhoz: A WNDCLASS regisztrálásakor beállítottuk a CS_VREDRAW, és CS_HREDRAW osztály stílusokat. Ezzel azt kértük, hogy az ablak átméretezésekor mindent rajzoljon újra, ne csak a megváltozott dolgokat. Elég rondán nézne ki, ha átméretezéskor a szöveget nem tördelné újra.

Fontok váltása

 

Általában minden program ami lehetővé teszi a felhasználónak a font változtatását, felkínálja a betű méretének és színének a változtatását is. Ahogyan a fájlnév kiválasztását is megkönnyíti a Megnyitás/Mentés párbeszéd ablak, ugyanígy van font kiválasztó is. Ez bármily meglepő a ChooseFont() lesz, ami a CHOOSEFONT struktúrával működik. Az alapértelmezett beállításokat kínálja fel, és a felhasználó választásával tér vissza.

HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);
COLORREF g_rgbText = RGB(0, 0, 0);
void DoSelectFont(HWND hwnd)
{
    CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
    LOGFONT lf;

    GetObject(g_hfFont, sizeof(LOGFONT), &lf);

    cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
    cf.hwndOwner = hwnd;
    cf.lpLogFont = &lf;
    cf.rgbColors = g_rgbText;

    if(ChooseFont(&cf))
    {
        HFONT hf = CreateFontIndirect(&lf);
        if(hf)
        {
            g_hfFont = hf;
        }
        else
        {
            MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
        }

        g_rgbText = cf.rgbColors;
    }
}

Ebben a hívásban a hwnd egyszerűen a a hívó ablak handlere, ami a font kiválasztó párbeszéd ablakot hívja. A legegyszerűbb ha az aktuális HFONT LOGFONTstruktúráját kapja meg. Beállítjuk a LOGFONTstruktúra címére az lpLogFont mezőt, valamint a CF_INITTOLOGFONTSTRUCT flaget bekapcsoljuk. Így már tudja a ChooseFont(), hogy a meglévő beállításokkal induljon. A CF_EFFECTS jelzi, hogy a felhasználó szabadon választhat színt, valamint választhatja az aláhúzott, áthúzott opciókat is. Érdekes módon a félkövér és dőlt beállítások nem számítanak effektnek, az a betűtípus sajátja. Ha el akarjuk kerülni hogy ezeket kiválaszthassa, akkor figyelnünk kell a választását, és kézzel visszaírjuk az lfWeight és lfItalic mezőket.

A font színe nem a HFONT része, azt külön kell tárolnunk a CHOOSEFONT rgbColors mezőjében. Ez tárolja a színt a kiválasztás előtt, és után is. A CF_SCREENFONTS azt jelzi, hogy ezt a fontot képernyőre tervezték, szemben azokkal, amiket például nyomtatóra. Van aki mindkettőt támogatja, van aki csak az egyiket. A használható flag-ek listáját megtalálhatjuk az MSDN-en.

Szín választás

 

Annak érdekében hogy a felhasználó kiválaszthasson egy színt, rendelkezésünkre áll a ChooseColor() párbeszéd ablak. Ebben a példában a felhasználó kiválaszthatja a háttér színét.

COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};
void DoSelectColour(HWND hwnd)
{
    CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};

    cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
    cc.hwndOwner = hwnd;
    cc.rgbResult = g_rgbBackground;
    cc.lpCustColors = g_rgbCustom;

    if(ChooseColor(&cc))
    {
        g_rgbBackground = cc.rgbResult;
    }
}

Ez meglehetősen egyszerű. Megint a hwnd-t használjuk, ez jelzi a párbeszéd ablak szülőjét (a mi ablakunkat).

Todo: Valaki?

The CC_RGBINIT parameter says to start off with the colour we pass in through the rgbResult member, which is also where we get the colour the user selected when the dialog closes.

A g_rgbCustom 16 elemű (COLORREF típusú) tömb tárolja a felhasználó által kiválasztott színeket. Ezeket el lehet tárolni mondjuk a rendszerleíró adatbázisban, különben a program bezárásakor elvesznek. Ez a paraméter kötelező.

Vezérlő fontok

 

 TODO:

Something else you might want to do at some point is change the font on the controls on your dialog or window. This is usually the case when using CreateWindow() to create controls as we've done in previous examples. Controls like windows use System by default, so we used WM_SETFONT to set a new font handle (from GetStockObject()) for the control to use. You can use this method with fonts you create from CreateFont() as well. Simply pass the font handle as wParam and set lParam to TRUE to make the control redraw.

I've done this in previous examples, but it makes sense to mention it here because it's relevant and very short:

    SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT, (WPARAM)hfFont, TRUE);

Where hfFont is of course the HFONT you want to use, and IDC_OF_YOUR_CONTROL is the ID of whichever control you want to change the font of.

Példa: anim_one

A beállítás

 

Mielőtt nagyon nekiállnánk animálni, szükségünk lesz egy struktúrára, amiben a labda két frissítés közötti pozícióit tároljuk. Ez a struktúra tárolja a labda pozícióján túl a méretét, a delta értéket, és hogy mennyire mozogjanak a képkockák.

Ha megvan a struktúra típusa, akkor ebből egy globális példányt hozunk létre. Ez így csak egyetlen labdához alkalmas, de ha több kell, akkor valami tömbbe vagy listába kell szervezni.

const int BALL_MOVE_DELTA = 2;

typedef struct _BALLINFO 
{
    int width;
    int height;
    int x;
    int y;

    int dx;
    int dy;
}BALLINFO;

BALLINFO g_ballInfo;

Létrehoztunk egy BALL_MOVE_DELTA nevű konstanst. Ez mutatja, hogy két képkocka között mennyit mozduljon el a labda. A delta értékeket azért tettük a struktúrába, mert így külön külön be tudjuk állítani vízszintesen és függőlegesen is őket. A BALL_MOVE_DELTA csak arra szolgál, hogy egységesen tudjuk megváltoztatni a deltákat.

Most betöltjük a képet, és inicializáljuk a struktúrát.

    BITMAP bm;
    GetObject(g_hbmBall, sizeof(bm), &bm);

    ZeroMemory(&g_ballInfo, sizeof(g_ballInfo));
    g_ballInfo.width = bm.bmWidth;
    g_ballInfo.height = bm.bmHeight;

    g_ballInfo.dx = BALL_MOVE_DELTA;
    g_ballInfo.dy = BALL_MOVE_DELTA;

A labda a bal felső sarokból indul, és jobbra balra, fel le mozog a struktúra dx és dy mezői szerint.

Időzítő beállítása

 

Ahhoz hogy egy egyszerű időzítőt adjunk az ablakunkhoz, legegyszerűbb a SetTimer() használata. Ez nagyon gyenge, játékhoz és komolyabb dolgokhoz már nem alkalmas, de ide jó lesz. Ha komolyabb kell, akkor nézzük meg a timeSetEvent()-et az MSDN-en.

const int ID_TIMER = 1;
    ret = SetTimer(hwnd, ID_TIMER, 50, NULL);
    if(ret == 0)
        MessageBox(hwnd, "Could not SetTimer()!", "Error", MB_OK | MB_ICONEXCLAMATION);

Deklaráltuk az ID_TIMER azonosítót hogy később tudjunk rá hivatkozni (a leöléséhez), és a fő ablak WM_CREATE handlerében beállítjuk az időzítőt. Minden alkalommal amikor letelik a beállított idő, küld egy WM_TIMER üzenetet az ablaknak, és wParam-ban átadja az ID-t. Erre az azonosítóra most nincs szükségünk, de ha több időzítőt használnánk, akkor meg tudnánk különböztetni őket egymástól. Most ez az idő 50ms-onként telik le, tehát kb 20 képkockát jelent másodpercenként. Csak kb, mert a SetTimer() pontatlan. Ide viszont teljesen jó, pár ms ide vagy oda annyira mindegy.

 Animálás a WM_TIMER-ben

 

A WM_TIMER elkapásakor kiszámolhatjuk a labda új pozícióját, és frissítjük a képernyőn.

case WM_TIMER:
    {
        RECT rcClient;
        HDC hdc = GetDC(hwnd);

        GetClientRect(hwnd, &rcClient);

        UpdateBall(&rcClient);
        DrawBall(hdc, &rcClient);

        ReleaseDC(hwnd, hdc);
    }
    break;

Itt már szerepel az UpdateBall(), és DrawBall() is. Mindkettő a labda kezeléséhez egy-egy saját rutin. Érdemes így csinálni, mert így felhasználható akár másik timerben is anélkül hogy a kód újból szerepelne. Vegyük figyelembe, hogy a HDC minden esetben más és más, ezért érdemes ezt a kódot az üzenetkezelőben hagyni, és csak az eredményt átadni a DrawBall() eljárásnak.

void UpdateBall(RECT* prc)
{
    g_ballInfo.x += g_ballInfo.dx;
    g_ballInfo.y += g_ballInfo.dy;

    if(g_ballInfo.x < 0)
    {
        g_ballInfo.x = 0;
        g_ballInfo.dx = BALL_MOVE_DELTA;
    }
    else if(g_ballInfo.x + g_ballInfo.width > prc->right)
    {
        g_ballInfo.x = prc->right - g_ballInfo.width;
        g_ballInfo.dx = -BALL_MOVE_DELTA;
    }

    if(g_ballInfo.y < 0)
    {
        g_ballInfo.y = 0;
        g_ballInfo.dy = BALL_MOVE_DELTA;
    }
    else if(g_ballInfo.y + g_ballInfo.height > prc->bottom)
    {
        g_ballInfo.y = prc->bottom - g_ballInfo.height;
        g_ballInfo.dy = -BALL_MOVE_DELTA;
    }
}

Egy kis alap matek. A delta értékkel mozgatjuk a labda pozícióját. Ha eléri az ügyfélterület határát, akkor úgy módosítjuk a deltákat, hogy az megfeleljen a labda ütközésének, és most már visszafelé mozogjon.

void DrawBall(HDC hdc, RECT* prc)
{
    HDC hdcBuffer = CreateCompatibleDC(hdc);
    HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
    HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

    HDC hdcMem = CreateCompatibleDC(hdc);
    HBITMAP hbmOld = SelectObject(hdcMem, g_hbmMask);

    FillRect(hdcBuffer, prc, GetStockObject(WHITE_BRUSH));

    BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCPAINT);

    BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

    SelectObject(hdcMem, hbmOld);
    DeleteDC(hdcMem);

    SelectObject(hdcBuffer, hbmOldBuffer);
    DeleteDC(hdcBuffer);
    DeleteObject(hbmBuffer);
}

Ez lényegében ugyanaz, mint az előző példákban volt. Azzal a különbséggel, hogy a pozíciókat a BALLINFO struktúrából vesszük. Van azonban egy tényleges különbség..

A kettős pufferelés

 

Ha közvetlenül az ablak HDC-jére rajzolunk, akkor könnyen előfordulhat az az eset, hogy még nem fejeztük be a rajzolást, amikor a képernyőn már megjelenik az eredmény. Például éppen a maszkot rajzoljuk a színes kép előtt. Ennek az lesz az eredménye, hogy nem csak a végeredményt látjuk, hanem az egyes részfázisokat is, és ezek villognak, váltogatják egymást. Egy bonyolultabb képnél, vagy lassabb számítógépnél ez már olyan zavaró lehet, hogy szétesik a kép.

Ez borzasztóan zavaró dolog, de szerencsére könnyen orvosolható. Nem a képernyőre rajzolunk hanem a memóriába, majd amikor már készen vagyunk, akkor az egész mesterművel egyben átmásoljuk a képernyőre a BitBlt() utasítással. Így a képernyő frissülésekor a kész régi képet látjuk, majd pedig a kész újat. Tökéletes!

Ezért ideiglenesen létrehozunk egy HBITMAP-ot a memóriában, aminek a mérete pontosan megegyezik a képernyővel. 

    HDC hdcBuffer = CreateCompatibleDC(hdc);
    HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
    HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

 Most már tudunk hova rajzolni a memóriában. Minden rajzolási műveletet a hdcBuffer-be végzünk (és nem az ablak HDC-jébe). Az eredményt egészen addig itt tároljuk, amíg teljesen készen nincs. Ekkor egyetlen lendülettel átmásoljuk az ablakra.

 BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

És ennyi. Ezután kitakarítjuk a használt HDC-ket, és HBITMAP-eket.

A gyorsabb kettős pufferelés

 

Ebben a példában minden képkockánál létrehozzuk és megsemmisítjük a kettős pufferhez használt bitmap-et, egyszerűen azért, hogy nyomon tudjuk követni, el ne felejtődjön, stb. Az ablakot is átméretezhetjük, mert új puffer létrehozásakor automatikusan követi a méretét.

Ellenben sokkal hatékonyabb, ha egyszer globálisan létrehozzuk ezt a bitmap-et, és nem engedjük az ablak átméretezését. Vagy ha engedjük, akkor a bitmap-et is átméretezzük. Így viszont nem kell minden kockánál újra létrehozni és megsemmisíteni. Ez rajtunk múlik, hogy melyik módszert követjük. Nyilván egy játék fejlesztésekor érdemes az utóbbi utat járni.

 

Timer leölése

 

Amikor az ablak megsemmisül, akkor érdemes az összes használt erőforrást felszabadítani. Ebbe beletartozik a timer is. Ahhoz hogy leállítsuk, csak hívjuk meg a KillTimer() eljárást, és adjuk át neki a timer ID-jét.

 KillTimer(hwnd, ID_TIMER);

 

Példa: bmp_two

Átlátszó bitképek[images/bmp_two.gif]

 

Elég egyszerűen tudunk a képek kinézetének átlátszóságot adni. Ehhez a következő feltételek szükségesek: Először is a színes képen minden átlátszónak szánt képpont legyen fekete. Továbbá a maszk képen mindenhol ahol átlátszóságot szeretnénk, a szín legyen fehér, egyébként fekete. A szín és maszk képeket mutatja be a példán látható kép bal oldala.

A BitBlt() műveletek

 

Hogyan működik az átlátszóság? Kell a BitBlt(), aminek átadjuk egyszer a maszk képet SRCAND művelettel, majd a színes képet SRCPAINT művelettel. Ahol átlászóságot akartunk, akkor nem változik a HDC, ahol viszont nem, ott meg rendesen megrajzolta a képpontokat.

    SelectObject(hdcMem, g_hbmMask);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);

Egyszerűnek tűnik? Szerencsére igen. Azért egy kérdés így is marad. Honnan származik a maszk? Erre van több módszer is. Az egyik, ha magunk elkészítjük egy rajzoló programmal. Ez akkor ajánlatos, ha kis számú képet használunk, mert csak felvesszük erőforrásnak, és a LoadBitmap()-el betöltjük.

Azt is lehet, hogy kiválasztunk futás közben egy színt a képen, ezt nevezzük ki "átlátszó színnek", és generálunk hozzá egy maszkot, hogy ahol ez a szín szerepelt, ott fehér, mindenhol máshol meg fekete legyen.

Ha az első módot választjuk, akkor ennyi, végeztünk. Ha viszont a másodikat, akkor most még jön hozzá pár dolog.

Maszk létrehozása

 

A legegyszerűbb az lenne, ha a kép minden képpontján végigmennénk, és az aktuális szín alapján állítanánk be a maszkot fehérre, vagy feketére. Ez jól hangzik, de a SetPixel() nagyon lassú, így nem választhatjuk ezt az utat. Kicsit jobbak vagyunk, ha a BitBlt() használatával átkonvertáljuk a színes képet fekete-fehérré.

TODO: Ez nagyjából megvan, de elég kusza:

If you BitBlt() (using SRCCOPY) from an HDC holding a colour image into an HDC holding a black and white image, it will check what colour is set as the Background Colour on the colour image, and set all of those pixels to White, any pixel that is not the background colour will end up Black.

Ez teljesen jól működik egészen addig, amíg a maszk egy bites fekete/fehér színű. Ha viszont a bitmélység több mint egy bit (pl. 16, 24 bit), akkor már nem működik.

Emlékszünk még a sikeres maszkolás első feltételére? Úgy szólt, hogy a színes képen legyen minden fekete, ahol átlátszóságot akarunk. A példánkban ez már teljesült, ezért nincs több tennivalónk. De ha másik képet használnánk másik átlátszósági színnel (megegyezés szerint az élénk pink szokott ez a szín lenni), akkor szükségessé válik a második lépés, amikor az átlátszósági színt megváltoztatjuk feketére.

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
    HDC hdcMem, hdcMem2;
    HBITMAP hbmMask;
    BITMAP bm;

    // Create monochrome (1 bit) mask bitmap.  

    GetObject(hbmColour, sizeof(BITMAP), &bm);
    hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

    // Get some HDCs that are compatible with the display driver

    hdcMem = CreateCompatibleDC(0);
    hdcMem2 = CreateCompatibleDC(0);

    SelectBitmap(hdcMem, hbmColour);
    SelectBitmap(hdcMem2, hbmMask);

    // Set the background colour of the colour image to the colour
    // you want to be transparent.
    SetBkColor(hdcMem, crTransparent);

    // Copy the bits from the colour image to the B+W mask... everything
    // with the background colour ends up white while everythig else ends up
    // black...Just what we wanted.

    BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    // Take our new mask and use it to turn the transparent colour in our
    // original colour image to black so the transparency effect will
    // work right.
    BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

    // Clean up.

    DeleteDC(hdcMem);
    DeleteDC(hdcMem2);

    return hbmMask;
}

Megjegyzés: Ez a függvény a SelectObject()-et hívja meg, hogy átmenetileg kiválassza a színes képet, és átadja azt a HDC-nek. Egy bitképet nem használhat több HDC egyszerre, ezért győződjünk meg róla, hogy a függvény hívásakor éppen nem használja másik HDC! Ellenkező esetben a hívás sikertelen lesz. Most már létrehozhatjuk a maszkot az eredeti képből:

 case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);

        g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));
        if(g_hbmMask == NULL)
            MessageBox(hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;

A második paraméter természetesen az eredeti képben átlátszóságra jelölt szín. Jelenleg a fekete.

Hogyan működik ez az egész?

 

Remélhetőleg már kenjük-vágjuk a C, C++ -t, tehát értjük mi az az OR, XOR, AND és NOT ésatöbbi bináris műveletek. Ugyanis nem rágjuk bele magunkat ebbe, de megnézünk egy példát, hogy hogyan használjuk. Ha végül mégsem tiszta az ügy, akkor olvassunk utána a bináris műveleteknek! Persze nem feltétlenül kell megérteni, mert attól még működik!

SRCAND

 

Az SRCAND raszter művelet vagy ROP kód a BitBlt()-hez a biteket ÉS (AND) kapcsolatba hozza egymással.Tehát csak azok a bitek lesznek 1-be állítva, ahol a forrás és a cél is 1 volt. Ahol a maszk fekete volt (ez a nulla bitnek felel meg) ott az eredmény fekete lesz, és ahol a maszk fehér (1-es bit) ott pedig átlátszó lesz. A 0 értéket bármivel ÉS-eljük, az eredmény 0 (fekete) lesz. Az 1 (fehér) értéket maszknak használva az eredmény átlátszó, mert visszakapjuk az eredeti színt. (Tudom, az "ÉS-elés" nagyon furán hangzik :)) Az ÉS művelet ereményét látjuk a példa kép jobb felső negyedében.

SRCPAINT

 

Az SRCPAINT gyakorlatilag a VAGY (OR) műveletnek felel meg. Ha az egyik (vagy mindkét) bit értéke beállított (beállított, tehát 1-es), akkor az eredmény bit is 1-es lesz. Ha az átlátszó részt (fekete) kombináljuk a színes képpel, akkor az eredmény az érintetlen színes kép lesz. Ugyanakkor, ha nem fekete résszel kombináljuk a színes képet, akkor az eredmény egy olyan színes valami lesz, ahol a színek a forrás és cél színek kombinációja lesz. Erre a jelenségre mutat példát a jobb alsó példakép. Igazából ez az egész maszkolásos játék erre megy ki. Előbb feketére cseréljük az alap színt, és erre tesszük a színeset. Így nem keveredik össze a színes kép az alatta lévő réteg színeivel.

SRCINVERT

 

Ez a KIZÁRÓ VAGY (XOR) műveletet jelenti. Ezzel beállíthatjuk az eredeti átlátszó színt feketére (ha még nem volt az). Ahol fekete volt a maszk és nem háttér színű a pixel, ott érintetlenül hagyja, míg a maszk fehér színét a háttérszínnel feketére festi.

Ezzel az egész GDI színezéssel túl sokat ne foglalkozzunk, mert csak fejfájást okoz!

 

Példa: bmp_one

GDI

 

A windóz valódi nagy dobása a DOS-hoz képest, hogy nem kell ismernünk a hardvert ami a grafikáért felelős. Ehelyett egy grafikus eszköz illesztő felületet (Graphics Device Interface - GDI) biztosít számunkra. A GDI olyan általános grafikai készletet használ, amivel rajzolhatunk a képernyőre, a memóriába, vagy akár a nyomtatóra.

Eszköz környezetek

 

Van egy úgynevezett eszköz környezet (device context - DC), amit a HDC (Handle of Device Context) adattípus képvisel számunkra. Ez egy olyan kezelő, ami a képernyőt szimbolizálja, vagy az ablakot, a memóriát, egy képet, vagy éppen a nyomtatót. A szép az egészben az, hogy nem is kell érdekeljen hogy ténylegesen melyik, hiszen ugyanúgy kell kezelni mindet.

Ha például az ablakra akarunk rajzolni, akkor fogjuk a HDC-t amit a GetDC() szolgáltat (ez képviseli az ablakot), és ezzel a HDC-vel használhatjuk a GDI bármelyik funkcióját. Kitehetünk képet ( BitBlt() ),  írhatunk szöveget ( TextOut() ), húzhatunk vonalat ( LineTo() ) ésatöbbiésatöbbi.

Bitképek

 

A bitképeket ugyanúgy tölthetjük be mint a korábbi példákban az ikonokat, erre a LoadBitmap() szolgál. Ez egyszerűen betölti a bitkép erőforrást. Használhatjuk a LoadImage() hívást is, egy képfájlokhoz van (*.bmp).

Most jön a csavar!

A bitkép objektumokat (HBITMAP típus) nem tudjuk közvetlenül rajzoltatni! Ahhoz hogy a rajzoló funkciókat a bitképen használni tudjuk, az kell hogy létrehozzunk egy Memory DC-t, majd a SelectObject()-tel kiválasztni a HBITMAP-ot. TODO: Ezt most nem értem.

Ennek az lesz a hatása, hogy ha valamit ügyködünk a HDC eszközön, akkor az megjelenik a bitképen is.

GDI szivárgás

 

Van egy kis gond a HDC-kkel. A számuk korlátozott. Ez főképpen a Windows95 előtti verzióknál volt nagyon durva, mert a programunk megosztva használja a rendszerrel. Akár működésképtelenné is teheti azt! Szerencsére a Windows2000 és XP óta ez már nem játszik, mert ha gáz van, akkor felszabadítják maguknak amit kell. NT alapú rendszereknél már elméletileg nem tudjuk lenyúlni a rendszer erőforrásait. Kivéve ha hiba csúszik a gépezetbe. Ha egy ideig jól működik a programunk, aztán egyszercsak elkezd furcsaágokat művelni rajzolás közben, akkor ez utalhat arra, hogy GDI szivárgás esete áll fenn.

Itt kifejtjük hogy vissza kell hozni a már használt GDI erőforrást hogy aztán felszabadíthassuk, ellenkező esetben az újból és újból foglalás miatt kifogyhatunk a GDI erőforrásokból. Volt is gond ebből egy képernyővédőnél.

Bitképek megjelenítése

 

A rajzoló műveleteket a WM_PAINT végzi el. Amikor az ablakunk először jelenik meg, vagy lecsukott állapotból újra kinagyítjuk, akkor a WM_PAINT üzenet indul útjára, hogy az ablak tartalmát újra kell rajzolni. Amikor valamit kirajzoltunk, akkor az nem marad úgy! Minden alkalommal amikor kérés érkezik, sajnos újra kell rajzolni!

 HBITMAP g_hbmBall = NULL;

    case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;

 Első lépés természetesen a bitkép betöltése. Ez ugyanúgy megy mint erőforrás esetén, gyerünk tovább!

   case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(hwnd, &ps);

        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);

        GetObject(g_hbmBall, sizeof(bm), &bm);

        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
    }
    break;

Felvezetjük a használni kívánt változóinkat. Figyeljük meg, hogy az első típusa BITMAP, és nem HBITMAP!  A BITMAP egy olyan struktúra, ami információt tartalmaz az aktuális GDI objektum HBITMAP-járól. Szükséges megállapítnunk a HBITMAP szélességét és magasságát, erre a GetObject()-et használjuk. Ez nem magát az objektumot adja vissza, csak infókat róla. A helyes név a GetObjectInfo() lett volna. Ezt kaptuk, használjuk. A GetObject() különböző GDI aobjektumokkal együtt tud működni, ami a második paraméter alapján képes a struktúra méretét megállapítani.

A PAINTSTRUCT egy olyan struktúra, ami az ablak festéséről tartalmaz információkat. Most egyszerűen nem foglalkozunk vele, de a BeginPaint(), és EndPaint() működéséhez szükséges lesz. A BeginPaint() ahogy a neve is jelzi, a WM_PAINT üzenetet kezeli. Ha nem kezeljük a WM_PAINT üzenetet, akkor a GetDC()-re lesz szükségünk, ahogyan azt az időzítős animációs példánál látni fogjuk. Addig viszont használjuk a BeginPaint(), és EndPaint() párost!

A BeginPaint() visszatér a HDC-vel, ami a HWND-t képviseli. Minden rajzolási művelet amit a HDC-vel végzünk, azonnal megjelenik a képernyőn.

A Memória DC bitképre állítása

 

Annak érdekében hogy rajzolhassunk a bitképre, szükséges egy DC-t létrehozni a memóriában. A könnyebb út a CreateCompatibleDC(). Kapunk egy Memory DC-t, ami átveszi a képernyő színmélységét és beállításait. Most meghívjuk a SelectObject()-et hogy kiválasszuk a bitképet, ezzel megkapjuk az alap képet, amit később kicserélhetünk. Ezzel elkerülhetjük a GDI szivárgást.

Rajzolás

 

Ahogy megvannak a bitkép méretei, kitöltjük a BITMAP struktúrát. Meghívjuk a BitBlt()-t, ezzel átmásoljuk a képet a memória DC-ből az ablak DC-jébe, amivel megjelenítjük a képernyőn. Mint mindig, ennek is utána nézhetünk az MSDN-en, de röviden itt is összefoglaljuk a lényeget: A cél, a pozíció, a méret, a forrás, a forrás pozíciója, és végül a "raszter művelet" (ROP code) határozzák meg, hogyan történjen a másolás. Most egyszerűen átmásoljuk és kész.

Takarítás

 

Ezen a ponton már kint lehet kép a képernyőn, ki kell takarítnunk magunk után. Először a Memory DC-t visszaállítjuk az eredeti állapotába, majd kicseréljük a bitmapunkat az egyik tárolt változatra. Ezután a DeleteDC()-vel végleg törölhetjük. Az EndPaint()-tel eldobjuk a Window DC-t, amit a BegintPaint() adott.

A HDC megsemmisítése kissé problémás, mert a létrehozásától függően három féleképpen is megtehetjük. Lássuk a párosokat:

GetDC() - ReleaseDC()
BeginPaint() - EndPaint()
CreateCompatibleDC() - DeleteDC()

Végezetül, amikor a programunk véget ér, felszabadítjuk a lefoglalt erőforrásokat. Elvileg ez nem feltétlenül szükséges, hiszen a modern windózok már megteszik ezt maguktól is. Elvileg. Gyakorlatilag azonban elképzelhető hogy vannak még hibák a GDI megvalósításában, szabadítsuk csak fel!

    case WM_DESTROY:
        DeleteObject(g_hbmBall);
        PostQuitMessage(0);
    break;

 

Példa: app_four

MDI áttekintés

 

Először egy kicsit a háttérről.. Minden ablak rendelkezik ügyfélterülettel (Client Area). Ez az a terület, ahol a legtöbb program képeket rajzol, vezérlőket helyez el. Az ügyfélterület nem különül el az ablaktól, hanem egy kissebb speciális régiója. Néhány ablak csak ebből áll, mások pedig kissebbek, hogy legyen helye a menünek, görgetősávnak, stb.

 Az MDI felől nézve a fő ablakunk egy keret, aminek a gyermek ablaka az MDI. Ez az MDI egy teljesen különálló ablak, saját ügyfélterülettel, saját vezérlőkkel és talán saját pár pixeles kerettel. Az MDI kliensnek nem küldhetünk közvetlenül üzeneteket, ellenben előre definiált ablak osztály (az MDICLIENT) segítségével igen. Amikor megjelenik a programunk, akkor egy üzenetet küldünk, ami által létrejön az új általunk megadott ablak. Az új ablak az MDI kliens gyermeke lesz, ami viszont a keret ablak gyermeke. Továbbá lehetséges az MDI kliensnek is saját gyermek ablaka, mint például a szerkesztő vezérlőnk ebben a példában.

Írnunk kell kettő (vagy több) Window eljárást, egyet a fő ablaknak (frame), másikat pedig az MDI gyermek részére. Most tehát egynél több típusú gyermekünk van, így minden egyes típusra külön külön meg kell írnuk.

Ha még nem tiszta, akkor az alábbi kép majd gondoskodik a tisztaságról.

 Ismerkedés az MDI-vel

 

Mivel az MDI kezeléséhez az egész programra kiható apró változtatások szükségesek, olvassuk el figyelmesen a következőket! Ha nem működik az MDI programunk, vagy furcsa, ne várt dolgokat produkál, akkor elképzelhető hogy az egyik ilyen módosítást nem tettük meg.

MDI ügyfélablak

Mielőtt létrehoznánk MDI ablakunkat, meg kell változtassuk az alap üzenetkezelőt, hogy a mi saját ablakkezelőnk dolgozzon. Mi magunk hozzuk létre az MDI ügyfelet a fő ablakban. Meg kell változtatni a DefWindowProc hívását DefFrameProc-ra. Ez képes kezelni a speciális üzeneteket, amik az MDI működéséhez szükségesek.

default:
        return DefFrameProc(hwnd, g_hMDIClient, msg, wParam, lParam);

Következő lépés az MDI kliens ablak létrehozása a főablakban, ezt mint mindig a WM_CREATE-tel tesszük meg.

    CLIENTCREATESTRUCT ccs;

    ccs.hWindowMenu  = GetSubMenu(GetMenu(hwnd), 2);
    ccs.idFirstChild = ID_MDI_FIRSTCHILD;

    g_hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "mdiclient", NULL,
        WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        hwnd, (HMENU)IDC_MAIN_MDI, GetModuleHandle(NULL), (LPVOID)&ccs);

Ezt meg valaki fordítsa le nekem, mert éppen átmegy kínaiba!

The menu handle is the handle to the popup menu that the MDI client will add items to representing each window that is created, allowing the user to select the window they want to activate from the menu, we'll add functionality shortly to handle this case. In this example it's the 3rd popup (index 2) since I've added Edit and Window to the menu after File.

 A ccs.idFirstChild egy szám, amit első ID-nek használunk az ablak menü kliens elemeihez. Ez egy olyan szám legyen, ami eléggé elkülönül az általunk használt ID-ktől. Legyen például 50.000 az értéke! Ekkora nagyságrendben már biztosan nem használunk ID-ket.

A megfelelő működéshez ki kell egészítsük a WM_COMMAND kezelőt.

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case ID_FILE_EXIT:
                PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;

            // ... handle other regular IDs ...

            // Handle MDI Window commands
            default:
            {
                if(LOWORD(wParam) >= ID_MDI_FIRSTCHILD)
                {
                    DefFrameProc(hwnd, g_hMDIClient, msg, wParam, lParam);
                }
                else 
                {
                    HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
                    if(hChild)
                    {
                        SendMessage(hChild, WM_COMMAND, wParam, lParam);
                    }
                }
            }
        }
    break;

 Hozzáadtunk egy default opciót. Ha üzenetet fogunk, akkor megnézzük hogy az kissebb-e mint ID_MDI_FIRSTCHILD. Mert ha igen, akkor a fő ablak kapja meg feldolgozásra ( DefFrameProc() ). Ha viszont nem, akkor a fő ablak gyermek ablaka kapja meg. Ez lehetővé teszi, hogy a gyermek ablakok akár teljesen különbözőképpen kezeljék az egyes parancsokat. A példában csak azokat az általános parancsokat dolgozzuk fel, amik a szülő ablaknak is részei.

Jön az üzenethurok módosítása

 while(GetMessage(&Msg, NULL, 0, 0))
    {
        if (!TranslateMDISysAccel(g_hMDIClient, &Msg))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    }

 Hozzáadtunk egy plusz lépést ( TranslateMDISysAccel() ), ez ellenőrzi az előre definiált gyors-billentyűket. Ctrl+F6 vált a következő ablakra, Ctrl+F4 bezárja az aktuális gyermek ablakot, stb. Ha ezt az ellenőrzést nem tennénk meg, akkor a felhasználó nem lenne képes használni a megszokott viselkedést, így azt nekünk magunknak kellene megvalósítanunk.

 Gyermek ablak osztály

 

Amellett hogy van egy fő ablakunk (ami a szülő, vagy keret ablak), minden gyermek ablaknál szükséges megmondanunk annak típusát. Van szerkesztő-, kép-, stb ablaktípus. Ebben a példában csak egy féle, a szerkesztő típust használjuk.

BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance)
{
    WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = MDIChildWndProc;
    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_3DFACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szChildClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(0, "Could Not Register Child Window", "Oh Oh...",
            MB_ICONEXCLAMATION | MB_OK);
        return FALSE;
    }
    else
        return TRUE;
}

 Ez lényegében megegyezik a hagyományos fő ablak regisztrálásával, nincsenek különleges flag-ek. A menü be van állítva NULL-ra, és a window eljárás a gyermek ablakra mutat, amit mindjárt megírunk.

MDI gyermek eljárás

Pár kivételtől eltekintve az MDI gyermek window eljárása megegyezik bármi máséval. A default üzenetet a DefMDIChildProc() kapja meg a DefWindowProc() helyett.

Szeretnénk lehetőséget tenni arra, hogy a menüben az Edit, és a Window menüpontok inaktívvá válhassanak, ha nincs rájuk szükség. Egyszerűen mert az jó dolog. Ezért használni fogunk egy WM_MDIACTIVATE parancsot, és ezzel engedjük vagy tiltjuk attól függően, hogy az ablak aktív, vagy nem. Ha különböző típusú ablakot használunk, akkor ezen a ponton avatkozhatunk be a működésbe olyanképp, hogy az egyes ablakok a tőlük elvárt módon viselkedjenek. A teljesség kedvéért tiltjuk a Close és Save as menüpontokat is.

LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CREATE:
        {
            HFONT hfDefault;
            HWND hEdit;

            // Create Edit Control

            hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", 
                WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 
                0, 0, 100, 100, hwnd, (HMENU)IDC_CHILD_EDIT, GetModuleHandle(NULL), NULL);
            if(hEdit == NULL)
                MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

            hfDefault = GetStockObject(DEFAULT_GUI_FONT);
            SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
        }
        break;
        case WM_MDIACTIVATE:
        {
            HMENU hMenu, hFileMenu;
            UINT EnableFlag;

            hMenu = GetMenu(g_hMainWindow);
            if(hwnd == (HWND)lParam)
            {      //being activated, enable the menus
                EnableFlag = MF_ENABLED;
            }
            else
            {                          //being de-activated, gray the menus
                EnableFlag = MF_GRAYED;
            }

            EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
            EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);

            hFileMenu = GetSubMenu(hMenu, 0);
            EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | EnableFlag);

            EnableMenuItem(hFileMenu, ID_FILE_CLOSE, MF_BYCOMMAND | EnableFlag);
            EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | EnableFlag);

            DrawMenuBar(g_hMainWindow);
        }
        break;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_FILE_OPEN:
                    DoFileOpen(hwnd);
                break;
                case ID_FILE_SAVEAS:
                    DoFileSave(hwnd);
                break;
                case ID_EDIT_CUT:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0, 0);
                break;
                case ID_EDIT_COPY:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0, 0);
                break;
                case ID_EDIT_PASTE:
                    SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0, 0);
                break;
            }
        break;
        case WM_SIZE:
        {
            HWND hEdit;
            RECT rcClient;

            // Calculate remaining height and size edit

            GetClientRect(hwnd, &rcClient);

            hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
            SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
        }
        return DefMDIChildProc(hwnd, msg, wParam, lParam);
        default:
            return DefMDIChildProc(hwnd, msg, wParam, lParam);
    
    }
    return 0;
}

Beépítettük a File Open és Save as parancsokat. A DoFileOpen(), DoFileSave() közel ugyanaz mint a korábbi példában volt, megváltozott a szerkesztő vezérlő ID-je, és a gyermek ablak fejléce módosult a fájlnévre. A szerkesztési parancsok könnyűek, mert a szerkesztő vezérlő alapból tud mindent.

Most nézzük azt a bizonyos furcsa viselkedést! A WM_SIZE parancs végén a DefMDIChildProc() hívása nagyon ajánlott, különben a rendszernek nincs esélye elkapni a saját üzeneteit. Nézzük meg az MSDN-en a DefMDIChildProc() üzeneteit, és adjuk oda neki amit kér!

 Ablak létrehozása, és bezárása

 

Az MDI gyermek ablakokat nem közvetlenül hozzuk létre, hanem küldünk egy WM_MDICREATE üzenetet a kilens ablaknak. Azt, hogy milyen ablakot szeretnénk létrehozni, a MDICREATESTRUCT mezői mondják meg. A mezők lehetséges értékeit a dokumentációban találjuk. A WM_MDICREATE visszatérési értékét az újonnan létrejött ablak handleréből tudjuk meg.

HWND CreateNewMDIChild(HWND hMDIClient)
{ 
    MDICREATESTRUCT mcs;
    HWND hChild;

    mcs.szTitle = "[Untitled]";
    mcs.szClass = g_szChildClassName;
    mcs.hOwner  = GetModuleHandle(NULL);
    mcs.x = mcs.cx = CW_USEDEFAULT;
    mcs.y = mcs.cy = CW_USEDEFAULT;
    mcs.style = MDIS_ALLCHILDSTYLES;

    hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);
    if(!hChild)
    {
        MessageBox(hMDIClient, "MDI Child creation failed.", "Oh Oh...",
            MB_ICONEXCLAMATION | MB_OK);
    }
    return hChild;
}

Az MDICREATESTRUCT egyik mezője nagyon hasznos, noha itt nem használtuk. Ez az lParam. Ezzel egy 32 bites értéket tudunk a gyermek számára küldeni (mint mutató), így tetszőleges adatot tudunk az új ablakkal megosztani. A gyermek ablak WM_CREATE handlerében létrehozunk egy CREATESTRUCT rekordot, amit ráállítunk lParam-ra. Ennek a rekordnak a lpCreatePArams mezője mutat a pMDICreateStruct rekordra. Ezt elmondani bonyolultabb mint leírni :)

case WM_CREATE:
    {
        CREATESTRUCT* pCreateStruct;
        MDICREATESTRUCT* pMDICreateStruct;

        pCreateStruct = (CREATESTRUCT*)lParam;
        pMDICreateStruct = (MDICREATESTRUCT*)pCreateStruct->lpCreateParams;

        /*
        pMDICreateStruct now points to the same MDICREATESTRUCT that you
        sent along with the WM_MDICREATE message and you can use it
        to access the lParam.
        */
    }
    break;

 Ezt lehet egyetlen lépésben is, és akkor nem kell két mutatóval szórakozni.

((MDICREATESTRUCT*)((CREATESTRUCT*)lParam)->lpCreateParams)->lParam

 Most megvalósítjuk a File menüpontot a keret ablakunk menüjében.

case ID_FILE_NEW:
        CreateNewMDIChild(g_hMDIClient);
    break;
    case ID_FILE_OPEN:
    {
        HWND hChild = CreateNewMDIChild(g_hMDIClient);
        if(hChild)
        {
            DoFileOpen(hChild); 
        }
    }
    break;
    case ID_FILE_CLOSE:
    {
        HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
        if(hChild)
        {
            SendMessage(hChild, WM_CLOSE, 0, 0);
        }
    }
    break;

 Végül egy kis apróság, ez már igazán nem sok:

    case ID_WINDOW_TILE:
        SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
    break;
    case ID_WINDOW_CASCADE:
        SendMessage(g_hMDIClient, WM_MDICASCADE, 0, 0);
    break;

 

 

 

 

Példa: app_three

 Vannak az alap, vagy standard vezérlők, ezek alapból benne vannak az API-ban. Ellenben vannak más, bonyolultabb vezérlők is, a common control, vagy közös vezérlő. Nem tudom hogyan lehetne normálisan fordítani, úgyhogy most marad így. Szóval ezeket a vezérlőket külön be kell regisztrálnunk, hogy használni tudjuk őket.

Használatuk előtt ki kell adni az InitCommonControls() parancsot. Szükséges még egy include is:

#include <commctrl.h>

A linker beállításokhoz hozzá kell adni a comctl32.lib befűzését ha még nem lenne ott.

Megjegyzés: Az InitCommonControls() helyett újabb és korszerűbb az InitCommonControlsEx(), de nekünk most elég lesz a sima is, így azt fogjuk használni.

Eszköztárak

 

Létrehozhatnánk egy eszköztárat a CreateToolbarEx()-szel, de nem tesszük. 

 hTool = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
        hwnd, (HMENU)IDC_MAIN_TOOL, GetModuleHandle(NULL), NULL);

Nem is bonyolult. A TOOLBARCLASSNAME egy a közös vezérlők fejlécállományában definiált konstans. Hwnd a szülő ablak, amelyikbe az eszköztárat építjük. IDC_MAIN_TOOL azonosítja az eszköztárat, hogy később a GetDlgItem() visszaadhassa.

// Send the TB_BUTTONSTRUCTSIZE message, which is required for
// backward compatibility.
SendMessage(hTool, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);

A TB_BUTTTONSTRUCTSIZE üzenetet elküldjük, hogy visszafelé kompatibilis legyen. Na ki tudja hogy ez mit jelent?!?

 

Az eszköztár gombjai

 

Gombokból kétféle is rendelkezésünkre áll. Az egyik a comctl32.dll által biztosított alap gombok, a másik pedig a felhasználó által definiált, vagyis magunknak kell elkészíteni. A gombokat és a képeket külön külön kell az eszköztárhoz hozzáadni. Előbb a képeket, majd csak utána a gombokat, amik a képeket használják.

Hagyományos gombok

 

Most hogy már van eszköztárunk, tegyünk rá gombokat is! Szerencsére az alap képek benne vannak a vezérlő könyvtárában, így nem kell külön elkészítenünk.

Először deklaráljuk a TBBUTTON, és TBADDBITMAP elemeket,

    TBBUTTON tbb[3];
    TBADDBITMAP tbab;

majd hozzáadjuk a standard képeket az eszköztárhoz.

    tbab.hInst = HINST_COMMCTRL;
    tbab.nID = IDB_STD_SMALL_COLOR;
    SendMessage(hTool, TB_ADDBITMAP, 0, (LPARAM)&tbab);

Most hogy a képek rendben vannak, tegyünk fel gombokat is, amik használják őket.

    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = STD_FILENEW;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = ID_FILE_NEW;

    tbb[1].iBitmap = STD_FILEOPEN;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = ID_FILE_OPEN;

    tbb[2].iBitmap = STD_FILESAVE;
    tbb[2].fsState = TBSTATE_ENABLED;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = ID_FILE_SAVEAS;

    SendMessage(hTool, TB_ADDBUTTONS, sizeof(tbb)/sizeof(TBBUTTON), (LPARAM)&tbb);

Most már hozzáadtuk a New, Open, SaveAs gombokat a megszokott képekkel. Ez azért is előnyös, mert már megszoktuk őket, mindenki felismeri elsőre. Minden kép indexe benne van a fejléc fájlban, és megtalálható az MSDN-en is. Minden gomb kapott egy ID-t, pont mint a menünél volt. A gombok WM_COMMAND üzeneteket generálnak, így azokkal már nincs több dolgunk! Ha olyan gombot helyezünk el, ami nem szerepelt a menüben, akkor csak egy úja azonosítót kell neki adnunk, és az azonosítóhoz egy WM_COMMAND kezelőt.
A végén a TB_ADDBUTTONS kéri hogy hány gombot hozunk létre (ez lesz a wParam). Ezt megadhatnánk kézzel is, de ha új gombot kell felvenni, akkor megint bele kell nyúlni a kódba. Helyette kiszámoltatjuk vele, hogy a gombok tárhelyigényét elosztjuk egy gomb tárhelyével, akkor pont hármat kapunk :)

Státusz sor

 

Ezt mind tudjuk micsoda, egyetlen sorocska az ablak alján, ahová mindenféle infókat írhatunk.

    hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
        WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
        hwnd, (HMENU)IDC_MAIN_STATUS, GetModuleHandle(NULL), NULL);

Megadhatjuk hogy hány részből álljon. Ha nem adjuk meg, akkor a teljes szélességét felöleli. Ilyenkor a SetWindowText() állítja be a szöveget. Ahhoz, hogy beállítsuk az egyes szakaszok szélességeit, létre kell hoznunk egy egészekből álló tömböt. Itt minden elem az aktuális szakasz hossza pixelben. Ha -1 értéket adunk meg, akkor a szakasz a teljes maradék területet kapja.

    int statwidths[] = {100, -1};

    SendMessage(hStatus, SB_SETPARTS, sizeof(statwidths)/sizeof(int), (LPARAM)statwidths);
    SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)"Hi there :)");

SB_SETPARTS parancshoz a wParam értékét ismét a tömb elemeiből számoljuk.

A megfelelő méret

 

Eddig nem foglalkoztunk a vezérlő elhelyezésével. Ettől még a fő ablakunk területéből veszik el a helyet, amit az editor  megjelenítésekor figyelembe kell venni. Van egy eszköztárunk meg egy állapotsorunk is, nyilván ennyivel kevesebb hely áll rendelkezésre a szerkesztő számára.

    HWND hTool;
    RECT rcTool;
    int iToolHeight;

    HWND hStatus;
    RECT rcStatus;
    int iStatusHeight;

    HWND hEdit;
    int iEditHeight;
    RECT rcClient;

    // Size toolbar and get height

    hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
    SendMessage(hTool, TB_AUTOSIZE, 0, 0);

    GetWindowRect(hTool, &rcTool);
    iToolHeight = rcTool.bottom - rcTool.top;

    // Size status bar and get height

    hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
    SendMessage(hStatus, WM_SIZE, 0, 0);

    GetWindowRect(hStatus, &rcStatus);
    iStatusHeight = rcStatus.bottom - rcStatus.top;

    // Calculate remaining height and size edit

    GetClientRect(hwnd, &rcClient);

    iEditHeight = rcClient.bottom - iToolHeight - iStatusHeight;

    hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
    SetWindowPos(hEdit, NULL, 0, iToolHeight, rcClient.right, iEditHeight, SWP_NOZORDER);

 

 

Példa: app_two

Megnyitás

 

Első lépés hogy egy fájlt megnyissunk vagy mentsünk, hogy kiderítjük a nevét. Az nem egy hasznos megoldás, ha fixen belekódoljuk a programba. Ez egy nagyon gyakori feladat, ezért a rendszer alapból tartalmaz egy vezérlőt, ami a felhasználótól kér egy fájlnevet. A fájl megnyitásához és mentéséhez használhatjuk a GetOpenFileName(), vagy GetSaveFileName() hívásokat. Közös bennük, hogy mindkettő OPENFILENAME struktúrát használ.

OPENFILENAME ofn;
    char szFileName[MAX_PATH] = "";

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";

    if(GetOpenFileName(&ofn))
    {
        // Do something usefull with the filename stored in szFileName 
    }

Kezdjük a ZeroMemory() API-val! Ez egy nagyon hasznos cucc, a rekord minden mezőjét NULL-lal tölti fel. Néhány API különösen háklis arra, hogy a nem használt mezői NULL-lal legyenek feltöltve. Így nem kell mindet egyesével nullázni, egyszerre mehet az egész.

Az egyes mezők jelentését részletesen leírja a dokumentáció. Az lpstrFilter egy olyan sztringre mutat, ami több NULL-t is tartalmaz, beleértve a végén levőt is. Ráadásul a fordító a végére betesz mégegy NULL-t (nekünk már nem kell). A szűrő sztringben a NULL-ok jelentik az egyes darabok lezárását. Az első darab a leírás:  "Text Files (*.txt)". A helyettesítő karakterek itt nem kellenének, de szemléltetnek. A következő rész a tényleges szűrő "*.txt". Ezután ugyanígy leírás, szűrő. Akárhányat felvehetünk.

Az lpstrFile a fájlnévnek fenntartott pufferre mutat. A fájlnév max hossza nem lehet nagyobb, mint MAX_PATH!

A Flag-ek jelzik, hogy csak létező fájlt nyithatunk meg (nem hozhatunk létre újat), a csak olvasható módot nem támogatjuk, végül beállítjuk, hogy alapértelmezetten .txt kiterjesztéssel dolgozunk. Ha a user beírja hogy "valami", és nincs ilyen fájl, akkor előbb megpróbáljuk a "valami.txt"-t is megnyitni, mielőtt feladjuk.

 Ha menteni szeretnénk a megnyitás helyett, akkor a kód közel azonos lesz. Eltérés csak annyi, hogy a GetSaveFileName() API-t hívjuk, és a Flag-eket alkalmasan a mentéshez állítjuk be:

ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

 Ebben az esetben már nem kell a fájlnak léteznie, de a könyvtárnak igen. Ha még nem létezik jó, de ha igen akkor előbb szól nekünk, hogy akarjuk-e felülírni a már meglévőt.

Megjegyzés:

Az MSDN-ben ez áll az lStructSize mezővel kapcsolatban:

lStructSize
Specifies the length, in bytes, of the structure.

Windows NT 4.0: In an application that is compiled with WINVER and _WIN32_WINNT >= 0x0500, use OPENFILENAME_SIZE_VERSION_400 for this member.

Windows 2000/XP: Use sizeof (OPENFILENAME) for this parameter.

 Egyszerűen arról van szó, hogy a Windows2000 hozzáadott pár mezőt a struktúrához, emiatt annak megváltozott a mérete. Elképzelhető, hogy nem működik a kód, mert az általunk használt fordító szerint nem egyezik a méret a rendszerbeli mérethez képest, így a hívás meghiúsul (például Win98, NT4). Ebben az esetben használjuk az OPENFILENAME_SIZE_VERSION_400-t a sizeof(ofn) helyett!

 Fájlok olvasása

 

A fájlok elérésre a windózban több lehetőségünk is van. Használhatjuk a régi io.h open(), read(), write() eljárásait, az stdio.h fopen(), fread(), fwrite(), valamint a c++-os iostream-okat is. Azonban a windóz végül mindegyiknél ugyanazokat az API-kat fogja meghívni. Használhatjuk a megszokott módszerek bármelyikét, vagy használhatjuk az alábbiakat.

Fájlok megnyitásához az OpenFile() vagy  CreateFile() használható. Előbbi a Microsoft szerint már elavult, a CreateFile() ügyesebb, jobban kézben tartható.

Már lehetővé tettük a felhasználónak, hogy kiválasszon egy fájlt.

BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSize;

        dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF)
        {
            LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
                DWORD dwRead;

                if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead, NULL))
                {
                    pszFileText[dwFileSize] = 0; // Add null terminator
                    if(SetWindowText(hEdit, pszFileText))
                        bSuccess = TRUE; // It worked!
                }
                GlobalFree(pszFileText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

Ez a teljes eljárás, amivel be tudjuk olvasni a fájlt a vezérlőbe. Paraméterként átadjuk a vezérlő handlerét, valamint a megnyitásra szánt fájl nevét. Sok ellenőrzéssel kezdünk. A fájlkezelés egy olyan terület, ahol rengeteg hiba felléphet. Ezeket módszeresen ki kell védenünk.

dwRead változót nem használjuk, de a ReadFile() nem működik nélküle.
A CreateFile()-ban a GENERIC_READ azt jelenti, hogy csak olvasni akarjuk a fájlt.
A FILE_SHARE_READ jelzi, hogy nincs bajunk azzal ha másik programok is olvassák a fájlt, de írniuk nem szabad, amíg megnyitva tartjuk.
Az OPEN_EXISTING előírja, hogy csak már létező fájlt nyithatunk meg, ne hozza létre, és ne írja felül.

Ha már sikerült megnyitnunk, akkor ellenőrízhetjük a fájl méretét, ebből tudjuk hogy mekkora memórát kell lefoglalnunk a teljes beolvasásához. Ha a memóriafoglalás sikeres volt, akkor a ReadFile() fogja betölteni a fájlt a memóriába. Az API-k nem tudják mi az a szöveges fájl. Egyszerűen beolvassa a szöveget, és nulla lezárást tesz a blokk végére. Emiatt a plusz nulla lezárás miatt kellett egyel nagyobbra venni a blokk méretét. Ezután a SetWindowText() kapja meg a szövegblokk címét.

Ha minden jól sikerült, akkor felszabadítjuk a memóriát, lezárjuk a fájlt, és a visszatérési értéket TRUE-ra állítjuk.

Fájlok írása

BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwTextLength;

        dwTextLength = GetWindowTextLength(hEdit);
        // No need to bother if there's no text.
        if(dwTextLength > 0)
        {
            LPSTR pszText;
            DWORD dwBufferSize = dwTextLength + 1;

            pszText = GlobalAlloc(GPTR, dwBufferSize);
            if(pszText != NULL)
            {
                if(GetWindowText(hEdit, pszText, dwBufferSize))
                {
                    DWORD dwWritten;

                    if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
                        bSuccess = TRUE;
                }
                GlobalFree(pszText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

Nagyon hasonló az olvasáshoz, pár eltéréssel. A CreateFile() írási kéréssel indul, és minden esetben létrehozzuk a fájlt. Ha már létezik, akkor törli és létrehozza, ha még nem létezett, akkor normál attribútumokkal hozza létre.

Elkérjük a vezérlőtől a szükséges memória méretét, lefoglaljuk a helyet, majd GetWindowText() feltölti a vezérlő szövegével. A WriteFile() fogja a memóriából a lemezre kiírni. A dWritten paraméter mondja meg, hogy mennyit sikerült kiírni, ezt itt sem használjuk. Felszabadítjuk a memóriát, lezárjuk a fájlt, és TRUE értékkel visszatérünk.

 

 

 

 

 

 

 

Példa: app_one

A következőkben készítünk egy szövegszerkesztő programot, ami képes fájl megnyitására, szerkesztésére és mentésére. Lehetne a kész programot is vizsgálgatni, de sok értelme nem lenne. Ezért menet közben fogjuk felépíteni, kezdve a csupasz váztól, majd egyre több hasznos dologgal vértezzük fel, míg végül elkészül.

Első lépésként létrehozunk egy ablakot egy EDIT vezérlővel. Ez lesz a programunk gerince. A vázat a Simple Window alkalmazás jelenti, amihez hozzáadunk egy #define sort az új vezérlőnek és két új üzenet kezelőt.

#define IDC_MAIN_EDIT	101
 case WM_CREATE:
    {
        HFONT hfDefault;
        HWND hEdit;

        hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", 
            WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 
            0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT, GetModuleHandle(NULL), NULL);
        if(hEdit == NULL)
            MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

        hfDefault = GetStockObject(DEFAULT_GUI_FONT);
        SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
    }
    break;
    case WM_SIZE:
    {
        HWND hEdit;
        RECT rcClient;

        GetClientRect(hwnd, &rcClient);

        hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
        SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
    }
    break;

Vezérlő létrehozása

 

 A vezérlők létrehozása mint minden más ablaknál a CreateWindowEx() API hívással történik. Átadjuk az előre regisztrált osztályt amire szükségünk van (jelen esetben ez az EDIT), és máris van egy hagyományos szerkesztő vezérlőnk. Írunk egy listát hogy milyen vezérlőket szeretnénk az ablakunkban látni, és a CreateWindowEx() az erőforrásban leírtak szerint szépen elkészíti.

hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", 
        WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 
        0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT, GetModuleHandle(NULL), NULL);
    if(hEdit == NULL)
        MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

Láthatjuk, hogy jó pár stílust megadtunk. Az elején még egyértelmű hogy mik ezek: Gyermekobjektum legyen, látható, scrollbar-okkal. A többi már nem egyértelmű. Legyen a vezérlő több soros, és a scroll-ok automatikusan gördüljenek a végére gépelés közben. A szabályos-, és bővített stílusok (WS_*, WS_EX_*) listáját megnézhetjük az MSDN-en, és vezérlőspecifikusan is (itt ES_*).

hEdit néven elmentjük a szerkesztő vezérlőnk handlerét, hogy később tudjunk rá hivatkozni. A méretét most nem adjuk meg, hanem később a WM_SIZE parancssal az ablak mindenkori méretéhez igazítjuk.

A vezérlő méretének dinamikus beállítása

 

Általában ha az ablakunk méretezhető, akkor gondoskodnunk kell arról, hogy a vezérlők is rendben átméreteződjenek, és a pozíciójuk megfelelő legyen.

GetClientRect(hwnd, &rcClient);

hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);

Mivel most csak egy vezérlőnk van, viszonylag egyszerű a dolgunk. A GetClientRect() megadja nekünk az ablak "kliens területének" méretét, vagyis azt a téglalapot, ahol a vezérlőnk megjelenhet. Ezen értékek közül a bal felső sarok koordinátái mindig (0,0) ezért ezzel nem kell foglalkozni. Fontosabb a jobb alsó, mert ez adja meg a téglalap méretét.

A GetDlgItem() megadja a vezérlő handlerét, aminek segítségével már átmérezezhetjük a vezérlőt. Ezt a SetWindowPos() fogja nekünk megtenni. Beállítja a vezérlő bal felső sarkát az ablak bal felső sarkába, és átméretezi, hogy pontosan akkora legyen mint az ablak általunk használható hasznos területe. Természetesen megtehetjük, hogy csak az ablak felét töltse ki, és a fennmaradó részre tehetünk további vezérlőket.

Más vezérlők létrehozása futásidőben

 

Na ez az, amit most kihagyunk, mert különben átmegyünk unalmasba! :) Az MSDN-en meg lehet mindet találni, ugyanaz mint fentebb. Ráadásul a következőkben úgyis előkerülnek, szóval nagyon fogjuk tudni.

 

 

11. GyIK (FAQ)

2014.06.08. 20:24

Példa: dlg_three

 Ez egy rövidke fejezet lesz, a sokszor előforduló gondokkal.

Szín megváltoztatása

 

Érdemes kerülni a szöveg színezését, mert csak bántja a szemet. Mindazonáltal néha valóban hasznos dolog, például ha egy URL link működését szeretnénk szimulálni.

Képesek vagyunk megváltoztatni a színeket, hiszen ezek is a windóz üzenetei. Például a DialogBox színe a WM_CTLCOLORDLG segítségével módosítható.

Először létrehozunk egy ecsetet a háttér festésére, és tároljuk későbbre.

HBRUSH g_hbrBackground = CreateSolidBrush(RGB(0, 0, 0));

    case WM_CTLCOLORDLG:
        return (LONG)g_hbrBackground;
    case WM_CTLCOLORSTATIC:
    {
        HDC hdcStatic = (HDC)wParam;
        SetTextColor(hdcStatic, RGB(255, 255, 255));
        SetBkMode(hdcStatic, TRANSPARENT);
        return (LONG)g_hbrBackground;
    }
    break;

Figyeljük meg azt a sort, ami a hátteret beállítja átlátszóra! Ha elhagyjuk ezt a sort, akkor az ecset kifesti a megadott színnel a hátteret. Ekkor viszont ha a szöveget rajzolja, akkor felülírja az alap háttérszínnel. Állítsuk a szöveg rajzoló módot átlátszóra, hogy megoldjuk ezt a problémát!

 A hagyományos vezérlőknek nagyon sok színe megváltoztatható, ezek listáját megtaláljuk ha megnézzük a Win32 referenciát (pl MSDN). Egyszerűen keressünk rá a WM_CTLCOLOR* kulcsszóra! A vezérlő kétféle választ adhat. Vagy WM_CTLCOLOREDIT ha megáltoztatható, vagy WM_CTLCOLORSTATIC ha nem. Ha több STATIC vezérlőnk is van, amiknek eltér a színe, akkor az ID alapján tudjuk őket megkülönböztetni. A HWND-t kell nézni az lParam-ban, és a GetDlgCtlrID() segítségével kapjuk meg az ID-t. Ne feledjük, hogy a szerkesztő minden STATIC vezérlőnek IDC_STATIC (-1) értéket ad, tehát ha meg akarjuk őket különböztetni egymástól, akkor külön ID-t kell nekik adnunk.

 Az ikon beállítása

 

Ez viszonylag egyszerű feladat, csak ki kell adnunk a WM_SETICON üzenetet a párbeszéd ablaknak. Ezt kétszer kell megtennünk, egyszer a kis ikonnak az ablak sarkában, egyszer pedig a nagy ikonnak ha Alt+Tab-ot nyomna a user. Ha csak egy méretű ikonunk van, akkor elég ugyanazt a handlert elküldeni mindkét esetben.

Az alábbi kóddal beállíthatjuk az alkalmazás alapértelmezett ikonját:

 SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)));
 SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)));

Amikor saját ikonnal helyettesítjük a default ikont, nem szabad elfelejteni lecserélni a LoadIcon() HINSTANCE paraméterét az alkalmazásunk példányára (ha nincs eltárolva a WinMain()-ban, akkor a GetModuleHandle(NULL) segítségével kideríthetjük).

Miért nem működik a ComboBox-om?

 

Sokszor előforduló hiba, hogy létrehozunk egy kombinált listát, de az nem jelenik meg a program futásakor. Amikor megadjuk a magasságát, akkor az egész vezérlő magasságát adjuk meg, beleértve a legördülő lista méretét is! Ha például 100 pixelnek adjuk meg, akkor ebből 30 pixel (legyen ez most az alapértelmezett) a nyíl amire rákattintunk, és marad még 70 pixel a legördülő résznek. Ha VC++ erőforrás szerkesztőt használunk, akkor a függőleges méretet nem is adhatjuk meg. Rá kell bökni a nyílra, és a legördülő részt méretezhetjük tetszés szerint.

Referenciák

 

Érdemes a továbbiak ügyében szétnézni a Microsoft MSDN oldalán itt.
A kulcsszavak ezek: User Interface Services, Windows Controls, néha a Platform SDK.

 

 

 

 

 

 

süti beállítások módosítása