Search code examples
androiddelphifiremonkeydelphi-10.2-tokyo

TAniIndicator not spinning - loading a tlistview


I'm new with the TAniindicator component, so for testing purposes I have put together a project that will build a listview and display/spin the Aniindicator whilst the listview is being built.

type
TLoadThread = class(TThread)
public
 constructor Create; reintroduce;
protected
 procedure Process;
 procedure Execute; override;
end;    

constructor TLoadThread.Create;
begin
 inherited Create(True);
 FreeOnTerminate := True;
end;

procedure TLoadThread.Process;
begin
 Form1.BuildListView;
end;

procedure TLoadThread.Execute;
begin
 inherited;
 FreeOnTerminate := True;
 Synchronize(Process);
end;

var _loadThread : TLoadThread;

procedure TForm1.ThreadTerminated(Sender: TObject);
begin  
 AniIndicator1.Enabled := False;
 AniIndicator1.Visible := False;
end;

procedure TForm1.BuildListView;
var i : integer;
    LI : TListViewItem;
 begin
  Listview1.BeginUpdate;
 try
  for i := 1 to 2000 do
  begin
   LI := Listview1.Items.Add;
   LI.Text := 'Listview Item ' + IntToStr(i);
  end;
 finally
  Listview1.EndUpdate;
 end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 AniIndicator1.Visible := False;
 _loadThread := nil;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 _loadThread := TLoadThread.Create;
 _loadThread.OnTerminate := ThreadTerminated;
 _loadThread.Start;
 AniIndicator1.Enabled := True;
end;

I thought I was on the right track but this doesn't appear to work, can anyone explain what I'm doing wrong please?


Solution

  • Your worker thread is spending all of its time inside of its Process() method, which is being called by TThread.Synchronize() so it runs in the main UI thread. Process() is not processing UI messages, which is why TAniIndicator does not work.

    As-is, your worker thread is completely useless. All of your code is running in the main UI thread. So, you may as well get rid of TLoadThread altogether:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      AniIndicator1.Visible := False;
    end;
    
    procedure TForm1.BuildListView;
    var
      i : integer;
      LI : TListViewItem;
    begin
      AniIndicator1.Visible := True;
      AniIndicator1.Enabled := True;
      ListView1.BeginUpdate;
      try
        for i := 1 to 2000 do
        begin
          LI := ListView1.Items.Add;
          LI.Text := 'ListView Item ' + IntToStr(i);
          if (i mod 100) = 0 then
            Application.ProcessMessages;
        end;
      finally
        ListView1.EndUpdate;
        AniIndicator1.Enabled := False;
        AniIndicator1.Visible := False;
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      BuildListView;
    end;
    

    Otherwise, if you use a thread, do not synchronize the loop itself, only the pieces that actually touch the UI:

    type
      TLoadThread = class(TThread)
      public
        constructor Create; reintroduce;
      protected
        procedure Execute; override;
      end;
    
    constructor TLoadThread.Create;
    begin
      inherited Create(True);
      FreeOnTerminate := True;
    end;
    
    procedure TLoadThread.Execute;
    begin
      Form1.BuildListView;
    end;
    
    var
      _loadThread : TLoadThread = nil;
    
    procedure TForm1.ThreadTerminated(Sender: TObject);
    begin
      _loadThread := nil;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      AniIndicator1.Visible := False;
    end;
    
    procedure TForm1.BuildListView;
    var
      i : integer;
    begin
      TThread.Synchronize(nil,
       procedure
       begin
         AniIndicator1.Visible := True;
         AniIndicator1.Enabled := True;
         ListView1.BeginUpdate;
       end
      );
      try
        for i := 1 to 2000 do
        begin
          TThread.Synchronize(nil,
            procedure
            var
              LI : TListViewItem;
            begin
              LI := ListView1.Items.Add;
              LI.Text := 'ListView Item ' + IntToStr(i);
            end
          );
        end;
      finally
        TThread.Synchronize(nil,
          procedure
          begin
            ListView1.EndUpdate;
            AniIndicator1.Enabled := False;
            AniIndicator1.Visible := False;
          end
        );
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      if _loadThread <> nil then
      begin
        _loadThread := TLoadThread.Create;
        _loadThread.OnTerminate := ThreadTerminated;
        _loadThread.Start;
      end;
    end;