Partager via


TN021 : Routage des commandes et des messages

Remarque

La note technique suivante n'a pas été mise à jour depuis son inclusion initiale dans la documentation en ligne. Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrectes. Pour obtenir les informations les plus récentes, il est recommandé de rechercher l'objet qui vous intéresse dans l'index de la documentation en ligne.

Cette note décrit l’architecture de routage et de répartition des commandes, ainsi que les rubriques avancées dans le routage des messages de fenêtre générale.

Reportez-vous à Visual C++ pour plus d’informations générales sur les architectures décrites ici, en particulier la distinction entre les messages Windows, les notifications de contrôle et les commandes. Cette note suppose que vous êtes très familiarisé avec les problèmes décrits dans la documentation imprimée et ne traite que des rubriques très avancées.

La fonctionnalité de routage des commandes et de répartition MFC 1.0 évolue vers l’architecture MFC 2.0

Windows a le message WM_COMMAND surchargé pour fournir des notifications de commandes de menu, de touches d’accélérateur et de notifications de contrôle de boîte de dialogue.

MFC 1.0 basé sur cela un peu en autorisant un gestionnaire de commandes (par exemple, « OnFileNew ») dans une CWnd classe dérivée à appeler en réponse à un WM_COMMAND spécifique. Cela est collé avec une structure de données appelée carte de messages et entraîne un mécanisme de commande très efficace dans l’espace.

MFC 1.0 a également fourni des fonctionnalités supplémentaires pour séparer les notifications de contrôle des messages de commande. Les commandes sont représentées par un ID 16 bits, parfois appelé ID de commande. Les commandes démarrent normalement à partir d’un CFrameWnd (c’est-à-dire un menu sélectionné ou un accélérateur traduit) et sont acheminées vers une variété d’autres fenêtres.

MFC 1.0 a utilisé le routage des commandes dans un sens limité pour l’implémentation de l’interface multidocument (MDI). (Commandes déléguées d’une fenêtre frame MDI à sa fenêtre enfant MDI active.)

Cette fonctionnalité a été généralisée et étendue dans MFC 2.0 pour permettre aux commandes d’être gérées par un large éventail d’objets (pas seulement des objets fenêtre). Il fournit une architecture plus formelle et extensible pour le routage des messages et réutilise le routage cible de commandes pour non seulement la gestion des commandes, mais également pour la mise à jour d’objets d’interface utilisateur (comme les éléments de menu et les boutons de barre d’outils) afin de refléter la disponibilité actuelle d’une commande.

ID de commande

Consultez Visual C++ pour obtenir une explication du processus de routage et de liaison de commandes. La note technique 20 contient des informations sur le nommage d’ID.

Nous utilisons le préfixe générique « ID_ » pour les ID de commande. Les ID de commande sont >= 0x8000. La ligne de message ou la barre d’état affiche la chaîne de description de commande s’il existe une ressource STRINGTABLE avec les mêmes ID que l’ID de commande.

Dans les ressources de votre application, un ID de commande peut apparaître à plusieurs endroits :

  • Dans une ressource STRINGTABLE qui a le même ID que l’invite de ligne de message.

  • Dans de nombreuses ressources MENU éventuellement attachées aux éléments de menu qui appellent la même commande.

  • (AVANCÉ) dans un bouton de boîte de dialogue pour une commande GOSUB.

Dans le code source de votre application, un ID de commande peut apparaître à plusieurs endroits :

  • Dans votre RESSOURCE. H (ou un autre fichier d’en-tête de symbole principal) pour définir des ID de commande spécifiques à l’application.

  • PEUT-être dans un tableau d’ID utilisé pour créer une barre d’outils.

  • Dans une macro ON_COMMAND.

  • PEUT-être dans une macro ON_UPDATE_COMMAND_UI.

Actuellement, la seule implémentation dans MFC qui nécessite des ID de commande soit >= 0x8000 est l’implémentation de dialogues/commandes GOSUB.

Commandes GOSUB, utilisation de l’architecture de commandes dans les boîtes de dialogue

L’architecture de commande du routage et l’activation des commandes fonctionnent bien avec les fenêtres frame, les éléments de menu, les boutons de barre d’outils, les boutons de barre de dialogue, d’autres barres de contrôle et d’autres éléments d’interface utilisateur conçus pour mettre à jour à la demande et acheminer les commandes ou les ID de contrôle vers une cible de commande principale (généralement la fenêtre de trame principale). Cette cible de commande principale peut acheminer les notifications de commande ou de contrôle vers d’autres objets cibles de commande selon les besoins.

Une boîte de dialogue (modale ou sans mode) peut tirer parti de certaines des fonctionnalités de l’architecture de commande si vous affectez l’ID de contrôle du contrôle de dialogue à l’ID de commande approprié. La prise en charge des boîtes de dialogue n’est pas automatique. Vous devrez peut-être écrire du code supplémentaire.

Notez que pour que toutes ces fonctionnalités fonctionnent correctement, vos ID de commande doivent être >= 0x8000. Étant donné que de nombreux dialogues peuvent être routés vers le même frame, les commandes partagées doivent être >= 0x8000, tandis que les IDCs non partagés dans un dialogue spécifique doivent être <= 0x7FFF.

Vous pouvez placer un bouton normal dans une boîte de dialogue modale normale avec l’IDC du bouton défini sur l’ID de commande approprié. Lorsque l’utilisateur sélectionne le bouton, le propriétaire de la boîte de dialogue (généralement la fenêtre cadre principale) obtient la commande comme n’importe quelle autre commande. Il s’agit d’une commande GOSUB, car elle est généralement utilisée pour afficher un autre dialogue (un GOSUB du premier dialogue).

Vous pouvez également appeler la fonction CWnd::UpdateDialogControls sur votre boîte de dialogue et la transmettre à l’adresse de votre fenêtre de cadre principale. Cette fonction active ou désactive vos contrôles de boîte de dialogue selon qu’ils ont des gestionnaires de commandes dans le cadre. Cette fonction est appelée automatiquement pour vous pour les barres de contrôle dans la boucle inactive de votre application, mais vous devez l’appeler directement pour les dialogues normaux que vous souhaitez avoir cette fonctionnalité.

Quand ON_UPDATE_COMMAND_UI est appelé

La maintenance de l’état activé/case activée de tous les éléments de menu d’un programme tout le temps peut être un problème coûteux de calcul. Une technique courante consiste à activer/case activée éléments de menu uniquement lorsque l’utilisateur sélectionne le POPUP. L’implémentation MFC 2.0 de CFrameWnd handles le message WM_INITMENUPOPUP et utilise l’architecture de routage des commandes pour déterminer les états des menus via ON_UPDATE_COMMAND_UI gestionnaires.

CFrameWnd gère également le message WM_ENTERIDLE pour décrire l’élément de menu actif sélectionné dans la barre d’état (également appelé ligne de message).

La structure de menu d’une application, modifiée par Visual C++, est utilisée pour représenter les commandes potentielles disponibles à WM_INITMENUPOPUP moment. ON_UPDATE_COMMAND_UI gestionnaires peuvent modifier l’état ou le texte d’un menu, ou pour des utilisations avancées (comme la liste MRU de fichier ou le menu contextuel OLE Verbs), modifiez réellement la structure du menu avant le dessin du menu.

Le même type de traitement ON_UPDATE_COMMAND_UI est effectué pour les barres d’outils (et d’autres barres de contrôle) lorsque l’application entre dans sa boucle inactive. Pour plus d’informations sur les barres de contrôle, consultez la référence de la bibliothèque de classes et la note technique 31.

Menus contextuels imbriqués

Si vous utilisez une structure de menu imbriquée, vous remarquerez que le gestionnaire ON_UPDATE_COMMAND_UI pour le premier élément de menu dans le menu contextuel est appelé dans deux cas différents.

Tout d’abord, il est appelé pour le menu contextuel lui-même. Cela est nécessaire, car les menus contextuels n’ont pas d’ID et nous utilisons l’ID du premier élément de menu contextuel pour faire référence à l’ensemble du menu contextuel. Dans ce cas, la variable membre m_pSubMenu de l’objet CCmdUI est non NULL et pointe vers le menu contextuel.

Ensuite, il est appelé juste avant que les éléments de menu contextuel soient dessinés. Dans ce cas, l’ID fait référence uniquement au premier élément de menu et la variable membre m_pSubMenu de l’objet CCmdUI sera NULL.

Cela vous permet d’activer le menu contextuel distinct de ses éléments de menu, mais nécessite que vous écriviez du code prenant en compte le menu. Par exemple, dans un menu imbriqué avec la structure suivante :

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

Les commandes ID_NEW_SHEET et ID_NEW_CHART peuvent être activées ou désactivées indépendamment. Le nouveau menu contextuel doit être activé si l’un des deux est activé.

Le gestionnaire de commandes pour ID_NEW_SHEET (la première commande dans la fenêtre contextuelle) ressemble à ceci :

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);
}

Le gestionnaire de commandes pour ID_NEW_CHART est un gestionnaire de commandes de mise à jour normal et ressemble à ceci :

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

ON_COMMAND et ON_BN_CLICKED

Les macros de mappage de messages pour ON_COMMAND et les ON_BN_CLICKED sont identiques. Le mécanisme de routage des notifications de commande et de commande MFC utilise uniquement l’ID de commande pour décider où acheminer. Les notifications de contrôle avec le code de notification de contrôle zéro (BN_CLICKED) sont interprétées comme des commandes.

Remarque

En fait, tous les messages de notification de contrôle passent par la chaîne de gestionnaires de commandes. Par exemple, il est techniquement possible d’écrire un gestionnaire de notification de contrôle pour EN_CHANGE dans votre classe de document. Cela n’est généralement pas conseillé, car les applications pratiques de cette fonctionnalité sont peu nombreuses, la fonctionnalité n’est pas prise en charge par ClassWizard et l’utilisation de la fonctionnalité peut entraîner un code fragile.

Désactivation de la désactivation automatique des contrôles de bouton

Si vous placez un contrôle de bouton sur une barre de dialogue ou dans une boîte de dialogue à l’aide de l’emplacement où vous appelez CWnd ::UpdateDialogControls par vous-même, vous remarquerez que les boutons qui n’ont pas ON_COMMAND ou ON_UPDATE_COMMAND_UI gestionnaires seront automatiquement désactivés pour vous par le framework. Dans certains cas, vous n’aurez pas besoin d’avoir un gestionnaire, mais vous souhaiterez que le bouton reste activé. Le moyen le plus simple d’y parvenir consiste à ajouter un gestionnaire de commandes factice (facile à faire avec ClassWizard) et à ne rien y faire.

Routage des messages de fenêtre

Les rubriques suivantes décrivent certaines rubriques plus avancées sur les classes MFC et la façon dont le routage des messages Windows et d’autres rubriques les affectent. Les informations fournies ici ne sont décrites que brièvement. Reportez-vous à la référence de la bibliothèque de classes pour plus d’informations sur les API publiques. Pour plus d’informations sur les détails de l’implémentation, reportez-vous au code source de la bibliothèque MFC.

Reportez-vous à la note technique 17 pour plus d’informations sur window propre up, une rubrique très importante pour toutes les classes dérivées de CWnd.

Problèmes CWnd

La fonction membre d’implémentation CWnd ::OnChildNotify fournit une architecture puissante et extensible pour les fenêtres enfants (également appelées contrôles) pour se connecter ou être informé des messages, commandes et notifications de contrôle qui vont à leur parent (ou « propriétaire »). Si la fenêtre enfant (/control) est un objet C++ CWnd lui-même, la fonction virtuelle OnChildNotify est appelée en premier avec les paramètres du message d’origine (autrement dit, une structure MSG ). La fenêtre enfant peut laisser le message seul, le manger ou modifier le message pour le parent (rare).

L’implémentation CWnd par défaut gère les messages suivants et utilise le hook OnChildNotify pour autoriser les fenêtres enfants (contrôles) à accéder d’abord au message :

  • WM_MEASUREITEM et WM_DRAWITEM (pour le tirage automatique)

  • WM_COMPAREITEM et WM_DELETEITEM (pour le tirage automatique)

  • WM_HSCROLL et WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Vous remarquerez que le hook OnChildNotify est utilisé pour modifier les messages de dessin propriétaire en messages de dessin automatique.

En plus du hook OnChildNotify , les messages de défilement ont un comportement de routage supplémentaire. Pour plus d’informations sur les barres de défilement et les sources de WM_HSCROLL et de messages WM_VSCROLL, consultez ci-dessous.

Problèmes CFrameWnd

La classe CFrameWnd fournit la plupart des implémentations de routage de commandes et de mise à jour de l’interface utilisateur. Cela est principalement utilisé pour la fenêtre d’image principale de l’application (CWinApp ::m_pMainWnd), mais s’applique à toutes les fenêtres frame.

La fenêtre cadre principale est la fenêtre avec la barre de menus et est le parent de la barre d’état ou de la ligne de message. Reportez-vous à la discussion ci-dessus sur le routage des commandes et les WM_INITMENUPOPUP.

La classe CFrameWnd fournit la gestion de l’affichage actif. Les messages suivants sont routés via la vue active :

  • Tous les messages de commande (l’affichage actif obtient d’abord l’accès à ces messages).

  • WM_HSCROLL et WM_VSCROLL messages provenant de barres de défilement frères (voir ci-dessous).

  • WM_ACTIVATE (et WM_MDIACTIVATE pour MDI) sont transformées en appels à la fonction virtuelle CView ::OnActivateView.

Problèmes CMDIFrameWnd/CMDIChildWnd

Les deux classes de fenêtre d’images MDI dérivent de CFrameWnd et sont donc toutes deux activées pour le même type de routage de commandes et la mise à jour de l’interface utilisateur fournie dans CFrameWnd. Dans une application MDI classique, seule la fenêtre de trame principale (autrement dit, l’objet CMDIFrameWnd ) contient la barre de menus et la barre d’état et est donc la source principale de l’implémentation du routage des commandes.

Le schéma de routage général est que la fenêtre enfant MDI active obtient d’abord l’accès aux commandes. Les fonctions PreTranslateMessage par défaut gèrent les tables d’accélérateurs pour les fenêtres enfants MDI (d’abord) et le cadre MDI (deuxième) ainsi que les accélérateurs de commande système MDI standard gérés normalement par TranslateMDISysAccel (dernier).

Problèmes de barre de défilement

Lors de la gestion du défilement-message (WM_HSCROLL/OnHScroll et/ou WM_VSCROLL/OnVScroll), vous devez essayer d’écrire le code du gestionnaire afin qu’il ne s’appuie pas sur l’emplacement d’où provient le message de barre de défilement. Il ne s’agit pas seulement d’un problème Windows général, car les messages de défilement peuvent provenir de contrôles de barre de défilement true ou de WS_HSCROLL WS_VSCROLL/ barres de défilement qui ne sont pas des contrôles de barre de défilement.

MFC étend cela pour permettre aux contrôles de barre de défilement d’être des enfants ou frères de la fenêtre en cours de défilement (en fait, la relation parent/enfant entre la barre de défilement et la fenêtre en cours de défilement peut être n’importe quoi). Cela est particulièrement important pour les barres de défilement partagées avec des fenêtres de fractionnement. Reportez-vous à la note technique 29 pour plus d’informations sur l’implémentation de CSplitterWnd , y compris plus d’informations sur les problèmes de barre de défilement partagés.

D’un côté, il existe deux classes dérivées CWnd où les styles de barre de défilement spécifiés au moment de la création sont piégés et ne sont pas passés à Windows. Lorsqu’elle est passée à une routine de création, WS_HSCROLL et WS_VSCROLL peuvent être définies indépendamment, mais après la création ne peut pas être modifiée. Bien sûr, vous ne devez pas tester ou définir directement les bits de style WS_SCROLL de la fenêtre qu’ils ont créée.

Pour CMDIFrameWnd , les styles de barre de défilement que vous transmettez à Create ou LoadFrame sont utilisés pour créer le MDICLIENT. Si vous souhaitez avoir une zone MDICLIENT à défilement (comme le Gestionnaire de programmes Windows) veillez à définir les deux styles de barre de défilement (WS_HSCROLL | WS_VSCROLL) pour le style utilisé pour créer le CMDIFrameWnd.

Pour CSplitterWnd , les styles de barre de défilement s’appliquent aux barres de défilement partagées spéciales pour les régions de fractionnement. Pour les fenêtres de fractionnement statiques, vous ne définissez normalement pas le style de barre de défilement. Pour les fenêtres de fractionnement dynamique, vous aurez généralement le style de barre de défilement défini pour la direction que vous fractionnerez, autrement dit, WS_HSCROLL si vous pouvez fractionner des lignes, WS_VSCROLL si vous pouvez fractionner des colonnes.

Voir aussi

Notes techniques par numéro
Notes techniques par catégorie