Ahogy az már itt-ott előjött, az ablakra való rajzoláshoz, szövegkiíráshoz egy eszköz-környezet objektumra van szükségünk, amely az adott ablakhoz, vagy eszközhöz van rendelve, amely fogadja az outputot. Ez lehet akár egy nyomtató, vagy a képernyő, vagy csak egy ablak, vagy az ablak egy része.
A példaprogram egy tetris lett.
Tetrist már mindenki látott.
Négy blokkból álló figurákat kell egymásra ejteni. Ha betelik egy sor,
azt töröljük, ha nem tud több beesni, vesztettük. Nyerni meg nemigen lehet.
A megvalósítás egyszerű (mármint
egy quake-hez képest). Arra azért vigyázzunk, hogy senki se írjon Tetris
nevű Tetrist, mert az jogilag le vagyon védve. A tetrisprogramozás rejtelmeibe
nem nagyon fogok belemélyedni, aki kicsit is tud programozni, van róla
fogalma, hogyan kell megoldani a figurák forgatását, mozgatását, eltárolását,
sorok törlését stb... Inkább az MFC -vel történő grafikus kezelés és a bittérképek
témakörében szeretnék segíteni.
Az Eszköz-környezet Objektum
(Device Context)
Ahogy az már itt-ott előjött,
az ablakra való rajzoláshoz, szövegkiíráshoz egy eszköz-környezet objektumra
van szükségünk, amely az adott ablakhoz, vagy eszközhöz van rendelve, amely
fogadja az outputot. Ez lehet akár egy nyomtató, vagy a képernyő, vagy
csak egy ablak, vagy az ablak egy része. Az eszköz-környezet tartalmazza
a kiválasztott rajzolóeszközöket, az attribútumokat, és a különböző alakzatok
rajzolásához, és bittérképműveletekhez szükséges tagfüggvényeket.
Ha egy nézetablakban (View)
akarunk grafikát megjeleníteni, akkor annak az OnDraw függvényének átadott
eszköz-környezettel tudjuk megtenni. Ha egy Dialógusablakban, akkor pedig
az OnPaint függvényben kell újrarajzolni.
void CTetrisWnd::OnPaint() { CPaintDC dc(this); // device context for painting
Eszköz-környezet objektumból
több típusú is van, de mindegyik a CDC osztályból származtatott.
CPaintDC Csak az OnPaint függvényből
használható, egy dialógusablak, vagy egy Control- nak az újrarajzolásakor.
CClientDC Segítségével a nézetablakba
(Client Area) rajzolhatunk
CWindowDC Az ablak által használt
egész területre rajzolhatunk.
CMetafileDC Windows metaFile-ba
rajzolhatunk..
Eszköz-környezetek létrehozása:
A CpaintDC, CClientDC és
CWindowDC számára meg kell adni a CWnd osztály címét, ahonnan létrehoztuk.
Ebből következik, hogy őket csak egy olyan osztályból tudjuk létrehozni,
amely a CWnd ből származik. Ez nem okoz gondot, hiszen minden ablak osztály
őse.
Pl.
CClient dc(this);
Ha az ablak görgethető, akkor
az OnPrepareDC függvényit is meg kell hívni. De erről már volt szó.
Bittérképek és bitműveletek.
A bittérkép egy kép pontos
reprezentációja a memóriában, vagy egy fájlban. Minden egyes pixel színét,
tárolja, ez elég a megjelenítéshez.
Ha MFC-ben bittérképet akarunk
használni, a CBitmap osztály egy példányát kell használnunk. Ez az osztály
biztosítja a bittérképek létrehozását, tárolását, betöltését. Egy példány
létrehozása:
CBitmap m_bitmap;
Egy bittérképet létrehozhatunk
üresen, betölthetünk file-ból, vagy erőforrásból.
Bittérkép betöltése Erőforrásból.
Egy bittérkép erőforrásként
történő kezelése azzal az előnnyel jár, hogy a fordító beleszerkeszti az
adatot az .exe állományba, így nem fog külön bmp-ként megjelenni.
Az Insert/Resource menüpontnál,
vagy a New gombra nyomunk, ekkor a Visual C++ szerkesztőjében tudjuk megrajzolni.
Ha az Import gombot választjuk, egy bmp állomány-t rakhatunk be az erőforrások
közé. Ez csak akkor jelenik meg a szerkesztőben, ha kevesebb, mint 256
színű.
Minden bittérképnek, ahogy
más erőforrásnak is van azonosítója (ID-je). Ezt az ID-t dupla kattintással
a Resource View-ban be tudjuk állítani, és megjegyezni sem árt.
Ha sikerült a bittérképet
valahogy betuszkolni az erőforrások közé, a programból valahogy be is kell
tölteni.
m_bitmap.LoadBitmap(IDB_BITMAP);
A LoadBitmap függvénynek
átadott paraméter az az azonosító, melyet hozzárendeltünk a szerkesztőben.
A bittérkép automatikusan kompatíbilis lesz az outputtal.
Üres bittérkép létrehozása
Szerkesztőben való létrehozás
helyett hozhatunk létre bittérképet a memóriában is a program futása közben.
Ekkor a képet az MFC rajzolófüggvényeivel, és bittérképműveleteivel tudjuk
létrehozni, miután eszköz-környezet objektumot rendeltünk hozzá. A bittérkép
létrehozását a CBitmap CreateCompatibleBitmap tagfüggvénye végzi.
Pl.:
m_pWinDC = new CWindowDC(this); m_Bitmap.CreateCompatibleBitmap(m_pWinDC,size.cx,size.cy);
CreateCompatibleBitmap függvény
első paramétere egy eszköz-környezet objektum címe, ezzel lesz kompatíbilis
a bittérkép. Ez azt jelenti, hogy az adatok ugyanúgy lesznek tárolva, ahogy
az outputot végző eszköz tárolja azokat. Például ha a monitor true color
módban van 3 byte határozza meg egy pixel színét a képernyő-memóriában.
Egy ezzel kompatíbilis bittérképen szintén 3 byte határozza meg a pixelt.
A függvény második paramétere
a bittérkép szélessége, a harmadik a magassága pixelekben
A függvény egy memóriablokkot
rendel a bittérképhez, melyen a pixelek színei definiálatlanok. A bittérképet
a CDC rajzolófüggvényeivel tudjuk megrajzolni. Ahhoz viszont, hogy rajzolni
tudjunk a bittérképre, létre kell hozni, és hozzá kell rendelni egy eszköz-környezet
objektumhoz. A Windows különleges típusú eszköz-környezet objektumokat
biztosít a bittérképek használatára, ezek a memória eszköz-környezetek.
Egy ilyen létrehozásához a CDC osztály egy példányát kell definiálnunk,
majd a CreateCompatibleDC függvényt meghívva létrehozzuk.
Pl.:
m_pMemDC = new CDC; m_pMemDC->CreateCompatibleDC(m_pWinDC) ;
A CreateCompatibleDC függvénynek
a paramétere egy eszköz-környezet objektum címe, a kapott eszköz-környezetünk
avval az eszközzel lesz kompatíbilis, amely a paraméternek megadotthoz
volt hozzárendelve. Így aztán az átadott eszköz-környezetnek ugyanahhoz
az eszközhöz kell kapcsolódnia, amihez a CreateCompatibleBitmap-nek megadottnak.
Ezt praktikusan úgy érjük el, hogy ugyanazt az eszköz-környezet objektum
címet adjuk át mindkét függvénynek.
Tehát esetünkben lett egy
a képernyővel kompatíbilis eszköz-környezetünk. Ami ugye nincsen hozzárendelve
semmihez sem.
Még hozzá kell rendelni
a bittérképhez. A CDC SelectObject tagfüggvényével.
Pl.:
m_pMemDC->SelectObject( &m_bitmap);
A függvénynek a bittérkép
címét kell megadni paraméterként. Visszatérési értéke az eddig hozzárendelt
bittérkép.
Ha ezzel megvagyunk, már
rajzolhatunk is a bittérképre a memória eszköz-környezet objektumot használva.
Ha bittérképet megsemmisítjük
előtte a memória eszköz-környezetet is meg kell semmisíteni, vagy egy másik
bittérképet hozzárendelni. Például a régit, amit visszaadott a SelectObject
függvény.
A függvény készít egy m_Buffer
nevű bittérképet, és készít egy memória eszköz-környezetet, melyhez hozzárendeli
a bittérképet. Arra is figyel, ha már ezek el vannak készítve, először
megsemmisíti őket. A létrehozott memória-eszközkörnyezet a programban Frame
Buffer-ként fog funkcionálni.
Bittérképek megjelenítése (másolása)
Egy bittérkép megjelenítése
tulajdonképpen csak másolás, eszköz-környezetek közötti adatcsere. Az adatok
továbbítása eszköz-környezetek között a CDC osztály BitBlt tagfüggvényével
történik.
BOOL BitBlt(int x,
int y,
int nWidth,
int nHeight,
CDC* pSrcDC,
int xSrc,
int ySrc,
DWORD dwRop);
Az első két paramétere a
másolás céljának bal felső sarkának logikai koordinátái, a második kettő
a bittérkép szélessége és magassága a következő a forrás eszköz-környezet
objektum címe, aztán a másolandó bittérkép bal felső koordinátái, majd
a raszterműveleti kód.
A függvény az ötödik paraméterként
megadott eszköz-környezethez rendelt eszközről másol át egy adatblokkot
arra az eszközre, amely ahhoz az eszközkörnyezethez volt rendelve, amelyre
a függvényt meghívtuk.
A raszterműveleti kódokat
nem írom le, megtalálhatóak a help-ben. Platform SDK, Graphics and Multimedia
Services, GDI, Raster Operation Codes
Ami nekünk most kell az az
SRCCOPY, amely változtatás nélküli másolást végez.
A függvény a m_pWinDC eszköz-környezethez
rendelt ablakra másolja az m_pMemDC eszköz-környezethez rendelt bittérképet
mindenféle változtatás nélkül. Röviden kirakjuk a Frame Buffer-ünket. Némileg
bonyolultabb a dolog, ha nincs eszköz-környezet objektum rendelve a bittérképhez.
Egy példa, ahol egy mátrixból
felépítjük a képet a tetris blokkok másolásával. A blokkok egy bittérképen
vannak, amihez nincs eszköz-környezet rendelve, tehát kell egyet készítetnünk.
Először létrehozunk egy memória
eszköz-környezetet, és meghívjuk a CreateCompatibleDC függvényét. Ha a
függvényt NULL paraméterrel hívjuk, a képernyővel kompatíbilis eszköz-környezetet
készít. A SelectObject függvénnyel hozzárendeljük a bittérképet, majd a
BitBlt alkalmazásával a m_pMemDC-re felépítjük a képet a dc eszközkörnyezeten
található blokkokból.
A CDC osztály PatBlt tagfüggvényéről
még nem esett szó. Evvel tudjuk feltölteni a bittérképet. Az első két paraméter
a feltöltendő terület bal felső sarkának logikai koordinátái, a harmadik
és negyedik a feltöltendő terület szélessége és magassága, az utolsó pedig
a raszterműveleti kód. Esetünkben a BLACKNESS azt jelenti, hogy feketével
töltjük fel a bittérképet.
Ennyit a részletekről.
A program dialógusablak alapú,
ezért le kellett mondani a szokásos kurzormozgató billentyűkkel történő
irányításról. Dialógusablak lévén, ezek a billentyűk a gombok közti mozgásra
valók, és az MFC nem töri magát agyon, hogy tudassa velünk mikor melyik
nyilat nyomták meg. Például nem jön létre WM_KEYDOWN üzenet, így az OnKeyDown-ban
nem tudnánk kezelni őket.
Ezért lett ez a "balkezes"
kiosztás.
Az ablak, ahol a játék fut,
a CStatic osztályból származtatott. Ebből volt a legegyszerűbb. A ClassWizard-al
össze kell rendelni a control-t az objektummal, így a control-t a mi Classunk
fogja kezelni.