Search code examples
user-interfacetimewxpythonblocking

wxpython using time.sleep() without blocking complete GUI


I want to change the text displayed in my GUI at specific time intervals. After a lot of approaches, I find that, specifically to my requirements, I must use time.sleep() instead of wx.Timer, but time.sleep() freeze the complete GUI. Here's an example of my code:

import wx
import time

DWELL_TIMES = [1, 2, 1, 3]
SCREEN_STRINGS = ['nudge nudge', 'wink wink', 'I bet she does', 'say no more!']

class DM1(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        panel = wx.Panel(self)
        text_display = wx.StaticText(panel, pos = (400, 150))

        for dwell_time in DWELL_TIMES:
            text_display.SetLabel(SCREEN_STRINGS[dwell_time])
            time.sleep(float(DWELL_TIMES[dwell_time]))


app = wx.App()
DM1Frame = DM1(None, size = (800, 600))
DM1Frame.Center()
DM1Frame.Show()
app.MainLoop()

Does somebody know why this happen, and how to make the GUI doesn't block? I guess that Threading could help me, doesn't it? If it does, which is the correct way to put threads inside this code? Is there an alternative to Threading?

Thanks a lot!


Solution

  • As mentioned by others, wx.CallAfter and wx.CallLater are your friends. Study them and learn them. Here is a complete, working example using wx.CallLater. I included other refactoring as I saw fit.

    import wx
    
    DATA = [
        (1, 'nudge nudge'),
        (2, 'wink wink'),
        (1, 'I bet she does'),
        (3, 'say no more!'),
    ]
    
    class Frame(wx.Frame):
        def __init__(self):
            super(Frame, self).__init__(None)
            panel = wx.Panel(self)
            self.text = wx.StaticText(panel)
            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.AddStretchSpacer(1)
            sizer.Add(self.text, 0, wx.ALIGN_CENTER)
            sizer.AddStretchSpacer(1)
            panel.SetSizer(sizer)
            self.index = 0
            self.update()
        def update(self):
            duration, label = DATA[self.index]
            self.text.SetLabel(label)
            self.index = (self.index + 1) % len(DATA)
            wx.CallLater(int(duration * 1000), self.update)
    
    if __name__ == '__main__':
        app = wx.App(None)
        frame = Frame()
        frame.SetTitle('Example')
        frame.SetSize((400, 300))
        frame.Center()
        frame.Show()
        app.MainLoop()