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