DirectX (16.)

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.

16.1. Knihovna Engine.dll

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.

16.2. Projekt Game

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().

16.3. Zßv∞r

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.

Ji°φ Formßnek