Jump to content
KMarb

How to force update to label during a loop

Recommended Posts

Delphi 11.1 create android app...

 

Say I have a label on my form, label1

 

and I have a loop like this:

 

  for i  := 1 to 10 do
    begin
      label1.Text := 'Step ' + intToStr (i);
      application.ProcessMessages;
      sleep (200);
    end;
end;

 

Running this on Windows, the label updates while the loop is running. Without application.processmessages, the label does not change until the loop ends. I'm just trying to give some feedback to user while things are happening.

 

What can I use in place of application.processMessages so that the label is updated on Windows and android? I've searched and haven't found a good answer.

Share this post


Link to post

Tried begin/end update and that did not work:

  for i  := 1 to 10 do
    begin
      label1.BeginUpdate;
      label1.Text := 'Step ' + intToStr (i);
      label1.EndUpdate;

      sleep (200);
    end;
 

Also, .refresh is not valid in fmx.

Share this post


Link to post
1 hour ago, KMarb said:

Also, .refresh is not valid in fmx.

No, but Repaint() is.

 

In any case, depending on what the rest of your loop is doing, it may be worthwhile to move the logic into a worker thread, and then have that post updates to the UI thread when it wants to display something.

Edited by Remy Lebeau
  • Like 4

Share this post


Link to post

Repaint does not work - I should have said I tested it, sorry. So far, I cannot find a way to update the label in this simple loop. Any other ideas?

 

I am learning TTask.Run and making progress, and it does seem like TThread.Synchronize updates the UI well enough, but I have left an option in the app to not use TTask.Run (especially when I first have users run the new version), so it can run in the same mode (non-threaded) as I originally wrote it 7 years ago. Hence the need to do force a control to update/repaint during the course of other processing.

 

Does anyone know how to force label1 to update in the loop below?

 

 for i  := 1 to 10 do
    begin
      label1.Text := 'Step ' + intToStr (i);

      // what to do here to make label1 show new value on the screen?
      sleep (200);
    end;

Share this post


Link to post
1 hour ago, KMarb said:

Repaint does not work - I should have said I tested it, sorry.

Did you try Repaint()'ing the Form itself, rather than just the TLabel?

Share this post


Link to post

I have not tried that, but the form does not have a repaint or refresh method. How do you tell the form or any control to repaint now?

 

I tried      

  form6.InvalidateRect(label1.ClipRect);

and

  label1.InvalidateRect(label1.UpdateRect);
 

Neither worked

Share this post


Link to post
1 hour ago, KMarb said:

Hence the need to do force a control to update/repaint during the course of other processing.

Better to work with the controls than forcing them.  Here's a code using a timer to update UI plus a way to stop the updating if needed.  

procedure TBaaSToDoList.Button4Click(Sender: TObject);
begin
   Inc(FthinkerIndex);
   FthinkerIndex := FthinkerIndex mod 11;
   label1.Text := 'Thinking about ' + FthinkerIndex.tostring;
   //looking at which control sent message 
   if Sender is TButton then
   with timer1 do Enabled := not Enabled; 
end;

procedure TBaaSToDoList.Timer1Timer(Sender: TObject);
begin
  Button4Click(Timer1);  //or call with your thread
end;

  private
    FthinkerIndex: Integer;// and in create set to zero

 

Share this post


Link to post

Not really sure how a timer helps. When the timer fires I would still need a way to say:

label1.RepaintYourselfNowAlready;

 

Can you elaborate and explain how a timer fits in with this loop, to make the label update every .2 seconds?

 

 for i  := 1 to 10 do
    begin
      label1.Text := 'Step ' + intToStr (i);

      // what to do here to make label1 show new value on the screen?
      sleep (200);
    end;

Share this post


Link to post
5 minutes ago, KMarb said:

Can you elaborate and explain how a timer fits in with this loop, to make the label update every .2 seconds?

The timer events allow the UI to update. The for loop cannot without Application.Processmessages inside the loop.  Plus using a sleep is a reason not to use a loop.   

Share this post


Link to post

Your answer I think applies to a multi-threaded app, but not one where there is only one main thread. My need is to update the display in the context of a long-running process in the main thread, say 2-3 seconds of processing that must happen in the main thread. I know that is not proper design, and that anything that makes the UI unresponsive should be run outside the main thread, but my question is not about proper design, it is about updating the screen while the main thread is doing some processing. Thanks for your reply, though, always good to see how people solve problems.

Share this post


Link to post
54 minutes ago, KMarb said:

I know that is not proper design, and that anything that makes the UI unresponsive should be run outside the main thread,

The UI needs to communicate that the program is computing or updating... In the past the mouse cursor was changed to hour glass and spinner is used in Android.   MS XL and Edge use spinners why wouldn't you?

 

The following completes the for..loop emulation and set timer1.interval to 200. 

 

//   if FthinkerIndex = 0 then
//   with timer1 do Enabled := not Enabled;

 

the Example needed a button click to break out of loop.    

 

 

 

Share this post


Link to post
2 hours ago, emileverh said:

Never use Application.ProcessMessages ! Sooner or later you get other troubles

I would't subscribe to that. But it definitely shouldn't be used deep inside some library (or even unit) but only in the code inside a form.

 

So if you want to use Application.ProcessMessages from inside such a library, add some call back to the library code which is then assigned some methode in the form which calls Application.ProcessMessages. That way it's a lot less likely to bite you, because (a) you know that it's being called and (b) you can control when. And if you later decide to execute that (library) code in a secondary thread, all you have to change is the code of that callback handler.

Share this post


Link to post

About the prcessmessages, here's my own proc (tx for toolbox) :

 

class procedure Tx.DoEvents;
begin
    if MainThreadId = TThread.Current.ThreadId then
        Application.ProcessMessages;
end;

 

Using this, I no longer have problem with processmessages.

 

To refresh, espacially under Android, I had to run the loop in a thread, and inside this thread, call a thread.synchronize in which I put the code I want to be executed to display something somewhere.

 

The method runs fine crossplatform.

 

Hope this helps

Share this post


Link to post
On 9/3/2022 at 10:20 AM, KMarb said:

I have not tried that, but the form does not have a repaint or refresh method.

<sigh> Turns out they are methods of TControl, which TForm is not derived from in FMX, unlike in VCL.  And this is just one of a hundred reasons why I hate FMX and will never ever use it.

Share this post


Link to post

Tried label1.ApplyStyleLookup in the loop, but it does not update the label while loop is running, only when done it changes to "Step 10". This is somewhat academic at this point, for me, because I'm moving my long running code to threads, but so far there doesn't appear to be away to force a label to update within the context of constant main thread processing.

 

 for i  := 1 to 10 do
    begin
      label1.Text := 'Step ' + intToStr (i);

      label1.ApplyStyleLookup;

      sleep (200);
    end;

Share this post


Link to post

Just call Invalidate for the entire form. Or (if you really have to) call InvalidateRect for the rectangle of your label. Note that InvalidateRect is still a method of your form, not of the label. But you will have to use some form of threading to get that application responsive anyway...

Share this post


Link to post

form1.Invalidate does not update the label while the loop is active. On android, even application.processmessages does not update the UI. Conclusion: If you need to do a second or three of processing on android, and you want to do this in the main form, you cannot update the screen in any way while you do that short processing. Please correct me if that is wrong. I don't need someone to explain that I should do the 1-3 seconds of processing in a thread, I understand that, but sometimes the nature of the processing might make threading quite a bit more work than what would seem necessary.

Share this post


Link to post
1 hour ago, KMarb said:

Conclusion: If you need to do a second or three of processing on android, and you want to do this in the main form, you cannot update the screen in any way while you do that short processing. Please correct me if that is wrong. I don't need someone to explain that I should do the 1-3 seconds of processing in a thread, I understand that, but sometimes the nature of the processing might make threading quite a bit more work than what would seem necessary.

You don't necessarily need threading, you just need to return control to the main UI message loop periodically.  For instance, break up your loop into chunks that you can trigger with TThread.ForceQueue() for each iteration, eg:

procedure TMyForm.DoProcessing(i: Integer);
begin
  Label1.Text := 'Step ' + IntToStr(i);
  Inc(i); 
  if i <= 10 then
    TThread.ForceQueue(nil, procedure begin DoProcessing(i); end, 200)
  else
    TThread.ForceQueue(nil, AfterProcessing);
end;

procedure TMyForm.StartProcessing;
begin
  DoProcessing(1);
end;

procedure TMyForm.AfterProcessing;
begin
  ...
end;

 

  • Like 2

Share this post


Link to post
On 3/18/2023 at 9:36 PM, programmerdelphi2k said:

why not use "message" to force redraw?

Can to elaborate more?

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×