21
May 08

Delphi: SetTimer

:: articles :: by Gilberto Saraiva

Camaradas,

O SetTimer é uma função disponibilizada pela plataforma Windows ( API ) que consiste em criar um Temporizador que notifica um Objeto Windows ( Handle ) sempre que atingir o términio do tempo estipulado. Um temporizador desse tipo é gerenciado pelo Windows, ou seja, não será relacionado com o processamento do seu aplicativo, ficando assim com uma precisão boa mesmo enquanto seu aplicativo processa alguma coisa.

Um detalhe muito importante sobre o processamento da notificação do temporizador ( Timer ), é que como a notificação padrão é feita via mensagem, a WM_TIMER, e mensagens são administradas pela VCL através do Objeto TApplication application, em 2 respectivos locais, o primeiro em ProcessMessages que processa a fila inteira de mensagens que a aplicação possuí, e o segundo HandleMessage que processa apenas a primeira mensagem da fila, se existir alguma, sinaliza o Evento OnIde e aguarda uma nova mensagem (em caso de fila a liberação é instantanea), você podera perder a precisão do temporizador por falta de manipulação da recepção de mensagens. Já vi muita gente reclamando a precisão do SetTimer na utilização dentro de um aplicativo Delphi, mas nunca vi essa reclamação de um programador C, pois o mesmo tem o trabalho de gerênciar as notificações para o objeto receptor.

Então o fato é, temporizadores não são ruins, são ótimas opções para criação de simuladores de interação, Inteligências artificiais em alguns pontos, e até mesmo para criação de cronometros e agendamentos.

 Delphi |  copy code |? 
1
{$EXTERNALSYM SetTimer}
2
function SetTimer(hWnd: HWND; nIDEvent, uElapse: UINT;
3
  lpTimerFunc: TFNTimerProc): UINT; stdcall;

O 1º parametro é referente ao Objeto Windows ( Handle ) receptor, qualquer objeto com Handle ( Objeto Windows ).
O 2º parametro é referente ao índice controlado pelo programador, servirá como identificador na hora do processamento da notificação WM_TIMER.
O 3º parametro indica o intervalo, em milisegundos, em que o temporizador notificará o objeto.
O 4º parametro é nosso cargo chefe, deve ser analizado como primeira opção sempre pois utilizando este parametro deixaremos a necessidade de utilizar a VCL ou qualquer ou controle de Objetos Windows para a recepção da notificação.

Chegando neste ponto, podemos perceber que possuímos 2 caminhos, o primerio através de um Objeto Windows e o outro através de um Callback, a escolha é você quem faz na hora da utilização, vou exemplificar as duas maneiras de utilização, então preste atenção, pois é bastante interessante :D .

Como utilizar um temporizador através de um Objeto Windows?

Para facilitar o entendimento e não aprofundar muito em matérias que não são a questão do artigo, vamos utilizar a VCL para administrar nosso Objeto Windows.

 Delphi |  copy code |? 
01
02
  TfrmTest = class(TForm)
03
    btnStart: TButton;
04
    procedure btnStartClick(Sender: TObject);
05
  private
06
    { Private declarations }
07
  public
08
    procedure WMTimer(var Msg: TWMTimer); message WM_TIMER;
09
  end;
10

Declaramos o a manipulação da mensagem WM_TIMER para o nosso “TForm” frmTest. No frmTest colocamos também um TButton btnStart para criamos o temporizador através de seu evento OnClick.

 Delphi |  copy code |? 
1
procedure TfrmTest.btnStartClick(Sender: TObject);
2
begin
3
  SetTimer(Handle, 0, 1000, nil);
4
end;

Este é o evento OnClick do TButton btnStart. Entendendo melhor o que acontece aqui:

  • Handle é o Handle do frmTest, como é Handle é Objeto Windows e pode receber mensagens.
  • 0 é o índice que eu informei para o temporizador, então sempre que eu receber um WM_TIMER vou verificar qual índice ele contém e poder estruturar tarefas em cima disso.
  • 1000 é o intervalo em milisegundos que o temporizador irá me notificar no Handle do frmTest. 1000 milisegundos = 1 segundo, logo 60000 milisegundos = 1 minuto.
  • nil é indicado como NIL pois queremos interagir com a mensagem através de mensagens.
  •  Delphi |  copy code |? 
    1
    2
    procedure TfrmTest.WMTimer(var Msg: TWMTimer);
    3
    begin
    4
      KillTimer(Handle, Msg.TimerID);
    5
      ShowMessage('Evento do temporizador: ' + IntToStr(Msg.TimerID));
    6
    end;

    Este procedimento é a parte fundamental no funcionamento de um temporizador via Objeto Windows. Perceba que já aparece uma nova função, a KillTimer, que nada mais é que a contra-API da SetTimer, ou seja, cria o temporizador com SetTimer, e destroí o mesmo com KillTimer.

     Delphi |  copy code |? 
    1
    {$EXTERNALSYM KillTimer}
    2
    function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL; stdcall;

    O 1º parametro é o Objeto Windows que recebe a notificação do temporizador, tem que ser igual ao que foi utilizado no SetTimer.
    O 2º parametro é o índice criado pelo programador, tem que ser igual ao que foi utilizado no SetTimer.

    Pois bem, já vimos que a utilização do temporizador através da VCL é fácil, e pode se extender a um universo infinito de possibilidades. Vou mostrar um exemplo prático:

     Delphi |  copy code |? 
    01
    02
    type
    03
      PTimerParam = ^TTimerParam;
    04
      TTimerParam = record
    05
        s: string;
    06
        i: integer;
    07
      end;
    08
     
    09
    procedure TfrmTest.btnStartClick(Sender: TObject);
    10
    var
    11
      Param: PTimerParam;
    12
    begin
    13
      New(Param);
    14
      Param^.s := 'Teste de título animado';
    15
      Param^.i := 1;
    16
      SetTimer(Handle, Integer(Param), 100, nil);
    17
    end;
    18
     
    19
    procedure TfrmTest.WMTimer(var Msg: TWMTimer);
    20
    begin
    21
      KillTimer(Handle, Msg.TimerID);
    22
      with PTimerParam(Msg.TimerID)^ do
    23
      begin
    24
        Caption := Copy(s, 1, i);
    25
        i := i + 1;
    26
        if i <= Length(s) then
    27
          SetTimer(Handle, Msg.TimerID, 100, nil)
    28
        else
    29
          Dispose(Pointer(Msg.TimerID));
    30
      end;
    31
    end;
    32

    Agora a segunda parte da história:

    Como utilizar um temporizador através de um Callback?

    Primeiramente, a utilização do SetTimer por Callback é mais eficiente em relação ao processo via Objeto Windows, pois não há dependência de objeto para que o reconhecimento da notificação do temporizador seja feita em relação a aplicação, pois o callback será chamado internamente pela DefWindowProc.

    Então vamos entender como declarar um procedimento no padrão do Callback TimerProc:

     C |  copy code |? 
    1
    2
    VOID CALLBACK TimerProc(      
    3
        HWND hwnd,
    4
        UINT uMsg,
    5
        UINT_PTR idEvent,
    6
        DWORD dwTime
    7
    );

    Esse modelo traduzido para Delphi:
     Delphi |  copy code |? 
    1
    procedure TimerProc(hwnd: HWND; uMsg, idEvent: UINT; dwTime: DWORD); stdcall;

    Então após entendermos a estrutura do último parametro do SetTimer podemos escrever um exemplo prático de resultado semelhante ao exemplo anterior:

     Delphi |  copy code |? 
    01
    02
    type
    03
      PTimerParam = ^TTimerParam;
    04
      TTimerParam = record
    05
        s: string;
    06
        i: integer;
    07
      end;
    08
     
    09
    procedure TimerProc(hwnd: HWND; uMsg, idEvent: UINT; dwTime: DWORD); stdcall;
    10
    begin
    11
      KillTimer(hwnd, idEvent);
    12
      with PTimerParam(idEvent)^ do
    13
      begin
    14
        frmMain.Caption := Copy(s, 1, i);
    15
        i := i + 1;
    16
        if i <= Length(s) then
    17
          SetTimer(hwnd, idEvent, 100, @TimerProc)
    18
        else
    19
        begin
    20
          Dispose(Pointer(idEvent));
    21
          DeallocateHWnd(hwnd);
    22
        end;
    23
      end;
    24
    end;
    25
     
    26
    procedure TfrmMain.btnRunClick(Sender: TObject);
    27
    var
    28
      Param: PTimerParam;
    29
    begin
    30
      New(Param);
    31
      Param^.s := 'Teste de título animado';
    32
      Param^.i := 1;
    33
      SetTimer(AllocateHWnd(nil), Integer(Param), 100, @TimerProc);
    34
    end;
    35

    Antes de analizar o código vou citar um detalhe importante:

    Veja que ao chamar o SetTimer eu utilizo o AllocateHWnd como primeiro parametro, o porquê disso é que o SetTimer opera de maneira diferente ao se indicar um Objeto Windows que não pertence à aplicação, a definição é a seguinte:
    Caso o Handle passado como parametro pertença a aplicação, mais especificamente a mesma Thread que chama a SetTimer. No caso do Handle ser igual a 0 ( zero ) ou não pertencer a aplicação, o parametro passado como índice do temporizador será ignorado e um novo índice será criado e o retorno da chamada ao SetTimer indicará o novo índice.

    Depois de entender esse detalhe importante podemos perceber porque fazemos a criação de um Objeto Windows utilizando o AllocateHWnd. A finalidade disso é não passar um Objeto Windows inválido e ter o índice, que no nosso caso indica um ponteiro que serve de parametro, resetado.

    Agora entendendo um pouco o código:

    Perceba que sempre que recebemos a notificação de términio do intervalo do temporizador, finalizamos o temporizador, e porque fazemos isso? Fazemos isso pois se dentro da notificação, seja na utilização do callback ou por mensagem ( WM_Timer ), o processo que escrevermos lá demorar mais do que o intervalo, criaremos uma fila de processamento, e que acaba por criar uma sequência de processamento e o efeito de execução por intervalo não será perceptível.

    Fluxograma para entendimento rápido:

    Qualquer dúvida fique avontade em perguntar!

    Abraço a todos



    19
    May 08

    Delphi: SendMessage

    :: articles :: by Gilberto Saraiva

    Camaradas,

    O SendMessage é uma função disponibilizada pela plataforma windows (API) que nos permite interagir sobre objetos Window, ou seja, todos os objetos que possuem Handle, seja esse handle de qual tipo for: HWND, HFILE, HMENU, HHOOK, HGLOBAL, HLOCAL, HINST e outros vários. Diante isso podemos entender que o acesso a certos objetos pode ser feito através da utilização dessa API.

     Delphi |  copy code |? 
    1
    {$EXTERNALSYM SendMessage}
    2
    function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

    Basicamente o SendMessage necessita apenas do Handle e da Mensagem ( Message ), os outros 2 parametros serão dependentes da Mensagem enviada.

    Um exemplo:

     Delphi |  copy code |? 
    1
      SendMessage(btn2.Handle, CN_COMMAND, BN_CLICKED, 0);

    Envia uma mensagem do tipo CN_COMMAND para o btn2 (no nosso caso esse é um TButton) com o parametro BN_CLICKED que faz o TButton btn2 entender que se deve executar o procedimento de click.

    Outro exemplo interessante:

     Delphi |  copy code |? 
    1
      SendMessage(form1.Handle, WM_SETTEXT, 0, Integer(PChar('Novo Título')));

    O código acima modifica o título do TForm form1 para o valor “Novo Título”.

    A infinidade de coisas que dá pra se fazer com essa pequena API é incalculável.
    Se quiser ver como é vasta a capacidade proponha-me um problema! Eu ficarei grato pelo desafio.

    Abraço a todos