Po m∞sφci tu mßme dalÜφ lekci kurzu DirectX. A Φφm se dnes budeme zab²vat? Nejprve si shrneme, co jsme ud∞lali v minulΘ lekci tj. zaΦali jsme implementovat knihovnu Engine.dll. V tΘto lekci budeme pokraΦovat a p°idßme dalÜφ t°φdy CGItem, CGButton atd. Touto lekcφ takΘ zakonΦφme nßÜ p°φklad a vlastn∞ i DirectDraw.
Jak jsem zmφnil v ·vodu, v minulΘ lekci jsme zaΦali utvß°et knihovnu Engine.dll, kterß bude obsahovat systΘm naÜeho menu. Dnes ji zcela dokonΦφme, i kdy₧ bude samoz°ejm∞ zcela na Vßs, jak budete pokraΦovat (zda-li budete pokraΦovat). NaÜφm dneÜnφm ·kolem tedy bude p°idat nßsledujφcφch p∞t t°φd: CGItem, CGButton, CGLabel, CGPage a CGMenu. CGItem je rodiΦovskß t°φda pro t°φdy CGButton a CGLabel. CGMenu obsahuje pole prvk∙ CGPage a tato t°φda obsahuje pole prvk∙ CGItem.
16.1.1 CGItem
ZaΦneme od nejni₧Üφ vrstvy tj. od t°φdy CGItem. Tato t°φda je zßkladnφ t°φdou pro vÜechny budoucφ prvky menu. Instanci tΘto t°φdy nikdy nemusφte vytvß°et (je to zcela zbyteΦnΘ, jednß se toti₧ o zcela obecn² prvek kter² nic neumφ). Jako zßkladnφ t°φda je velice jednoduchß. Podφvejme se na deklaraci t°φdy (tentokrßt vynechßm tabulku, proto₧e uvedenΘ t°φdy jsou mnohem jednoduÜÜφ):
class AFX_EXT_CLASS
CGItem : public CSprite
{
// atributes
protected:
DWORD m_dwID;
BOOL m_CanFocus;
public:
// callback func.
MENUPROC
ProcessFunc;
public:
// visual aspect
virtual void
Enable() {}
virtual void Disable() {}
// drawing
virtual void
UpdateItem();
virtual HRESULT ProcessItem(CGPage* _Page, void* _Data, UINT
_Action);
// return func.
void SetID(DWORD
dwID) {m_dwID = dwID;}
DWORD GetID() {return m_dwID;}
// focus func.
BOOL CanBeFocused()
{return m_CanFocus;}
void SetCBFocused(BOOL _Value) {m_CanFocus = _Value;}
public:
CGItem();
virtual ~CGItem();
};
Vidφte, ₧e t°φda obsahuje spoustu virtußlnφch metod. V∞tÜina t∞chto metod implicitn∞ ned∞lß nic a jsou urΦeny k p°etφ₧enφ z potomka. Obsahuje pouze t°i atributy a to sice ID objektu, kterΘ musφ b²t unikßtnφ v∙Φi ostatnφm objekt∙m na jednΘ strßnce. Druh² atribut °φkß, zda-li prvek m∙₧e dostat fokus. Nap°φklad tlaΦφtko fokus mφt m∙₧e zatφmco statick² text nikoli. Poslednφ atribut je ukazatel na obslu₧nou funkci menu. Tuto funkci musφte definovat takto:
typedef HRESULT (CALLBACK* MENUPROC)(VOID*, VOID*, UINT);
Tento zßpis definuje obecnou funkci MENUPROC se t°emi parametry. Tato funkce se volß pokud nastane n∞jakß akce s prvkem (nap°φklad stisknutφ tlaΦφtka apod.). Blφ₧e si ji probereme pozd∞ji (ve skuteΦnosti se volß, i kdy₧ se s prvkem ned∞je nic).
P°ejd∞me rovnou k implementaci t°φdy, kterß je velmi krßtkß:
void CGItem::UpdateItem()
{
UpdateSprite();
}
HRESULT CGItem::ProcessItem(CGPage* _Page, void* _Data, UINT _Action)
{
return ProcessFunc((void*)_Page, (void*)this, _Action);
}
Metoda UpdateItem() obnovφ sprite tj. zavolß metodu UpdateSprite(), kterou jsme psali minule. Tato metoda jde po odvozenφ upravit. DalÜφ metody t°φdy jsou inline metody typu set/get a nebudu je zde podrobn∞ji rozebφrat.
Za povÜimnutφ snad stojφ volßnφ funkce ProcessFunc(), co₧ je metoda typu MENUPROC. Mß t°i parametry: ukazatel na strßnku, ukazatel na vlastnφ prvek a akci prvku. O akcφch prvk∙ si povφme dßle. Ukazatele musφme p°etypovat na void*, co₧ je vid∞t z deklarace MENUPROC.
16.1.2. CGButton
Prvnφ potomek t°φdy CGItem je t°φda CGButton, kterß rovn∞₧ nenφ nikterak slo₧itß. P°edstavuje tlaΦφtko, kterΘ m∙₧e mφt Φty°i stavy: normßlnφ, s fokusem, stisknutΘ a nep°φstupnΘ. P°φkald jak mohou vypadat Φty°i stavy pro tlaΦφtko vidφte na obrßzku:
Stav normßlnφ (tlaΦφtko v klidu):
Stav s fokusem (nad tlaΦφtkem je kurzor):
ZamßΦknutΘ tlaΦφtko:
A nep°φstupnΘ tlaΦφtko:
Nenφ podmφnkou, aby ka₧dΘ tlaΦφtko m∞lo vÜechny Φty°i stavy. Podφvejme se na deklaraci:
#define BS_NORMAL
0
#define BS_FOCUS 1
#define BS_PRESS 2
#define BS_DISABLE 3
class AFX_EXT_CLASS CGButton : public CGItem
{
private:
CSpriteState m_NormalState;
CSpriteState m_FocusState;
CSpriteState m_PressState;
CSpriteState m_DisState;
public:
//creation
HRESULT
CreateButton(int x, int y, LPCSTR szNormal, LPCSTR szFocus = NULL, LPCSTR
szPress = NULL, LPCSTR szDis = NULL);
//update
virtual void
UpdateItem();
virtual HRESULT ProcessItem(CGPage* _Page, void* _Data, UINT
_Action);
//visual
virtual void
Enable() {SetState(BS_NORMAL);}
virtual void Disable() {SetState(BS_DISABLE);}
public:
CGButton();
virtual ~CGButton();
};
Nejprve definujeme Φtve°ici ID pro jednotlivΘ stavy tlaΦφtka. T°φda obsahuje pouze objekty Φty° stav∙ popsan²ch v²Üe. PotΘ obsahuje metodu, pomocφ kterΘ definujeme pozici a bitmapy tlaΦφtka. Hodnotou IP_CENTER zajistφme, ₧e tlaΦφtko bude umφst∞no uprost°ed obrazovky (a¥ u₧ horizontßln∞ nebo vertikßln∞). Musφme definovat nejmΘn∞ prvnφ bitmapu pro stav, kdy tlaΦφtko je v normßlnφm stavu.
void CGButton::CreateButton(int
x, int y, LPCSTR szNormal, LPCSTR szFocus, LPCSTR szPress, LPCSTR szDis)
{
//
// Create and defines segment for normal state of button -
clear button
m_NormalState.CreateState(BS_NORMAL,
szNormal);
AddState(&m_NormalState);
//
// Create and defines segment for focused state of button -
focused button
if(szFocus) {
m_FocusState.CreateState(BS_FOCUS,
szFocus);
AddState(&m_FocusState);
}
//
// Create and defines segment for pressed state of button -
pressed button
if(szPress) {
m_PressState.CreateState(BS_PRESS,
szPress);
AddState(&m_PressState);
}
//
// Create and defines segment for disabled state of button -
disabled button
if(szDis) {
m_DisState.CreateState(BS_DISABLE,
szDis);
AddState(&m_DisState);
}
//
// Center position of button
if(m_pCurState) {
if(x == IP_CENTER) {
x =
disGetResolution().cx / 2 - m_pCurState->GetSourceSurface()->Width() / 2;
}
if(y == IP_CENTER) {
y =
disGetResolution().cy / 2 - m_pCurState->GetSourceSurface()->Height() / 2;
}
}
SetPosition(CPoint(x, y));
}
V tΘto metod∞ postupn∞ definujeme sprite-stavy pro jednotlivΘ tlaΦφtko-stavy. Dßle musφme nastavit pozici tlaΦφtka. Pokud u₧ivatel chce mφt tlaΦφtko uprost°ed obrazovky, musφ polohu definovat pomocφ konstanty IP_CENTER. Pak se spoΦφtß skuteΦnß poloha podle vztahu: rozliÜenφ / 2 - Üφ°ka (v²Üka) tlaΦφtka / 2.
Metodou UpdateItem() tlaΦφtko vykreslφme a zajistφme, aby nemohlo dostat fokus pokud je ve stavu NEP╪═STUPN╔.
void CGButton::UpdateItem()
{
if(GetState()->GetID() == BS_DISABLE) {
SetCBFocused(FALSE);
}
else {
SetCBFocused(TRUE);
}
CGItem::UpdateItem();
}
Musφme volat metodu rodiΦovskΘ t°φdy, aby se tlaΦφtko v∙bec vykreslilo.
Nakonec tu mßme metodu ProcessItem(), kterß je volßna pro ka₧dΘ tlaΦφtko na strßnce v ka₧dΘm cyklu aplikace.
HRESULT CGButton::ProcessItem(CGPage*
_Page, void* _Data, UINT _Action)
{
if(GetState()->GetID() != BS_DISABLE) {
switch(_Action) {
case IA_MOUSEMOVE:
if(GetState()->GetID()
!= BS_PRESS) {
SetState(BS_FOCUS);
}
break;
case IA_MOUSECLICK_UP:
if((*((UINT*)_Data))
== LEFT_MOUSE_BUTTON) {
SetState(BS_FOCUS);
}
break;
case IA_MOUSECLICK_DOWN:
if((*((UINT*)_Data))
== LEFT_MOUSE_BUTTON) {
SetState(BS_PRESS);
}
break;
case IA_KEYPRESS:
//none handling
break;
case IA_NONE:
SetState(BS_NORMAL);
break;
}
}
return CGItem::ProcessItem(_Page, (void*)_Data, _Action);
}
Pokud je tlaΦφtko ve stavu NEP╪═STUPN╔, ned∞je se nic. Dßle u tlaΦφtka rozliÜujeme p∞t r∙zn²ch akcφ. Pokud u₧ivatel najede na tlaΦφtko kurzorem, nastavφ se fokus (IA_MOUSEMOVE). Pokud u₧ivatel stiskne tlaΦφtko, nastavφ se stav STISKNUTO (IA_MOUSECLICK_DOWN), kdy₧ nßsledn∞ tlaΦφtko pustφ, nastavφ se op∞t tlaΦφtko s fokusem (IA_MOUSECLICK_UP). Pokud se ned∞je nic, nastavφ se stav normßlnφ (IA_NONE). Nakonec se zavolß metoda rodiΦovskΘ t°φdy, kterß volß obslu₧nou funkci menu, kde pracujeme s chovßnφm vlastnφho menu. Existuje jeÜt∞ poslednφ akce a to je stisk klßvesy, kterß ovÜem pro tlaΦφtko nemß smysl (IA_KEYPRESS).
Tφmto zp∙sobem nastavφme implicitnφ chovßnφ ka₧dΘho tlaΦφtka - po najetφ kurzorem nad tlaΦφtko se nastavφ fokus, po stisku se tlaΦφtko promßΦkne atd. Proto je d∙le₧itΘ, aby ka₧dΘ tlaΦφtko m∞lo definovßno stavy: normßlnφ, s fokusem a stisknutΘ. Stav "nep°φstupnΘ tlaΦφtko" je takov² nadstandard:-)
16.1.3. CGLabel
A je tu dalÜφ potomek t°φdy CGItem - CGLabel. Tato t°φda p°edstavuje statick² text dopl≥ujφcφ tlaΦφtka na strßnce. Tento prvek je snad jeÜt∞ jednoduÜÜφ:
#define LABEL_STATE 0
class AFX_EXT_CLASS CGLabel : public CGItem
{
CSpriteState m_LabelState;
public:
void CreateLabel(int x, int y, CString _BMPFile);
public:
CGLabel();
virtual ~CGLabel();
};
Proto₧e ka₧d² sprite musφ mφt alespo≥ jeden stav, i zde musφme definovat stav LABEL_STATE. T°φda obsahuje pouze jednu metodu pro definici textu - jeho polohu a zdrojovou bitmapu.
void CGLabel::CreateLabel(int
x, int y, CString _BMPFile)
{
// Create one
state for each label
m_LabelState.CreateState(LABEL_STATE,
_BMPFile);
// remeber this
state
AddState(&m_LabelState);
// set position
of label
if(m_pCurState) {
if(x == IP_CENTER) {
x =
disGetResolution().cx / 2 - m_pCurState->GetSourceSurface()->Width() / 2;
}
if(y == IP_CENTER) {
y =
disGetResolution().cy / 2 - m_pCurState->GetSourceSurface()->Height() / 2;
}
}
SetPosition(CPoint(x, y));
}
Nejprve vytvo°φme stav pro objekt sprite. Vyu₧ijeme k tomu
konstantu LABEL_STATE a cestu k bitmap∞
_BMPFile. Dßle inicializujeme polohu ·pln∞ stejn²m
zp∙sobem jako u tlaΦφtka.
╪ekli jsme si, ₧e tento prvek nem∙₧e dostat fokus:
CGLabel::CGLabel()
{
SetCBFocused(FALSE);
}
Nynφ p°ejedeme ke slo₧it∞jÜφ Φßsti, ke t°φdßm CGPage a CGMenu.
16.1.4. CGPage
Tato t°φda p°edstavuje strßnku menu, kterß obsahuje pole prvk∙. Tyto prvky jsou reprezentovßny t°φdou CGItem respektive jejφmi potomky CGButton a CGLabel. Ka₧dß strßnka musφ mφt unikßtnφ ID v∙Φi objektu CGMenu tzn. v∙Φi celΘ aplikaci (objekt menu je pouze jeden). U tΘto t°φdy si op∞t zavedeme tabulku, proto₧e obsahuje vφce Φlen∙:
Atributy |
||
Typ | JmΘno | Popis |
DWORD | m_dwID | Unikßtnφ ID strßnky |
CPtrArray | m_Items | Pole prvk∙, kterΘ jsou umφst∞ny na strßnce * |
MENUPROC | ProcessFunc | Ukazatel na obslu₧nou funkci menu |
Metody | ||
Nßvratovß hodnota | JmΘno a parametry | Popis |
void | CreateItem(DWORD, CGItem*) | Metoda p°idß dalÜφ prvek na strßnku tj. ulo₧φ ukazatel na tento prvek do pole. P°itom kontroluje zda-li nenφ na strßnce prvek se stejn²m ID. |
void | ReleasePage() | Metoda pro uvoln∞nφ alokovanΘ pam∞ti apod. |
CPtrArray* | GetItems() | Vracφ ukazatel na pole prvk∙. |
void | TestMouseMove(CPoint) | Zjistφ, zda-li je kurzor myÜi nad n∞jak²m prvkem a volß metodu ProcessItem() vÜech vlo₧en²ch prvk∙. |
void | TestMouseClick(CPoint, UINT, UINT) | Tato metoda se volß pokud u₧ivatel stiskne tlaΦφtko myÜi. Po tΘ se zjistφ, jestli je kurzor nad n∞kter²m prvkem a volß se metoda ProcessItem(). |
void | UpdatePage() | Metoda slou₧φ k vykreslenφ strßnky. Postupn∞ projde vÜechny vlo₧enΘ prvky a zavolß metodu UpdateItem(). |
void | ResetPage() | Tuto metodu je nutno volat p°i p°epφnßnφ strßnek. Nastavuje u vÜech prvk∙ normßlnφ stav - pokud nenφ prvek ve stavu nep°φstupn². |
void | SetID(DWORD) | Nastavuje ID strßnky. |
DWORD | GetID() | Vracφ ID strßnky. |
Poznßmka:
* Pokud nechcete pou₧φvat pole CPtrArray
knihovny MFC, m∙₧ete nap°φklad pou₧φt lineßrnφ spojov² seznam. Zde odkazuji na
Kurz C++ pro ty, co nev∞dφ, jak takov² seznam
vytvo°it. V dneÜnφ a p°φÜtφ lekci se toti₧ dovφte jak na to.
Podrobn∞ji si metody rozebereme p°i jejich implementaci. Nejd°φve je t°eba definovat n∞kterΘ symbolickΘ konstanty.
Tyto konstanty p°edstavujφ akce prvku na strßnce. Slou₧φ k upozorn∞nφ prvku, co se s nimi vlastn∞ d∞je:
#define IA_MOUSEMOVE
0
#define IA_MOUSECLICK_DOWN 1
#define IA_MOUSECLICK_UP 2
#define IA_KEYPRESS
3
#define IA_NONE
4
P°i volßnφ metody TestMouseClick(), musφme urΦit, kterΘ tlaΦφtko bylo stisknuto a zda-li bylo prßv∞ stisknuto nebo puÜt∞no:
#define LEFT_MOUSE_BUTTON
0
#define RIGHT_MOUSE_BUTTON 1
#define BA_UP 10
#define BA_DOWN 20
Pomocnß konstanta, kterou urΦφme polohu prvku (viz. v²Üe):
#define IP_CENTER -1
Nynφ se vrhn∞me na deklaraci samotnΘ t°φdy:
class AFX_EXT_CLASS CGPage
{
//atributes
private:
DWORD m_dwID;
CPtrArray m_Items;
public:
//creating items
void CreateItem(DWORD
dwID, CGItem* _NewItem);
void ReleasePage();
CPtrArray* GetItems() {return &m_Items;}
//general
void
TestMouseMove(CPoint _Cursor);
void TestMouseClick(CPoint _Cursor, UINT _MouseButton, UINT _ButtonAction);
//drawing
void UpdatePage();
void ResetPage();
//
// Get ID of page
void SetID(DWORD
dwID) {m_dwID = dwID;}
DWORD GetID() {return m_dwID;}
public:
//callback func.
MENUPROC
ProcessFunc;
public:
CGPage();
~CGPage();
};
Zde nenφ nic neobvyklΘho. Parametry metod si rozebereme podrobn∞ji za chvilku. T°φda musφ b²t exportovßna z knihovny, proto₧e to vy₧aduje zp∙sob, jak²m vytvß°φme strom menu (mimochodem to platφ i pro t°φdy prvk∙).
ZaΦn∞me t°φdu postupn∞ implementovat. Metoda CreateItem() slou₧φ k p°ipojenφ objektu libovolnΘho prvku na strßnku:
void CGPage::CreateItem(DWORD
dwID, CGItem* _NewItem)
{
//
// Check item pointer
if(_NewItem == NULL) {
DXTHROW("Pointer to page is NULL.");
}
CGItem* pItem;
// Check if the
item with ID is not already on the page
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
if(pItem->GetID() == dwID) {
DXTHROW("Item
is already on the page.");
}
}
// Set some
atributes
_NewItem->SetID(dwID);
_NewItem->ProcessFunc = ProcessFunc;
// Add item
pointer
m_Items.Add(_NewItem);
// Show item
pointer
DXTRACE("Creating item. ID: %d\tPointer: 0x%X", dwID, int(_NewItem));
}
Nejprve otestujeme vstupnφ parametry. Pokud zvenku u₧ivatel poÜle mφsto platnΘho ukazatele na prvek NULL, metoda vyhodφ v²jimku. Pokud se u₧ivatel pokusφ vlo₧it dva prvky se stejn²m ID na jednu strßnku, metoda op∞t vyhodφ v²jimku. Pokud tyto vstupnφ parametry prob∞hnou v po°ßdku, nastavφ se ID novΘho prvku a ukazatel na obslu₧nou funkci menu. Nakonec se ukazatel prvku ulo₧φ do pole.
Poznßmka: JeÜt∞ se zmφnφm o chytßnφ v²jimek (o nich se podrobn∞ dovφte v p°φÜtφ lekci Kurzu C++). V naÜem p°φpad∞ vyhazujeme °et∞zec, kter² zachytφte nßsledovn∞:
try
{
VolaniFunkceKteraVyahazujeVyjimku()
}
catch(LPCSTR str) {
DXTRACE(str);
}
Po skonΦenφ aplikace musφme pole prvk∙ uvolnit. To za°φdφ metoda ReleasePage(), kterß je volßna z destruktoru strßnky:
void CGPage::ReleasePage()
{
CGItem *pItem;
DXTRACE("Page has %d item(s).", m_Items.GetSize());
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
DXTRACE("Deleting item. ID: %d\tPointer:
0x%X", pItem->GetID(), int(pItem));
SAFE_DELETE(pItem);
}
//
// Remove items from array
m_Items.RemoveAll();
}
Projdeme celΘ pole a sma₧eme (dealokujeme) vÜechny prvky, kterΘ alokuje u₧ivatel - z toho plyne, ₧e prvky musφ b²t alokovßny dynamicky. KonkrΘtnφ zp∙sob, jak budeme prvky a strßnky menu vytvß°et, si ukß₧eme na zßv∞r tΘto lekce.
Dßle tu mßme dvojici metod, kterΘ m∞nφ stavy prvk∙ podle stavu myÜi. Za prvΘ podle polohy kurzoru a za druhΘ podle stavu tlaΦφtek.
void CGPage::TestMouseMove(CPoint
_Cursor)
{
CGItem * pItem = NULL;
CRect rcDestin;
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
pItem->GetDestin(&rcDestin);
if(pItem && rcDestin.PtInRect(_Cursor))
{
pItem->ProcessItem(this,
0, IA_MOUSEMOVE);
}
else {
pItem->ProcessItem(this,
0, IA_NONE);
}
}
}
Prvnφ z t∞chto metod testuje prßv∞ pohyb myÜi a pokud se n∞jak² prvek ocitne pod kurzorem, volß metodu ProcessItem() s parametrem IA_MOUSEMOVE. Tak prvek poznß, ₧e mß nastavit fokus. Pro ostatnφ prvky musφme volat metodu s parametrem IA_NONE, proto₧e se s nimi ned∞je nic.
Dßle budeme testovat stisk tlaΦφtka. To u₧ bude o trochu slo₧it∞jÜφ, proto₧e musφme rozliÜit jakΘ tlaΦφtko bylo stisknuto a zda-li bylo prßv∞ stisknuto nebo puÜt∞no:
void CGPage::TestMouseClick(CPoint
_Cursor, UINT _MouseButton, UINT _ButtonAction)
{
CGItem * pItem = NULL;
CRect rcDestin;
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
pItem->GetDestin(&rcDestin);
if(rcDestin.PtInRect(_Cursor)) {
if(_ButtonAction
== BA_UP) {
pItem->ProcessItem(this, (void*)&_MouseButton, IA_MOUSECLICK_UP);
}
if(_ButtonAction
== BA_DOWN) {
pItem->ProcessItem(this, (void*)&_MouseButton, IA_MOUSECLICK_DOWN);
}
}
else {
pItem->ProcessItem(this,
(void*)&_MouseButton, IA_NONE);
}
}
}
K tomu slou₧φ dva poslednφ parametry metody. Op∞t prochßzφme vÜechny prvky a testujeme, zda-li nenφ prvek pod kurzorem. Pokud ano, poÜleme tlaΦφtku zprßvu, ₧e bylo stisknuto. Zde ovÜem musφme rozliÜit pravΘ a levΘ tlaΦφtko - _MouseButton m∙₧e nab²vat dvou hodnot: LEFT_MOUSE_BUTTON nebo RIGHT_MOUSE_BUTTON. Parametr _ButtonAction °φkß, zda-li tlaΦφtko bylo stisknuto nebo puÜt∞no. Podle toho prvku poÜleme bu∩ akci IA_MOUSECLICK_DOWN nebo UP. To je vÜe.
Strßnka se musφ v ka₧dΘm cyklu obnovit - vykreslit. To za°φdφ metoda UdpatePage():
void CGPage::UpdatePage()
{
CGItem *pItem;
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
pItem->UpdateItem();
}
}
JednoduÜe projde vÜechny prvky na strßnce a volß metody UpdateItem().
Nakonec tu mßme metodu, kterou jsem ji₧ umφnil v²Üe. Jednß se o metodu ResetPage(), kterß nastavφ vÜem aktivnφm (to znamenß ne nep°φstupn²m) prvk∙m zßkladnφ stav s ID = 0. Je to t°eba ud∞lat p°ed tφm, ne₧ zm∞nφme aktußlnφ strßnku menu.
void CGPage::ResetPage()
{
CGItem * pItem = NULL;
for(int i = 0; i < m_Items.GetSize(); i++) {
pItem = (CGItem*) m_Items[i];
if(pItem->GetState()->GetID() != BS_DISABLE)
{
pItem->SetState(0);
}
}
}
Projdeme vÜechny prvky na strßnce a pokud jsou aktivnφ, nastavφme stav 0. To m∙₧eme ud∞lat, proto₧e tlaΦφtko musφ mφt nastaven alespo≥ normßlnφ stav 0 a objekt label mß rovn∞₧ stav s ID = 0.
16.1.5. CGMenu
KoneΦn∞ tu mßme poslednφ t°φdu CGMenu, kterß je v mnoha ohledech podobnß t°φd∞ CGPage, jen nepracuje s prvky, ale se strßnkami tj. s objekty CGPage.
Atributy |
||
Typ | JmΘno | Popis |
BOOL | m_bInit | - |
CPtrArray | m_Pages | Pole objekt∙ CGPage. Zde jsou ulo₧eny vÜechny strßnky menu. P°epφnat je lze pomocφ metody SetCurrentPage(). |
CGPage* | m_pCurrentPage | Ukazatel na aktußlnφ strßnku, tato strßnka se vykresluje. |
MENUPROC | ProcessFunc | Ukazatel na obslu₧nou funkci menu. Tento ukazatel se nastavuje pomocφ metody InitMenu() a je pak p°edßvßn vÜem strßnkßm a poslΘze u prvk∙m. |
Metody | ||
Nßvratovß hodnota | JmΘno a parametry | Popis |
void | CreatePage(DWORD, CGPage*) | P°idß novou strßnku do systΘmu menu. Ov∞°φ takΘ ID strßnky. V menu samoz°ejm∞ nesmφ b²t dv∞ strßnky se stejn² ID. |
CGPage* | GetPage(DWORD) | Vracφ ukazatel na strßnku podle ID strßnky. Pokud strßnka v menu nenφ, vracφ NULL. |
HRESULT | InitMenu(MENUPROC) | InicializaΦnφ metoda pro objekt CGMenu. Nastavuje obslu₧nou funkci. |
HRESULT | DefineProcessFunc(MENUPROC) | Tato metoda slou₧φ k nastavenφ a otestovßnφ obslu₧nΘ metody, kterß musφ vracet 0, kdy₧ se poÜle NULL v prvnφm parametru. |
void | SetCurrentPage(DWORD) | Nastavφ viditelnou strßnku podle ID. |
CGPage* | GetCurrentPage() | Vracφ ukazatel na viditelnou strßnku. M∙₧e b²t i NULL. |
void | ReleaseMenu() | Uvolnφ alokovanou pam∞¥ pro strßnky a prvky. |
void | UpdateMenu() | Volß metodu UpdatePage() aktußlnφ strßnky. |
void | TestMouseMove(CPoint) | Volß metodu TestMouseMove() aktußlnφ strßnky, pokud n∞jakß je. |
void | TestMouseClick(CPoint, UINT, UINT) | Volß metodu TestMouseClick() aktußlnφ strßnky. |
Podφvejme se na deklaraci t°φdy:
class CGMenu
{
//private
atributes
private:
BOOL m_bInit;
CPtrArray m_Pages;
CGPage *m_pCurrentPage;
public:
//callback func.
MENUPROC
ProcessFunc;
//public fuc.
public:
CGPage* GetCurrentPage() {if(m_pCurrentPage) { return m_pCurrentPage;}
void SetCurrentPage(DWORD dwID);
//init menu
HRESULT InitMenu(MENUPROC
_ProcessFunc);
HRESULT DefineProcessFunc(MENUPROC _ProcessFunc);
void ReleaseMenu();
//pages
void
CreatePage(DWORD dwID, CGPage *_NewPage);
CGPage* GetPage(DWORD dwID);
//general
void UpdateMenu();
void TestMouseMove(CPoint _Cursor);
void TestMouseClick(CPoint _Cursor, UINT _MouseButton, UINT _ButtonAction);
public:
CGMenu();
~CGMenu();
};
Tuto t°φdu nemusφme exportovat, proto₧e s nφ nebudeme pracovat p°φmo, ale prost°ednictvφm exportovan²ch funkcφ. Tyto funkce definujeme za chviliΦku.
Nejprve musφme zavolat metodu InitMenu():
HRESULT CGMenu::InitMenu(MENUPROC
_ProcessFunc)
{
DXTRACE("Initializing menu...");
//
// Menu is initilized
m_bInit = TRUE;
// Callback menu
function is OK.
return
DefineProcessFunc(_ProcessFunc);
}
Tato metoda musφ otestovat sprßvnost obslu₧nΘ funkce pro
menu. K tomu slou₧φ metoda DefineProcessFunc():
HRESULT CGMenu::DefineProcessFunc(MENUPROC
_ProcessFunc)
{
//
// Save pointer to call back function of menu
ProcessFunc = _ProcessFunc;
// Check function
if(ProcessFunc((void*) NULL, 0, 0) != ERROR_SUCCESS) {
DXTHROW("Menu process function is not
valid. Must return ERROR_SUCCESS(0).");
}
return ERROR_SUCCESS;
}
Pokud obslu₧nß funkce vrßtφ n∞co jinΘho ne₧ hodnotu ERROR_SUCCESS, metoda vyhodφ v²jimku. Pokud jako prvnφ nezavolßte metody InitMenu(), vÜechny ostatnφ metody menu vßm budou vyhazovat v²jimky.
Nßsledujφcφ metoda p°idß novou strßnku do menu:
void CGMenu::CreatePage(DWORD
dwID, CGPage *_NewPage)
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
// Check input
parametr
if(_NewPage ==
NULL) {
DXTHROW("NULL page passed.");
}
// Check if the
page is not already in the page array
CGPage *pRet;
for(int i = 0; i < m_Pages.GetSize(); i++) {
pRet = (CGPage*) m_Pages[i];
if(pRet->GetID() == dwID) {
DXTHROW("Page
is already in the menu system.");
}
}
//
// Set some atrributes of the page
_NewPage->SetID(dwID);
_NewPage->ProcessFunc = ProcessFunc;
//
// Add page
m_Pages.Add(_NewPage);
//
// Show adrress fo the new page
DXTRACE("Creating
page. ID: %d\tPointer: 0x%X", dwID, int(_NewPage));
//
// Set current page if is not page is set
if(m_pCurrentPage ==
NULL) {
m_pCurrentPage = _NewPage;
}
}
Prvnφm parametrem urΦφme ID novΘ strßnky kterΘ musφ b²t unikßtnφ, jinak metoda vyhodφ v²jimku. Druh² parametr je ukazatel na vlastnφ strßnku. Tento ukazatel musφme inicializovat p°edem, jinak metoda op∞t vyhodφ v²jimku. PotΘ do objektu strßnky ulo₧φme jejφ ID a ukazatel na obslu₧nou funkci menu. Dßle ulo₧φme ukazatel na strßnku. Na zßv∞r jeÜt∞ zinicializujeme ukazatel na aktußlnφ strßnku, pokud je NULL tj. p°i prvnφm vlo₧enφ, abychom nemuseli explicitn∞ volat metodu SetCurrentPage().
Metoda GetPage() vybere z pole po₧adovanou strßnku podle ID a vrßtφ jejφ ukazatel. Pokud se strßnka v menu nenachßzφ, vracφ NULL.
CGPage* CGMenu::GetPage(DWORD
dwID)
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
CGPage *pRet = NULL;
//
// Find page in the array and return pointer at it
for(int i = 0; i < m_Pages.GetSize(); i++) {
pRet = (CGPage*) m_Pages[i];
// Check page's IDs
if(pRet->GetID() == dwID) {
break;
}
}
return pRet;
}
Zde nenφ co °eÜit, podobn² k≤d jsme psali ji₧ n∞kolikrßt.
Metoda UpdateMenu() slou₧φ k obnovenφ aktußlnφ strßnky. StaΦφ tedy zavolat metodu UpdatePage() pro aktußlnφ viditelnou strßnku:
void CGMenu::UpdateMenu()
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
//
// If some page is selected, update this page
if(m_pCurrentPage) {
m_pCurrentPage->UpdatePage();
}
}
Za povÜimnutφ snad jen stojφ kontrola inicializace menu (toho si ostatn∞ vÜimnete u vÜech metodu CGMenu).
Nßsleduje dvojice velice jednoduch²ch metod, kterΘ pouze volajφ tytΘ₧ metody pro aktußlnφ strßnku:
void CGMenu::TestMouseMove(CPoint
_Cursor)
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
if(m_pCurrentPage) {
m_pCurrentPage->TestMouseMove(_Cursor);
}
}
void CGMenu::TestMouseClick(CPoint _Cursor, UINT _MouseButton, UINT _ButtonAction)
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
if(m_pCurrentPage) {
m_pCurrentPage->TestMouseClick(_Cursor,
_MouseButton, _ButtonAction);
}
}
O co si tyto metody v principu starajφ, jsem popsal o n∞co v²Üe.
Poslednφ metoda je velice d∙le₧itß, proto₧e nßm dovoluje zm∞nit aktußlnφ strßnku:
void CGMenu::SetCurrentPage(DWORD
dwID)
{
//
// Check initialization of menu
if(!m_bInit) {
DXTHROW("Menu is not initialzed. Call
menInitMenu() first.");
}
CGPage *pRet = NULL;
//
// Set normal states for all items
m_pCurrentPage->ResetPage();
// Find page in
the array
for(int i = 0; i < m_Pages.GetSize(); i++) {
pRet = (CGPage*) m_Pages[i];
// Check IDs
if(pRet->GetID() == dwID && !(pRet ==
m_pCurrentPage)) {
// Set current page
m_pCurrentPage
= pRet;
return;
}
}
DXTRACE("Specified page is not defined");
}
Nejprve vyhledßme v poli po₧adovanou strßnku podle ID (pokud strßnka neexistuje, metoda neud∞lß krom∞ vypsßnφ hlßÜky nic). Pokud ovÜem strßnku najde resetuje aktußlnφ strßnku a potΘ zm∞nφ ukazatel na novou stranu menu. Tato strana se vykreslφ v dalÜφm cyklu aplikace.
16.1.6. Export funkcφ
Jak jsem se ji₧ zmφnil, objekt CGMenu vytvo°φme p°φmo v knihovn∞ a pracovat s nφm budeme pomocφ exportovan²ch funkcφ. Seznam exportovan²ch funkcφ je nßsledujφcφ:
MENU_API HRESULT
menInitMenu(MENUPROC _ProcessFunc);
MENU_API void menUpdateMenu();
MENU_API void menTestMouseMove(CPoint _Cursor);
MENU_API void menTestMouseClick(CPoint _Cursor, UINT _MouseButton,
UINT _ButtonAction);
MENU_API void menCreatePage(DWORD dwID, CGPage *_NewPage);
MENU_API void menSetVisiblePage(int PageID);
MENU_API void menReleaseMenu();
P°iΦem₧ v²taz MENU_API musφte definovat dv∞ma rozliÜn²mi zp∙soby. V hlaviΦkovΘm souboru menu takto:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef MENU_API
#define MENU_API __declspec( dllimport )
#endif // MENU_API
#include "gpage.h"
class CGMenu
A v implementaΦnφm souboru takto:
#include "stdafx.h"
#define MENU_API __declspec(dllexport)
#include "..\Common\Common.h"
#include "GMenu.h"
Tento zßpis za°φdφ, ₧e funkce budou exportovßny z knihovny a importovßny do
projektu Game. Takov² zp∙sob exportu funkcφ jsme ji₧ pou₧ili v minul²ch
projektech.
Na zßv∞r lekce jeÜt∞ drobn∞ upravφme projekt Game, aby bylo vid∞t n∞co z toho, o co jsme se celou dobu pokouÜeli. Zkrßtka vybudujeme jednoduch² strom menu.
Za prvΘ musφme vlo₧it soubor GMenu.h do hlavnφho implementaΦnφ ho souboru Game.cpp:
#include "..\Engine\GMenu.h"
Dßle nadefinujeme ID pro strßnky a prvky menu:
#define PAGE_MAIN 0
#define PAGE_EXIT 1
#define PAGE_OPTIONS 2
#define BUTTON_STARTGAME 0
#define BUTTON_OPTIONS 1
#define BUTTON_CREDITS 2
#define BUTTON_EXIT 3
#define LABEL_ROCKET7 4
#define BUTTON_YES 5
#define BUTTON_NO 6
#define LABEL_AREYOUSURE 7
#define BUTTON_DONE 8
#define LABEL_CONTROLS 9
Vidφte, ₧e budeme mφt t°i strßnky - hlavnφ, odchozφ a nastavenφ.
Ve funkci WinMain() musφme zinicializovat menu. K tomuto ·Φelu vytvo°φme funkci InitMenu(), kterou si popφÜeme dßle. Do WinMain() p°ipiÜte modr² °ßdek:
DXERR("Cannot open data file due", dwRet);
return dwRet;
}
disInit(g_hWnd, DDFLG_CLIPPER|DDFLG_FULLSCREEN);
inpCreateDirectInputSystem(hInstance, g_hWnd,
disGetResolution());
disDefineBackground(_S_BACKGROUND, 0);
InitMenu();
// Run
message loop
while( TRUE )
{
// Look for messages, if none are
found then
// update the state and display it
A jak tedy bude vypadat InitMenu():
Nejprve volßme funkci menInitMenu(), jak jsme si popisovali p°ed chvilkou:
HRESULT dwRet;
dwRet = menInitMenu(ProcessMenu);
if(dwRet != ERROR_SUCCESS) {
DXERR("Cannot init menu due ", dwRet);
return;
}Dßle vytvo°φme prvnφ hlavnφ strßnku a prvky na nφ:
CGPage * p_pMain = new CGPage;
menCreatePage(PAGE_MAIN, p_pMain);
CGLabel *l_pLabel1 = new CGLabel;
l_pLabel1->CreateLabel(IP_CENTER, 130, "\\Graphics\\Menu\\Labels\\rocket7.bmp");
p_pMain->CreateItem(LABEL_ROCKET7, l_pLabel1);
CGButton *b_pNewGame = new CGButton;
b_pNewGame->CreateButton(IP_CENTER, 230, "\\Graphics\\Menu\\Buttons\\startgame_clear.bmp", "\\Graphics\\Menu\\Buttons\\startgame_focus.bmp", "\\Graphics\\Menu\\Buttons\\startgame_press.bmp", "\\Graphics\\Menu\\Buttons\\startgame_dis.bmp");
p_pMain->CreateItem(BUTTON_STARTGAME, b_pNewGame);
b_pNewGame->Disable();
CGButton *b_pOptions = new CGButton;
b_pOptions->CreateButton(IP_CENTER, 280, "\\Graphics\\Menu\\Buttons\\options_clear.bmp", "\\Graphics\\Menu\\Buttons\\options_focus.bmp","\\Graphics\\Menu\\Buttons\\options_press.bmp");
p_pMain->CreateItem(BUTTON_OPTIONS, b_pOptions);
CGButton *b_pCredits = new CGButton;
b_pCredits->CreateButton(IP_CENTER, 330, "\\Graphics\\Menu\\Buttons\\credits_clear.bmp", "\\Graphics\\Menu\\Buttons\\credits_focus.bmp", "\\Graphics\\Menu\\Buttons\\credits_press.bmp", "\\Graphics\\Menu\\Buttons\\credits_dis.bmp");
p_pMain->CreateItem(BUTTON_CREDITS, b_pCredits);
b_pCredits->Disable();
CGButton *b_pExit = new CGButton;
b_pExit->CreateButton(IP_CENTER, 380, "\\Graphics\\Menu\\Buttons\\exitgame_clear.bmp", "\\Graphics\\Menu\\Buttons\\exitgame_focus.bmp", "\\Graphics\\Menu\\Buttons\\exitgame_press.bmp");
p_pMain->CreateItem(BUTTON_EXIT, b_pExit);
Pak tu mßme odchozφ strßnku:
CGPage * p_pExit = new CGPage;
menCreatePage(PAGE_EXIT, p_pExit);
CGLabel *l_pLabel2 = new CGLabel;
l_pLabel2->CreateLabel(IP_CENTER, 130, "\\Graphics\\Menu\\Labels\\areyousure1.bmp");
p_pExit->CreateItem(LABEL_AREYOUSURE, l_pLabel2);
CGButton *b_pYes = new CGButton;
b_pYes->CreateButton(IP_CENTER, 230, "\\Graphics\\Menu\\Buttons\\yes_clear.bmp", "\\Graphics\\Menu\\Buttons\\yes_focus.bmp", "\\Graphics\\Menu\\Buttons\\yes_press.bmp");
p_pExit->CreateItem(BUTTON_YES, b_pYes);
CGButton *b_pNo = new CGButton;
b_pNo->CreateButton(IP_CENTER, 280, "\\Graphics\\Menu\\Buttons\\no_clear.bmp", "\\Graphics\\Menu\\Buttons\\no_focus.bmp", "\\Graphics\\Menu\\Buttons\\no_press.bmp");
p_pExit->CreateItem(BUTTON_NO, b_pNo);
A nakonec strßnku s nastavenφm:
CGPage * p_pOptions = new CGPage;
menCreatePage(PAGE_OPTIONS, p_pOptions);
CGButton *b_pDone = new CGButton;
b_pDone->CreateButton(IP_CENTER, 450, "\\Graphics\\Menu\\Buttons\\done_clear.bmp", "\\Graphics\\Menu\\Buttons\\done_focus.bmp", "\\Graphics\\Menu\\Buttons\\done_press.bmp");
p_pOptions->CreateItem(BUTTON_DONE, b_pDone);
CGLabel *l_pLabel3 = new CGLabel;
l_pLabel3->CreateLabel(IP_CENTER, 80, "\\Graphics\\Menu\\Labels\\controls.bmp");
p_pOptions->CreateItem(LABEL_CONTROLS, l_pLabel3);
Princip funkce je velice jednoduch² a hlavn∞ se po°ßd
opakuje. V₧dy musφme alokovat mφsto pro novou strßnku nebo prvek. Pak volßme
inicializaΦnφ metody a¥ u₧ tlaΦφtka nebo textu (u strßnky nemusφme nastavovat
nic). Nakonec musφme ulo₧it ukazatel na strßnku nebo prvek. To provßdφ metody
CreatePage() nebo CreateItem(),
kde zßrove≥ urΦφme ID objektu. Vertikßlnφ sou°adnice urΦuji absolutn∞, to
znamenß, ₧e p°i jinΘm rozliÜenφ m∙₧e dojφt k menÜφ kolizi.
DalÜφ funkce, kterou musφme vytvo°it, je obslu₧nß funkce pro
menu. Tu musφme deklarovat takto:
HRESULT CALLBACK
ProcessMenu(void *_Page, void *_Item, UINT _Action);
A naÜe definice vypadß takto:
HRESULT CALLBACK
ProcessMenu(void *_Page, void *_Item, UINT _Action)
{
CGPage* Page = (CGPage*) _Page;
CGItem* Item = (CGItem*) _Item;
if(!Page) {
return ERROR_SUCCESS;
}
if(Item->GetState()->GetID() != BS_DISABLE) {
switch(Page->GetID()) {
case PAGE_MAIN:
case BUTTON_OPTIONS:
if(_Action == IA_MOUSECLICK_UP) {
menSetVisiblePage(PAGE_OPTIONS);
}
break;
case BUTTON_EXIT: // EXIT GAME
if(_Action == IA_MOUSECLICK_UP) {
menSetVisiblePage(PAGE_EXIT);
}
break;
}
break;
case PAGE_EXIT:
switch(Item->GetID())
{
case BUTTON_YES:
if(_Action == IA_MOUSECLICK_UP) {
PostMessage(g_hWnd, WM_CLOSE, 0, 0);
}
break;
case BUTTON_NO:
if(_Action == IA_MOUSECLICK_UP) {
menSetVisiblePage(PAGE_MAIN);
}
break;
}
break;
case PAGE_OPTIONS:
switch(Item->GetID())
{
case BUTTON_DONE:
if(_Action == IA_MOUSECLICK_UP) {
menSetVisiblePage(PAGE_MAIN);
}
break;
}
break;
}
}
return 0;
}
Za prvΘ si p°etypujeme ukazatele na strßnku a prvek, abychom s nimi mohli rovnou pracovat jako s objekty CGPage a CGItem. Dßle musφme vrßtit ERROR_SUCCESS pokud je ukazatel na strßnku NULL (to je urΦeno pro testovßnφ funkce). Rozd∞lφme si funkci na bloky-strßnky a ka₧d² tento blok jeÜt∞ rozd∞lφme na podbloky-prvky (tlaΦφtka). O prvek se budeme starat jen tehdy, pokud nenφ v nep°φstupnΘm stavu.
Na ·pln² zßv∞r jeÜt∞ lehce modifikujeme metodu UpdateFrame():
void UpdateFrame()
{
disUpdateBackground();
inpProcessInput();
// Pri stisknuti klavesy Esc ukoncime aplikaci
if(inpIsKeyDown(DIK_ESCAPE, FALSE)) {
PostMessage(g_hWnd, WM_DESTROY, 0,
0);
}
menTestMouseMove(inpGetCursor());
if(inpIsLButtonDown()) {
menTestMouseClick(inpGetCursor(),
LEFT_MOUSE_BUTTON, BA_DOWN);
}
if(inpIsLButtonUp()) {
menTestMouseClick(inpGetCursor(),
LEFT_MOUSE_BUTTON, BA_UP);
}
menUpdateMenu();
inpUpdateCursor();
disPresent();
}
Zde musφme za prvΘ testovat pohyb myÜi a takΘ stisk levΘho tlaΦφtka. Samoz°ejm∞ takΘ musφme menu vykreslit pomocφ funkce menUpdateMenu().
Tak a jsme u konce naÜeho mega-p°φkladu. Aplikace je tvo°ena tak, aby Üla libovoln∞ rozÜφ°it nejen o dalÜφ prvky menu, ale i o dalÜφ mo₧nosti.
SpuÜt∞nß aplikace by mohla vypadat n∞jak takto:
Grafika je pou₧itß z mΘho p°edeÜlΘho projektu a najdete ji v datovΘm souboru na CD. Grafiku vytvo°il Michal BuriÜin.
V p°φÜtφ lekci bych cht∞l p°idat jeÜt∞ jednu knihovnu Audio.dll. Pomocφ tΘto knihovny zakomponujeme do naÜeho p°φkladu zvuk a hudbu. Knihovna vyu₧φvß komponentu DirectMusic.
T∞Üφm se p°φÜt∞ nashledanou.