UIs werden immer komplexer. Und manche UI passt sicher nicht mehr in ein simples und einfaches Frame/Document/View Modell. Oft genug wird hier noch ein Tool-Window gedockt, dort noch ein Ausgabefenster und hier noch ein nicht modaler Dialog mit Status Infos.
Bringt man nun in einem solchen Fenster Buttons oder Toolbars unter, dann wünscht man sich in manchen Fällen, dass ein Command-Handler ausgelöst werden soll, der in einem anderen Objekt (z.B. dem aktiven View) liegt.
Oder man möchte, dass eben auch in solch einem Fenster ein Command-Handler berücksichtigt wird.
Der Ansatz andere Fenster in das Command Routing einzubauen ist die Funktion CCmdTarget::OnCmdMsg. Diese Methode hat zwei Funktionen.
- Sie dient dazu, dass in Menüs (auch Kontextmenüs) und auch in Toolbars Menüpunkte und Buttons ein- und auszuschalten. (ON_UPDATE_COMMAND Handler)
- Sie leitet die eigentliche WM_COMMAND Nachricht zur Ausführung an den entsprechenden zuständigen Handler weiter. (ON_COMMAND Handler).
Zum besseren Verständnis des Command Routings das die MFC als Standard vorsieht empfehle ich als Lektüre TN021. Um abenteuerliche Konstrukte und üble Verbiegungen der guten Vorgaben in der MFC zu vermeiden sollten die Grundlagen des Routings in Fleisch und Blut übergegangen sein.
Und dann ist es an sich nicht schwer, Anpassungen vorzunehmen, die es wirklich einfach machen auch andere Fenster in das eigene Routing aufzunehmen ohne die MFC zu verbiegen.
Nun zum Eingemachten:
Will man also Handler in anderen assoziierten Fenstern nutzen, dann muss man diese Fenster in die eigene angepasste OnCmdMsg Routing Struktur einbauen. Der beste Punkt dafür ist in vielen Fällen das hostende CMainFrame. Der Grund ist einfach: Das CMainFrame ist in 99% aller Fälle die erste Adresse, an die ein WM_COMMAND ausgeliefert wird.
Eine entsprechende Funktion kann so aussehen:
BOOL CMainFrame::OnCmdMsg(UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
// Give a special window the first chance to handle the command
if (m_pSomeWindow->GetSafeHwnd() &&
m_pSomeWindow->IsWindowVisible() &&
m_pSomeWindow->IsActiveInSomeWayOrHasFocus())
{
if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
return TRUE;
}
// Do the standard routing (View, Frame, Application)
if (__super::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
return TRUE;
// If not handled up to this point just give another window a chance
if (m_pSomeOtherWindow->GetSafeHwnd() &&
m_pSomeOtherWindow->IsWindowVisible() &&
m_pSomeOtherWindow->IsActiveInSomeWayOrHasFocus())
{
if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
return TRUE;
}
// not handled
return FALSE;
}
Anmerkungen:
- Meistens ist es bei diesen Fällen wichtig, dass Routing nicht immer durchzuführen. Z.B. nur dann, wenn das Fenster sichtbar ist, den Fokus hat oder ähnliches (siehe Beispielcode).
- Weiterhin ist es für Kontextmenüs wichtig als Parent-Fenster ein von CFrameWnd abgeleitetes Fenster zu verwenden. Geschieht dies nicht, dann wird WM_INITMENUPOPUP nicht behandelt, und die Menüpunkte werden nicht enabled bzw. disabled. Wird das obige Verfahren mit OnCmdMsg korrekt angewendet, kann man als Parent Fenster für Kontextmnüs immer AfxGetMainWnd verwenden.
- Toolbars funktionieren nur dann korrekt wenn hier der korrekte Owner gesetzt wird. Das geschieht mit CToolBar::SetOwner. Auch hier sollte das äußere Frame Window die WM_COMMAND Nachrichten erhalten, die dann über den normalen weiteren (angepassten) Weg geroutet werden.
Siehe auch Command Routing der MFC bei Kontext Menüs mit TrackPopupMenu