Juande
Juande

Reputation: 73

Why in Firemonkey when create controls at runtime are display when finish iteration?

I create over 100 rectangles at runtine in code below;

var
  RectT: TRectangle;
  MyThread: TThread;
  Layout1: TLayout;
begin
  MyThread := TThread.CreateAnonymousThread(procedure()
  begin
        TThread.Synchronize(nil, procedure()
        var
            z, i: integer;
        begin
            z := 0;
            for i := 0 to 99 do
            begin
                 RectT := TRectangle.Create(Self);
                 RectT.Name := 'Rectangle' + IntToStr(i);
                 RectT.Align := TAlignLayout.Top;
                 RectT.Margins.Top := 6;
                 RectT.Position.Y := z;
                 RectT.Height := 20;
                 RectT.Parent := Layout1;
                 if (i mod 10) = 0 then Layout1.UpdateEffects;
                 inc(z, 20);
            end;
        end);
  end);
  MyThread.FreeOnTerminate := True;
  MyThread.Start;

end;

Why didn't display the rectangle when is created and only are displayed when finish the iteration of all rectangles?.

Upvotes: 0

Views: 1165

Answers (2)

Deltics
Deltics

Reputation: 23036

If this code is running on the main thread (which appears to be the case since you don't mention any threading) then the first opportunity that the FMX runtime has of visually updating the UI is when your code has itself finished running.

If you want the UI to update to display the rectangles as they are added, then you will need to re-write this to use an approach that allows the UI an opportunity to repaint periodically.

UPDATE

Your updated code in the question now involves a thread. However, in your posted code you Synchronize() all of the work in that thread. Synchronized code runs in the main thread so the consequence of synchronizing all of the work is to remove any benefit of the thread at all.

You are nearly there however.

A small change to your posted code so that the layout child objects are added in the thread, synchronizing only the repainting of the layout object itself periodically, then you get the result you are seeking:

var
  MyThread: TThread;
begin
  MyThread := TThread.CreateAnonymousThread
  (
    procedure()
    var
      z, i: integer;
      RectT: TRectangle;
    begin
      z := 0;
      for i := 0 to 999 do
      begin
        RectT := TRectangle.Create(Self);
        RectT.Name := 'Rectangle' + IntToStr(i);
        RectT.Align := TAlignLayout.Top;
        RectT.Margins.Top := 6;
        RectT.Position.Y := z;
        RectT.Height := 20;
        RectT.Parent := Layout1;

        TThread.Synchronize(nil, procedure()
                                 begin
                                   Layout1.Repaint;
                                 end);

        inc(z, 20);
      end;
    end
  );

  MyThread.FreeOnTerminate := True;
  MyThread.Start;
end;

I have increased the number of child objects in this demonstration of the approach to 999 since 99 was not enough to see any appreciable change in performance.

As written, the above code also repaints after every rectangle has been added, but this could be easily modified in a way similar to your posted code so that the layout is repainted only after "batches" of rectangles have been added:

if (i mod 10) = 0 then 
  TThread.Synchronize(nil, procedure()
                           begin
                             Layout1.Repaint;
                           end);

This is a simplistic approach, addressing the immediate problem of updating the UI to show the progress of some background changes made to that UI using this very simple test case. Whether this is actually the most appropriate approach in your specific case only you can really say.

Upvotes: 1

John Kouraklis
John Kouraklis

Reputation: 686

First, you need to move the for loop in one thread and the creation of the rectangles in a Synchronize call, as Deltics has done. The difference is that you do not need the call to Repaint and you need to use the currentthread to pass the call for synchronization.

Try this (in OnClick event of a Button):

procedure TForm4.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(procedure
  var
    I,z: Integer;
    Total: Integer;
  begin
    Total := 0;
    for I := 1 to 99 do
    begin
        TThread.Synchronize (TThread.CurrentThread,
          procedure
          var
            RectT: TRectangle;
          begin
            RectT := TRectangle.Create(Self);
            RectT.Name := 'Rectangle' + IntToStr(i);
            RectT.Align := TAlignLayout.Top;
            RectT.Margins.Top := 6;
            RectT.Position.Y := z;
            RectT.Height := 20;
            RectT.Parent := Layout1;
            Inc(z, 20);
          end);
    end;
  end).Start;
end;

Upvotes: 2

Related Questions