0

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?.

Kromster
  • 7,181
  • 7
  • 63
  • 111
Juande
  • 73
  • 1
  • 10
  • `BeginUpdate` suspends screen update until `EndUpdate`. – LU RD Mar 19 '17 at 21:46
  • When `BeginUpdate` and `EndUpdate` are supressed it produces same effect – Juande Mar 19 '17 at 21:53
  • 3
    See [Firemonkey - updating visual components](http://stackoverflow.com/q/18074417/576719) or [Do FireMonkey controls have an equivalent to the VCL Invalidate() method?](http://stackoverflow.com/q/8409995/576719) – LU RD Mar 19 '17 at 22:10
  • Please do not keep changing the code you have posted. It makes any answers posted in response to the previous code appear irrelevant. By all means post updates as new blocks of code to show all the various attempts. – Deltics Mar 20 '17 at 20:11
  • Also, in the latest revision you are now creating a thread but by then running all your previous main thread code in a **Synchronize**'d method, that method **still** runs in the main thread and blocks that thread until the entire procedure has completed, completely defeating the purpose of having a thread. – Deltics Mar 20 '17 at 20:12

2 Answers2

2

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;
John Kouraklis
  • 686
  • 4
  • 12
1

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.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Thanks Deltics, I've edited my code with `Layout1.Repaint` and has no effect, same with previous version without `Repaint` – Juande Mar 20 '17 at 11:24
  • Ok, works fine with `Synchronize` and `Repaint` within iteration, thanks a lot Deltics! – Juande Mar 21 '17 at 23:18