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:
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)
MsgBox str(ticks - t)[/code]
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.
the ProgressBar object class most likely has a REFRESH as part of the VALUE assignment
lb.invalidate(false). // faster then .refresh
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
Use a timer instead of a loop.
[quote=326145:@Christoph De Vocht]Or better
lb.invalidate(false). // faster then .refresh[/quote]
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.
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 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.[quote=326153:@Michel Bujardet]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.[/quote]
Thanks Michel. I use a lot of timers but it’s its relation with threads what I liked to investigate.
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.
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
Do not do that. Never do that. Ignore anyone who tells you to do that. (Sorry Christoph.)
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
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 .
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
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.
in 64bit apps on OSX refresh doesnt work anymore anyway. The UI wont 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 didnt call refresh to the labels or progress bar a million times Youd 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 cant do that from the main thread anymore on OSX anyway. I dont know about windows.
Windows is just the same.
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?
If you don’t want to use a thread, move the code in loops to a timer.