TN021: comando e roteamento de mensagem

Observação

A nota técnica a seguir não foi atualizada desde que foi incluída pela primeira vez na documentação online. Como resultado, alguns procedimentos e tópicos podem estar desatualizados ou incorretos. Para obter as informações mais recentes, é recomendável que você pesquise o tópico de interesse no índice de documentação online.

Esta nota descreve a arquitetura de roteamento e expedição de comandos, bem como tópicos avançados no roteamento geral de mensagens de janela.

Consulte o Visual C++ para obter detalhes gerais sobre as arquiteturas descritas aqui, especialmente a distinção entre mensagens do Windows, notificações de controle e comandos. Esta observação pressupõe que você esteja muito familiarizado com os problemas descritos na documentação impressa e aborda apenas tópicos muito avançados.

A funcionalidade de Roteamento de Comandos e Expedição do MFC 1.0 evolui para a arquitetura MFC 2.0

O Windows tem a mensagem WM_COMMAND que está sobrecarregada para fornecer notificações de comandos de menu, teclas de aceleração e notificações de controle de caixa de diálogo.

O MFC 1.0 aperfeiçoou um pouco isso, permitindo que um manipulador de comando (por exemplo, "OnFileNew") em uma classe derivada CWnd seja chamado em resposta a uma WM_COMMAND específica. Isso é reunido com uma estrutura de dados chamada mapa de mensagens e resulta em um mecanismo de comando muito eficiente em termos de espaço.

O MFC 1.0 também fornecia funcionalidade adicional para separar notificações de controle de mensagens de comando. Os comandos são representados por uma ID de 16 bits, às vezes conhecida como ID de comando. Os comandos normalmente começam de um CFrameWnd (ou seja, um menu selecionado ou um acelerador traduzido) e são roteados para uma variedade de outras janelas.

O MFC 1.0 usava o roteamento de comando em um sentido limitado para a implementação da MDI (Interface de Vários Documentos). (Uma janela do quadro MDI delega comandos para a respectiva janela filho do MDI ativa.)

Essa funcionalidade foi generalizada e estendida no MFC 2.0 para permitir que os comandos sejam manipulados por uma gama mais ampla de objetos (não apenas objetos de janela). Ele fornece uma arquitetura mais formal e extensível para roteamento de mensagens e reutiliza o roteamento de destino de comando não apenas para a manipulação de comandos, mas também para atualizar objetos de interface do usuário (como itens de menu e botões da barra de ferramentas) para refletir a disponibilidade atual de um comando.

IDs de comando

Confira o Visual C++ para obter uma explicação do processo de roteamento e associação de comandos. A Nota Técnica 20 contém informações sobre a nomenclatura de ID.

Usamos o prefixo genérico "ID_" para IDs de comando. IDs de comando são >= 0x8000. A linha de mensagem ou a barra de status mostrarão a cadeia de caracteres de descrição do comando se houver um recurso STRINGTABLE com as mesmas IDs que a ID do comando.

Nos recursos do aplicativo, uma ID de comando pode ser exibida em vários locais:

  • Em um recurso STRINGTABLE que tem a mesma ID que o prompt de linha de mensagem.

  • Em possivelmente muitos recursos MENU anexados a itens de menu que invocam o mesmo comando.

  • (AVANÇADO) em um botão de diálogo para um comando GOSUB.

No código-fonte do aplicativo, uma ID de comando pode ser exibida em vários locais:

  • Em seu RESOURCE.H (ou outro arquivo de cabeçalho de símbolo principal) para definir IDs de comando específicas a um aplicativo.

  • Possivelmente, em uma matriz de ID usada para criar uma barra de ferramentas.

  • Em uma macro ON_COMMAND.

  • Possivelmente, em uma macro ON_UPDATE_COMMAND_UI.

Atualmente, a única implementação no MFC que exige que IDs de comando sejam >= 0x8000 é a implementação de diálogos/comandos GOSUB.

Comandos GOSUB, usando a arquitetura de comando em caixas de diálogo

A arquitetura de comando de roteamento e habilitação de comandos funciona bem com janelas de quadro, itens de menu, botões da barra de ferramentas, botões de barra de diálogo, outras barras de controle e outros elementos de interface do usuário projetados para atualizar sob demanda e rotear comandos ou IDs de controle para um destino de comando principal (geralmente a janela do quadro principal). Esse destino de comando principal pode rotear as notificações de comando ou controle para outros objetos de destino de comando conforme apropriado.

Uma caixa de diálogo (modal ou sem modo) pode se beneficiar de alguns dos recursos da arquitetura de comando se você atribuir a ID de controle do controle de diálogo à ID de comando apropriada. O suporte para caixas de diálogo não é automático, portanto, talvez você precise escrever algum código adicional.

Observe que, para que todos esses recursos funcionem corretamente, as suas as IDs de comando devem ser >= 0x8000. Como muitos diálogos podem ser roteado para o mesmo quadro, os comandos compartilhados devem ser >= 0x8000, enquanto os IDCs não compartilhados em uma caixa de diálogo específica devem ser <= 0x7FFF.

Você pode colocar um botão normal em uma caixa de diálogo modal normal com o IDC do botão definido como a ID de comando apropriada. Quando o usuário seleciona o botão, o proprietário da caixa de diálogo (geralmente a janela do quadro principal) obtém o comando como qualquer outro comando. Isso é chamado de comando GOSUB, pois geralmente é usado para abrir outra caixa de diálogo (um GOSUB da primeira caixa de diálogo).

Você também pode chamar a função CWnd::UpdateDialogControls na caixa de diálogo e passá-la pelo endereço da janela do quadro principal. Essa função habilitará ou desabilitará seus controles de diálogo, dependendo de eles terem ou não manipuladores de comando no quadro. Essa função é chamada automaticamente para você para barras de controle no loop ocioso do aplicativo, mas você precisa chamá-la diretamente para caixas de diálogo normais que você deseja que tenham esse recurso.

Quando ON_UPDATE_COMMAND_UI é chamado

Manter o estado habilitado/selecionado de todos os itens de menu de um programa o tempo todo pode ser um problema com custo computacional elevado. Uma técnica comum é habilitar/selecionar itens de menu somente quando o usuário seleciona o POPUP. A implementação do MFC 2.0 de CFrameWnd manipula a mensagem WM_INITMENUPOPUP e usa a arquitetura de roteamento de comando para determinar os estados dos menus por meio de manipuladores ON_UPDATE_COMMAND_UI.

CFrameWnd também manipula a mensagem WM_ENTERIDLE para descrever o item de menu atual selecionado na barra de status (também conhecida como linha de mensagem).

A estrutura de menus de um aplicativo, editada pelo Visual C++, é usada para representar os comandos potenciais disponíveis na hora WM_INITMENUPOPUP. Manipuladores ON_UPDATE_COMMAND_UI podem modificar o estado ou o texto de um menu ou para usos avançados (como a lista MRU de arquivo ou o menu pop-up de verbos OLE), na verdade modificar a estrutura do menu antes que o menu seja desenhado.

O mesmo tipo de processamento de ON_UPDATE_COMMAND_UI é feito para barras de ferramentas (e outras barras de controle) quando o aplicativo entra em loop ocioso. Consulte a Referência da Biblioteca de Classes e a Nota Técnica 31 para obter mais informações sobre barras de controle.

Menus pop-up aninhados

Se você estiver usando uma estrutura de menu aninhada, observará que o manipulador de ON_UPDATE_COMMAND_UI para o primeiro item de menu no menu pop-up é chamado em dois casos diferentes.

Primeiro, ele é chamado para o menu pop-up em si. Isso é necessário porque os menus pop-up não têm IDs e usamos a ID do primeiro item de menu do menu pop-up para nos referirmos a todo o menu pop-up. Nesse caso, a variável de membro m_pSubMenu do objeto CCmdUI não será NULL e apontará para o menu pop-up.

Em segundo lugar, ele é chamado antes que os itens de menu no menu pop-up sejam desenhados. Nesse caso, a ID se referirá apenas ao primeiro item de menu, e a variável de membro m_pSubMenu do objeto CCmdUI será NULL.

Isso permite que você habilite o menu pop-up independentemente dos itens de menu dele, mas exige que você escreva algum código com reconhecimento de menu. Por exemplo, em um menu aninhado com a seguinte estrutura:

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

Os comandos ID_NEW_SHEET e ID_NEW_CHART podem ser habilitados ou desabilitados independentemente. O menu pop-up New deverá ser habilitado se um dos dois estiver habilitado.

O manipulador de comando para ID_NEW_SHEET (o primeiro comando no pop-up) seria semelhante a:

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

O manipulador de comandos para ID_NEW_CHART seria um manipulador de comando de atualização normal e seria semelhante a:

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

ON_COMMAND e ON_BN_CLICKED

As macros de mapa de mensagens para ON_COMMAND e ON_BN_CLICKED são as mesmas. O mecanismo de roteamento de notificação de controle e comando do MFC usa apenas a ID de comando para decidir para onde rotear. As notificações de controle com código de notificação de controle de zero (BN_CLICKED) são interpretadas como comandos.

Observação

Na verdade, todas as mensagens de notificação de controle passam pela cadeia do manipulador de comandos. Por exemplo, tecnicamente é possível escrever um manipulador de notificação de controle para EN_CHANGE em sua classe de documento. Isso geralmente não é aconselhável porque os aplicativos práticos desse recurso são poucos, o recurso não é compatível com ClassWizard e o uso do recurso pode resultar em código frágil.

Como desabilitar a desabilitação automática de controles de botão

Se você colocar um controle de botão em uma barra de diálogo ou em uma caixa de diálogo em que você está chamando CWnd::UpdateDialogControls por conta própria, você observará que os botões que não têm manipuladores ON_COMMAND ou ON_UPDATE_COMMAND_UI serão desabilitados automaticamente para você pela estrutura. Em alguns casos, você não precisará ter um manipulador, mas desejará que o botão permaneça habilitado. A maneira mais fácil de fazer isso é adicionar um manipulador de comando fictício (fácil de fazer com ClassWizard) e não fazer nada nele.

Roteamento de Mensagens de Janela

A seguir, são descritos alguns tópicos mais avançados sobre as classes MFC e como o roteamento de mensagens do Windows e outros tópicos os afetam. As informações aqui são descritas apenas brevemente. Consulte a Referência da Biblioteca de Classes para obter detalhes sobre APIs públicas. Consulte o código-fonte da biblioteca MFC para obter mais informações sobre detalhes da implementação.

Consulte a Nota Técnica 17 para obter detalhes sobre a limpeza de janelas, um tópico muito importante para todas as classes derivadas de CWnd.

Problemas do CWnd

A função membro de implementação CWnd::OnChildNotify fornece uma arquitetura poderosa e extensível para janelas filho (também conhecidas como controles) para configurar um gancho ou de outra forma ser informado sobre mensagens, comandos e notificações de controle que vão para o pai (ou "proprietário"). Se a janela filho (/control) for um objeto CWnd C++, a função virtual OnChildNotify será chamada primeiro com os parâmetros da mensagem original (ou seja, uma estrutura MSG ). A janela filho pode deixar a mensagem como está, excluí-la ou modificar a mensagem para o pai (o que é raro).

A implementação padrão do CWnd manipula as seguintes mensagens e usa o gancho OnChildNotify para permitir que as janelas filho (controles) acessem a mensagem pela primeira vez:

  • WM_MEASUREITEM e WM_DRAWITEM (para autodesenhar)

  • WM_COMPAREITEM e WM_DELETEITEM (para autodesenhar)

  • WM_HSCROLL e WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Você observará que o gancho OnChildNotify é usado para alterar mensagens de desenho do proprietário em mensagens autodesenhadas.

Além do gancho OnChildNotify, as mensagens de rolagem têm comportamento de roteamento adicional. Confira abaixo mais detalhes sobre barras de rolagem e origens de mensagens WM_HSCROLL e WM_VSCROLL.

Problemas de CFrameWnd

A classe CFrameWnd fornece a maior parte do roteamento de comando e implementação de atualização da interface do usuário. Isso é usado principalmente para a janela do quadro principal do aplicativo (CWinApp::m_pMainWnd), mas se aplica a todas as janelas de quadro.

A janela do quadro principal é a janela com a barra de menus e é o pai da barra de status ou da linha de mensagem. Consulte a discussão acima sobre roteamento de comando e WM_INITMENUPOPUP.

A classe CFrameWnd fornece gerenciamento do modo de exibição ativo. As seguintes mensagens são roteadas por meio do modo de exibição ativo:

  • Todas as mensagens de comando (o modo de exibição ativo obtém o primeiro acesso a elas).

  • Mensagens de barras de rolagem irmãs WM_HSCROLL e WM_VSCROLL (veja abaixo).

  • WM_ACTIVATE (e WM_MDIACTIVATE para MDI) são transformados em chamadas para a função virtual CView::OnActivateView.

Problemas de CMDIFrameWnd/CMDIChildWnd

Ambas as classes de janela do quadro MDI derivam de CFrameWnd e, portanto, são habilitadas para o mesmo tipo de roteamento de comando e atualização da interface do usuário fornecida em CFrameWnd. Em um aplicativo MDI típico, apenas a janela do quadro principal (ou seja, o objeto CMDIFrameWnd ) mantém a barra de menus e a barra de status e, portanto, é a principal fonte da implementação de roteamento de comando.

O esquema de roteamento geral é que a janela filho do MDI ativo obtém o primeiro acesso aos comandos. As funções PreTranslateMessage padrão manipulam tabelas de aceleradores para janelas filho do MDI (primeiro) e de quadro MDI (segundo), bem como os aceleradores de comando do sistema MDI padrão normalmente manipulados por TranslateMDISysAccel (último).

Problemas de barra de rolagem

Ao manipular a mensagem de rolagem (WM_HSCROLL/OnHScroll e/ou WM_VSCROLL/OnVScroll), você deve tentar escrever o código do manipulador para que ele não dependa de onde a mensagem da barra de rolagem veio. Esse não é apenas um problema geral do Windows, uma vez que as mensagens de rolagem podem vir de controles de barra de rolagem verdadeiros ou de barras de rolagem WS_HSCROLL/WS_VSCROLL, que não são controles de barra de rolagem.

O MFC estende isso para permitir que os controles da barra de rolagem sejam filhos ou irmãos da janela que está sendo rolada (na verdade, a relação pai/filho entre a barra de rolagem e a janela que está sendo rolada pode ser qualquer coisa). Isso é especialmente importante para barras de rolagem compartilhadas com janelas divisoras. Consulte a Nota Técnica 29 para obter detalhes sobre a implementação de CSplitterWnd, incluindo mais informações sobre problemas de barra de rolagem compartilhada.

Em uma observação lateral, há duas classes derivadas de CWnd em que os estilos de barra de rolagem especificados no momento da criação são presos e não passados para o Windows. Quando passados para uma rotina de criação, WS_HSCROLL e WS_VSCROLL podem ser definidos de maneira independente, mas não podem ser alterados após a criação. É claro que você não deve testar nem definir diretamente os bits de estilo de WS_SCROLL da janela que eles criaram.

Para CMDIFrameWnd, os estilos de barra de rolagem que você passa para Create ou LoadFrame são usados para criar o MDICLIENT. Se você quiser ter uma área MDICLIENT rolável (como o Gerenciador de Programas do Windows), defina os dois estilos de barra de rolagem (WS_HSCROLL | WS_VSCROLL) para o estilo usado para criar o CMDIFrameWnd.

Para CSplitterWnd, os estilos de barra de rolagem se aplicam às barras de rolagem compartilhadas especiais para as regiões de divisor. Para janelas divisoras estáticas, normalmente você não definirá nenhum estilo de barra de rolagem. Para janelas divisoras dinâmicas, você geralmente terá o estilo da barra de rolagem definido para a direção que você dividirá, ou seja, WS_HSCROLL se puder dividir linhas , WS_VSCROLL se puder dividir colunas.

Confira também

Observações técnicas por número
Observações técnicas por categoria