Superclasse form base per programmi gestionali (e non) #4 - wait animation

Con questo articolo vedremo come implementare una semplice animazione di attesa.
Come per gli altri articoli della serie, anche questa caratteristica sarà implementata nella classe TBaseForm in modo da avere un progetto incrementale. Potete però prendere tranquillamente spunto per implementare l'animazione nei vostri progetti.

implementare l'animazione in un thread
L'animazione entra in ballo quando l'utente deve attendere che l'applicazione porti a termine un certo compito - per il quale ci vuole del tempo - ma non è possibile determinarne la durata (altrimenti avremmo usato una progress bar).
Visto che il mainthread è occupato a svolgere il suo compito è bene che l'animazione venga gestita in un thread separato, in questo modo l'animazione procederà per conto suo e dovremo solo ricordarci di stoppare il thread al termine.
L'integrazione in uno scheletro di applicazione già esistente è utile per fare delle considerazioni su come dovrà essere implementata questa applicazione:


procurarsi una animazione
Non descriverò come mi sono procurato le immagini, ma il risultato finale. Nella sottocartella res trovate nr. 6 immagini png 16x16 (ed altrettanti xcf, il formato grafico di The Gimp) che rappresentano i 6 momenti dell'animazione. Queste immagini le ho poi caricate in un TImageList della form principale e ho assegnato il puntatore alla "toolbox" di modo da facilitare l'acquisizione di queste immagini per tutte le istanze delle form che verranno create.

il thread
Il thread lo potete trovare nel file ubaseform.pas

Codice: [Seleziona]

  { TBaseForm_WaitThread }

  TBaseForm_WaitThread = class(TThread)
  private
    FIndex: integer;
    FCanvas: TCanvas;
    FOffsetX, FOffsetY: integer;
    FWaitImageList: TImageList;
    procedure BlankArea;
    procedure ShowStatus;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: boolean);
    procedure SetCanvas(ACanvas: TCanvas; x, y: integer);
    property WaitImageList: TImageList read FWaitImageList write FWaitImageList;
  end;


constructor TBaseForm_WaitThread.Create(CreateSuspended: boolean);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate := True;
end;

procedure TBaseForm_WaitThread.SetCanvas(ACanvas: TCanvas; x, y: integer);
begin
  FCanvas := ACanvas;
  FOffsetX:=x;
  FOffsetY:=y;
end;

procedure TBaseForm_WaitThread.BlankArea;
begin
  FCanvas.Pen.Color:=clBtnFace;
  FCanvas.Brush.Color:= clDefault; //  RGBToColor(228,236,241);
  FCanvas.Rectangle(FOffsetX, FOffsetY, FOffsetX + 16, FOffsetY + 16);
end;

procedure TBaseForm_WaitThread.ShowStatus;
begin
  BlankArea;
  WaitImageList.Draw(FCanvas, FOffsetX, FOffsetY, FIndex);
end;

procedure TBaseForm_WaitThread.Execute;
var FromIndex, ToIndex: integer;
begin
   FromIndex := 0;
   ToIndex   := WaitImageList.Count - 1;
   FIndex    := -1;
   while not Terminated do begin
      inc(FIndex);
      if FIndex >= ToIndex then
         FIndex := FromIndex;
      Synchronize(@Showstatus);
      sleep(200);
   end;

   if Terminated then
      BlankArea;
end;



mentre nella superclasse ho inserito una variabile che conterrà il suo indirizzo.
La variabile farà anche da semaforo:
- se è nil allora il thread può essere creato
- se non è nil allora il thread è attivo

la dichiarazione è nella stessa unit:

Codice: [Seleziona]

  TfmBaseForm = class(TForm)
  ...
   private
    FWaitThread: TBaseForm_WaitThread;
  ...
    function GetActiveWaitAnimation: boolean;
    procedure SetActiveWaitAnimation(AValue: boolean);
  ...

   public
    property ActiveWaitAnimation: boolean read GetActiveWaitAnimation write SetActiveWaitAnimation;

   end;


function TfmBaseForm.GetActiveWaitAnimation: boolean;
begin
   result := FWaitThread <> nil;
end;

procedure TfmBaseForm.SetActiveWaitAnimation(AValue: boolean);
begin

   case AValue of
      TRUE: begin
          if GetActiveWaitAnimation then
             exit
          else begin
             FWaitThread := TBaseForm_WaitThread.Create(TRUE);
             if Assigned(FWaitThread.FatalException) then
               raise FWaitThread.FatalException;
             FWaitThread.WaitImageList := ToolBox.WaitImageList;;
             FWaitThread.SetCanvas(Canvas, 200,2);
             FWaitThread.Start;
          end;
      end;

      FALSE: begin
          if not GetActiveWaitAnimation then
             exit
          else begin
             FWaitThread.Terminate;
             FWaitThread.Free;
             FWaitThread := nil;
          end;

      end;
   end;
end;



A questo punto l'utilizzo dell'animazione è semplicissimo, all'interno della sottoclasse (nel progetto in esempio: uBaseFormTester), per attivare l'animazione, basta scrivere il seguente codice:
Codice: [Seleziona]

  ActiveWaitAnimation:=TRUE;


Mentre per fermarla:
Codice: [Seleziona]

  ActiveWaitAnimation:=FALSE;


Nel progetto di esempio potete testarne il funzionamento aprendo più form contemporaneamente e andate a cliccare il tasto "Edit".
Per fermare l'animazione il tasto "Confirm".

L'implementazione all'interno della superclasse per terminare forzatamente il thread ancora non è stato implementato, ma arriverà presto.
Per chi volesse scaricare il progetto, può guardare in questo thread http://www.lazaruspascal.it/index.php?topic=1371.msg7990;topicseen#new o aggiornare i sorgenti da svn.

Buon lavoro.
nomorelogic

SMF 2.0.8 | SMF © 2011, Simple Machines
SMFAds for Free Forums
TinyPortal © 2005-2012

Go back to article