Windows Messages

Trabalhando com mensagens do Windows

Neste artigo veremos como trabalhar com mensagens do Windows, como também criar e utilizar nossas próprias mensagens. O Windows envia mensagens o tempo todo, e nossas aplicações Delphi respondem a estas mensagens como também envia para o Windows. Todo este processo de envio e recebimento de mensagens ocorre de forma transparente; isto é, toda a complexidade envolvida neste processo é ocultada pelo Delphi e pelo Windows.

 

O Windows possui muitas mensagens e controles de resposta para muitas mensagens que são expostas pelas nossas aplicações Delphi. Muitos componentes da VCL utilizam mensagens do Windows em seus métodos e eventos. A partir de uma aplicação Delphi nós podemos enviar mensagens para o Windows diretamente para que o mesmo execute uma determinada ação. Veremos no decorrer dos exemplos deste artigo como fazer o envio de mensagens do próprio Windows como também a criar e manipular nossas próprias mensagens.

 

Nota: Para a implementação dos exemplos deste artigo foi utilizado o Delphi 7; porém, você pode reproduzir os exemplos a partir do Delphi 2005 ou 2006 sem problemas. No último exemplo do artigo foi utilizado como servidor de banco de dados o Firebird 1.5.2.

Windows Messages

Para que você possa enviar mensagens a partir de uma aplicação Delphi você precisa conhecer as mensagens definidas no Windows. A instalação do Delphi 7 trás consigo o arquivo de help “Win32 Programmer’s Reference” o qual pode ser acessado a partir do menu iniciar do windows em Iniciar|Programas|Borland Delphi 7|Help|MS SDK Help Files. Dentro deste arquivo de ajude nós temos acesso às mensagens definidas no sistema operacional Windows como também a descrição e propósito de cada uma. Por exemplo: entre no índice do arquivo e digite “Button Messages”. Serão listadas todas as mensagens relacionadas ao botão. Clicando em cima de uma mensagem nós temos informações sobre parâmetros de entrada, valor de retorno, como também mensagens relacionadas.

 

As mensagens do Windows são definidas normalmente com um prefixo bastante intuitivo, como por exemplo, LB_ para mensagens relacionadas ao ListBox e CB_ para mensagens relacionadas ao ComboBox. Uma mensagem do Windows nada mais é que um número pré-definido. O Delphi trás consigo uma unit denominada Messages a qual trás a declaração das mensagens do Windows através de constantes, cada uma contendo seu respectivo número.

SendMessage

Para enviar uma mensagem nós podemos utilizar o método SendMessage o qual possui sua declaração presente dentro da unit Windows do Delphi. Veja abaixo a assinatura do método:

 

function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;

    lParam: LPARAM): LRESULT; stdcall;

 

O parâmetro hWnd diz respeito a janela de destino a qual receberá a mensagem. O parâmetro Msg contém a mensagem que será enviada. Os parâmetros wParam e lParam dizem respeito ao primeiro e segundo parâmetro de entrada do método respectivamente.  Acredito que a maioria dos desenvolvedores Delphi já precisou implementar em seus sistemas a funcionalidade de trocar a função da tecla tab pelo enter para mover o cursor pro próximo controle da tela. Para implementar está funcionalidade nós podemos utilizar uma mensagem.

 

Através da mensagem WM_NEXTDLGCTL, a qual está presente na unit Messages, nós podemos enviar uma mensagem ao formulário Delphi solicitando o movimento do cursor para o próximo componente. Como exemplo, crie uma nova aplicação Delphi, adicione alguns componentes visuais (Edit, ComboBox, ...) ao formulário principal, altere a propriedade KeyPreview do form para True e adicione o seguinte código ao seu evento OnKeyPress:

 

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);

begin

  if Key = #13 then //Enter

  begin

    Key := #0;

    SendMessage(handle, WM_NEXTDLGCTL, 0, 0);

  end;

end;

 

No código acima nós utilizamos o método SendMessage para enviar a mensagem WM_NEXTDLGCTL ao handle do próprio formulário para que o mesmo passe o foco do componente ativo para o próximo da janela. Para esses casos, onde enviamos a mensagem para a própria janela, nós podemos utilizar como alternativa o método Perform da classe TControl. Veja o código abaixo:

 

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);

begin

  if Key = #13 then //Enter

  begin

    Key := #0;

    Perform(WM_NEXTDLGCTL, 0, 0);

  end;

end;

 

Neste código utilizamos o método Perform para realizar a mesma função do SendMessage; porém, neste caso não há a necessidade de passar o handle da janela visto que este método envia mensagens apenas para sua própria janela.

PostMessage

O método PostMessage possui o mesmo propósito do método SendMessage. Entretanto, o PostMessage envia a mensagem ao handle de destino e não aguarda por uma resposta do mesmo. Para situações que você precise enviar uma mensagem a uma determinada janela e aguardar uma resposta para poder continuar com a execução do programa utilize SendMessage, caso contrário, use PostMessage. Veja o quadro abaixo:

 

Método

Descrição

Tipo

SendMessage

Envia uma mensagem para a janela e aguarda o retorno para continuar a execução.

Síncrono

PostMessage

Envia uma mensagem para a janela e não aguarda o retorno para continuar a execução.

Assíncrono

 

Para que possamos visualizar melhor a diferença entre os métodos vamos criar um pequeno exemplo. Inicie uma nova aplicação (File|New|Application) e adicione três botões ao form principal. Altere a propriedade Caption de cada botão para SendMessage, PostMessage e Perform respectivamente.

 

Obs: Neste exemplo criaremos uma mensagem customizada para que possamos demonstrar a diferença entre os métodos. Abordaremos na seqüência o uso, em detalhes, de mensagens customizadas.

 

Na unit do form principal declare uma constante logo abaixo da seção uses da unit como mostrada abaixo:

 

const

  WM_NOVAMENSAGEM = WM_APP + 500;

 

Na seção private da unit declare a seguinte procedure:

 

procedure NOVAMENSAGEM(var Msg: TMessage); message WM_NOVAMENSAGEM;

 

Para criar o “esqueleto” da nova procedure dentro da seção implementation da unit utilize a opção Complete class at cursor (Shift+Ctrl+C). Implemente a procedure como mostrado abaixo:

 

procedure TFrmPrincipal.NOVAMENSAGEM(var Msg: TMessage);

begin

  ShowMessage('Mensagem "WM_NOVAMENSAGEM"');

end;

 

Após definida a mensagem e a funcionalidade para a mesma vamos implementar o evento OnClick de cada botão utilizando cada um dos métodos que vimos até o momento. Para o botão SendMessage digite o seguinte código:

 

procedure TFrmPrincipal.btnSendClick(Sender: TObject);

begin

  SendMessage(handle, WM_NOVAMENSAGEM, 0, 0); //Síncrono

  ShowMessage('Depois da Mensagem "WM_NOVAMENSAGEM" ter sido enviada (SendMessage)');

end;

 

No código acima, quando o botão SendMessage for pressionado será mostrada a caixa de mensagem referente a chamada a nova mensagem (WM_NOVAMENSAGEM). Somente após o fechamento da caixa de mensagem será executada a instrução ShowMessage definida após o método SendMessage. Para o botão PostMessage entre com o seguinte código:

 

procedure TFrmPrincipal.btnPostClick(Sender: TObject);

begin

  PostMessage(Self.handle, WM_NOVAMENSAGEM, 0, 0); //Assíncrono

  ShowMessage('Depois da Mensagem "WM_NOVAMENSAGEM" ter sido enviada (PostMessage)');

end;

 

No caso do botão PostMessage, ao pressioná-lo será mostrado tanto a caixa de mensagem referente a nova mensagem (WM_NOVAMENSAGEM) como também o ShowMessage definido após a chamada ao método PostMessage. Para o último botão, Perform, entre com o seguinte código:

 

procedure TFrmPrincipal.btnPerformClick(Sender: TObject);

begin

  Perform(WM_NOVAMENSAGEM, 0, 0); //Síncrono

  ShowMessage('Depois da Mensagem "WM_NOVAMENSAGEM" ter sido enviada (Perform)');

end;

 

O método Perform funciona de forma equivalente ao SendMessage, isto é, ambos são síncronos. Ao clicarmos no botão Perform será mostrada a caixa de mensagem referente à nova mensagem definida. Só após o fechamento da mesma que a execução do código continuará e o ShowMessage definido após a chamada ao Perform será mostrado.

Definindo novas mensagens

Através das mensagens do Windows nós podemos realizar as mais diversas operações, como:

·         Realizar operações de drag and drop

·         Manipular o comportamento de componentes

·         Fazer a chamada ao menu iniciar do windows

·         Desligar ou reiniciar o computador

·         Etc

 

Além das mensagens pré-definidas do Windows nós podemos criar nossas próprias mensagens, onde para cada uma podemos implementar uma funcionalidade diferente. Como vimos anteriormente cada mensagem é definida com um número. Uma mensagem válida precisa conter um número entre 1 e 49151. Como o Windows já possui diversas mensagens pré-definidas nós não podemos sair pegando qualquer número para definir nossas mensagens, visto que este número pode já estar sendo utilizado pelo Windows. O Windows define uma constante de nome WM_USER a qual possui o valor de 1024.

 

Teoricamente, qualquer número que pegarmos acima desta variável é um número seguro para utilizarmos em nossas mensagens customizadas. Existem ainda outras constantes, como a WM_APP a qual possui o número 32768 em sua definição. Está constante normalmente é utilizada para especificar outras constantes específicas de uma aplicação. Para o próximo exemplo vamos criar duas aplicações, uma chamada Emissora e outra denominada Receptora. Para ambas aplicações vamos definir algumas mensagens para que possamos executar determinados procedimentos a partir de diferentes aplicações. Começaremos realizando a implementação da aplicação receptora. Inicie uma nova aplicação Delphi (File|New|Application). Altere o nome do formulário para FrmReceptora e salve sua unit como untFrmReceptora. Para o arquivo de projeto de o nome de Receptora.dpr. Adicione alguns componentes visuais e não-visuais e configure-os como mostra a Figura 1.

 

Figura 1. Layout em tempo de designer da aplicação Receptora.

Selecione o componente conEmployee (SQLConnection) e configure o mesmo para acessar o banco de dados Employee.fdb o qual acompanha a instalação do Firebird.

 

Nota: Você pode utilizar o driver dbExpress do Interbase para acessar o banco de dados Employee.fdb.

 

Com o componente sdsClientes (SimpleDataSet) selecionado aponte sua propriedade Connection para conEmployee e adicione a seguinte instrução SQL a sua propriedade DataSet|CommandText:

 

select * from CUSTOMER

 

Aponte a propriedade DataSet do componente dsClientes (DataSource) para o sdsClientes. Selecione os componentes DBNavigator e DBGrid e aponte suas propriedade Datasource para dsClientes. Para o DBNavigator mantenha habilitado apenas os botões de navegação (First, Prior, Next e Last). Depois de configurado os componentes de acesso a dados entre com o seguinte código para os eventos OnCreate e OnClose do formulário FrmReceptora:

 

procedure TFrmPrincipal.FormCreate(Sender: TObject);

begin

  sdsClientes.Open;

end;

 

procedure TFrmPrincipal.FormClose(Sender: TObject; var Action: TCloseAction);

begin

  sdsClientes.Close;

end;

 

Para a aplicação receptora, a qual receberá as mensagens e executará a funcionalidade relacionada a cada uma, vamos criar quatro novas mensagens. Crie quatro constantes logo abaixo da seção uses da unit untFrmReceptora como mostrado abaixo:

 

const

  CM_INSERIR = WM_APP + 500;

  CM_EXCLUIR = WM_APP + 501;

  CM_GRAVAR = WM_APP + 502;

  CM_EDITAR = WM_APP + 503;

 

No código acima nós criamos quatro constantes, onde cada uma possui um valor único que é definido a partir do valor da constante WM_APP mais um número qualquer. Quando definimos uma mensagem utilizando a constante WM_APP, por exemplo, nós asseguramos que o número da mensagem não estará sendo utilizado pelo Windows quando fizermos o envio da mesma.

Para a identificação de cada mensagem nós podemos utilizar o nome que quisermos, lembrando apenas, que devemos utilizar nomes sugestivos para facilitar a codificação e manutenção do código. Na seção private da unit entre com as seguintes declarações de procedures:

 

procedure Inserir(var Msg: TMessage); message CM_INSERIR;

procedure Excluir(var Msg: TMessage); message CM_EXCLUIR;

procedure Gravar(var Msg: TMessage); message CM_GRAVAR;

procedure Editar(var Msg: TMessage); message CM_EDITAR;

 

Reparem que utilizamos a palavra reservada message no final da procedure seguido do nome da mensagem customizada. Isto é necessário para que o nosso aplicativo saiba qual procedimento executar quando receber uma determinada mensagem. Pressione as teclas Shift+Ctrl+C para criar o “esqueleto” dos quatro métodos. Implemente os métodos como mostrado abaixo:

 

procedure TFrmReceptora.Inserir(var Msg: TMessage);

begin

  sdsClientes.Insert;

  ShowMessage('Inserindo um novo registro!');

end;

 

procedure TFrmReceptora.Gravar(var Msg: TMessage);

begin

  if sdsClientes.State in [dsInsert, dsEdit] then

  begin

    sdsClientes.Post;

    sdsClientes.ApplyUpdates(0);

    ShowMessage('Registro Salvo com sucesso!');

  end

  else

    ShowMessage('Não há registro em inserção ou alteração');

end;

 

procedure TFrmReceptora.Editar(var Msg: TMessage);

begin

  sdsClientes.Edit;

  ShowMessage('Alterando o Registro!');

end;

 

procedure TFrmReceptora.Excluir(var Msg: TMessage);

begin

  sdsClientes.Delete;

  sdsClientes.ApplyUpdates(0);

  ShowMessage('Registro Excluído!');

end;

 

Repare que no final de cada método fazemos a chamada a um ShowMessage. Para facilitar a visualização da execução das mensagens mostraremos uma caixa de mensagem na tela cada vez que uma mensagem customizada for recebida pela aplicação receptora. Neste ponto se compilarmos e executarmos a aplicação receptora veremos que só conseguimos navegar pelos registros.

 

Para que possamos realizar operações de inserção, exclusão e alteração, utilizaremos as mensagens customizadas as quais serão enviadas a partir de outra aplicação, a emissora. Inicie uma nova aplicação Delphi (File|New|Application). Altere o nome do formulário para FrmEmissora e salve sua unit como untFrmEmissora. Para o arquivo de projeto de o nome de Emissora.dpr. Adicione alguns componentes visuais e configure-os como mostra a Figura 2.

 

Figura 2. Layout em tempo de designer da aplicação Emissora.

Utilizaremos está aplicação para enviar mensagens personalizadas para a aplicação receptora. Antes de implementar o evento OnClick de cada botão nós precisamos definir as mensagens que serão enviadas. Da mesma maneira que fizemos anteriormente, declare quatro constantes logo abaixo da seção uses da unit untFrmEmissora:

 

const

  CM_INSERIR = WM_APP + 500;

  CM_EXCLUIR = WM_APP + 501;

  CM_GRAVAR = WM_APP + 502;

  CM_EDITAR = WM_APP + 503;

 

Nota: Os nomes e valores atribuídos as constantes devem ser os mesmos que foram designados para as constantes na aplicação receptora.

 

Para a seção private da unit declare a seguinte variável:

 

FrmReceptora : THandle;

 

Utilizaremos está variável para guardar o handle da janela da aplicação receptora, caso a mesma esteja sendo executada na máquina. Para os botões, Novo, Editar, Salvar e Excluir entre com os seguintes códigos respectivamente:

 

procedure TFrmEmissora.btnNovoClick(Sender: TObject);

begin

  FrmReceptora := FindWindow('TFrmReceptora', PChar('Receptora'));

  if FrmReceptora <> 0 then

    SendMessage(FrmReceptora, CM_INSERIR, 0, 0) //Inserir um registro

  else

    ShowMessage('Janela receptora não encontrada!');

end;

 

procedure TFrmEmissora.btnEditarClick(Sender: TObject);

begin

  FrmReceptora := FindWindow('TFrmReceptora', PChar('Receptora'));

  if FrmReceptora <> 0 then

    SendMessage(FrmReceptora, CM_EDITAR, 0, 0) //Alterar um registro

  else

    ShowMessage('Janela receptora não encontrada!');

end;

 

procedure TFrmEmissora.btnSalvarClick(Sender: TObject);

begin

  FrmReceptora := FindWindow('TFrmReceptora', PChar('Receptora'));

  if FrmReceptora <> 0 then

    SendMessage(FrmReceptora, CM_GRAVAR, 0, 0) //Gravar um registro

  else

    ShowMessage('Janela receptora não encontrada!');

end;

 

procedure TFrmEmissora.btnExcluirClick(Sender: TObject);

begin

  FrmReceptora := FindWindow('TFrmReceptora', PChar('Receptora'));

  if FrmReceptora <> 0 then

    SendMessage(FrmReceptora, CM_EXCLUIR, 0, 0) //Excluir um registro

  else

    ShowMessage('Janela receptora não encontrada!');

end;

 

Em todos os botões utilizamos o método SendMessage para enviar nossas mensagens customizadas para a janela da aplicação receptora. Através do método FindWindow nós podemos capturar o handle de uma janela a partir de sua classe e/ou título da janela. Caso a janela não seja encontrada o método FindWindow retorna zero, caso contrário um número inteiro positivo é retornado para nossa variável FrmReceptora. Pronto, nosso exemplo está finalizado. Basta agora executarmos as aplicações para que possamos enviar nossas mensagens customizadas entre elas. Veja os aplicativos do exemplo em execução na Figura 3.

 

Figura 3. Aplicação Emissora e Receptora em tempo de execução.

Conclusão

O principal objetivo deste artigo foi mostrar que através de mensagens nós podemos enviar “comandos” entre aplicações diferentes de forma simples e eficaz. Além das mensagens pré-definidas do Windows nós podemos criar nossas próprias mensagens, e o melhor, com as funcionalidades que quisermos. Um abraço e até a próxima.