ProgressDialog Pulse Help Again

I am struggling again with ProgressDialog behaviour in wx 4.1.1 for an application which I am porting from 3.0.2.0. (windows 10)

I open a ProgressDialog & set it to “Pulse()” while I read a list of files from a network directory & then display them in my main application ListCtrl.

In wx 3.0.2.0 the code example below works fine (see also the animated GIF).
In wx 4.1.1 the dialog does not pulse until just before it closes.

I have other applications with 4.1.1 where the Pulse is working, so Im sure its my code but I cant see the difference in my usage.

Im guessing its because my code is executing the task (e.g. Sleep()) within the OnButtonClick function so the GUI is not given time to “update” the window.
However, I dont understand why it worked before & not now.
I have tried adding wx.Yield() or wx.GetApp().Yield() but this does not change the behaviour.

Can anyone see what I’m missing ??

As Simple Example is below which shows this behaviour:

import wx
import time

application = wx.App()
framework = wx.Frame(parent=None, title='MyFrame')
panel = wx.Panel(framework)                                     # create a panel as a child of the frame
button = wx.Button(panel, label='Click Me', style=wx.BU_LEFT)   # create a button as a child of the panel
sizer = wx.BoxSizer(wx.HORIZONTAL)                              # create a horizontal sizer
sizer.Add(button, 0, wx.ALL, 5)                                 # add the button to the sizer with some padding
panel.SetSizer(sizer)                                           # set the sizer for the panel

def on_button_click(event):
    with wx.ProgressDialog("My Viewer", "Copying File...", maximum=1, parent=framework) as open_dlg:
        open_dlg.Pulse()
        time.sleep(5)
        open_dlg.Update(1)

button.Bind(wx.EVT_BUTTON, on_button_click)

framework.Show()
application.MainLoop()

ProgressDialog(3.0.2.0) ProgressDialog(4.1.1)

I have done a short test displaying a counter instead of “Pulse” and it seems that the GUI sometimes misses some of the ProgressDialog.Update() calls.

def on_button_click(event):
    open_dlg = wx.ProgressDialog("My Viewer", "Copying File...", maximum=10, parent=framework)
    for ctr in range(11):
        open_dlg.Update(ctr, "Copying File...{}".format(ctr))
        time.sleep(1)

You can see in the examples below, sometimes the “0” event is missed, sometimes “2”, sometimes “3” but after a 2-3 seconds then it seems to work correctly (4, 5, 6, 7, 8, 9 are displayed).

Is this maybe a strange behaviour from Windows rather than a problem with my code?

ProgressDialog(Ctr 0+2) ProgressDialog(Ctr 2) ProgressDialog(Ctr 3)

You seem to be confusing Update and Pulse (indeterminate mode) and you only call Update once after 5 seconds.
Update mode expects updates up to the the maximum value, whilst Pulse goes into indeterminate mode and expects a call to Pulse, whenever you require a movement.
Without knowing exactly the behaviour you require, there are quite a few options, especially with the various style options, I can only suggest you read the manual again. wx.GenericProgressDialog — wxPython Phoenix 4.2.1 documentation

import wx
import time

application = wx.App()
framework = wx.Frame(parent=None, title='MyFrame')
panel = wx.Panel(framework)                                     # create a panel as a child of the frame
button = wx.Button(panel, label='Click Me', style=wx.BU_LEFT)   # create a button as a child of the panel
sizer = wx.BoxSizer(wx.HORIZONTAL)                              # create a horizontal sizer
sizer.Add(button, 0, wx.ALL, 5)                                 # add the button to the sizer with some padding
panel.SetSizer(sizer)                                           # set the sizer for the panel

def on_button_click(event):
    with wx.ProgressDialog("My Viewer", "Copying File...", parent=framework, \
                                 style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME| wx.PD_CAN_ABORT) as open_dlg:
        keep_going = True
        while keep_going:
            res, skip = open_dlg.Pulse()
            if res:
                wx.MilliSleep(200)
            else:
                keep_going = False

button.Bind(wx.EVT_BUTTON, on_button_click)

framework.Show()
application.MainLoop()

Thanks for the reply Dietmar,
I am looking for the behaviour I had before in wx3.0.2.0.
I want the ProgressDialog to Pulse() while a task is running.
I am not using threading in my applications, so when I call open_dlg.Pulse() once, I am expecting that the gauge will pulse on its own without further input until it is “Updated” or closed when the function is complete.

In wx4.1.1, if I change the “Parent” window to “None” then the window will “Pulse” on its own as I am expecting (but obviously it is no longer linked to the application which is not what I want - I want the ProgressDialog to block the main application while the function completes).
ProgressDialog(Parent=None)
I dont understand why the parent affects the display of the dialog?
Note: there is this open issue when using “Parent=None”: https://github.com/wxWidgets/Phoenix/issues/2189

def on_button_click(event):
    with wx.ProgressDialog("My Viewer", "Copying File...", maximum=1, parent=None) as open_dlg:
        open_dlg.Pulse()
        time.sleep(5)

I have tried the example you provided with multiple calls to Pulse() & the gauge view is very “jerky” in the display.
ProgressDialog(MultiCall)

Here is a possible solution:

import wx


application = wx.App()
framework = wx.Frame(parent=None, title='MyFrame')
panel = wx.Panel(framework)                                     # create a panel as a child of the frame
button = wx.Button(panel, label='Click Me', style=wx.BU_LEFT)   # create a button as a child of the panel
sizer = wx.BoxSizer(wx.HORIZONTAL)                              # create a horizontal sizer
sizer.Add(button, 0, wx.ALL, 5)                                 # add the button to the sizer with some padding
panel.SetSizer(sizer)                                           # set the sizer for the panel


def on_button_click(event):
    with wx.ProgressDialog("My Viewer", "Copying File...", maximum=1, parent=framework) as open_dlg:
        open_dlg.Pulse()
        x = 500
        # do the work and constantly call wx.Yield() so the dialog will pulse
        while x:
            wx.MilliSleep(1)
            wx.Yield()
            x -= 1
        open_dlg.Update(1)


button.Bind(wx.EVT_BUTTON, on_button_click)

framework.Show()
application.MainLoop()

time.sleep is blocking, so wx can’t update the dialog. In the code above i used short sleeping times and called wx.Yield which gives wx a chance to update the dialog.

Thanks for the reply Thorsten/Dietmar,
Is this really how the “Pulse()” feature is intended to work?
You must tell it to move each time you need it to?

If so I find this a backward step from the previous wx 3.0.2.0 where you can call Pulse() once & the UI will scroll the display while executing the next functions without threading or yielding to the UI.

I have used wx4.1.1 the same way in two other application & it works but the “following” action is from win32com which I guess executes an external function & just waits for a reply:

open_dlg.Pulse()
win32com.Client.Dispatch("......")
# some more functions linked to the dispatch...

Do you understand why it works (without continuous Yield()ing) when parent=None ?

In the documentation for ProgressDialog.Pulse it says:

Each call to this function moves the progress bar a bit to indicate that some progress was done.

I also checked the docs for 4.1.1 and they say the same.

I also find this behaviour annoying and a step backward. It was so much better before when you just called “Pulse” at the beginning and the gauge would spin back and forth independently - and I only had to call Pulse() again if I needed to change the label.

Now either the gauge is all jerky in its updates or it gets stuck even if I repeatedly call Pulse() at regular intervals (if some other heavy task is running concurrently). Of course, this is on Windows.

Not sure what has changed, it would take me forever to track it down in the C++ code so I’d better leave it to more expert people.

Andrea.

1 Like

I downloaded the documentation for wxPython 3.0.2.0 from here. Which turned out to actually be the documentation for wxWidgets 3.0.2.

In that documentation it shows wxProgressDialog as derived from wxGenericProgressDialog. The section for the Pulse() method is shown below:

Notice it only describes the progress bar moving ‘a bit’ for each call.

I also downloaded the documentation for wxPython 2.8.0.1 (wxWidgets 2.8.0) and the wording for the Pulse() method effectively says the same thing.

Perhaps the behaviour where the progress bar moved independently was always just an undocumented feature?

In wx3.0.2.0 if you enable the elapsed time display through the style, you see that whilst the display pulses the elapsed time is not updated unless you make a new call to pulse() so perhaps it was an unexpected behaviour in that version as you suggest.

How can I request that they add this bug back in? :rofl:

Does anyone understand why 4.1.1 & parent = None shows the same behaviour ( pulsing after only1 call)?

I was using Pulse() as a way to simplify the code so I didn’t need to calculate how many files would be processed e.g. By a SVN folder update or reading data from a network drive.
I will look at ways to use Update() instead of Pulse() as at least in windows the call to pulse() moves the gauge very slowly compared with the normal windows pulsing

I think what you are after is a BusyInfo or ActivityIndicator :sunglasses: (completely customizable)

from threading import Thread, Event
import wx

class Gui(wx.Frame):

    def __init__(self, parent):
        super().__init__(parent, title='Activity indicator')

        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)

        vbox.Add(wx.Button(pnl, label='start / stop'))
        self.Bind(wx.EVT_BUTTON, lambda _: act_ind.start_stop())
        act_ind = ActInd(pnl)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(act_ind, 0, wx.LEFT|wx.RIGHT|wx.TOP, 20)
        vbox.Add(hbox)
        pnl.SetSizer(vbox)
        self.Centre()
        self.Show()

class ActInd(wx.Window):

    def __init__(self, parent, width=300, height=5):

        super().__init__(parent,
                size=(width, height), style=wx.BORDER_NONE)

        self.indicator = wx.Window(self, size=(60, height))
        self.indicator.SetBackgroundColour('blue')
        self.SetBackgroundColour('white')

        self.evt_quit = Event()
        self.evt_stop = Event()
        self.evt_resize = Event()
        self.Bind(wx.EVT_WINDOW_DESTROY,
                                        lambda _: self.evt_quit.set())
        self.Bind(wx.EVT_SIZE, self.evt_size)
        self.width = 0
        Action(self, self.evt_quit, self.evt_stop, self.evt_resize,
                                        self.indicator).start()
        self.stop = True

    def evt_size(self, evt):
        self.width = evt.GetSize()[0] - self.indicator.GetSize()[0]
        self.evt_resize.set()

    def start_stop(self):
        self.evt_stop.set() if self.stop else self.evt_stop.clear()
        self.stop = not self.stop

class Action(Thread):

    def __init__(self, window, evt_quit, evt_stop, evt_resize, indicator):
        super().__init__()
        self.window = window
        self.evt_quit = evt_quit
        self.evt_stop = evt_stop
        self.evt_resize = evt_resize
        self.indicator = indicator

    def run(self):
        win = self.window
        ind = self.indicator
        drn = 1
        x = 0
        while not self.evt_quit.is_set():
            if not self.evt_stop.is_set():
                if self.evt_resize.is_set():
                    x = max(0, min(x, win.width))
                    self.evt_resize.clear()
                elif x < 0 or x > win.width:
                    drn *= -1
                x = x + drn
                ind.Move(x, 0)
            self.evt_quit.wait(0.01)

app = wx.App()
Gui(None)
app.MainLoop()
1 Like

Possibly this one - the comments in the code seem to hint at “not being able to afford continuous animation”, but I am not sure:

Maybe I am on the wrong track altogether :slight_smile: .

Andrea.

Thanks Andrea, that does seem like it would create this behaviour.
It would seem to make Pulse() pretty redundant though, at least in Windows as it wont move unless you Yield() or call Pulse() millions of times which will make a long task even longer…

I am still a bit confused why it Pulses when parent=None but perhaps this is related to the bug I posted above.

In all my other uses of ProgressDialog.Pulse() I am using win32com.client.Dispatch() to open/load an external application.
While the python application is “waiting” for win32com call to return the dialog pulses nicely & it then pauses briefly during “python” commands and continues to pulse while waiting for the next win32com action to finish.

@da-dada thanks for your BusyInfo, its very nice!!
Unfortunately I dont think it would be accepted for my applications.
Another reason for using Pulse() was to keep the code simple. I am not really a SW developer, just mutilating something that was created by someone before me… :wink: but for my personal learning it is good to see how others are using wx!

Thanks everyone for your input.