How to update UI from worker threads?

I’m trying to update the 5 cells in the second row of the ListCtrl every 0.5 seconds from the worker thread, but none of the 5 cells gets updated until 5*0.5 seconds later.

I googled for hours and was surprised that there’s no (obvious) canonical solution for this problem. On Android, runOnUiThread is a no-brainer.

I would like to ask what’s the right way to do it, or where’s the right place to look for solutions.

Many thanks.

import wx
from wx.lib.newevent import NewEvent
import subprocess
import threading

MyEvent, MY_EVENT = NewEvent()

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None)
        self.panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.list_ctrl = wx.ListCtrl(self.panel, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        sizer.Add(self.list_ctrl, 1, wx.EXPAND)
        self.btn = wx.Button(self.panel, wx.ID_ANY, "button")
        self.btn.Bind(wx.EVT_BUTTON, self.on_btn)
        sizer.Add(self.btn)
        self.panel.SetSizer(sizer)
        self.Layout()

        self.Bind(MY_EVENT, self.on_my_event)

    def add_data(self):
        for i in range(5):
            self.list_ctrl.AppendColumn(f"col{i}")

        for i in range(3):
            self.list_ctrl.Append([f"init{i}-{j}" for j in range(5)])

    def on_btn(self):
        threading.Thread(target=self._do_on_btn).run()

    def _do_on_btn(self):
        # run on another thread
        for i in range(5):
            subprocess.run(["sleep", "0.5"])
            wx.PostEvent(self, MyEvent(i=i))

    def on_my_event(self, evt):
        self.list_ctrl.SetItem(1, evt.i, f"new1-{evt.i}")
        self.list_ctrl.Refresh()
        self.list_ctrl.Update()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame()
        self.frame.add_data()
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

def main():
    app = MyApp()
    app.MainLoop()


main()

Hi Champignoom,

Try:

threading.Thread(target=self._do_on_btn).start()

instead of:

threading.Thread(target=self._do_on_btn).run()

Edit: the on_btn() method needs a second parameter for the event, even though it is not used.

Aha! Thanks a lot!