Share via


Mensagens de janela (Introdução ao Win32 e C++)

Um aplicativo de GUI deve responder a eventos do usuário e do sistema operacional.

  • Os eventos do usuário incluem todas as maneiras pelas quais alguém pode interagir com seu programa: cliques do mouse, traços de teclas, gestos de tela sensível ao toque e assim por diante.
  • Os eventos do sistema operacional incluem qualquer coisa "fora" do programa que possa afetar o comportamento do programa. Por exemplo, o usuário pode conectar um novo dispositivo de hardware ou o Windows pode entrar em um estado de energia inferior (suspensão ou hibernação).

Esses eventos podem ocorrer a qualquer momento enquanto o programa está em execução, em quase qualquer ordem. Como você estrutura um programa cujo fluxo de execução não pode ser previsto com antecedência?

Para resolver esse problema, o Windows usa um modelo de passagem de mensagens. O sistema operacional se comunica com a janela do aplicativo passando mensagens para ele. Uma mensagem é simplesmente um código numérico que designa um evento específico. Por exemplo, se o usuário pressionar o botão esquerdo do mouse, a janela receberá uma mensagem com o código de mensagem a seguir.

#define WM_LBUTTONDOWN    0x0201

Algumas mensagens têm dados associados a elas. Por exemplo, a mensagem WM_LBUTTONDOWN inclui a coordenada x e a coordenada y do cursor do mouse.

Para passar uma mensagem para uma janela, o sistema operacional chama o procedimento de janela registrado para essa janela. (E agora você sabe para que serve o procedimento de janela.)

O loop de mensagem

Um aplicativo receberá milhares de mensagens enquanto ele é executado. (Considere que cada pressionamento de tecla e clique no botão do mouse gera uma mensagem.) Além disso, um aplicativo pode ter várias janelas, cada uma com seu próprio procedimento de janela. Como o programa recebe todas essas mensagens e as entrega ao procedimento de janela correto? O aplicativo precisa de um loop para recuperar as mensagens e expedi-las para as janelas corretas.

Para cada thread que cria uma janela, o sistema operacional cria uma fila para mensagens de janela. Essa fila contém mensagens para todas as janelas criadas nesse thread. A fila em si está oculta do programa. Você não pode manipular a fila diretamente. No entanto, você pode efetuar pull de uma mensagem da fila chamando a função GetMessage .

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Essa função remove a primeira mensagem do cabeçalho da fila. Se a fila estiver vazia, a função será bloqueada até que outra mensagem seja enfileirada. O fato de que os blocos GetMessage não deixarão seu programa sem resposta. Se não houver mensagens, não haverá nada para o programa fazer. Se você precisar executar o processamento em segundo plano, poderá criar threads adicionais que continuam sendo executados enquanto GetMessage aguarda outra mensagem. (Consulte Evitando gargalos no procedimento de janela.)

O primeiro parâmetro de GetMessage é o endereço de uma estrutura MSG . Se a função for bem-sucedida, ela preencherá a estrutura MSG com informações sobre a mensagem. Isso inclui a janela de destino e o código da mensagem. Os outros três parâmetros permitem filtrar quais mensagens você obtém da fila. Em quase todos os casos, você definirá esses parâmetros como zero.

Embora a estrutura MSG contenha informações sobre a mensagem, você quase nunca examinará essa estrutura diretamente. Em vez disso, você o passará diretamente para duas outras funções.

TranslateMessage(&msg); 
DispatchMessage(&msg);

A função TranslateMessage está relacionada à entrada do teclado. Ele converte pressionamentos de tecla (tecla para baixo, tecla para cima) em caracteres. Você realmente não precisa saber como essa função funciona; lembre-se de chamá-lo antes de DispatchMessage. O link para a documentação do MSDN fornecerá mais informações, se você estiver curioso.

A função DispatchMessage instrui o sistema operacional a chamar o procedimento de janela da janela que é o destino da mensagem. Em outras palavras, o sistema operacional pesquisa o identificador de janela em sua tabela de janelas, localiza o ponteiro de função associado à janela e invoca a função.

Por exemplo, suponha que o usuário pressione o botão esquerdo do mouse. Isso causa uma cadeia de eventos:

  1. O sistema operacional coloca uma mensagem WM_LBUTTONDOWN na fila de mensagens.
  2. Seu programa chama a função GetMessage .
  3. GetMessage efetua pull da mensagem WM_LBUTTONDOWN da fila e preenche a estrutura MSG .
  4. Seu programa chama as funções TranslateMessage e DispatchMessage .
  5. Dentro de DispatchMessage, o sistema operacional chama o procedimento de janela.
  6. O procedimento da janela pode responder à mensagem ou ignorá-la.

Quando o procedimento de janela é retornado, ele retorna para DispatchMessage. Isso retorna ao loop de mensagem para a próxima mensagem. Enquanto o programa estiver em execução, as mensagens continuarão a chegar na fila. Portanto, você deve ter um loop que efetua pull contínuo de mensagens da fila e as envia. Você pode pensar no loop como fazendo o seguinte:

// WARNING: Don't actually write your loop this way.

while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

Como escrito, é claro, esse loop nunca terminaria. É aí que entra o valor retornado da função GetMessage . Normalmente, GetMessage retorna um valor diferente de zero. Quando você quiser sair do aplicativo e sair do loop de mensagem, chame a função PostQuitMessage .

        PostQuitMessage(0);

A função PostQuitMessage coloca uma mensagem WM_QUIT na fila de mensagens. WM_QUIT é uma mensagem especial: faz com que GetMessage retorne zero, sinalizando o fim do loop de mensagem. Aqui está o loop de mensagem revisado.

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Desde que GetMessage retorne um valor diferente de zero, a expressão no loop while será avaliada como true. Depois de chamar PostQuitMessage, a expressão se tornará falsa e o programa sairá do loop. (Um resultado interessante desse comportamento é que o procedimento de janela nunca recebe uma mensagem WM_QUIT . Portanto, você não precisa ter uma instrução case para essa mensagem em seu procedimento de janela.)

A próxima pergunta óbvia é quando chamar PostQuitMessage. Retornaremos a essa pergunta no tópico Fechando a janela, mas primeiro precisamos escrever nosso procedimento de janela.

Mensagens postadas versus mensagens enviadas

A seção anterior falava sobre mensagens que iam para uma fila. Às vezes, o sistema operacional chamará um procedimento de janela diretamente, ignorando a fila.

A terminologia dessa distinção pode ser confusa:

  • Postar uma mensagem significa que a mensagem vai para a fila de mensagens e é expedida por meio do loop de mensagem (GetMessage e DispatchMessage).
  • Enviar uma mensagem significa que a mensagem ignora a fila e o sistema operacional chama o procedimento de janela diretamente.

Por enquanto, a diferença não é muito importante. O procedimento de janela manipula todas as mensagens. No entanto, algumas mensagens ignoram a fila e vão diretamente para o procedimento de janela. No entanto, isso poderá fazer a diferença se o aplicativo se comunicar entre janelas. Você pode encontrar uma discussão mais completa sobre esse problema no tópico Sobre Mensagens e Filas de Mensagens.

Avançar

Gravando o procedimento de janela