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
Tags: AllocateHWnd, API, DeallocateHWnd, Handle, HandleMessage, KillTimer, Message, Objeto Windows, ProcessMessages, SetTimer, TApplication, Temporizador, Timer, VCL, WM_TIMER

Oi Gilberto Saraiva,
Eu tive dificuldade em entender seu exemplo usando um objeto windows. Eu não entendi porque você criou o tipo TTimerParam.
Estou desenvolvendo um programa em delphi para ler dados através da porta serial e quero temporizar a aquisição. Já tentei fazer isso de diversas maneiras, mas não obtive sucesso. Se você pudesse me explicar como eu posso usar esse temporizador para o meu caso ou me indicar outro método eu ficaria MUITO grato.
Atenciosamente,
Thiago José da Silva
Ola Thiago José da Silva,
Veja que no começo do artigo eu informo que o 2 parametro da API SetTimer é indicada pelo programador, e o mesmo faz o uso como necessário entendendo que o parametro é númerico de tamanho 4 (ex: integer, cardinal, longint, pointer e outros).
O que fiz no exemplo foi indicar como 2 parametro o ponteiro de uma variável que tenho na memória, que é do tipo TTimerParam. Então ao receber a mensagem no Objeto Windows eu apenas forço o entendimento do 2 parametro como ponteiro de uma variável desse tipo, assim eu acesso as informações que nela contém.
Eu ainda não expliquei isso aqui no site, mas pra termos de ajuda:
typeAssim é possível utilizar as funções de alocação/desalocação de memória por tipo, New e Dispose
Exemplo:
varbegin// crio um espaço na memória para guardar as informações que precisobegin// aqui a memória foi desalocada, então não é possível mais ter acesso// ao que estava lá.Amigo…
Atualmente estou desenvolvendo um objeto para comunicacao.
Dentro desse objeto tenho outros que sao criados((parente).
Voce sabe como faco para que o identifique como por exemplo num formulario (Objeto.Objeto.Objeto)
Ex: form1.novoobjeto.novoObjeto
Parabéns pelo Post.
Eu preciso executar uma tarefa a cada milisegundo. Não consegui fazer com “while” nem com Timer pois ambos pulam alguns milisegundos.
Tentei utilizar o SetTimer do seu post atual e mesmo assim ele pula alguns segundos.
Eu dentro da funcao eu faço apenas
FrmPrincipal.Memo1.Lines.Add(FormatDateTime(’hh:mm:ss:zzz’, now));
Tem outra coisa para me sugerir?
Cleriston,
Exatidão dessa é muito difícil quando se tem uma interface gráfica pouco otimizada(interface de componentes), as que lhe permitiria ter uma exatidão dessas são apenas aquelas que tem aceleração gráfica e consomem menos tempo de processador para a parte visual.
Uma maneira de aperfeiçoar a precisão é usando thread, assim você teria no mínimo um processamento exclusivo para a obtenção do intervalo desejado.
Abraços.