Partilhar via


TN021: Roteamento de Comandos e Mensagens

Observação

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

Esta nota descreve a arquitetura de roteamento e despacho de comandos, bem como tópicos avançados em roteamento geral de mensagens de janela.

Consulte 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 nota 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 e despacho de comandos 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 aceleradoras e notificações de controle de diálogo.

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

MFC 1.0 também forneceu 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 a partir de um CFrameWnd (ou seja, uma seleção de menu ou um acelerador traduzido) e são roteados para uma variedade de outras janelas.

MFC 1.0 usou roteamento de comando em um sentido limitado para a implementação de Multiple Document Interface (MDI). (Uma janela de quadro MDI delega comandos à sua janela MDI filho 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 manipulação de comandos, mas também para atualizar objetos da 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

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

Usamos o prefixo genérico "ID_" para IDs de comando. Os IDs de comando são >= 0x8000. A linha de mensagem ou a barra de status mostrará 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 seu aplicativo, um ID de comando pode aparecer em vários locais:

  • Num recurso STRINGTABLE que possui o mesmo ID que o prompt da linha de mensagem.

  • Possivelmente, existem muitos recursos de menus que estão anexados a itens de menu que invocam o mesmo comando.

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

No código-fonte do seu aplicativo, um ID de comando pode aparecer em vários lugares:

  • No seu RECURSO. H (ou outro arquivo de cabeçalho de símbolo principal) para definir IDs de comando específicas do aplicativo.

  • TALVEZ Em uma matriz de ID usada para criar uma barra de ferramentas.

  • Em uma macro ON_COMMAND.

  • TALVEZ Em uma macro ON_UPDATE_COMMAND_UI.

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

Comandos GOSUB, utilizando a arquitetura de comandos em diálogos

A arquitetura de ativação e roteamento de comandos funciona bem com janelas de moldura, itens de menu, botões da barra de ferramentas, botões da barra de diálogo, outras barras de controle e outros elementos da 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 da moldura principal). O principal alvo de comando pode transmitir as notificações de comando ou controlo para outros objetos de alvo de comando, conforme apropriado.

Uma caixa de diálogo (modal ou não modal) pode beneficiar-se de alguns dos recursos da arquitetura de comando se o identificador de controlo do controle de diálogo for atribuído ao identificador de comando apropriado. O suporte para caixas de diálogo não é automático, então você pode ter que escrever algum código adicional.

Observe que, para que todos esses recursos funcionem corretamente, seus IDs de comando devem ser >= 0x8000. Como muitas caixas de diálogo podem ser roteadas 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 para o ID de comando apropriado. Quando o usuário seleciona o botão, o proprietário da caixa de diálogo (geralmente a janela do quadro principal) recebe 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 sua caixa de diálogo e passar-lhe o endereço da sua janela principal. Essa função habilitará ou desabilitará seus controles de diálogo com base no fato de eles terem manipuladores de comando no quadro. Esta função é chamada automaticamente para barras de controle no ciclo ocioso do seu aplicativo, mas deve chamá-la diretamente para diálogos normais nos quais deseja ter esta funcionalidade.

Quando ON_UPDATE_COMMAND_UI é chamado

Manter o estado ativado/verificado de todos os itens de menu de um programa o tempo todo pode ser um problema computacionalmente caro. Uma técnica comum é ativar/verificar itens de menu somente quando o usuário seleciona o POPUP. A implementação MFC 2.0 do CFrameWnd manipula a mensagem WM_INITMENUPOPUP e usa a arquitetura de roteamento de comando para determinar os estados dos menus através de manipuladores de ON_UPDATE_COMMAND_UI.

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

A estrutura de menu de um aplicativo, editada pelo Visual C++, é usada para representar os comandos potenciais disponíveis no momento WM_INITMENUPOPUP. ON_UPDATE_COMMAND_UI gestores podem modificar o estado ou o texto de um menu, ou, para usos avançados (como a lista de Arquivo MRU ou o menu pop-up de OLE Verbs), modificar realmente a estrutura do menu antes que ele 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 seu 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, notará que o manipulador de ON_UPDATE_COMMAND_UI para o primeiro item de menu no menu pop-up é chamado em dois casos diferentes.

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

Em segundo lugar, ele é chamado imediatamente antes de os itens de menu no menu pop-up vão ser desenhados. Neste caso, o ID refere-se apenas ao primeiro item de menu e a variável membro m_pSubMenu do CCmdUI objeto será NULL.

Isso permite que você separe o menu pop-up dos seus itens de menu, mas requer que você escreva algum código que reconheça menus. 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 ativados ou desativados de forma independente. O menu pop-up Novo deve ser ativado se qualquer um dos dois estiver ativado.

O manipulador de comandos para ID_NEW_SHEET (o primeiro comando no pop-up) teria a seguinte aparência:

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 teria a seguinte aparência:

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

ON_COMMAND e ON_BN_CLICKED

As macros do mapa de mensagens para ON_COMMAND e ON_BN_CLICKED são as mesmas. O mecanismo de roteamento de notificação de comando e controle MFC usa apenas a ID do comando para decidir para onde encaminhar. 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 as aplicações práticas desse recurso são poucas, o recurso não é suportado pelo ClassWizard e o uso do recurso pode resultar em código frágil.

Desativando a desativaçã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 usando onde você está chamando CWnd::UpdateDialogControls por conta própria, você notará que os botões que não têm manipuladores ON_COMMAND ou ON_UPDATE_COMMAND_UI serão automaticamente desativados para você pela estrutura. Em alguns casos, você não precisará ter um manipulador, mas desejará que o botão permaneça ativado. A maneira mais fácil de conseguir isso é adicionar um manipulador de comando fictício (fácil de fazer com ClassWizard) e não fazer nada nele.

Encaminhamento de mensagens de janela

A seguir descrevemos 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 os 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 do CWnd.

Questões CWnd

A função de membro de implementação CWnd::OnChildNotify fornece uma arquitetura poderosa e extensível para permitir que janelas filho (também conhecidas como controles) se conectem ou sejam informadas sobre mensagens, comandos e notificações de controle que são enviadas para a janela pai (ou "proprietário"). Se a janela filha (/controlo) for um objeto C++ CWnd por si só, a função virtual OnChildNotify será chamada primeiro com os parâmetros da mensagem original (ou seja, uma estrutura MSG). A janela secundária pode ignorar a mensagem, consumi-la ou modificar a mensagem para a janela principal (raro).

A implementação padrão de CWnd lida com as seguintes mensagens e usa o gancho OnChildNotify para permitir que janelas filho (controles) acessem a mensagem primeiro:

  • WM_MEASUREITEM e WM_DRAWITEM (para auto-desenho)

  • WM_COMPAREITEM e WM_DELETEITEM (para auto-desenho)

  • WM_HSCROLL e WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Você notará que o gancho OnChildNotify é usado para alterar mensagens de desenho do proprietário para mensagens de autodesenho.

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

Questões do 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 de 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 linha de mensagem. Consulte a discussão acima sobre o roteamento de comandos e WM_INITMENUPOPUP.

A classe CFrameWnd fornece gerenciamento da exibição ativa. As seguintes mensagens são encaminhadas através da vista ativa:

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

  • WM_HSCROLL e WM_VSCROLL mensagens das barras de rolamento adjacentes (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 de quadro MDI derivam de CFrameWnd e, portanto, ambas são habilitadas para o mesmo tipo de roteamento de comando e atualização de interface do usuário fornecido em CFrameWnd. Em um aplicativo MDI típico, apenas a janela do quadro principal (ou seja, o objeto CMDIFrameWnd ) contém a barra de menus e a barra de status e, portanto, é a principal fonte da implementação de roteamento de comando.

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

Problemas com a barra de rolagem

Ao manipular mensagens 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. Este 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 WS_HSCROLL/WS_VSCROLL barras de rolagem que não são controles de barra de rolagem.

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 nas janelas divisórias. Consulte a Nota Técnica 29 para obter detalhes sobre a implementação do CSplitterWnd , incluindo mais informações sobre problemas de barra de rolagem compartilhada.

Em uma nota lateral, há duas classes derivadas do CWnd onde 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 forma independente, mas após a criação não podem ser alterados. Claro, você não deve testar ou definir diretamente os bits de estilo 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ê deseja ter uma área MDICLIENT rolável (como o Gerenciador de Programas do Windows), certifique-se de definir ambos os 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 divisoras. Para janelas divisórias estáticas, você normalmente não definirá nenhum estilo de barra de rolagem. Para janelas divisórias dinâmicas, você geralmente terá o estilo da barra de rolagem definido para a direção que você dividirá, ou seja, WS_HSCROLL se você pode dividir linhas, WS_VSCROLL se você pode dividir colunas.

Ver também

Notas técnicas por número
Notas técnicas por categoria