TFloatAnimation в анонимном потоке. Тема не умрет, а анимация не перезапустится.

Я боролся с этим в течение дня или двух и нигде не могу найти ответ. Я думал, что этот ответ может помочь, но это не помогло.

В приведенном ниже примере кода у меня есть два компонента Timage, каждый из которых содержит «начальное изображение». При нажатии кнопки «Пуск» создаются два анонимных потока: один анимирует изображение Image1 между начальным изображением и конечным изображением, а другой делает то же самое для Image2.

Моя проблема заключается в том, что когда для логического значения KillAnimation установлено значение True, обе анимации должны останавливаться (что они и делают), но только один из потоков завершается, другой прекращает анимацию, но оставляет изображение в середине анимации.

У меня такая же проблема, если я использую предопределенный поток.

Образец приложения имеет только два изображения, реальное приложение может иметь от 15 до 24. Анонимные потоки, кажется, подходят, потому что я могу их создать, и мне не нужно беспокоиться об определении до 24 TThreads. Я надеюсь, что в этом есть смысл.

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.

Я бы подумал, что создание Transition и FloatAnimation в потоке означает, что они будут уничтожены, когда будут выполнены, потому что FreeOnTerminate имеет значение True.

Когда я пытаюсь сбросить изображения с помощью «image1.bitmap.loadfromfile», это не меняется.

Изображения представляют собой файлы png размером 170 x 170.

Что я здесь сделал не так?

Моя цель - передать TImage и файл изображения в поток, позволить ему анимироваться, пока не будет остановлено. Маловероятно, что мне понадобятся все 24 изображения для анимации, но кто знает.

Извините, если я не должен был публиковать весь код из моего примера. По крайней мере, вы можете видеть все, что происходит.


person Craig    schedule 06.11.2017    source источник
comment
Во-первых, использование потока для обработки вызовов непосредственно к компоненту GUI не рекомендуется. Вы также вызываете Application.HandleMessage из потока, что неверно. Wiggle и TheFloat должны быть завершены кодом. Я предлагаю вам вместо этого попытаться заставить все это работать в основном потоке.   -  person LU RD    schedule 06.11.2017
comment
@LURD Я пробовал анимацию в основном потоке пользовательского интерфейса, но кнопки и другие элементы перестают реагировать, особенно когда 4 или 5 анимаций повторяются до тех пор, пока не будет нажата кнопка. Я думал, что компоненты FMX, включая Transitions и FloatAnimate, являются потокобезопасными. Интересно, что application.processmessages не работал, и анимация не воспроизводилась. Спасибо за ваш комментарий и мысли :)   -  person Craig    schedule 06.11.2017
comment
@LURD Меня также озадачивает тот факт, что начинаются 2 потока, но заканчивается только 1. Это немного странно, потому что код идентичен. Обе анимации останавливаются, но нить остается висеть на ветру.   -  person Craig    schedule 06.11.2017
comment
Трудно комментировать, почему код, работающий в основном потоке, не отвечает, не видя кода. Всегда предполагайте, что компоненты FMX и VCL работают только в основном потоке. Испортите это соглашение и ожидайте, что случится что-то плохое.   -  person LU RD    schedule 06.11.2017
comment
Я не знаком с FMX, но в VCL вызов Application.Handle приводит к тому, что поток сам создает очередь сообщений, а затем ожидает ее. Я ожидаю, что анонимный поток почти не получит сообщений, кроме широковещательных, поэтому нормально видеть, что поток зависает на handlemessage и не возвращается. Я на самом деле удивлен, что любой из ваших потоков вообще вышел. Действительно ли это действительно для FMX, это ваша домашняя работа, чтобы узнать! ;)   -  person Ken Bourassa    schedule 06.11.2017
comment
@kenbourassa Мое намерение с помощью handlemessage состояло в том, чтобы просто дать ему что-то делать, пока для KillAnimation не будет установлено значение True. Я удалил его после комментариев LU-RD, и ветка умерла, но анимация не запустилась. Думаю, больше домашнего задания для меня :)   -  person Craig    schedule 07.11.2017


Ответы (1)


Вы НЕ должны использовать поток для этого вообще. Элементы пользовательского интерфейса, включая визуальные эффекты, следует использовать только в основном потоке пользовательского интерфейса. И если вы не разрабатываете свое приложение для мобильных платформ, объекты не уничтожаются автоматически, когда они выходят за рамки, вы должны уничтожить их самостоятельно, когда закончите их использовать, или же назначить им владельца, который уничтожит их для вас.

Попробуйте это вместо этого:

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.
person Remy Lebeau    schedule 06.11.2017
comment
Спасибо @Remy. В конечном итоге это действительно будет мобильное приложение. Первоначально я сделал это в основном пользовательском интерфейсе, но другие кнопки не реагировали достаточно быстро, учитывая, что может воспроизводиться до 15 или более анимаций. Это игровой автомат (для моей жены). Все барабаны вращаются внутри потоков (их 5) и все работают и выходят корректно. Я поработаю над этим еще немного и сделаю анимацию тайлов в основном пользовательском интерфейсе, насколько смогу. Спасибо за помощь :) - person Craig; 07.11.2017
comment
Забавный факт: процедура ResetImages работает только при первом запуске. После анимации стартовое изображение не показывается. При первом запуске у Timages нет детей, после запуска они есть, поэтому я посмотрю на это. - person Craig; 07.11.2017
comment
Вы не можете запустить анимированный визуальный эффект в рабочем потоке и заставить его управлять элементом управления пользовательского интерфейса без синхронизации с основным потоком пользовательского интерфейса. Эффект должен выполнять свои манипуляции только в основном потоке пользовательского интерфейса, и в этом случае рабочий поток становится бесполезным. Если у вас работает множество анимированных эффектов, а ваш пользовательский интерфейс слишком медленно реагирует на взаимодействие с пользователем, это говорит о том, что либо эффекты неэффективны, либо вы делаете что-то еще, что вызывает блокировку в основном потоке пользовательского интерфейса, когда вы не должны этого делать. . - person Remy Lebeau; 07.11.2017