RMPR
RMPR

Reputation: 3531

How to update the state of a Toggle Button after process completion?

I want to execute a task when a Toggle Button is clicked(on) and toggle it off after the task is completed, I execute the task in a new process because I don't want it to block the UI, event.GetEventObject().SetValue(False) seems to update correctly the value of the Toggle but it doesn't reflect on the UI.

from  multiprocessing import Process
import time
import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.toggle_button = wx.ToggleButton(self, wx.ID_ANY, "OK")
        control = Control()
        self.Bind(wx.EVT_TOGGLEBUTTON, control.action, self.toggle_button)
        self.SetTitle("Update UI with a process")
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.toggle_button, 0, 0, 0)
        self.SetSizer(sizer)
        self.Layout()

class Control():
    def update_toggle(self, duration, event):
        time.sleep(duration)
        event.GetEventObject().SetValue(False)
        print("Toggled")

    def action(self, event):
        if event.GetEventObject().GetValue():
            self.update_toggle_process = Process(target = self.update_toggle,
                                                args=(5, event,))
            self.update_toggle_process.start()
        else:
            print("UnToggled")

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

A call to event.GetEventObject().Update() or event.GetEventObject().Refresh() after changing the value of the Toggle doesn't seem to change anything.

EDIT:

If I use a Thread instead of Process, it works correctly, but I chose Process over Thread because I want the ability to kill it cleanly whenever I need to.

Python version: 3.7

WxPython version: 4.0.1

Upvotes: 3

Views: 276

Answers (1)

Petr Blahos
Petr Blahos

Reputation: 2433

You must remember, that the your update_toggle runs in a new process. Simply put, it has got a copy of data, so if you call event.GetEventObject().SetValue(False) it happens in the new process, and the original one with the Window and Button won't know.

You must somehow pass a message from the new process to the original. I would suggest that the first thing you try is:

        self.update_toggle_process.start()
        self.update_toggle_process.join()
        print("the process has finished")

This will block, but at least you will see if the "update_toggle_process" has finished and if this approach works. After that, there are a few possibilities:

  • Set up a times and periodically call self.update_toggle_process.is_alive()
  • Create a new thread, call the update_toggle_process.start() from it, and also join(). When finished, tell the main thread to toggle the button (remember that you may only manipulate the UI from the main thread in wx)
  • Maybe you do not need a new process, a thread will be enough
  • Look at the multiprocessing IPC

Upvotes: 3

Related Questions