Sdílet prostřednictvím


TN021: Směrování příkazů a zpráv

Poznámka:

Následující technická poznámka se od prvního zahrnutí do online dokumentace neaktualizovala. V důsledku toho můžou být některé postupy a témata zastaralé nebo nesprávné. Nejnovější informace doporučujeme vyhledat v online indexu dokumentace, které vás zajímá.

Tato poznámka popisuje architekturu směrování a odesílání příkazů a také pokročilá témata v obecném směrování zpráv okna.

Obecné podrobnosti o zde popsaných architekturách najdete v jazyce Visual C++, zejména rozdílu mezi zprávami Windows, oznámeními o řízení a příkazy. Tato poznámka předpokládá, že jste velmi obeznámeni s problémy popsanými v tištěné dokumentaci a řeší pouze velmi pokročilá témata.

Funkce prostředí MFC 1.0 směrování příkazů a odesílání se vyvíjejí v architektuře MFC 2.0.

Systém Windows má WM_COMMAND zprávu, která je přetížená tak, aby poskytovala oznámení o příkazech nabídky, klávesách akcelerátoru a oznámeních řízení dialogových oken.

MFC 1.0 vychází z toho trochu tím, že povolí obslužnou rutinu příkazu (například "OnFileNew") v CWnd odvozené třídě, aby se volala v reakci na konkrétní WM_COMMAND. To je připevněno společně s datovou strukturou označovanou jako mapa zpráv a výsledkem je velmi prostorově efektivní mechanismus příkazů.

MFC 1.0 také poskytuje další funkce pro oddělení oznámení ovládacích prvků od zpráv příkazů. Příkazy jsou reprezentovány 16bitovým ID, někdy označovaným jako ID příkazu. Příkazy obvykle začínají z CFrameWnd (tj. výběr nabídky nebo přeloženého akcelerátoru) a jsou směrovány na řadu dalších oken.

MFC 1.0 používá směrování příkazů v omezeném smyslu pro implementaci rozhraní MDI (Multiple Document Interface). (Okno rámce MDI deleguje příkazy do aktivního podřízeného okna MDI.)

Tato funkce byla zobecněna a rozšířena v prostředí MFC 2.0, aby příkazy byly zpracovány širším rozsahem objektů (nejen objekty okna). Poskytuje formální a rozšiřitelnější architekturu pro směrování zpráv a opakovaně používá směrování cíle příkazu nejen pro zpracování příkazů, ale také pro aktualizaci objektů uživatelského rozhraní (například položek nabídky a tlačítek panelu nástrojů) tak, aby odrážela aktuální dostupnost příkazu.

ID příkazů

Vysvětlení procesu směrování a vazby příkazů najdete v jazyce Visual C++. Technická poznámka 20 obsahuje informace o pojmenování ID.

Pro ID příkazů používáme obecnou předponu "ID_". ID příkazů jsou >= 0x8000. Na řádku zprávy nebo stavovém řádku se zobrazí řetězec popisu příkazu, pokud existuje prostředek STRINGTABLE se stejnými ID jako ID příkazu.

V prostředcích vaší aplikace se ID příkazu může zobrazit na několika místech:

  • V jednom prostředku STRINGTABLE, který má stejné ID jako výzva na řádku zprávy.

  • V mnoha prostředcích menu, které jsou připojeny k položkám menu, jež vyvolávají stejný příkaz.

  • (POKROČILÉ) v dialogovém okně pro příkaz GOSUB.

Ve zdrojovém kódu aplikace se ID příkazu může zobrazit na několika místech:

  • Ve vašem PROSTŘEDKU. H (nebo jiný soubor hlaviček hlavního symbolu) k definování ID příkazů specifických pro aplikaci.

  • MOŽNÁ v poli ID použitém k vytvoření panelu nástrojů.

  • V makru ON_COMMAND.

  • Možná v ON_UPDATE_COMMAND_UI makru.

V současné době je jedinou implementací v prostředí MFC, která vyžaduje ID příkazů = >0x8000 je implementace dialogových oken nebo příkazů GOSUB.

Příkazy GOSUB využívající architekturu příkazů v dialogových oknech

Architektura směrování a umožnění příkazů funguje dobře s okny rámu, položkami nabídky, tlačítky panelu nástrojů, tlačítky na dialogovém panelu, dalšími ovládacími panely a dalšími prvky uživatelského rozhraní navrženými tak, aby se na vyžádání aktualizovaly a směrovaly příkazy nebo ID ovládacích prvků do hlavního cíle příkazu (obvykle okna hlavního rámu). Tento hlavní cíl příkazu může podle potřeby směrovat oznámení příkazu nebo řízení na jiné cílové objekty příkazu.

Dialogové okno (modální nebo nemodální) může těžit z některých funkcí architektury příkazů, pokud přiřadíte ID dialogového ovládacího prvku příslušnému ID příkazu. Podpora dialogových oken není automatická, takže možná budete muset napsat nějaký další kód.

Všimněte si, že aby všechny tyto funkce správně fungovaly, měly by být >ID příkazů = 0x8000. Vzhledem k tomu, že mnoho dialogů by mohlo být směrováno na stejný rámeček, mělo by být >= 0x8000 pro sdílené příkazy, zatímco nesdílené ID v konkrétním dialogovém okně by mělo být <= 0x7FFF.

Do normálního modálního dialogového okna můžete umístit běžné tlačítko s IDC tlačítka nastaveným na příslušné ID příkazu. Když uživatel tlačítko vybere, vlastník dialogového okna (obvykle hlavní okno rámce) získá příkaz stejně jako jakýkoli jiný příkaz. Tomu se říká příkaz GOSUB, protože se obvykle používá k vyvolání dalšího dialogového okna (GOSUB prvního dialogového okna).

Funkci CWnd::UpdateDialogControls můžete volat také v dialogovém okně a předat jí adresu okna hlavního rámce. Tato funkce povolí nebo zakáže ovládací prvky dialogového okna podle toho, zda mají obslužné rutiny příkazů ve struktuře rámce. Tato funkce se volá automaticky pro ovládací panely v nečinné smyčce vaší aplikace, ale musíte ji volat přímo pro normální dialogy, u kterých chcete tuto funkci.

Při zavolání ON_UPDATE_COMMAND_UI

Udržování povoleného nebo zaškrtnutého stavu všech položek nabídky programu může být neustále náročným problémem. Běžnou technikou je povolit nebo zkontrolovat položky nabídky pouze tehdy, když uživatel vybere popUP. Implementace MFC 2.0 CFrameWnd zajišťuje zpracování zprávy WM_INITMENUPOPUP a využívá architekturu směrování příkazů ke stanovení stavů nabídek pomocí obslužných rutin ON_UPDATE_COMMAND_UI.

CFrameWnd rovněž zpracovává zprávu WM_ENTERIDLE, která popisuje aktuální položku nabídky vybranou na stavovém řádku (známém také jako řádek zpráv).

Struktura nabídek aplikace upravená nástrojem Visual C++ se používá k zobrazení potenciálních příkazů dostupných v době WM_INITMENUPOPUP. ON_UPDATE_COMMAND_UI obslužné rutiny mohou změnit stav nebo text nabídky nebo u pokročilejších použití, jako je seznam nedávno použitých souborů (MRU) nebo vyskakovací nabídka OLE sloves, skutečně změnit strukturu nabídky před jejím vykreslením.

Stejný druh zpracování ON_UPDATE_COMMAND_UI se provádí pro panely nástrojů (a další řídicí panely), když aplikace vstoupí do smyčky nečinnosti. Další informace o ovládacích panelech najdete v referenčních informacích k knihovně tříd a technické poznámce 31 .

Vnořené místní nabídky

Pokud používáte strukturu vnořených nabídek, všimnete si, že obslužná rutina ON_UPDATE_COMMAND_UI první položky nabídky v kontextové nabídce se volá ve dvou různých případech.

Nejprve se volá samotná místní nabídka. To je nezbytné, protože místní nabídky nemají ID a my používáme ID první položky místní nabídky pro odkaz na celou místní nabídku. V tomto případě bude členová proměnná m_pSubMenu objektu CCmdUI nenulová a bude ukazovat na kontextovou nabídku.

Za druhé se volá těsně před tím, než se položky nabídky v místní nabídce nakreslí. V tomto případě ID odkazuje pouze na první položku nabídky a m_pSubMenu členské proměnné objektu CCmdUI bude NULL.

To umožňuje povolit samostatnou místní nabídku oddělenou od položek nabídky, ale je potřeba napsat kód zohledňující nabídku. Například v vnořené nabídce s následující strukturou:

File>
    New>
    Sheet (ID_NEW_SHEET)
    Chart (ID_NEW_CHART)

Příkazy ID_NEW_SHEET a ID_NEW_CHART lze nezávisle povolit nebo zakázat. Pokud je jeden z těchto dvou povolen, měla by být povolena místní nabídka Nový.

Obslužná funkce příkazu pro ID_NEW_SHEET (první příkaz v automaticky otevírané nabídce) by vypadala takto nějak:

void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
    if (pCmdUI->m_pSubMenu != NULL)
    {
        // enable entire pop-up for "New" sheet and chart
        BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
        // CCmdUI::Enable is a no-op for this case, so we
        // must do what it would have done.
        pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
            MF_BYPOSITION |
            (bEnable  MF_ENABLED : (MF_DISABLED | MF_GRAYED)));

        return;
    }
    // otherwise just the New Sheet command
    pCmdUI->Enable(m_bCanCreateSheet);
}

Obslužná rutina příkazu pro ID_NEW_CHART by byla normální obslužnou rutinou příkazu aktualizace a vypadala by nějak takto:

void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bCanCreateChart);
}

ON_COMMAND a ON_BN_CLICKED

Makra mapy zpráv pro ON_COMMAND a ON_BN_CLICKED jsou stejná. Mechanismus směrování oznámení a příkazů MFC používá pouze ID příkazu k rozhodnutí, kam se má směrovat. Oznámení s kódem ovládacího prvku nula (BN_CLICKED) jsou interpretována jako příkazy.

Poznámka:

Ve skutečnosti všechny notifikační zprávy ovládacích prvků procházejí obslužným řetězcem příkazů. Technicky možné je například napsat obslužnou rutinu oznámení ovládacího prvku pro EN_CHANGE ve třídě dokumentu. To obecně nedoporučujeme, protože praktické aplikace této funkce jsou jen málo, funkce není podporována ClassWizard a použití této funkce může vést k křehkému kódu.

Zakázání automatického zakázání ovládacích prvků tlačítek

Pokud umístíte ovládací prvek tlačítka na panel dialogového okna nebo do dialogového okna, pomocí kterého voláte CWnd::UpdateDialogControls sami, všimnete si, že tlačítka, která nemají ON_COMMAND nebo ON_UPDATE_COMMAND_UI obslužné rutiny, budou automaticky zakázány rozhraním. V některých případech nebudete muset mít obslužnou rutinu, nicméně si budete přát, aby tlačítko zůstalo aktivní. Nejjednodušší způsob, jak toho dosáhnout, je přidat fiktivní obslužnou rutinu (což jde snadno pomocí ClassWizard) a nic v ní nedělat.

Směrování zpráv okna

Následuje popis některých pokročilejších témat o třídách MFC a o tom, jak je ovlivňuje směrování zpráv systému Windows a další témata. Zde uvedené informace jsou popsány pouze stručně. Podrobnosti o veřejných rozhraních API najdete v referenčních informacích ke knihovně tříd . Další informace o implementaci najdete ve zdrojovém kódu knihovny MFC.

Podrobnosti o vyčištění okna, velmi důležité téma pro všechny třídy odvozené od CWnd, naleznete v technické poznámce 17.

Problémy s CWnd

Implementační členská funkce CWnd::OnChildNotify poskytuje výkonnou a rozšiřitelnou architekturu pro podřízená okna (označovaná také jako ovládací prvky) pro připojení a informování o zprávách, příkazech a řídicích oznámeních, které směřují na jejich nadřazeného (nebo 'vlastníka'). Pokud je podřízené okno (/control) objektem CWnd jazyka C++, virtuální funkce OnChildNotify je volána jako první s parametry z původní zprávy, tj. struktura MSG. Podřízené okno může zprávu ponechat bez změny, ignorovat ji nebo upravit zprávu pro rodiče (vzácně).

Výchozí implementace CWnd zpracovává následující zprávy a pomocí háku OnChildNotify umožňuje podřízeným oknům (ovládacím prvkům) přístup k této zprávě:

  • WM_MEASUREITEM a WM_DRAWITEM (pro samokreslení)

  • WM_COMPAREITEM a WM_DELETEITEM (pro samokreslení)

  • WM_HSCROLL a WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Všimněte si, že háček OnChildNotify se používá ke změně zpráv nakreslených vlastníkem do samokreslených zpráv.

Kromě háku OnChildNotify mají posouvací zprávy další směrovací chování. Další podrobnosti o posuvníkech a zdrojích WM_HSCROLL a WM_VSCROLL zpráv najdete níže.

Problémy s CFrameWnd

Třída CFrameWnd poskytuje většinu implementace směrování příkazů a aktualizace uživatelského rozhraní. To se primárně používá pro okno hlavního rámce aplikace (CWinApp::m_pMainWnd), ale platí pro všechna okna rámečku.

Hlavní okno rámu je okno s řádkem nabídek a je nadřazeným prvkem pro stavový řádek nebo řádek zpráv. Projděte si výše uvedenou diskuzi o směrování příkazů a WM_INITMENUPOPUP.

CFrameWnd třída poskytuje správu aktivního zobrazení. Prostřednictvím aktivního zobrazení se směrují následující zprávy:

  • Všechny příkazové zprávy (aktivní zobrazení má k nim první přístup).

  • WM_HSCROLL a WM_VSCROLL zprávy z vedlejších posuvníků (viz níže).

  • WM_ACTIVATE (a WM_MDIACTIVATE pro MDI) jsou převáděny na volání virtuální funkce CView::OnActivateView.

CMDIFrameWnd/CMDIChildWnd – problémy

Obě třídy oken MDI jsou odvozeny od CFrameWnd a proto umožňují stejný druh směrování příkazů a aktualizace uživatelského rozhraní poskytované v CFrameWnd. V typické aplikaci MDI obsahuje řádek nabídek a stavový řádek pouze okno hlavního rámce (tj. OBJEKT CMDIFrameWnd ), a proto je hlavním zdrojem implementace směrování příkazů.

Obecné schéma směrování je, že aktivní podřízené okno MDI získá první přístup k příkazům. Výchozí funkce PreTranslateMessage zpracovávají tabulky akcelerátorů pro podřízená okna MDI (první) i rámec MDI (druhý) i standardní akcelerátory systémových příkazů MDI, které obvykle zpracovává TranslateMDISysAccel (poslední).

Problémy s posuvníkem

Při zpracování posuvné zprávy (WM_HSCROLL/OnHScroll a/nebo WM_VSCROLL/OnVScroll) byste se měli pokusit napsat kód obslužné rutiny, takže se nespoléhá na to, odkud pochází zpráva posuvníku. Nejedná se pouze o obecný problém s Windows, protože zprávy o posouvání můžou pocházet z ovládacích prvků skutečného posuvníku nebo z posuvníků WS_HSCROLL/WS_VSCROLL, které nejsou ovládacími prvky posuvníku.

MFC rozšiřuje možnosti tak, aby ovládací prvky posuvníku mohly být buď podřízené, nebo na stejné úrovni jako okno, které se posouvá (fakticky může být vztah mezi posuvníkem a posunovaným oknem libovolný). To je zvlášť důležité pro sdílené posuvníky s rozdělovači oken. Podrobnosti o implementaci CSplitterWnd najdete v technické poznámce 29, včetně dalších informací o problémech se sdíleným posuvníkem.

Mimochodem, existují dvě odvozené třídy CWnd, kde styly posuvníku určené při vytvoření jsou zachyceny a nejsou předány systému Windows. Při předání do rutiny vytváření je možné nezávisle nastavit WS_HSCROLL a WS_VSCROLL , ale po vytvoření nelze změnit. Samozřejmě byste neměli přímo testovat ani nastavovat WS_SCROLL stylové bity okna, které vytvořilo.

Pro CMDIFrameWnd styly posuvníku, které předáte create nebo LoadFrame , se používají k vytvoření MDICLIENT. Pokud chcete mít posuvnou oblast MDICLIENT (například Správce programu Windows), nezapomeňte nastavit oba styly posuvníku (WS_HSCROLL | WS_VSCROLL) pro styl použitý k vytvoření CMDIFrameWnd.

Pro CSplitterWnd styly posuvníku platí pro speciální sdílené posuvníky pro oblasti rozdělovače. U statických oken rozdělovače obvykle nebudete nastavovat styl posuvníku. U dynamických oken rozdělovače budete mít obvykle nastaven styl posuvníku pro směr rozdělení, to znamená WS_HSCROLL , pokud můžete rozdělit řádky , WS_VSCROLL , pokud můžete rozdělit sloupce.

Viz také

Technické poznámky podle čísla
Technické poznámky podle kategorie