TN021: routing di comandi e messaggi
Nota
La seguente nota tecnica non è stata aggiornata da quando è stata inclusa per la prima volta nella documentazione online. Di conseguenza, alcune procedure e argomenti potrebbero essere non aggiornati o errati. Per le informazioni più recenti, è consigliabile cercare l'argomento di interesse nell'indice della documentazione online.
Questa nota descrive l'architettura di routing dei comandi e dispatch, nonché gli argomenti avanzati nel routing generale dei messaggi di finestra.
Fare riferimento a Visual C++ per informazioni generali sulle architetture descritte qui, in particolare la distinzione tra messaggi di Windows, notifiche di controllo e comandi. Questa nota presuppone che si abbia familiarità con i problemi descritti nella documentazione stampata e che si tratti solo di argomenti molto avanzati.
Il routing dei comandi e la funzionalità Dispatch MFC 1.0 si evolve fino all'architettura MFC 2.0
Windows ha il messaggio WM_COMMAND di overload per fornire notifiche di comandi di menu, tasti di scelta rapida e notifiche di controllo della finestra di dialogo.
MFC 1.0 basato su questo aspetto consentendo a un gestore di comandi (ad esempio, "OnFileNew") in una CWnd
classe derivata di chiamare in risposta a un WM_COMMAND specifico. Questo elemento viene associato a una struttura di dati denominata mappa messaggi e genera un meccanismo di comando molto efficiente nello spazio.
MFC 1.0 offre anche funzionalità aggiuntive per separare le notifiche di controllo dai messaggi di comando. I comandi sono rappresentati da un ID a 16 bit, talvolta noto come ID comando. I comandi in genere iniziano da un oggetto CFrameWnd
(ovvero da un menu selezionato o da un acceleratore tradotto) e vengono indirizzati a un'ampia gamma di altre finestre.
MFC 1.0 usava il routing dei comandi in un senso limitato per l'implementazione di Multiple Document Interface (MDI). (Comandi delegati della finestra cornice MDI alla relativa finestra figlio MDI attiva).
Questa funzionalità è stata generalizzata e estesa in MFC 2.0 per consentire la gestione dei comandi da parte di una gamma più ampia di oggetti (non solo oggetti finestra). Fornisce un'architettura più formale ed estendibile per il routing dei messaggi e riutilizza il routing della destinazione dei comandi per non solo la gestione dei comandi, ma anche per l'aggiornamento degli oggetti dell'interfaccia utente (ad esempio voci di menu e pulsanti della barra degli strumenti) in modo da riflettere la disponibilità corrente di un comando.
ID comandi
Vedere Visual C++ per una spiegazione del processo di routing e associazione dei comandi. La nota tecnica 20 contiene informazioni sulla denominazione degli ID.
Per gli ID comando viene usato il prefisso generico "ID_". Gli ID comando sono >= 0x8000. La riga del messaggio o la barra di stato mostrerà la stringa di descrizione del comando se è presente una risorsa STRINGTABLE con gli stessi ID del comando.
Nelle risorse dell'applicazione, un ID comando può essere visualizzato in diverse posizioni:
In una risorsa STRINGTABLE con lo stesso ID del prompt della riga di messaggio.
In probabilmente molte risorse MENU associate alle voci di menu che richiamano lo stesso comando.
(ADVANCED) in un pulsante di dialogo per un comando GOSUB.
Nel codice sorgente dell'applicazione, un ID comando può essere visualizzato in diverse posizioni:
Nella RISORSA. H (o un altro file di intestazione di simboli principale) per definire ID comando specifici dell'applicazione.
PERH piattaforma di strumenti analitici In una matrice ID usata per creare una barra degli strumenti.
In una macro ON_COMMAND.
PERH piattaforma di strumenti analitici In una macro ON_UPDATE_COMMAND_UI.
Attualmente, l'unica implementazione in MFC che richiede ID comando sia >= 0x8000 è l'implementazione di finestre di dialogo/comandi GOSUB.
Comandi GOSUB, uso dell'architettura dei comandi nelle finestre di dialogo
L'architettura dei comandi di routing e abilitazione dei comandi funziona bene con finestre cornice, voci di menu, pulsanti della barra degli strumenti, altre barre di controllo e altri elementi dell'interfaccia utente progettati per aggiornare su richiesta e indirizzare comandi o ID di controllo a una destinazione di comando principale (in genere la finestra cornice principale). Tale destinazione del comando principale può instradare il comando o controllare le notifiche ad altri oggetti di destinazione del comando in base alle esigenze.
Una finestra di dialogo (modale o modeless) può trarre vantaggio da alcune delle funzionalità dell'architettura dei comandi se si assegna l'ID del controllo del controllo al comando appropriato. Il supporto per le finestre di dialogo non è automatico, pertanto potrebbe essere necessario scrivere codice aggiuntivo.
Si noti che per il corretto funzionamento di tutte queste funzionalità, gli ID comando devono essere >= 0x8000. Poiché molti dialoghi potrebbero essere indirizzati allo stesso frame, i comandi condivisi devono essere = 0x8000, mentre gli IDC non condivisi in una finestra di dialogo specifica devono essere ><= 0x7FFF.
È possibile posizionare un pulsante normale in una normale finestra di dialogo modale con il data center IDC del pulsante impostato sull'ID comando appropriato. Quando l'utente seleziona il pulsante, il proprietario della finestra di dialogo (in genere la finestra cornice principale) ottiene il comando esattamente come qualsiasi altro comando. Questo comando viene chiamato comando GOSUB perché in genere viene usato per visualizzare un'altra finestra di dialogo (GOSUB del primo dialogo).
È anche possibile chiamare la funzione CWnd::UpdateDialogControls
nel dialogo e passarlo all'indirizzo della finestra cornice principale. Questa funzione abiliterà o disabiliterà i controlli della finestra di dialogo in base al fatto che abbiano gestori comandi nel frame. Questa funzione viene chiamata automaticamente per le barre di controllo nel ciclo di inattività dell'applicazione, ma è necessario chiamarla direttamente per le normali finestre di dialogo che si desidera avere questa funzionalità.
Quando viene chiamato ON_UPDATE_COMMAND_UI
La gestione dello stato abilitato/controllato di tutte le voci di menu di un programma può essere un problema di calcolo costoso. Una tecnica comune consiste nell'abilitare/controllare le voci di menu solo quando l'utente seleziona il POPUP. L'implementazione MFC 2.0 di CFrameWnd
gestisce il messaggio WM_INITMENUPOPUP e usa l'architettura di routing dei comandi per determinare gli stati dei menu tramite ON_UPDATE_COMMAND_UI gestori.
CFrameWnd
gestisce anche il messaggio WM_ENTERIDLE per descrivere la voce di menu corrente selezionata sulla barra di stato (nota anche come riga del messaggio).
La struttura dei menu di un'applicazione, modificata da Visual C++, viene usata per rappresentare i potenziali comandi disponibili in WM_INITMENUPOPUP momento. ON_UPDATE_COMMAND_UI gestori possono modificare lo stato o il testo di un menu o per usi avanzati (ad esempio l'elenco MRU file o il menu a comparsa Verbi OLE), modificare effettivamente la struttura di menu prima che venga disegnato il menu.
Lo stesso tipo di elaborazione ON_UPDATE_COMMAND_UI viene eseguita per le barre degli strumenti (e altre barre di controllo) quando l'applicazione entra nel ciclo di inattività. Per altre informazioni sulle barre di controllo, vedere Le informazioni di riferimento sulla libreria di classi e la nota tecnica 31.
Menu a comparsa annidati
Se si usa una struttura di menu nidificata, si noterà che il gestore ON_UPDATE_COMMAND_UI per la prima voce di menu nel menu a comparsa viene chiamato in due casi diversi.
In primo luogo, viene chiamato per il menu a comparsa stesso. Questo è necessario perché i menu a comparsa non hanno ID e usiamo l'ID della prima voce di menu del menu a comparsa per fare riferimento all'intero menu a comparsa. In questo caso, la variabile membro m_pSubMenu dell'oggetto CCmdUI
sarà non NULL e punterà al menu a comparsa.
In secondo luogo, viene chiamato poco prima che le voci di menu nel menu a comparsa siano da disegnare. In questo caso, l'ID fa riferimento solo alla prima voce di menu e la variabile membro m_pSubMenu dell'oggetto CCmdUI
sarà NULL.
In questo modo è possibile abilitare il menu a comparsa distinto dalle relative voci di menu, ma è necessario scrivere codice compatibile con il menu. Ad esempio, in un menu annidato con la struttura seguente:
File>
New>
Sheet (ID_NEW_SHEET)
Chart (ID_NEW_CHART)
I comandi ID_NEW_SHedizione Enterprise T e ID_NEW_CHART possono essere abilitati o disabilitati in modo indipendente. Il menu a comparsa Nuovo deve essere abilitato se una delle due è abilitata.
Il gestore dei comandi per ID_NEW_SHedizione Enterprise T (il primo comando nella finestra popup) sarà simile al seguente:
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);
}
Il gestore dei comandi per ID_NEW_CHART sarebbe un gestore dei comandi di aggiornamento normale e sarà simile al seguente:
void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bCanCreateChart);
}
ON_COMMAND e ON_BN_CLICKED
Le macro della mappa dei messaggi per ON_COMMAND e ON_BN_CLICKED sono le stesse. Il comando MFC e il meccanismo di routing delle notifiche di controllo usano solo l'ID comando per decidere dove instradare. Le notifiche di controllo con codice di notifica di controllo pari a zero (BN_CLICKED) vengono interpretate come comandi.
Nota
Infatti, tutti i messaggi di notifica del controllo passano attraverso la catena del gestore dei comandi. Ad esempio, è tecnicamente possibile scrivere un gestore di notifica del controllo per EN_CHANGE nella classe del documento. Questo non è in genere consigliabile perché le applicazioni pratiche di questa funzionalità sono poche, la funzionalità non è supportata da ClassWizard e l'uso della funzionalità può comportare codice fragile.
Disabilitazione automatica dei controlli pulsante
Se si posiziona un controllo pulsante su una barra delle finestre di dialogo o in una finestra di dialogo in cui si chiama CWnd::UpdateDialogControls autonomamente, si noterà che i pulsanti che non dispongono di ON_COMMAND o ON_UPDATE_COMMAND_UI gestori verranno disabilitati automaticamente dal framework. In alcuni casi, non sarà necessario disporre di un gestore, ma si vuole che il pulsante rimanga abilitato. Il modo più semplice per ottenere questo risultato consiste nell'aggiungere un gestore di comandi fittizi (facile da fare con ClassWizard) e non eseguire alcuna operazione in esso.
Routing messaggi finestra
Di seguito vengono descritti alcuni argomenti più avanzati sulle classi MFC e sul modo in cui il routing dei messaggi di Windows e altri argomenti influiscono sulle classi MFC. Le informazioni qui sono descritte solo brevemente. Per informazioni dettagliate sulle API pubbliche, vedere Le informazioni di riferimento sulla libreria di classi. Per altre informazioni sui dettagli sull'implementazione, vedere il codice sorgente della libreria MFC.
Per informazioni dettagliate sulla pulizia della finestra, vedere la Nota tecnica 17 , un argomento molto importante per tutte le classi derivate da CWnd.
Problemi di CWnd
La funzione membro di implementazione CWnd::OnChildNotify fornisce un'architettura potente ed estendibile per le finestre figlio (note anche come controlli) per associare o essere informati di messaggi, comandi e notifiche di controllo che passano al padre (o "proprietario"). Se la finestra figlio (/control) è un oggetto CWnd C++ stesso, la funzione virtuale OnChildNotify viene chiamata prima con i parametri del messaggio originale, ovvero una struttura MSG. La finestra figlio può lasciare il messaggio da solo, mangiarlo o modificare il messaggio per l'elemento padre (raro).
L'implementazione CWnd predefinita gestisce i messaggi seguenti e usa l'hook OnChildNotify per consentire alle finestre figlio (controlli) di accedere prima al messaggio:
WM_MEASUREITEM e WM_DRAWITEM (per auto-disegno)
WM_COMPAREITEM e WM_DELETEITEM (per auto-disegno)
WM_HSCROLL e WM_VSCROLL
WM_CTLCOLOR
WM_PARENTNOTIFY
Si noterà che l'hook OnChildNotify viene usato per modificare i messaggi di disegno proprietario in messaggi di disegno automatico.
Oltre all'hook OnChildNotify , i messaggi di scorrimento hanno un ulteriore comportamento di routing. Per altre informazioni sulle barre di scorrimento e sulle origini dei messaggi di WM_HSCROLL e WM_VSCROLL, vedere di seguito.
Problemi di CFrameWnd
La classe CFrameWnd fornisce la maggior parte del routing dei comandi e l'implementazione dell'aggiornamento dell'interfaccia utente. Viene usato principalmente per la finestra cornice principale dell'applicazione (CWinApp::m_pMainWnd), ma si applica a tutte le finestre cornice.
La finestra cornice principale è la finestra con la barra dei menu ed è l'elemento padre della barra di stato o della riga del messaggio. Fare riferimento alla discussione precedente sul routing dei comandi e WM_INITMENUPOPUP.
La classe CFrameWnd fornisce la gestione della visualizzazione attiva. I messaggi seguenti vengono instradati tramite la visualizzazione attiva:
Tutti i messaggi di comando (la visualizzazione attiva ottiene prima di tutto l'accesso a tali messaggi).
WM_HSCROLL e WM_VSCROLL messaggi dalle barre di scorrimento di pari livello (vedere di seguito).
WM_ACTIVATE (e WM_MDIACTIVATE per MDI) vengono trasformate in chiamate alla funzione virtuale CView::OnActivateView.
Problemi di CMDIFrameWnd/CMDIChildWnd
Entrambe le classi della finestra cornice MDI derivano da CFrameWnd e pertanto sono entrambe abilitate per lo stesso tipo di routing dei comandi e l'aggiornamento dell'interfaccia utente fornito in CFrameWnd. In un'applicazione MDI tipica, solo la finestra cornice principale ,ovvero l'oggetto CMDIFrameWnd , contiene la barra dei menu e la barra di stato e quindi è l'origine principale dell'implementazione del routing dei comandi.
Lo schema di routing generale è che la finestra figlio MDI attiva ottiene prima l'accesso ai comandi. Le funzioni PreTranslateMessage predefinite gestiscono le tabelle degli acceleratori sia per le finestre figlio MDI (prima) sia per il frame MDI (secondo) sia per gli acceleratori di comandi di sistema MDI standard gestiti normalmente da TranslateMDISysAccel (ultimo).
Problemi relativi alla barra di scorrimento
Quando si gestisce il messaggio di scorrimento (WM_HSCROLL/OnHScroll e/o WM_VSCROLL/OnVScroll), è consigliabile provare a scrivere il codice del gestore in modo da non basarsi sulla provenienza del messaggio della barra di scorrimento. Questo non è solo un problema generale di Windows, poiché i messaggi di scorrimento possono provenire da veri controlli barra di scorrimento o da WS_HSCROLL WS_VSCROLL/ barre di scorrimento che non sono controlli barra di scorrimento.
MFC estende tale opzione per consentire ai controlli barra di scorrimento di essere figlio o di pari livello della finestra di cui viene eseguito lo scorrimento (in effetti, la relazione padre/figlio tra la barra di scorrimento e la finestra di scorrimento può essere qualsiasi elemento). Ciò è particolarmente importante per le barre di scorrimento condivise con finestre di divisione. Per informazioni dettagliate sull'implementazione di CSplitterWnd, vedere la Nota tecnica 29, tra cui altre informazioni sui problemi della barra di scorrimento condivisa.
In una nota laterale, sono presenti due classi derivate CWnd in cui gli stili della barra di scorrimento specificati in fase di creazione vengono intrappolati e non passati a Windows. Quando viene passato a una routine di creazione, WS_HSCROLL e WS_VSCROLL possono essere impostati in modo indipendente, ma dopo la creazione non possono essere modificati. Naturalmente, non è consigliabile testare direttamente o impostare i bit di stile WS_SCROLL della finestra creata.
Per CMDIFrameWnd gli stili della barra di scorrimento passati a Crea o LoadFrame vengono usati per creare MDICLIENT. Se vuoi avere un'area MDICLIENT scorrevole ,ad esempio Windows Program Manager, assicurati di impostare entrambi gli stili barra di scorrimento (WS_HSCROLL | WS_VSCROLL
) per lo stile usato per creare CMDIFrameWnd.
Per CSplitterWnd gli stili della barra di scorrimento si applicano alle barre di scorrimento condivise speciali per le aree di divisione. Per le finestre di divisione statiche, in genere non verrà impostato alcuno stile barra di scorrimento. Per le finestre a divisione dinamica, in genere sarà impostato lo stile della barra di scorrimento per la direzione che verrà divisa, ovvero WS_HSCROLL se è possibile dividere le righe, WS_VSCROLL se è possibile dividere le colonne.