Trouble with UI update during a loop

  1. 3 years ago

    Ramon S

    14 Apr 2017 Pre-Release Testers, Xojo Pro UPC, Europe (Barcelona, Spain)

    I've been struggling with ProgressBars, loops, threads and timers for a while.
    Some times with success some times not. So I decided to start with super basic examples.
    I've created a new desktop application. One window with one Progressbar (named "pb") and a Label (named "lb").
    On the Activate event of the main window I've created this code:

    Self.show
    Dim t As Integer = Ticks
    pb.value = 0
    pb.Maximum = 10000
    
    For i As Integer = 1 To 10000000
      If i Mod 1000 = 0 Then
       ' pb.Value = i/1000
        'lb.Text =  Str(i/1000)
      End If
    Next i
    
    MsgBox str(ticks - t)

    It is a 10 million loop. At the end I get a message of 57 ticks. As far as good.
    If I uncomment lb.Text = Str(i/1000) it takes 80 ticks but I see only the last value for the label.
    It is OK because I understand that the UI is not updated during a loop.

    But if I uncomment pb.Value = i/1000 the ProgressBars fills smoothly, and at the end it is completely full. It takes 73 ticks.

    My question is:
    If a ProgressBar is a control, why does it get updated in the loop? Wasn't it necessary to use threads and timers?

    Thanks for your clarifying it.

  2. Dave S

    14 Apr 2017 San Diego, California USA
    Edited 3 years ago

    the ProgressBar object class most likely has a REFRESH as part of the VALUE assignment

    try adding

    lb.refresh

    after lb.text

  3. Christoph D

    14 Apr 2017 Pre-Release Testers, Xojo Pro
    Edited 3 years ago

    Or better

    lb.invalidate(false). // faster then .refresh

  4. Norman P

    14 Apr 2017 Xojo Inc, Pre-Release Testers, Xojo Pro Seeking work. npalardy@great-w...

    At least on OS X there are several controls that the OS frameworks create a background thread to update
    ProgressBars and Default Buttons are ones I'm aware of - there may be others
    So you don't have to do anything as the back ground thread handles, for progressbars, making sure that the progressbars is redrawn during a "main loop iteration" on that background thread. The OS frameworks do similar for the pulsing default button behavior on macOS.

    Labels have no such thing - hence why they don't update like the progressbar does
    A tight loop like this one doesn't allow Xojo to get back to the main event loop hence why the screen doesn't refresh if you just use "invalidate"
    So you need to force a refresh so the screen updates and the label appears to change (ie/ Refresh)
    And doing this WILL slow things down immensely to display the updates because the entire OS window buffers have to be flushed when things aren't normally expected to and this is slow

  5. Michel B

    14 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    Use a timer instead of a loop.

  6. Dave S

    14 Apr 2017 San Diego, California USA

    @Christoph Dnbsp;Vocht Or better

    lb.invalidate(false). // faster then .refresh

    not in this case. Invalidate updates at the end of the runloop..... Refresh does it "now"
    but as Norman said.... it slows things down a lot.

  7. Michel B

    14 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    The loop is a ridiculous way to do things. It has a cycle of often 1 or less microsecond, so there are a ton of unnecessary cycles. A timer lets the UI refresh at will, and does not waste computing cycles.

  8. Ramon S

    14 Apr 2017 Pre-Release Testers, Xojo Pro UPC, Europe (Barcelona, Spain)

    Thanks to all your feedback.
    It was just an exercise. And I've learned that ProgessBar (I checked it on Windows 10) has something special (different from Labels, as Norman pointed out) that makes some times unnecessary to use .thread-timer structure.

    @Michel B The loop is a ridiculous way to do things. It has a cycle of often 1 or less microsecond, so there are a ton of unnecessary cycles. A timer lets the UI refresh at will, and does not waste computing cycles.

    Thanks Michel. I use a lot of timers but it's its relation with threads what I liked to investigate.

  9. Michel B

    14 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    Any long loop like what you did has to be replaced. That is what I systematically do. Then you can avoid a lot of threading.

  10. Christoph D

    14 Apr 2017 Pre-Release Testers, Xojo Pro
    Edited 3 years ago

    There is another way - but Normal is going to kill me.

    Use app.doevent() in the loop.

    EDIT: for some odd reason Norman got autocorrected to Normal :-) :-) :-)

  11. Kem T

    14 Apr 2017 Pre-Release Testers, Xojo Pro, XDC Speakers Connecticut
    Edited 3 years ago

    Do not do that. Never do that. Ignore anyone who tells you to do that. (Sorry Christoph.)

  12. Norman P

    14 Apr 2017 Xojo Inc, Pre-Release Testers, Xojo Pro Seeking work. npalardy@great-w...

    doevents is really the wrong way to do this in a GUI app
    There are VERY VERY VERY VERY few places in a GUI to use doevents safely
    IT can cause reentrancy issues in sockets and serial controls and is better avoided

  13. Michel B

    14 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    Hush hush.

    Indeed, App.DoEvent will do it. But that does not mean a loop is not a gigantic waste of processor power. In the code posted by the OP, the program wastes probably 99.99% of the time cycling until i Mod 1000 = 0 .

  14. Antonio R

    15 Apr 2017 Pre-Release Testers, Xojo Pro, Third Party Store Europe (Italy)

    The progress bar is animated when it change the value.
    Within the loop in not animated, since it is "updated" at the end of the loop you see the animation, but it is not animated in the loop

  15. Tim H

    15 Apr 2017 Pre-Release Testers Portland, OR USA

    That's because the loop is blocking the main thread, which would normally do the animation. Therefore, don't use a loop like that on the main thread. Use a Thread or a Timer.

  16. James S

    15 Apr 2017 Pre-Release Testers, Xojo Pro

    in 64bit apps on OSX refresh doesn’t work anymore anyway. The UI won’t update until you return. So you have to do chunks in a timer or a thread to have the rest of the UI update.

    Traditionally if you needed to update the UI like that you didn’t call refresh to the labels or progress bar a million times ;) You’d just do some simple math and only update every half second or every 1000 records processed or something that made sense. You can display useful info that way without significantly slowing down your work. But again, you can’t do that from the main thread anymore on OSX anyway. I don’t know about windows.

  17. Michel B

    15 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    Windows is just the same.

  18. Joe H

    23 Apr 2017 Pre-Release Testers San Francisco

    Ouch!

    James thanks for the clear description. This is exactly the issue I'm facing in my 64 bit apps. I've got the carefully paced calls to ProgressBar.Refresh just like you describe. I'm loading a huge listbox and didn't want to do that from a background thread due to the warnings about not mixing GUI updates and background threads.

    Is there no longer any way to have a progress bar and a label updated without returning from my user code on the main thread and letting the main event loop run on its own?

  19. Norman P

    23 Apr 2017 Xojo Inc, Pre-Release Testers, Xojo Pro Seeking work. npalardy@great-w...

    have you tried ?
    https://forum.xojo.com/conversation/post/236689

  20. Michel B

    23 Apr 2017 Pre-Release Testers, Xojo Pro RubberViews.com

    @Joe H Is there no longer any way to have a progress bar and a label updated without returning from my user code on the main thread and letting the main event loop run on its own?

    If you don't want to use a thread, move the code in loops to a timer.

  21. Newer ›

or Sign Up to reply!