wx.Gauge fails to update beyond 25% in Windows, works in Linux
Question: I seem to have nothing but trouble with wxPython and cross-platform
compatibility :(

I have the function below. It's called when the user clicks a button, it does
some work which may take a while, during which a progress gauge is shown in
the status bar.

    
    
    def Go(self, event):       
        progress = 0
        self.statbar.setprogress(progress)
        self.Update()
    
        # ...
    
        for i in range(1, numwords + 1):
            progress = int(((float(i) / float(numwords)) * 100) - 1)
            self.wrdlst.Append(words.next())
            self.statbar.setprogress(progress)
            self.Update()
    
        self.wrdlst.Refresh() 
    
        # ...
    
        progress = 100
        self.PushStatusText(app.l10n['msc_genwords'] % numwords)        
        self.statbar.setprogress(progress)
    

The calls to `self.Update()` are apparently needed under Linux, otherwise the
gauge doesn't update until the function exits which makes it kinda pointless.
These calls seem to have no effect under Windows (Win 7 at least).

The whole thing works perfectly under Linux (with the calls to Update()), but
on Windows 7 the gauge seems to stop around the 20-25% mark, a while before
the function exits. So it moves as it should until it reaches ~25%, then the
gauge stops moving for no apparent reason but the function continues on just
fine and exits with the proper output.

In my attempt to find out the problem, I tried inserting a `print progress`
line just before updating the gauge inside the loop, thinking maybe the value
of `progress` wasn't what I thought it should be. To my big surprise, the
gauge now worked as it should, but the moment I remove that `print` it stops
working. I can also replace the print with a call to `time.sleep(0.001)`, but
even with such a short sleep the process still grinds to almost a halt, and if
I lower it even further the problem returns, so it's hardly very helpful.

I can't figure out what is going on or how to fix it, but I guess somehow
things move too fast under Windows so that `progress` doesn't get updated
properly after a while and just stays at a fixed value (~25). I have no idea
why that would be, however, it makes no sense to me. And of course, neither
`print` nor `sleep` are good solutions. Even if I print out "nothing", Windows
still opens another window for the non-existent output, which is annoying.

Let me know if you need further info or code.

**Edit:** Ok, here's a working application which (for me at least) has the
problem. It's still pretty long, but I tried to cut out everything not related
to the problem at hand.

It works on Linux, just like the complete app. Under Windows it either fails
or works depending on the value of `numwords` in the Go function. If I
increase its value to 1000000 (1 million) the problem goes away. I suspect
this may depend on the system, so if it works for you try to tweak the value
of `numwords`. It may also be because I changed it so it `Append()`s a static
text rather than calling a generator as it does in the original code.

Still, with the current value of `numwords` (100000) it does fail on Windows
for me.

    
    
    import wx
    
    class Wordlist(wx.TextCtrl):    
        def __init__(self, parent):
            super(Wordlist, self).__init__(parent,
                                           style=wx.TE_MULTILINE|wx.TE_READONLY)
            self.words = []
            self.SetValue("")
    
        def Get(self):
            return '\r\n'.join(self.words)
    
        def Refresh(self):
            self.SetValue(self.Get())
    
        def Append(self, value):
            if isinstance(value, list):
                value = '\r\n'.join(value)        
            self.words.append(unicode(value))
    
    class ProgressStatusBar(wx.StatusBar):
        def __init__(self, *args, **kwargs):
            super(ProgressStatusBar, self).__init__(*args, **kwargs)
    
            self._changed = False
    
            self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
            self.prog.Hide()
    
            self.SetFieldsCount(2)
            self.SetStatusWidths([-1, 150])
    
            self.Bind(wx.EVT_IDLE, lambda evt: self.__reposition())
            self.Bind(wx.EVT_SIZE, self.onsize)
    
        def __reposition(self): 
            if self._changed:
                lfield = self.GetFieldsCount() - 1
                rect = self.GetFieldRect(lfield)
                prog_pos = (rect.x + 2, rect.y + 2)
                self.prog.SetPosition(prog_pos)
                prog_size = (rect.width - 8, rect.height - 4)
                self.prog.SetSize(prog_size)
            self._changed = False
    
        def onsize(self, evt):
            self._changed = True
            self.__reposition()
            evt.Skip()
    
        def setprogress(self, val):
            if not self.prog.IsShown():
                self.showprogress(True)
    
            if val == self.prog.GetRange():
                self.prog.SetValue(0)
                self.showprogress(False)
            else:
                self.prog.SetValue(val)
    
        def showprogress(self, show=True):
            self.__reposition()
            self.prog.Show(show)
    
    class MainFrame(wx.Frame):
        def __init__(self, *args, **kwargs):
            super(MainFrame, self).__init__(*args, **kwargs)
    
            self.SetupControls()
    
            self.statbar = ProgressStatusBar(self)
            self.SetStatusBar(self.statbar)
    
            self.panel.Fit()
            self.SetInitialSize()
            self.SetupBindings()
    
        def SetupControls(self):
            self.panel = wx.Panel(self)
    
            self.gobtn = wx.Button(self.panel, label="Go")                               
            self.wrdlst = Wordlist(self.panel)
    
            wrap = wx.BoxSizer()
            wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
            wrap.Add(self.wrdlst, 0, wx.EXPAND|wx.ALL, 10)
            self.panel.SetSizer(wrap)
    
        def SetupBindings(self):
            self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
    
        def Go(self, event):       
            progress = 0
            self.statbar.setprogress(progress)
            self.Update()
    
            numwords = 100000
    
            for i in range(1, numwords + 1):
                progress = int(((float(i) / float(numwords)) * 100) - 1)
                self.wrdlst.Append("test " + str(i))
                self.statbar.setprogress(progress)
                self.Update()
    
            self.wrdlst.Refresh()
    
            progress = 100
            self.statbar.setprogress(progress)
    
    class App(wx.App):
        def __init__(self, *args, **kwargs):
            super(App, self).__init__(*args, **kwargs)
            framestyle = wx.MINIMIZE_BOX|wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU|\
                         wx.CLIP_CHILDREN
            self.frame = MainFrame(None, title="test", style=framestyle)
            self.SetTopWindow(self.frame)
            self.frame.Center()
            self.frame.Show()
    
    if __name__ == "__main__":
        app = App()
        app.MainLoop()
    

**Edit 2** : Below is an even simpler version of the code. I don't think I can
make it much smaller. It still has the problem for me. I can run it from
within IDLE, or directly by double clicking the .py file in Windows, either
way works the same.

I tried with various values of `numwords`. It seems the problem doesn't
actually go away as I first said, instead when I increase `numwords` the gauge
just reaches further and further before the `print` is called. At the current
value of 1.000.000 this shorter version reaches around 50%. In the longer
version above, a value of 1.000.000 reaches around 90%, a value of 100.000
reaches around 25%, and a value of 10.000 only reaches around 10%.

In the version below, once the `print` is called, the progress continues on
and reaches 99% even though the loop must have ended by then. In the original
version the call to `self.wrdlst.Refresh()`, which takes a few seconds when
numwords is high, must have caused the gauge to pause. So I think that what
happens is this: In the loop the gauge only reaches a certain point, when the
loop exits the function continues on while the gauge stays still, and when the
function exits the gauge continues on until it reaches 99%. Because a print
statement doesn't take a lot of time, the version below makes it seem like the
gauge moves smoothly from 0% to 99%, but the `print` suggests otherwise.

    
    
    import wx
    
    class MainFrame(wx.Frame):
        def __init__(self, *args, **kwargs):
            super(MainFrame, self).__init__(*args, **kwargs)        
            self.panel = wx.Panel(self)        
            self.gobtn = wx.Button(self.panel, label="Go")
            self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
            wrap = wx.BoxSizer()
            wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
            wrap.Add(self.prog, 0, wx.EXPAND|wx.ALL, 10)
            self.panel.SetSizer(wrap)
            self.panel.Fit()
            self.SetInitialSize()        
            self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
    
        def Go(self, event):        
            numwords = 1000000
            self.prog.SetValue(0)
            for i in range(1, numwords + 1):
                progress = int(((float(i) / float(numwords)) * 100) - 1)
                self.prog.SetValue(progress)
            print "Done"
    
    if __name__ == "__main__":
        app = wx.App()
        frame = MainFrame(None)
        frame.Show()
        app.MainLoop()
    


Answer: So, actually, **you are blocking the GUI thread** by your long running task.
It may and may not run fine on some platforms and/or computers.

    
    
    import wx
    from wx.lib.delayedresult import startWorker
    
    class MainFrame(wx.Frame):
        def __init__(self, *args, **kwargs):
            super(MainFrame, self).__init__(*args, **kwargs)        
            self.panel = wx.Panel(self)        
            self.gobtn = wx.Button(self.panel, label="Go")
            self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
            self.timer = wx.Timer(self)
    
            wrap = wx.BoxSizer()
            wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
            wrap.Add(self.prog, 0, wx.EXPAND|wx.ALL, 10)
            self.panel.SetSizer(wrap)
            self.panel.Fit()
            self.SetInitialSize()        
    
            self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
            self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
    
    
        def Go(self, event):
            # Start actual work in another thread and start timer which 
            # will periodically check the progress and draw it
            startWorker(self.GoDone, self.GoCompute)
            self.progress = 0
            self.timer.Start(100)
    
        def OnTimer(self, event):
            # Timer draws the progress
            self.prog.SetValue(self.progress)
    
        def GoCompute(self):
            # This method will run in another thread not blocking the GUI
            numwords = 10000000
            self.prog.SetValue(0)
            for i in range(1, numwords + 1):
                self.progress = int(((float(i) / float(numwords)) * 100) - 1)
    
        def GoDone(self, result):
            # This is called when GoCompute finishes
            self.prog.SetValue(100)
            self.timer.Stop()
            print "Done"
    
    if __name__ == "__main__":
        app = wx.App()
        frame = MainFrame(None)
        frame.Show()
        app.MainLoop()
    

Also notice that contrary your example:

  * Button goes back to unclicked state after clicked
  * You can move the window and it will not freeze

As a rule of thumb, every method which looks like this `def Something(self,
event)` should run just a few milliseconds.

**EDIT:** Another thing what I have observed on Windows 7. The gauge
**starts** to grow at the time you call `self.prog.SetValue()` and grows in
some time to specified value. It does not "jump" to that value, rather it
grows slowly to hit set value. It seems to be Windows 7 feature. I had to
switch off "Animate controls and element inside windows" in performance
options to get rid of this behavior.

