I've been struggling with this for a day or two and cannot find an answer anywhere. I thought this answer might help but it didn't.
In my sample code below I have two Timage Components each containing a "Start Image". When clicking on the "Start Button" two anonymous threads are created, one animates Image1 between the Start Image and End Image, the other does the same for Image2.
My problem is that when the KillAnimation boolean is set to True both animations should stop (which they do) but only one of the threads exit, the other one stops animating but leaves the image mid-animation.
I have the same problem if I use a predefined thread as well.
The sample application has only two images, the real world app can have anywhere from 15 to 24. Anonymous threads seem to suit because I can create them and no have to worry about defining up to 24 TThreads. I hope that makes sense.
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Objects,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Ani, FMX.Effects,
FMX.Filter.Effects;
type
TForm1 = class(TForm)
Image1: TImage;
Image2: TImage;
BtnStop: TButton;
BtnStart: TButton;
BtnReset: TButton;
procedure BtnStopClick(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnResetClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure createthread(TheImage: TImage; TargetBMP: String);
end;
var
Form1: TForm1;
KillAnimation: Boolean;
implementation
{$R *.fmx}
procedure TForm1.BtnStopClick(Sender: TObject);
begin
KillAnimation := true;
end;
procedure TForm1.createthread(TheImage: TImage; TargetBMP: String);
begin
{ The thread works right up until it is stopped. Even though two
threads are started only one finishes. In addition, the images
do not end up as the target image, and neither image can be
reset even if I reload them from files. }
TThread.CreateAnonymousThread(
procedure()
var
Wiggle: TWiggleTransitionEffect;
TheFloat: TFloatAnimation;
begin
TThread.NameThreadForDebugging('Animate ' + TheImage.Name);
Wiggle := TWiggleTransitionEffect.Create(Nil);
Wiggle.RandomSeed := 0.3;
Wiggle.Progress := 0;
Wiggle.Parent := TheImage;
TThread.Synchronize(TThread.CurrentThread,
procedure()
Begin
Wiggle.Target.LoadFromFile(TargetBMP)
end);
TheFloat := TFloatAnimation.Create(Nil);
TheFloat.Parent := Wiggle;
TheFloat.PropertyName := 'Progress';
TheFloat.Duration := 2;
TheFloat.AutoReverse := true;
TheFloat.Loop := true;
TheFloat.StartValue := 0;
TheFloat.StopValue := 100;
TheFloat.StartFromCurrent := false;
TheFloat.start;
while not KillAnimation do
application.handlemessage;
TheFloat.stop;
end).start;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Image1.Bitmap.LoadFromFile('c:\sample\startimage.png');
Image2.Bitmap.LoadFromFile('c:\sample\startimage.png');
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
KillAnimation := false;
createthread(Image1, 'c:\sample\endimage.png');
createthread(Image2, 'c:\sample\endimage.png');
end;
procedure TForm1.BtnResetClick(Sender: TObject);
begin
KillAnimation := false;
Image1.Bitmap.LoadFromFile('c:\sample\startimage.png');
Image2.Bitmap.LoadFromFile('c:\sample\startimage.png');
end;
end.
I would have thought that creating the Transition and FloatAnimation within the thread meant they would be destroyed when done because FreeOnTerminate is True.
When I try to reset the Images using "image1.bitmap.loadfromfile" it doesn't change.
The images are 170 x 170 png files.
What have I done wrong here?
My goal is to pass TImage and an Image File to the thread, let it animate until told to stop. It is unlikely that I'll need all 24 images to animate, but you never know.
Apologies if I shouldn't have posted all the code from my sample. At least you can see everything going on.
You should NOT be using a thread for this at all. UI elements, including visual effects, should be used only in the main UI thread. And unless you are developing your app for mobile platforms, objects are not destroyed automatically when they go out of scope, you have to destroy them yourself when you are done using them, or else assign them an Owner that will destroy them for you.
Try this instead:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Objects, FMX.Controls.Presentation,
FMX.StdCtrls, FMX.Ani, FMX.Effects, FMX.Filter.Effects;
type
TForm1 = class(TForm)
Image1: TImage;
Image2: TImage;
BtnStop: TButton;
BtnStart: TButton;
BtnReset: TButton;
procedure BtnStopClick(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnResetClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
Float1: TFloatAnimation;
Float2: TFloatAnimation;
function PrepareEffect(TheImage: TImage; const TargetBMP: String): TFloatAnimation;
procedure ResetImages;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.BtnResetClick(Sender: TObject);
begin
ResetImages;
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
Float1.start;
Float2.start;
end;
procedure TForm1.BtnStopClick(Sender: TObject);
begin
Float1.stop;
Float2.stop;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ResetImages;
Float1 := PrepareEffect(Image1, 'c:\sample\endimage.png');
Float2 := PrepareEffect(Image2, 'c:\sample\endimage.png');
end;
function TForm1.PrepareEffect(TheImage: TImage; const TargetBMP: String): TFloatAnimation;
var
Wiggle: TWiggleTransitionEffect;
TheFloat: TFloatAnimation;
begin
Wiggle := TWiggleTransitionEffect.Create(Self);
Wiggle.RandomSeed := 0.3;
Wiggle.Progress := 0;
Wiggle.Parent := TheImage;
Wiggle.Target.LoadFromFile(TargetBMP);
TheFloat := TFloatAnimation.Create(Self);
TheFloat.Parent := Wiggle;
TheFloat.PropertyName := 'Progress';
TheFloat.Duration := 2;
TheFloat.AutoReverse := true;
TheFloat.Loop := true;
TheFloat.StartValue := 0;
TheFloat.StopValue := 100;
TheFloat.StartFromCurrent := false;
Result := TheFloat;
end;
procedure TForm1.ResetImages;
begin
Image1.Bitmap.LoadFromFile('c:\sample\startimage.png');
Image2.Bitmap.LoadFromFile('c:\sample\startimage.png');
end;
end.