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
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
Related
import wx
import json
import queue
from collections import namedtuple
import threading
class MyDialog(wx.Frame):
def __init__(self, parent, title):
self.no_resize = wx.DEFAULT_FRAME_STYLE & ~ (wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)
wx.Frame.__init__(self, parent, title=title, size=(500, 450),style = self.no_resize)
self.panel = wx.Panel(self, size=(250, 270))
self.emp_selection = wx.ComboBox(self.panel, -1, pos=(40, 50), size=(200,100))
self.start_read_thread()
#code to load other GUI components
self.Centre()
self.Show(True)
def read_employees(self, read_file):
list_of_emails = queue.Queue()
with open(read_file) as f_obj:
employees = json.load(f_obj)
list_of_emails = [empEmail for empEmail in employees.keys()]
wx.CallAfter(self.emp_selection.Append, list_of_emails)
def start_read_thread(self):
filename = 'employee.json'
empThread = threading.Thread(target=self.read_employees, args=(filename,))
empThread.start()
I have a GUI application that loads a combobox, and starts a thread to read some data and load it into the combobox. I don't want the read to block, so that the other GUI components can load.
After calling thread.start() when is it appropriate to call thread.join()? From my understanding, join() waits for the thread to complete, I don't want that, I want to start the thread and allow all the other components to load. Is it bad practice not to call join()
It is perfectly ok to not call join() if you don't need its functionality to wait for the thread to finish.
By the way: In the main thread of a GUI application (the thread automatically created at start in which all GUI things happen) it is bad practice to call any function that sleeps or waits for anything as the GUI doesn't react (freezes) while waiting.
This is a follow-up to my previous question, wxPython popup from calling imported function.
I was able to figure out a way to create a wxPython dialog window to determine if a thread my GUI called should continue its execution. What I did was simply create the dialog window in the thread itself. However, once I made the thread exit by clicking "no" in this popup, the close button in my GUI became unresponsive, and I couldn't close the GUI itself. Once again, your help is much appreciated!
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.closeButton = wx.Button(panel, wx.ID_OK, "Run", pos=(250, 300))
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#close button event
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closeButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
def OnClose(self, event):
self.Destroy()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
#this is the code for the Yes/No prompt and what introduced the buggy behavior
if(os.path.isdir(mirror_source_path) or os.path.isdir(mirror_dest_path)):
app = wx.App(False)
dlg = wx.MessageDialog(None, "Something bad happened. Continue?","Warning",wx.YES_NO | wx.ICON_QUESTION)
retCode = dlg.ShowModal()
if (retCode == wx.ID_YES):
print "Continuing."
else:
print "Aborted."
return None
sys.exit(0)
dlg.Destroy()
#more function code...
print "Success!"
You cannot have 2 wxPython main loops running at the same time. That will cause some pretty screwy behavior. Personally I think I would split this code into 2 threads. When the first one finishes, it sends a message using wx.CallAfter and pubsub or wx.PostEvent and at that time you can do your if statement in a wxPython handler.
If you continue, then you spin up a second thread with the rest of the function code.
I have some text that goes somewhat continuously on the output (somewhat = every two seconds approx in the real application).
When just print into the terminal, everything is ok, no matter how long I keep the running loop.
However, when directing the print to a wx frame, the print action itself goes well, no problem here, but if I keep the running loop more than 10-20 cycles, I am no longer able to close the window normally (mouse or Ctrl+F4 in this example, but the same if I construct an explicit Exit event menu). The action in thread stops, but the application hangs -- it can only be closed by the OS (Windows in my case) with a "force close / wait for the program to respond" dialog. Though, it can be closed immediately with Ctrl+C when the terminal window is on focus. It looks like something fills up with too much text and locks the closing process.
Happens with either WriteText or AppendText on a multiline wxTextCtrl window, or with the SetLabel as in the test code below.
In this test, if I close the wx window almost immediately after launch, it closes well. If I keep running the loop longer (or by comment out the two time.sleep() lines), then the hang will succeed whenever trying to close the window.
What am I missing here ? (Is there something I have to flush ?)
edit Hmm, the issue could be related to "threadsafe method", as described briefly here. Will do some tests in that direction, have to find out the proper usage of each of the wx.CallAfter, wx.CallLater or wx.PostEvent.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
import time
import threading
class TestFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.sometext = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.thread = None
self.alive = threading.Event()
self.__set_properties()
self.__do_layout()
self.__attach_events()
self.StartThread()
def __set_properties(self):
self.SetTitle("test")
def __do_layout(self):
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.sometext, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0)
self.SetSizer(sizer)
sizer.Fit(self)
self.Layout()
def __attach_events(self):
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnClose(self, event):
""" stop & close on system window close """
self.StopThread()
self.Destroy()
def StartThread(self):
""" start the thread """
self.thread = threading.Thread(target=self.TestThread)
self.thread.setDaemon(1)
self.alive.set()
self.thread.start()
def StopThread(self):
""" stop the thread, wait util it is finished """
if self.thread is not None:
self.alive.clear()
self.thread.join()
self.thread = None
def TestThread(self):
""" main thread """
while self.alive.isSet():
self.sometext.SetLabel("Hello")
time.sleep(0.5)
self.sometext.SetLabel("Python")
time.sleep(0.5)
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TestFrame(None, -1, "")
self.SetTopWindow(frame)
frame.Show(1)
return 1
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
I've changed every SetLabal with AppendtText. I'm don't know why, but SetLabel function don't work correctly for me.
After a few seconds the application will stop working. This is my error output:
(python:14099): Pango-CRITICAL **: pango_layout_get_iter: assertion 'PANGO_IS_LAYOUT (layout)' failed
Segmentation fault
After that I used CallAfter function http://wiki.wxpython.org/CallAfter the application started correctly working for me. My version of TestThread:
def TestThread(self):
""" main thread """
while self.alive.isSet():
wx.CallAfter(self.sometext.AppendText, "Hello")
time.sleep(0.5)
wx.CallAfter(self.sometext.AppendText, "Python")
time.sleep(0.5)
wx.CallAfter(self.sometext.Clear)
I hope this help you.
I have a problem
My application on close has to logout from web application. It's take some time. I want to inform user about it with " logging out" information
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ico, gtk.BUTTONS_NONE, txt)
md.showall()
self.send('users/logout.json', {}, False, False)
gtk.main_quit()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
app = Belt()
app.main()
When I try to show dialog in destroy method only window does appear, without icon and text.
I want to, that this dialog have no confirm button, just the information, and dialog have to be destroy with all app.
Any ideas?
Sorry for my poor English
Basically, GTK has to have the chance to work through the event queue all the time. If some other processing takes a long time and the event queue is not processed in the meantime, your application will become unresponsive. This is usually not what you want, because it may result in your windows not being updated, remaining grey, having strange artefacts, or other kinds of visible glitches. It may even cause your window system to grey the window out and offer to kill the presumably frozen application.
The solutution is to make sure the event queue is being processed. There are two primary ways to do this. If the part that takes long consists of many incremental steps, you can periodically process the queue yourself:
def this_takes_really_long():
for _ in range(10000):
do_some_more_work()
while gtk.events_pending():
gtk.main_iteration()
In the general case, you'll have to resort to some kind of asynchronous processing. The typical way is to put the blocking part into its own thread, and then signal back to the main thread (which sits in the main loop) via idle callbacks. In your code, it might look something like this:
from threading import Thread
import gtk, gobject
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
self.show_all()
self.isLogged = True
self.iniError = False
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 0, gtk.BUTTONS_NONE, "Text")
md.show_all()
Thread(target=self._this_takes_very_long).start()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
def _this_takes_very_long(self):
self.send('users/logout.json', {}, False, False)
gobject.idle_add(gtk.main_quit)
if __name__ == "__main__":
app = Belt()
app.main()
In this program I get an error when I use a while True loop in the thread. Without the loop I get no error. Of course in the real program I don't update a label continuously. Any idea what I'm doing wrong?
This is the program:
import wx
import thread
class Example(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self,parent)
self.InitUI()
def InitUI(self):
self.SetSize((250, 200))
self.Show(True)
self.text = wx.StaticText(self, label='',pos=(20,30))
thread.start_new_thread(self.watch,(self,None))
def watch(self,dummy,e):
while True:
self.text.SetLabel('Closed')
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
And this is the error:
Pango:ERROR:/build/pango1.0-LVHqeM/pango1.0-1.30.0/./pango/pango- layout.c:3801:pango_layout_check_lines: assertion failed: (!layout->log_attrs) Aborted
Any suggestions as to what I'm doing wrong? I'm (obviously) new to threading.
I am not exactly sure if that is what causes you problem, but... You should not interact with the GUI from another thread. You should use wx.CallAfter(). I would consider adding sleep inside the loop also.
wx.CallAfter() documentation says:
Call the specified function after the current and pending event handlers have been completed. This is also good for making GUI method calls from non-GUI threads. Any extra positional or keyword args are passed on to the callable when it is called.
Updated code would than be:
import wx
import thread
import time
class Example(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self,parent)
self.InitUI()
def InitUI(self):
self.SetSize((250, 200))
self.Show(True)
self.text = wx.StaticText(self, label='',pos=(20,30))
thread.start_new_thread(self.watch,(self,None))
def watch(self,dummy,e):
while True:
time.sleep(0.1)
wx.CallAfter(self.text.SetLabel, 'Closed')
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
Maybe you can also consider using wx.Timer.
BTW: Your code runs OK on my PC with Windows 7 and wxPython 2.8.
In addition to the no updates from background threads rule, I've found that in similar situations (high frequency update of UI objects) that it really helps to only update the value if it has changed from what is already displayed. That can greatly reduce the load on the application because if the value does not change then there will be no need for sending and processing paint events, moving pixels to the screen, etc. So in this example I would add a new method that is called via CallAfter that compares the current value in the widget with the requested value, and only calls SetLabel if they are different.