Refresh Progressbar gadget

Just starting out? Need help? Post your questions and find answers here.
Barbarossa
User
User
Posts: 47
Joined: Fri Oct 12, 2012 8:50 am

Refresh Progressbar gadget

Post by Barbarossa »

I have a loop with 94 iterations. Since the total loop takes about 5 seconds (and will be more when data grows) I decided to add a Progressbar indicator. I've set it up with:

Code: Select all

ProgressBarGadget(#PROGRESSBAR,20,4,GadgetWidth(#STATUSBAR_3)-40,14,0,94)
As you can see I divided the bar into 94 parts (min value 0, max value 94) and update the bar at the end of the loop with:

Code: Select all

SetGadgetState(#PROGRESSBAR,GetGadgetState(#PROGRESSBAR)+1)
While WindowEvent() : Wend
I noticed that the refresh of the bar lags and is behind some 5 values from the loop. At the end of the loop when the value reaches 94 the display shows the bar visually at around 88.

I can imagine that something like a progressindicator is not updated very frequently by windows which makes sense. But I don't understand that when it DOES update it doesn't get the current value which is set by SetGadgetState(). It looks like some refresh events are still in the pipeline waiting to be fetched??

I searched the forums for information and tried some of the things suggested there like use Delay(), UpdateWindow_() but that did not help much. So maybe someone can point me in the right direction.

There is one thing that does work however, but obviously that is not the way to go because then the loops lasts several minutes instead of several seconds:

Code: Select all

SetGadgetState(#PROGRESSBAR,GetGadgetState(#PROGRESSBAR)+1)
For i=1 To 100
    WaitWindowEvent(500)
Next i
Any help will be much appreciated.

John
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Re: Refresh Progressbar gadget

Post by TerryHough »

Try adding the #PB_ProgressBar_Smooth flag to your ProgressBarGadget and see what that shows you. Appears correct in this sample code.

Code: Select all

  If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    TextGadget(3,  10, 10, 250,  20, "ProgressBar Standard  (50/100)", #PB_Text_Center)
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 94);,#PB_ProgressBar_Smooth)
    Repeat 
      Iteration + 1
      SetGadgetText(3,"Iteration " + Str(Iteration) + " of 94") ; update text display
      SetGadgetState(0,Iteration) ; update progress bar
      Delay(200) ; only to slow it down so it can be seen progressing
    Until Iteration = 94
    Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
  EndIf
I believe the ProgressBarGadget in non-smooth mode is showing the % of completion rounded to the next number of blocks and therefore shows 100% before your iteration count actually gets there.

FYI, I find it easier to show the ProgressBarGadget as 0 to 100 and actually display the rounded percentage of completion, eg. 50 of 94 is actually 53% on the progress bar. Of course that ties up a bit of computing time.

Code: Select all

  If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    TextGadget(3,  10, 10, 250,  20, "ProgressBar Standard  (50/100)", #PB_Text_Center)
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 100,#PB_ProgressBar_Smooth)
    Repeat 
      Iteration + 1
      SetGadgetText(3,"Iteration " + Str(Iteration) + " of 94") ; update text display
      PercentComplete = Round((Iteration/94)*100,#PB_Round_Nearest) ; compute % complete
      SetGadgetState(0,PercentComplete) ; update progress bar
      Delay(200) ; only to slow it down so it can be seen progressing
    Until Iteration = 94
    Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
  EndIf
Barbarossa
User
User
Posts: 47
Joined: Fri Oct 12, 2012 8:50 am

Re: Refresh Progressbar gadget

Post by Barbarossa »

Thanks for the answers.

I tried adding #PB_ProgressBar_Smooth but that doesn't do much.

Then I tried your example without changing anything. It does kind of the same thing as my code. When the counter reaches 94 the bar still isn't full. It catches up after that but that is probably because of the WaitWindowEvent()=#PB_Event_CloseWindow. And this of course means the bar is still "behind" on the real counter.

Image

The above screenshot shows what I mean and I captured it actually a little bit too late as well.

Another strange thing is, when your example is running the mouse cursor changes to a waiting state. In my loop it doesn't do that (in fact I have to put a SetCursor_ command in there to achieve this).

Maybe I should have mentioned this in my original question, but I am on Windows 7.
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Re: Refresh Progressbar gadget

Post by TerryHough »

Sorry, I only have WinXP Pro so cannot assist with Win7.

Hopefully someone else with help out.
User avatar
Tenaja
Addict
Addict
Posts: 1959
Joined: Tue Nov 09, 2010 10:15 pm

Re: Refresh Progressbar gadget

Post by Tenaja »

Barbarossa wrote:I noticed that the refresh of the bar lags and is behind some 5 values from the loop. At the end of the loop when the value reaches 94 the display shows the bar visually at around 88.
John,
From what I have seen, this is the way the Windows progressbar works. If you want it more accurate, use one of the "from scratch" progress bars posted on the forum. (Or make your own.) I do not have one to suggest, but have no doubt a search will turn up a couple at least.
User avatar
skywalk
Addict
Addict
Posts: 4241
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Refresh Progressbar gadget

Post by skywalk »

The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4991
Joined: Sun Apr 12, 2009 6:27 am

Re: Refresh Progressbar gadget

Post by RASHAD »

Code: Select all

If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    TextGadget(3,  10, 10, 250,  20, "ProgressBar Standard  (50/100)", #PB_Text_Center)
    ProgressBarGadget(0,  10, 30, 250,  22, 0, 94)
    AddWindowTimer(0,123,500)

Repeat
  Select WaitWindowEvent()
      
      Case #PB_Event_CloseWindow
            Quit = 1
            
      Case #PB_Event_Timer
                 Iteration + 1
                 SetGadgetState(0,Iteration)
                 SetGadgetText(3,"Iteration " + Str(Iteration) + " of 94") ; update text display                 
                 If Iteration = 94
                    RemoveWindowTimer(0,123)
                 EndIf


  EndSelect
 
 Until Quit = 1
  EndIf

Egypt my love
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Refresh Progressbar gadget

Post by davido »

Dear John (Barbarossa),

For your information I haven't yet used progress bars so I was interested in the code supplied by TerryHough and Rashad.

My system is Windows 7 with PureBasic 5.00.

All three examples work perfectly.

Maybe there is some conflict in your system somewhere.


Regards

Dave
DE AA EB
Barbarossa
User
User
Posts: 47
Joined: Fri Oct 12, 2012 8:50 am

Re: Refresh Progressbar gadget

Post by Barbarossa »

I have just tried my own code on a different Windows 7 system. And it works perfectly on there. So the suggestion that Davido made, that there must be something wrong with my system seems to be correct. It baffles me what is causing it, but that is another problem. :D

Thanks everybody for the help. I am new to PureBasic (bought it last week) and writing my first Windows application in it. Always nice when you're new to get some good advice quickly.

John

EDIT:
The above is not true. I did a last minute change on my workmachine and never tested it, until I brought it over to another Windows 7 system. My last minute change was to put an extra SetGadgetState() withing the body of my loop. Now the progressbar updates 13*94 times instead of 94 times. It works with this change but drops the processing speed a lot. I feel I am getting closer to a workable solution.
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: Refresh Progressbar gadget

Post by Danilo »

@Barbarossa:
On newer Windows' with enabled XP skin, the progressbar is automatically animated by Windows.
Values are not displayed instantly, it is an animated transition from the old value to the new value.

Try the following code to see it. The highest value is set, still it is smoothly animated:

Code: Select all

  If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 94);,#PB_ProgressBar_Smooth)
    SetGadgetState(0,94)
    Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
  EndIf
This code works correctly up to the end because there is an (empty) event loop.

Without the event loop after a state change, the progressbar display is not updated further.
That's the problem with your code. You set highest value 94, but it stops display at 88 or so,
because there is no event processing anymore. The one "While WindowEvent() : Wend" after
the state change does not help if the Windows animation is done by using a timer that fires
a few milliseconds later, after your While..Wend is already done.

You need to make sure somehow that there is always active event processing in your app.
What is the next step in your app after the last SetGadgetState with value 94?
Is there a long blocking call? If so, try to include more "While WindowEvent():Wend" there.

If possible, do long blocking work within threads. Start the worker thread and continue
event processing in the main thread, while waiting for the worker thread to finish.

To see the difference:

Blocking example, 5 seconds work:

Code: Select all

Procedure DoWork() ; blocking work
    For i = 0 To 10
        Delay(500)
    Next i
EndProcedure

If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 94);,#PB_ProgressBar_Smooth)
    TextGadget(1,10,65,250,30,"WORKING...")
    SetGadgetState(0,94)
    While WindowEvent():Wend

    DoWork()

    SetGadgetText(1,"DONE.")

    Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf
Threaded example, 5 seconds work:

Code: Select all

Procedure DoWork(t) ; non-blocking thread
    For i = 0 To 10
        Delay(500)
    Next i
EndProcedure

If OpenWindow(0, 0, 0, 320, 160, "ProgressBarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 94);,#PB_ProgressBar_Smooth)
    TextGadget(1,10,65,250,30,"WORKING...")
    SetGadgetState(0,94)
    While WindowEvent():Wend

    work = CreateThread(@DoWork(),0)
    While IsThread(work)
        While WindowEvent():Wend
    Wend

    SetGadgetText(1,"DONE.")

    Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf
Last edited by Danilo on Thu Nov 29, 2012 8:07 am, edited 1 time in total.
Barbarossa
User
User
Posts: 47
Joined: Fri Oct 12, 2012 8:50 am

Re: Refresh Progressbar gadget

Post by Barbarossa »

I already tried setting the #PB_ProgressBar_Smooth parameter and that doesn't help much (unless I deliberately wait for a long period after the loop is finished - what you don't want since this means more waiting for the user)

After my last iteration of 94 the procedure does not much. Set two variables, free the progressbar and exit the procedure.

I think I'll leave it for now (have to move on) and maybe for the next release of my software I will try to use threads and/or make my own progressbar.

Thanks for all the help.

John
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: Refresh Progressbar gadget

Post by Danilo »

Barbarossa wrote:As you can see I divided the bar into 94 parts (min value 0, max value 94) and update the bar at the end of the loop with:

Code: Select all

SetGadgetState(#PROGRESSBAR,GetGadgetState(#PROGRESSBAR)+1)
While WindowEvent() : Wend
I noticed that the refresh of the bar lags and is behind some 5 values from the loop. At the end of the loop when the value reaches 94 the display shows the bar visually at around 88.
I just remembered a small trick with the animated progressbar thing:
Windows automatically animates the progressbar only if the new value is higher.
Downwards it instantly displays the correct value.
So if you do "SetGadgetState(gadget, value+1) : SetGadgetState(gadget, value)",
the value is displayed instantly, because setting a lower value does not get animated.

Change your 2 lines above to:

Code: Select all

state = GetGadgetState(#PROGRESSBAR)
SetGadgetState(#PROGRESSBAR,state+2)
SetGadgetState(#PROGRESSBAR,state+1)
While WindowEvent() : Wend
Except for the last value, this should work much better and the display should not be behind some values.
Barbarossa
User
User
Posts: 47
Joined: Fri Oct 12, 2012 8:50 am

Re: Refresh Progressbar gadget

Post by Barbarossa »

This works indeed a lot better! And when I changed the +1 to +5 it was almost perfect (expect for the last one or two values like you said).

The "problem" with my loop was that most of the time the last 10 iterations took a lot less time than the previous ones. Having those at the end doesn't help of course.

When the loop lasted about 5 seconds the last 5 refreshes were missed. But when the loop lasted less (with simpler calculations) than that, more refreshes were missed. With a looptime of 0,5 seconds the bar never got further than one third of the distance.

However with the above trick all loops work (with the exception of the last bit, but I can live with that).

Thanks Danilo!
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: Refresh Progressbar gadget

Post by Danilo »

Barbarossa wrote:However with the above trick all loops work (with the exception of the last bit, but I can live with that).
The last bit is because you can't set a value higher as the maximum. So "SetGadgetState(gadget, value+1) : SetGadgetState(gadget, value)"
works only up to value 93, if the maximum is 94.

To make it work for the maximum value, you could change the range maximum temporarily (add 1), set maximum value, set maximum-1 value, change range maximum back to old maximum (sub 1).
Try this procedure for setting your values:

Code: Select all

Procedure SetProgressbarValue(gadget,value)
    Protected max = GetGadgetAttribute(gadget,#PB_ProgressBar_Maximum) ; get range maximum
    If value > max : value = max : EndIf

    If value = max
        SetGadgetAttribute(gadget,#PB_ProgressBar_Maximum,max+1)       ; change range maximum temporarily
        SetGadgetState(gadget,max+1)                                   ; set new maximum value
        SetGadgetState(gadget,max)                                     ; set old maximum value
        SetGadgetAttribute(gadget,#PB_ProgressBar_Maximum,max)         ; change range to old maximum
    Else
        SetGadgetState(gadget,value+1)
        SetGadgetState(gadget,value)
    EndIf
EndProcedure
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Refresh Progressbar gadget

Post by srod »

Nice little tip Danilo; I'll remember that. :)
I may look like a mule, but I'm not a complete ass.
Post Reply