Reputation: 539
I have this large program, one of whose parts includes the click of a button (this GUI was made in wxPython). Basically, the button is a timer, and when you click on it, the text of the button switches based on the time left, e.g. 5 Seconds, 4 Seconds, 3 Seconds... For some reason, though, when I click on the button in the GUI, the entire program freezes and I have to force quit the app. (Everything else in the app works fine and responds, until I click on this one button). Here is the event handler that is called when the button is clicked.
Thanks in advance!
def TossUpTimer(self, event):
while self.tossuptimer > -1:
while self.tossuptimer > 0:
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
time.sleep(1)
self.tossuptimer -= 1
if self.tossuptimer == 0:
self.tossupbutton.SetLabel("Time is Up!")
time.sleep(1)
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
self.tossuptimer = 5
Upvotes: 0
Views: 2102
Reputation: 365647
Why your GUI app freezes is an attempt to explain the basic idea, and all of the different options for dealing with it.
But here's a short version:
The whole point of a GUI app is that it's running an event loop, responding to events as they come in from the user or the OS. You write handlers for particular events. Until your handler returns, the GUI can't process the next event. That means that the entire app is frozen.
You need to return from your handler as quickly as possible. What you can't do, ever, is time.sleep(1)
or a long while
loop or anything of the kind directly in a handler.
There are various ways around this. The two most common are to ask the event loop to run some of your code later, or to move it to a background thread. (wx
adds a third way, SafeYield
and friends, which is appropriate in some cases, but probably not what you want here.)
For the first alternative, the idea is that, instead of looping around sleep
, you write a function that handles a single iteration of the loop, and schedule a timer to fire that function once/second. This is known as "turning your control flow inside out", which can be a bit hard to get your head around.
Unfortunately, I'm not 100% sure what you're trying to do, because, as Jon Clements points out, your logic doesn't make sense even in a sequential program. But I'll write something simple: when you click the button, it will count down once/second for 5 seconds, then stop counting.
def OnTossUpTimer(self, event):
self.tossuptimer -= 1
if self.tossuptimer > 0:
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
else:
self.tossupbutton.SetLabel("Time is Up!")
self.tossuptimer_timer.Stop()
def TossUpTimer(self, event):
self.tossuptimer = 5
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
self.tossuptimer_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTossUpTimer, self.tossuptimer_timer)
self.tossuptimer_timer.Start(1000, False)
See Using wx.Timers and the Timer
docs for more details.
The threaded version is simpler in some ways, more complicated in others. You can leave your sequential logic alone, but you can't talk to any GUI widgets except indirectly, by calling PostEvent
. So:
class LabelUpdateEvent(wx.PyEvent):
EVT_LABEL_UPDATE_ID = wx.NewId()
def __init__(self, data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_LABEL_UPDATE_ID)
self.data = data
def SetLabelOnMainThread(self, value):
wx.PostEvent(self.SetLabelForReal, ResultEvent(value))
def SetLabelForReal(self, event):
self.tossupbutton.SetLabel(event.data)
def TossUpTimerThread(self):
self.tossuptimer = 5
while self.tossuptimer > 0:
self.SetLabelOnMainThread(str(self.tossuptimer) + "Seconds")
time.sleep(1)
self.tossuptimer -= 1
self.SetLabelOnMainThread(str(self.tossuptimer) + "Time is Up!")
def TossUpTimer(self, event):
threading.Thread(target=self.TossUpTimerThread).start()
If you're doing a lot of this, you probably want to write more generic "on-main-thread" functions, instead of all this boilerplate for each function. (You can, in fact, write a single completely-generic function, which wxPython
really ought to come with but doesn't.)
Upvotes: 2