Compartilhar via


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

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

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

Esses eventos podem ocorrer a qualquer momento enquanto o programa estiver em execução, em praticamente 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 seguinte código de mensagem.

#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ê já sabe para que serve o procedimento de janela.)

O loop de mensagem

Um aplicativo receberá milhares de mensagens enquanto é executado. (Considere que cada pressionamento de tecla e cada 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 no procedimento correto de janela? O aplicativo precisa de um loop para recuperar as mensagens e enviá-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 questão fica oculta em seu 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 topo da fila. Se a fila estiver vazia, a função será bloqueada até que outra mensagem seja enfileirada. O fato de que GetMessage bloqueia não fará com que seu programa pare de responder. Se não houver mensagens, não há nada para o programa fazer. Se você precisar executar o processamento em segundo plano, poderá criar threads adicionais que continuarão a ser executados enquanto GetMessage aguarda outra mensagem. (Veja Como evitar 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 do 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ê recebe da fila. Em quase todos os casos, você definirá esses parâmetros como zero.

Apesar de a estrutura MSG conter informações sobre a mensagem, você raramente 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 traduz as teclas digitadas (tecla para baixo, tecla para cima) em caracteres. Você realmente não precisa saber como essa função funciona, basta lembrar de chamá-la antes de DispatchMessage.

A função DispatchMessage informa ao sistema operacional para chamar o procedimento de janela, da janela que é o alvo da mensagem. Em outras palavras, o sistema operacional procura 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 extrai a 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. Seu procedimento de janela pode responder à mensagem ou ignorá-la.

Quando o procedimento de janela retorna, 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 extraia continuamente as mensagens da fila e as despache. Você pode pensar no loop como se estivesse fazendo o seguinte:

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

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

Como está escrito, é lógico, esse loop nunca terminaria. É aí que entra o valor de devolução para a função GetMessage entrar. Normalmente, GetMessage retorna a 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 a mensagem WM_QUIT na fila de mensagem. WM_QUIT é uma mensagem especial e faz com que GetMessage retorne a 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 a um valor diferente de zero, a expressão no loop while é avaliado como verdadeira. Depois de chamar PostQuitMessage a expressão se torna falsa e o programa sai do loop. (Um resultado interessante desse comportamento é que seu procedimento de janela nunca recebe uma mensagem WM_QUIT portanto, você não precisa ter uma instrução case para essa mensagem no procedimento de janela.)

A próxima pergunta óbvia é quando chamar PostQuitMessage. Voltaremos a essa questão no tópico fechar a janela,mas primeiro temos que escrever nosso procedimento de janela.

Mensagens postadas versus mensagens enviadas

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

A terminologia para essa distinção pode ser confusa:

  • Postar uma mensagem significa que a mensagem vai para a fila de mensagens e é despachada por meio do loop de mensagens (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 lida com todas as mensagens. Entretanto, algumas mensagens ignoram a fila e vão diretamente para o procedimento de janela. Entretanto, pode fazer diferença se o aplicativo se comunicar entre as janelas. Você pode encontrar uma discussão mais completa sobre esse problema no tópico sobre mensagens e filas de mensagens.

Próximo

Como gravar o procedimento de janela