imagem na tela por um determinado tempo (era: Dúvida...)

Delphi

23/10/2007

[quote:74830e66ff=´Moderação´][color=red:74830e66ff]Título editado por Massuda

Por favor, procure usar um título descritivo.

Leia as :arrow: [url=http://forum.clubedelphi.net/viewtopic.php?t=6689]Regras de Conduta[/url] do fórum.[/color:74830e66ff][/quote:74830e66ff]
Olá.

Estou com problema para criar um algoritmo que resolva uma questão envolve tempo.

Preciso que uma imagem seja mantida visível na tela por um determinado tempo, depois deste tempo entre outra imagem por outro tempo determinado e assim por diante. Até aí tudo bem e já fiz da seguinte forma:

type
   TImgParam = packed record
      Path : ShortString;
      Tempo: Cardinal;
   end;

Var i: Integer;
    tPERCORRIDO, tEXPIRAR, tINICIO: Cardinal;
    ImgAtual: TImgParam;
begin
   tINICIO  := GetTickCount;
   tEXPIRAR := 0;
   i        := 0;

   while not Interromper do
   begin
      ImgAtual := ImagensList[i]; // "ImagensList" vem de outro lugar do programa, que não importa.
      FuncExibirImagem(ImgAtual);  // Isso é apenas para exeplificar como ocrre o processo.

      tEXPIRAR := tEXPIRAR + ImgAtual.Tempo

      repeat
         Sleep(1);
         tPERCORRIDO := GetTickCount - tINICIO;
      until tPERCORRIDO > tEXPIRAR;

      Inc(i);
      if i >= ImagensList.Count then i := 0;
   end;
end;


[b:74830e66ff]Importante[/b:74830e66ff]: tem que ser utilizado o GetTickCount como base de tempo. Não pode ser um Timer.

O PROBLEMA:
Quando o valor de GetTickCount estourar, o que ocorre a cada 49 dias aproximadamente se considerarmos o tempo a partir do zero, a referência de tempo é perdida e as imagens não serão mais trocadas, ou seja, ficará travada a exibição da última imagem antes de zerar o GetTickCount.

Alguém tem idéia de como resolver este problema?

vlw!


Rtava

Rtava

Curtidas 0

Respostas

Massuda

Massuda

23/10/2007

Use um TTimer, caso contrário seu programa vai ficar preso nesse seu loop. Cada vez que o timer estourar, você troca a imagem.


GOSTEI 0
Rtava

Rtava

23/10/2007

Olá Massuda.
Quanto ao título, como se tratava realmente de uma dúvida sobre lógica e não sobre Delphi propriamente, imaginei que o título estivesse adequado.

Quanto ao TTimer, conforme comentei a base de tempo tem que ser o GetTickCount e já estou usando esta rotina com sucesso, sem travar, de dentro de uma thread.

O problema que estou tendo não é detectar quando estourou o contador, mas sim o que fazer quando estourar. O problema é que, supondo que uma imagem tenha que ficar, por exemplo, 1 hora sendo exibida e quando está com 2 minutos de exibição o contador estoura e eu faço a troca de imagem. E os outros 58 minutos? Eu simplesmente perco. É isso que eu preciso evitar e corrigir.


GOSTEI 0
Massuda

Massuda

23/10/2007

Você não mencionou thread no seu post inciial.

Não testei, mas algo assim deve funcionar...
uses
  ...
  SyncObjs,
  ...

// isso deve ir no Execute da sua thread
var
  Timer: TEvent; // não é exatamente um timer...
...
  Timer := TEvent.Create(nil, False, False, ´´)
  while Timer.WaitFor(.TempoAEsperarEmMilissegundos.) = wrTimeout do begin
    // faz alguma coisa periódica
  end;
  Timer.Free;
end;


...note que isso independe de contadores.


GOSTEI 0
Rtava

Rtava

23/10/2007

Massuda,

A base de tempo precisa ser exclusivamente por meio de GetTickCount, pois só assim é possível manter uma sincronia perfeita. Que sincronia? Associado às imagens também são exibidos textos. Para exibir os textos eu uso objetos comuns como Label, Edit, etc. Este objetos de texto têm que existir. Quando uma imagem é exibida, um texto associado à imagem é exibido junto. Se a imagem trocar, o texto também tem que trocar exatamente ao mesmo tempo (ao menos visualmente ao mesmo tempo).

Agradeço o exemplo que você me enviou, que aliás pode me ser muito útil para outras coisas. Porém, para esta aplicação não funcionou.
Para testar eu fiz o seguinte teste: Criei este código abaixo no evento de um TButton mesmo e adicionei à tela um Memo que dei o nome de ´mm´. Depois rodei o programa e enquanto isso abri também alguns outros programas pesados (Photoshop, AutoCad, etc).

O problema ocorre que o TEvent, assim como o TTimer, é afetado pelo processamento da máquina, o que leva a um problema de sincronia na exibição de imagens e textos. Se você reproduzir o teste poderá verificar que no Memo os registros de tempo ficam certinhos enquanto o processamento da máquina não sofrer nenhum distúrbio. Quando o distúrbio ocorre, a contagem de tempo exibida no Memo fica totalmente fora.

procedure TfPrincipal.Button1Click(Sender: TObject);
var Tmr: TEvent;
    Init: Cardinal;
begin
   Tmr  := TEvent.Create(nil, False, False, ´´);
   Init := GetTickCount;
   
   mm.Lines.Add(inttostr(GetTickCount - Init));
   while Tmr.WaitFor(2000) = wrTimeout do
   begin
      mm.Lines.Add(´ok´);
      mm.Lines.Add(inttostr(GetTickCount - Init));
   end;

   Tmr.Free;
end;



GOSTEI 0
Massuda

Massuda

23/10/2007

Quando uma imagem é exibida, um texto associado à imagem é exibido junto. Se a imagem trocar, o texto também tem que trocar exatamente ao mesmo tempo (ao menos visualmente ao mesmo tempo).
Por que simplesmente não atualizar os elementos da UI imediatamente antes/depois de trocar a imagem?


GOSTEI 0
Rtava

Rtava

23/10/2007

Vamos lá...
O que precisa ser feito é:

1- Exibir imagens associadas e textos, sincronizados por tempo. São várias imagens e vários textos exibidos ao mesmo tempo, cada um com seu par e cada par com tempos diferentes;

2- Garantir que a sincronia não seja influenciada pelo processamento da máquina, pois o processamento pode sofrer a influência de outros processos que a máquina execute (isso foi o que me levou a usar uma base de tempo única, o GetTickCount). Se a imagem/texto foram programados para serem exibidos durante 10 minutos, 30 segundos e 347 milésimos por exemplo, é esse o tempo que tem que permanecer na tela, nem mais nem menos, independente de tudo mais que a máquina esteja processando;

3- Garantir que a cada troca de imagem, seu respectivo par de texto também seja trocado e vice-versa;

4- Garantir que quando a base de tempo estoure, que neste caso é formada por um tipo inteiro sem sinal de 4 bytes, as imagens e textos já em exibição não percam a referência de quando começaram a serem exibidas e quando devem terminar de serem exibidas.

Independente do que eu já fiz, se garantir estes quatro requisitos o problema estará solucionado. Em todos os testes que fiz, com TTimer, com GetTickCount e agora com TEvent, somente com o GetTickCount (base única de tempo para todos) o sistema funcionou como deveria.


GOSTEI 0
Micheus

Micheus

23/10/2007

[b:82287590a5]Massuda[/b:82287590a5], será que não daria para o colega [b:82287590a5]rtava[/b:82287590a5] utilizar a função da API, [i:82287590a5]SetTimer[/i:82287590a5], onde ele define o tempo em milisegundos em que sua callback seria chamada?


GOSTEI 0
Massuda

Massuda

23/10/2007

O timer mais preciso do Windows é o usado pelo sistema de multimídia do SO.

Experimente isso (como sempre, não testado)...
uses
  MMSystem,
  ...

type
  TForm1 = ....
    ...
    mm: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    Timer: MMRESULT;
    ...
  public
    Inicio: Cardinal;
    ...

....

procedure CallBackDoTimer(ID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
var
  Form: TForm1;
begin
  Form := TForm1(dwUser);
  Form.mm.Lines.Add(IntToStr(GetTickCount - Form.Inicio));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Inicio := GetTickCount;
  Timer := TimeSetEvent(2000 , 0, @CallBackDoTimer, DWORD(Self), TIME_PERIODIC);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  TimeKillEvent(Timer);
end;



GOSTEI 0
Rtava

Rtava

23/10/2007

Massuda,
Parabens pela dica. Muito boa! Assim como no código que estou usando, este também não trava o programa, funciona com sincronia e não é afetado por qualquer distúrbio de processamento. E ainda nem precisa de thread... muito bom mesmo.

Porém, e sempre tem um porém, a menos que eu esteja enganado ainda há o problema de perda da referência quando o contador estoura. Como não tenho como agir sobre o contador GetTickcount, então reproduzi o problema da seguinte forma: inseri um TTimer (com 100ms de Interval) para servir de base de tempo e criei uma variável ´Contador´ para substituir o GetTickCount. Conforme comentei, a menos que eu tenha me enganado, o problema ocorre também.

Coloquei o código inteiro da Unit para vc verificar. O problema ocorrido é o mesmo que relatei na abertura deste tópico.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, MMSystem, ExtCtrls;

type
  TForm1 = class(TForm)
    mm: TMemo;
    tmr: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure tmrTimer(Sender: TObject);
  private
    { Private declarations }
    Timer: MMRESULT;
  public
    { Public declarations }
    Inicio: Cardinal;
    Contador: Cardinal;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure CallBackDoTimer(ID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
var
   Form: TForm1;
begin
   Form := TForm1(dwUser);
   Form.mm.Lines.Add(IntToStr(Form.Contador - Form.Inicio));
   //Form.mm.Lines.Add(IntToStr(GetTickCount - Form.Inicio));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   Contador := 3;
   Inicio   := Contador;
//   Inicio := GetTickCount;
   Timer  := TimeSetEvent(100 , 0, @CallBackDoTimer, DWORD(Self), TIME_PERIODIC);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
   TimeKillEvent(Timer);
end;

procedure TForm1.tmrTimer(Sender: TObject);
begin
   Inc(Contador);
   if Contador >= 20 then Contador := 0;
end;

end.



GOSTEI 0
Rtava

Rtava

23/10/2007

Só um observação: o contador foi inicializado com 3 apenas para simular o que ocorreria com o GetTickCount, em que praticamente nunca conseguiremos pegá-lo em Zero.


GOSTEI 0
Massuda

Massuda

23/10/2007

...o problema de perda da referência quando o contador estoura.
Não entendo porque você precisa do GetTickCounter. Poderia explicar?


GOSTEI 0
Rtava

Rtava

23/10/2007

Desculpe, acabei esquecendo de comentar outra coisa. No lugar onde você colocou 2000ms, se substituir por valores menores que 500ms, começa a dar problema para execução. A rotina passa a levar mais que o tempo programado. Tente com 100ms por exemplo. Claro que para esta minha aplicação não são necessários tempos tão baixos. Só estou comentando a título de curiosidade.


GOSTEI 0
Rtava

Rtava

23/10/2007

Não entendo porque você precisa do GetTickCounter. Poderia explicar?

Suponha que existam dois Timers temporizando a exibição de duas imagens, uma com 10 segundos e outra com 10 segundos, OK? Agora suponha que algum processo que a máquina executou tenha feito dar aqueles ´trancos´ de processamento que demorou 2 segundos (como ocorre na abertura de um programa pesado). O efeito deste ´tranco´ de processamento age de formas diferentes sobre o Timer1 e Timer2, pois eles têm bases de tempo independentes. Assim, o tempo de exibição da imagem 1 será diferente de 10 segundos (não se sabe quanto) e da imagem 2 também será diferente de 10 segundos (da mesma forma, não se sabe quanto). Ou seja, as imagens que começaram a ser exibidas juntas, poderão não terminar a exibição juntas (já testei isso). Além disso haverá o acréscimo de parte do tempo desse ´tranco´ ao tempo dos dois Timers, mudando de 10 segundos para 10+X.

Da forma como estou fazendo a rotina de sincronia, como postei lá no início, como a base de tempo é única por meio do GetTickcount, mesmo que o tranco influenciasse neste tempo, o que não ocorre, ainda assim o distúrbio seria igualmente atribuído ao tempo das duas imagens. Dessa forma a exibição sempre começa e termina perfeitamente sincronizada.
Por isso estou usando o GetTickCount.


GOSTEI 0
Rtava

Rtava

23/10/2007

sobe!


GOSTEI 0
Rtava

Rtava

23/10/2007

sobe!


GOSTEI 0
Rtava

Rtava

23/10/2007

Resolvido.
Criei um GetTickCount com 64 bits.


GOSTEI 0
POSTAR