'How to avoid stopping the wx.Gauge progress with python when the program takes time?

My program uses a recursive function that takes a few seconds. This function is used several times in a loop. I want to show the progress of the loop with a gauge. But after a few passes through the loop, the gauge freezes and the program seems to stop working: "don't answer" appears in the title while the program is really running. Once the main loop is finished, the gauge fills up to 100% at once.

import wx


class Mywin(wx.Frame):

    def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title=title, size=(300, 200))
        self.InitUI()

    def InitUI(self):
        self.count = 0
        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)

        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)

        self.gauge = wx.Gauge(pnl, range=20, size=(250, 25), style=wx.GA_HORIZONTAL)
        self.btn1 = wx.Button(pnl, label="Start")
        self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn1)

        hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
        hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10)

        vbox.Add((0, 30))
        vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
        vbox.Add((0, 20))
        vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
        pnl.SetSizer(vbox)

        self.SetSize((300, 200))
        self.Centre()
        self.Show(True)

    def Recursive(self, param1, param2):
        for i in range(1000):  # complex work simulated by for loop in my recursive function
            print(i)
        # list1 is modified in my function
        return

    def OnStart(self, e):
        nb_elements = 150
        for n in range(nb_elements):
            list1 = []
            list2 = []
            self.Recursive(list1, list2)
            self.gauge.SetValue(int((n/nb_elements)*100))


ex = wx.App()
Mywin(None, 'wx.Gauge')
ex.MainLoop()


Solution 1:[1]

First be careful with that range value on the gauge.
Use wx.GetApp().Yield() to momentarily pass control back to the main loop, so the update to the gauge can occur.
If you don't want to unnecessarily pass back control, moderate the yields by only yielding occasionally.
Here I do that with divmod.

import wx
import time

class Mywin(wx.Frame):

    def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title=title, size=(300, 200))
        self.InitUI()

    def InitUI(self):
        self.count = 0
        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)

        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)

        self.gauge = wx.Gauge(pnl, range=1000, size=(250, 25), style=wx.GA_HORIZONTAL)
        self.btn1 = wx.Button(pnl, label="Start")
        self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn1)

        hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
        hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10)

        vbox.Add((0, 30))
        vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
        vbox.Add((0, 20))
        vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
        pnl.SetSizer(vbox)

        self.SetSize((300, 200))
        self.Centre()
        self.Show(True)

    def Recursive(self, param1, param2):
        for i in range(1000):  # complex work simulated by for loop in my recursive function
            time.sleep(0.001)
            quo,rem = divmod(i,50)
            if rem == 0:
                self.gauge.SetValue(i)
                wx.GetApp().Yield()
        # list1 is modified in my function
        return

    def OnStart(self, e):
        nb_elements = 10
        for n in range(nb_elements):
            list1 = []
            list2 = []
            self.Recursive(list1, list2)
        print("Finished")

ex = wx.App()
Mywin(None, 'wx.Gauge')
ex.MainLoop()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rolf of Saxony