Page 1 of 2

Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 12:50 pm
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

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 3:53 pm
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

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 4:40 pm
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.

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 5:17 pm
by TerryHough
Sorry, I only have WinXP Pro so cannot assist with Win7.

Hopefully someone else with help out.

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 6:37 pm
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.

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 6:43 pm
by skywalk

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 8:10 pm
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


Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 10:12 pm
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

Re: Refresh Progressbar gadget

Posted: Wed Nov 28, 2012 11:22 pm
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.

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 6:58 am
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

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 8:07 am
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

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 8:37 am
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.

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 9:25 am
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!

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 9:56 am
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

Re: Refresh Progressbar gadget

Posted: Thu Nov 29, 2012 11:17 am
by srod
Nice little tip Danilo; I'll remember that. :)