#!/usr/bin/env python3
import wx
import threading
import time
from pubsub import pub
# an attempt to combine the message handler and the wx.CallAfter call with a decorator
def pubsubdecorator(fn):
# check the arguments of the function passed
print(f"fn: {fn.__code__.co_varnames}")
# wrapper #1
# doesn't work - gives some error about unknown optional argumenr "percent"
#def wrapper(*args, **kwargs):
# wx.CallAfter(fn, *args, **kwargs)
# wrapper #2
# works, but completely impractical
def wrapper(self, percent):
wx.CallAfter(fn, self, percent)
# check the arguments of the function returned
print(f"wrapper: {wrapper.__code__.co_varnames}")
return wrapper
class MainWindow(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, wx.ID_ANY, title)
self.label = wx.StaticText(self)
self.button = wx.Button(self, wx.ID_ANY, "Start")
self.windowSizer = wx.BoxSizer(wx.VERTICAL)
self.windowSizer.Add(self.label)
self.windowSizer.Add(self.button)
self.button.Bind(wx.EVT_BUTTON, self.OnBtnStart)
#pub.subscribe(self.OnProgressNaive, "progress") # gives a runtime error sooner or later
pub.subscribe(self.OnProgressTwoSteps, "progress") # OK, but takes a two step approach with explicit wx.CallAfter
self.SetSizer(self.windowSizer)
def OnBtnStart(self, evt):
# the thread function - sends a message
# with a number in an infinite loop
def thread_fn():
pc = 0
while True:
pc = (pc + 1) % 100
pub.sendMessage("progress", percent = pc)
time.sleep(0.1)
t = threading.Thread(target = thread_fn)
t.start()
#@pubsubdecorator
def OnProgressNaive(self, percent):
self.label.SetLabel(f"{percent:03d}% thread: {threading.get_ident()}")
def OnProgressTwoSteps(self, percent):
wx.CallAfter(self.OnProgressTwoSteps_, percent)
def OnProgressTwoSteps_(self, percent):
self.label.SetLabel(f"{percent:03d}% thread: {threading.get_ident()}")
app = wx.App()
frame = MainWindow(f"Thread {threading.get_ident()}")
frame.Show(1)
app.MainLoop()
I use pubsub to update GUI from another thread. As I remember, on Windows directly manipulating GUI elements from within a pubsub message handler (see the OnProgressNaive function above) always worked, causing no visible problems - although that may still be wrong, working only by accident; on Linux, however, sooner or later it causes some Pango assertions, followed by the program termination due to an address boundary error. As I understand, that is because the message handler is still executed outside of the main GUI thread (if you run the code example above, and the thread id that appears inside the window after the “Start” button is pressed is different from the thread id in the window title, it will eventually crash; if they are same, it will not). A solution I found some long time ago is to handle the message in two steps (see the OnProgressTwoSteps functions above) - one responds to the message and calls the other through wx.CallAfter in the GUI thread. That works, but is rather cumbersome and error-prone.
Is there a way to somehow automate that wx.CallAfter call? I was thinking about a decorator, but pubsub seems to rely on the argument names, not kept by the standard *args, **kwargs approach (see the wrapper #1 fragment); it only works if I explicitly use the same exact parameter names as in the message handler (see the wrapper #2 fragment), but that “solution” is rather useless.
Or is there a better way to update the GUI from another thread, that is different altogether?