Compartilhar via


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 apenas aborda tópicos muito avançados.

Funcionalidade do MFC 1.0 de roteamento e expedição de comando evolui para a arquitetura do 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 baseou-se um pouco nisso, permitindo que um manipulador de comando (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 espaço.

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.

O MFC 1.0 usou 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 um intervalo maior de objetos (não apenas objetos de janela). Ele fornece uma arquitetura mais formal e extensível para rotear mensagens e reutiliza o roteamento de destino de comando para não apenas o tratamento de comandos, mas também para atualizar objetos de interface do usuário (como itens de menu e botões de barra de ferramentas) para refletir a disponibilidade atual de um comando.

IDs de comando

Consulte 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 barra de status ou linha de mensagem 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 aplicativo, uma ID de comando pode ser exibida em vários lugares:

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

  • Possivelmente em muitos recursos de 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 lugares:

  • Em seu RESOURCE.H (ou outro arquivo de cabeçalho de símbolo principal) para definir IDs de comando específicas a um 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 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 quadros, itens de menu, botões da barra de ferramentas, botões da 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 de 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 seja necessário escrever algum código adicional.

Observe que, para que todos esses recursos funcionem corretamente, suas IDs de comando devem ser >= 0x8000. Como muitas caixas de diálogo podem ser roteadas para o mesmo frame, 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 de quadro principal) obtém o comando exatamente como qualquer outro comando. Isso é chamado de comando GOSUB, pois ele 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 no diálogo e passar o endereço da janela de quadro principal para ela. Essa função habilitará ou desabilitará seus controles de diálogo com base em se eles têm manipuladores de comando no quadro. Essa função é chamada automaticamente para barras de controle no ciclo ocioso do aplicativo, mas você deve chamá-la diretamente para caixas de diálogo normais, se desejar que tenham esse recurso.

Quando ON_UPDATE_COMMAND_UI é chamado

Manter o estado habilitado/verificado de todos os itens de menu de um programa o tempo todo pode ser um problema computacionalmente caro. Uma técnica comum é habilitar/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 utiliza a arquitetura de roteamento de comandos para determinar os estados dos menus por meio dos 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 menu de um aplicativo, editada pelo Visual C++, é usada para representar os comandos potenciais disponíveis no evento 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 fazer referência a todo o menu pop-up. Nesse caso, a variável de membro m_pSubMenu do CCmdUI objeto 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 refere-se apenas ao primeiro item de menu e a variável de membro m_pSubMenu do CCmdUI objeto será NULL.

Isso permite que você habilite o menu pop-up separadamente dos seus itens de menu, mas requer que você escreva algum código com suporte 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 habilitados ou desabilitados de forma independente. O novo menu pop-up 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 comandos 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 igual a zero (BN_CLICKED) são interpretadas como comandos.

Observação

Na verdade, todas as mensagens de notificação de controle passam pela cadeia de manipuladores 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 tem suporte do ClassWizard e o uso do recurso pode resultar em código frágil.

Desabilitando 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 usando onde você está chamando CWnd::UpdateDialogControls por conta própria, você observará que 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, descreve 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 da janela, 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 auto-desenho)

  • WM_COMPAREITEM e WM_DELETEITEM (para auto-desenho)

  • 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. Veja abaixo mais detalhes sobre barras de rolagem e fontes de mensagens WM_HSCROLL e WM_VSCROLL.

Problemas de CFrameWnd

A classe CFrameWnd fornece a maior parte da implementação de roteamento de comando e 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 quadros.

A janela de quadro principal é a janela com a barra de menus e é a mãe da barra de status ou da barra de mensagens. 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.

Questões relacionadas ao CMDIFrameWnd/CMDIChildWnd

Ambas as classes de janela de quadro MDI derivam de CFrameWnd e, portanto, são habilitadas para o mesmo tipo de roteamento de comando e atualização de interface do usuário fornecida no CFrameWnd. Em um aplicativo MDI típico, somente a janela de 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 comandos.

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, já 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.

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 divididas. 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.

A título de observação, há duas classes derivadas de CWnd em que os estilos de barra de rolagem especificados no momento da criação são interceptados e não são passados para o Windows. Quando passado 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 que 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ê fornece 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 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 de divisor. Para janelas de divisor estático, normalmente você não definirá nenhum estilo de barra de rolagem. Para janelas de divisor dinâmico, você geralmente terá o estilo da barra de rolagem definido para a direção que você dividirá, ou seja, WS_HSCROLL se você puder dividir linhas, WS_VSCROLL se você puder dividir colunas.

Consulte também

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