KMarb 5 Posted September 2, 2022 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
hsauro 40 Posted September 2, 2022 Label1.refresh should work I think at least on windows it should. Share this post Link to post
SwiftExpat 65 Posted September 2, 2022 Wrap it with begin / end update label1.beginupdate label1.text := ... label1.endupdate Share this post Link to post
KMarb 5 Posted September 2, 2022 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
Remy Lebeau 1396 Posted September 2, 2022 (edited) 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 September 2, 2022 by Remy Lebeau 4 Share this post Link to post
KMarb 5 Posted September 3, 2022 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
Remy Lebeau 1396 Posted September 3, 2022 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
KMarb 5 Posted September 3, 2022 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
Pat Foley 51 Posted September 3, 2022 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
KMarb 5 Posted September 3, 2022 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
Pat Foley 51 Posted September 3, 2022 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
KMarb 5 Posted September 3, 2022 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
Pat Foley 51 Posted September 3, 2022 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
emileverh 21 Posted September 4, 2022 Never use Application.ProcessMessages ! Sooner or later you get other troubles 1 Share this post Link to post
dummzeuch 1505 Posted September 4, 2022 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
TigerLilly 16 Posted September 5, 2022 Maybe this helps a bit: https://learndelphi.org/how-to-improve-gui-and-make-it-more-responsive-using-background-thread/ Share this post Link to post
weabow 6 Posted September 5, 2022 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
Remy Lebeau 1396 Posted September 6, 2022 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
KMarb 5 Posted September 8, 2022 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
Sherlock 663 Posted September 8, 2022 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
KMarb 5 Posted September 8, 2022 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
Remy Lebeau 1396 Posted September 8, 2022 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; 2 Share this post Link to post
programmerdelphi2k 237 Posted March 19, 2023 why not use "message" to force redraw? Share this post Link to post
Remy Lebeau 1396 Posted March 20, 2023 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