wxPython thread that does not block the GUI? - multithreading

I am new to wxPython and threading. I am trying to move some code into a thread so that it no longer blocks my GUI while executing, and I can push another button to kill the thread. Having done this, however, the code still blocks my GUI. How can I write this code so that my thread does not block the GUI?
import wx, sys
import threading
import time
class mywxframe(wx.Frame):
global sizer2, WorkerThread
def __init__(self):
wx.Frame.__init__(self, None)
pnl = wx.Panel(self)
szr = wx.BoxSizer(wx.VERTICAL)
pnl.SetSizer(szr)
szr2 = sizer2(self, pnl)
szr.Add(szr2, 1, wx.ALL | wx.EXPAND, 10)
log = wx.TextCtrl(pnl, -1,style=wx.TE_MULTILINE, size=(300,-1))
szr.Add(log, 1, wx.ALL, 10)
btn3 = wx.Button(pnl, -1, "Stop")
btn3.Bind(wx.EVT_BUTTON, self.OnStop)
szr.Add(btn3, 0, wx.ALL, 10)
redir = RedirectText(log)
sys.stdout=redir
szr.Fit(self)
self.Show()
def sizer2(self, panel):
sizer = wx.BoxSizer(wx.HORIZONTAL)
btn2 = wx.Button(panel, -1, "OK",)
self.Bind(wx.EVT_BUTTON, self.OnStart, btn2)
sizer.Add(btn2, 0, wx.ALL, 10)
return sizer
def WorkerThread(self):
self.dead = False
while (not self.dead):
for i in range(0,10):
print "printing", i
time.sleep(3)
def OnStart(self, event):
our_thread = threading.Thread(target=WorkerThread(self))
our_thread.start()
def OnStop(self, event):
self.dead = True
class RedirectText(object):
def __init__(self, aWxTextCtrl):
self.out=aWxTextCtrl
def write(self, string):
self.out.WriteText(string)
app = wx.PySimpleApp()
frm = mywxframe()
app.MainLoop()

You've got a couple problems there.
First of all, if you're going to define sizer2 and WorkerThread as methods, you should use them as methods, not globals.
Next, when you were creating the thread you were calling WorkerThread and passing its return value (None) to the thread. This is where you were blocking the GUI.
def OnStart(self, event):
our_thread = threading.Thread(target=WorkerThread(self))
our_thread.start()
Instead you should be passing a callable object (WorkerThread without the ()) to the thread so it will then be able to call it in the context of the new thread.
Finally, since self.out.WriteText manipulates a UI object it must be called in the context of the GUI thread. Using wx.CallAfter is an easy way to do that.
Here is your example updated with these changes:
import wx, sys
import threading
import time
print wx.version()
class mywxframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
pnl = wx.Panel(self)
szr = wx.BoxSizer(wx.VERTICAL)
pnl.SetSizer(szr)
szr2 = self.sizer2(pnl)
szr.Add(szr2, 1, wx.ALL | wx.EXPAND, 10)
log = wx.TextCtrl(pnl, -1,style=wx.TE_MULTILINE, size=(300,-1))
szr.Add(log, 1, wx.ALL, 10)
btn3 = wx.Button(pnl, -1, "Stop")
btn3.Bind(wx.EVT_BUTTON, self.OnStop)
szr.Add(btn3, 0, wx.ALL, 10)
redir = RedirectText(log)
sys.stdout=redir
szr.Fit(self)
self.Show()
def sizer2(self, panel):
sizer = wx.BoxSizer(wx.HORIZONTAL)
btn2 = wx.Button(panel, -1, "OK",)
self.Bind(wx.EVT_BUTTON, self.OnStart, btn2)
sizer.Add(btn2, 0, wx.ALL, 10)
return sizer
def WorkerThread(self):
self.dead = False
while (not self.dead):
for i in range(0,10):
print "printing", i
if self.dead:
break
time.sleep(3)
print 'thread exiting'
def OnStart(self, event):
our_thread = threading.Thread(target=self.WorkerThread)
our_thread.start()
def OnStop(self, event):
self.dead = True
class RedirectText(object):
def __init__(self, aWxTextCtrl):
self.out=aWxTextCtrl
def write(self, string):
wx.CallAfter(self.out.WriteText, string)
app = wx.App()
frm = mywxframe()
app.MainLoop()

Related

Resetting a wxPython app / destroying everything and starting from initial frame

I've read other answers here, but they all seem to deal with just closing an app altogether, making sure all processes and frames and such are destroyed in the process. What I want to do is a little different.
My app consists of three frames (StartFrame, ParaFrame, ResultFrame) as well as a custom class for storing and manipulating data. The basics are: StartFrame is just some text and a "start" button. "Start" creates an instance of ParaFrame, hides the StartFrame, and shows the ParaFrame instance. Paraframe has a bunch of widgets for the user to select a file and how to analyze it. When all the fields are entered, an "Analyze" button pops up, which (when clicked) instantiates the custom DataHandler class, sets its parameters according to the user selections, calls a DataHandler method which analyzes the data, hides itself, and instantiates+shows the ResultsFrame. Not surprisingly, the ResultsFrame shows the results of the analysis.
I want to add a "Start Over" control which will destroy everything (all the frames, panels, the DataHandler instance, etc.) and display a fresh StartFrame instance, or otherwise destroy everything except the already-instantiated-but-hidden StartFrame, showing that frame again, but I'm at a loss.
A simplified example follows:
import wx
class StartFrame(wx.Frame):
"""App start frame"""
FRAME_MIN_SIZE = (900,600)
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="LOD Calculator", size=wx.Size(900,600),
style=wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL)
self.startpnl = wx.Panel(self)
self.startvsizer=wx.BoxSizer(wx.VERTICAL)
self.startbtnsizer=wx.BoxSizer(wx.HORIZONTAL)
self.btn = wx.Button(self.startpnl, wx.ID_ANY, "Start Analysis",\
size = (200,60))
self.btn.Bind(wx.EVT_BUTTON, self._OnStart)
self.startbtnsizer.AddStretchSpacer()
self.startbtnsizer.Add(self.btn, 0, wx.CENTER)
self.startbtnsizer.AddStretchSpacer()
self.startvsizer.AddStretchSpacer()
self.startvsizer.Add(self.startbtnsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.startvsizer.AddStretchSpacer()
self.startpnl.SetSizerAndFit(self.startvsizer)
def _OnStart(self,event):
self.frm2 = ParaFrame(None)
self.Hide()
self.frm2.Show()
class ParaFrame(wx.Frame):
"""Data load frame"""
FRAME_MIN_SIZE = (950,800)
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="LOD Calculator", size=wx.Size(950,800),
style=wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL)
self.mainpnl = wx.Panel(self)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.analyzebtn = wx.Button(self.mainpnl, wx.ID_ANY, u"Analyze",\
wx.DefaultPosition, wx.DefaultSize, 0)
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.analyzebtn, 0, wx.ALL, 10)
self.vsizer.AddStretchSpacer()
self.mainsizer.AddStretchSpacer()
self.mainsizer.Add(self.vsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.mainsizer.AddStretchSpacer()
self.mainpnl.SetSizerAndFit(self.mainsizer)
self.analyzebtn.Bind(wx.EVT_BUTTON, self._analyze)
def _analyze(self, event):
dhandler = DataHandler(self)
dhandler.data = "a bunch"
dhandler.data2 = " of data"
dhandler.data3 = " goes here"
dhandler._analyzeData()
self.resfrm = ResultFrame(self, dhandler.data, dhandler.data2)
self.Hide()
self.resfrm.Show()
class DataHandler:
def __init__(self,parent):
self.data = ''
self.data2 = ''
self.data3 = ''
def _analyzeData(self):
anastr = self.data + self.data2 + " gets analyzed"
print(anastr)
class ResultFrame(wx.Frame):
def __init__(self, parent, targets, dftables):
super(ResultFrame, self).__init__(parent, title="results", size=(1535,935))
self.targets = targets
self.dftables = dftables
self.InitUI()
self.Layout()
def InitUI(self):
self.mainpnl = wx.Panel(self)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.restartbtn = wx.Button(self.mainpnl, wx.ID_ANY, u"Start Over",\
wx.DefaultPosition, wx.DefaultSize, 0)
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.restartbtn, 0, wx.ALL, 10)
self.vsizer.AddStretchSpacer()
self.mainsizer.AddStretchSpacer()
self.mainsizer.Add(self.vsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.mainsizer.AddStretchSpacer()
self.mainpnl.SetSizerAndFit(self.mainsizer)
self.restartbtn.Bind(wx.EVT_BUTTON, self.OnRestart)
### this is where I try to destroy the ParaFrame instance and everything spawned by it, unsuccessfully
def OnRestart(self, event):
frm.frm2.Destroy()
frm.Show()
def main():
import wx.lib.mixins.inspection
app = wx.App()
frm = StartFrame(None)
frm.Show()
wx.lib.inspection.InspectionTool().Show(refreshTree=True)
app.MainLoop()
if __name__ == "__main__":
main()
As you can see, in the ResultFrame I have a "Start Over" button bound to a method in which I try to destroy the ParaFrame instance (and thus the DataHandler and ResultFrame instances spawned from it) by using frm.frm2.Destroy(), but get an error:
Traceback (most recent call last):
File "C:\Python\Scripts\stackexchange code.py", line 102, in OnRestart
frm.frm2.Destroy()
NameError: name 'frm' is not defined
If I change the line to just frm2.Destroy(), I get the same error, but stating name 'frm2' is not defined.
What am I doing wrong here, and how can I accomplish my goal? I'm still newish to OOP and quite new to wxPython, so any help is appreciate it. All I want is for that button to destroy everything and display the/an initial StartFrame again. Thank you :)
As long as you create each class without a parent, you can use self.Destroy() after creating the next frame in the chain, without it destroying any children. (Pass any data required as a parameter other than parent)
So your code, looks something like this:
import wx
class StartFrame(wx.Frame):
"""App start frame"""
FRAME_MIN_SIZE = (900,600)
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="Load Calculator", size=wx.Size(900,600))
self.startpnl = wx.Panel(self)
self.startvsizer=wx.BoxSizer(wx.VERTICAL)
self.startbtnsizer=wx.BoxSizer(wx.HORIZONTAL)
self.btn = wx.Button(self.startpnl, wx.ID_ANY, "Start Analysis",\
size = (200,60))
self.btn.Bind(wx.EVT_BUTTON, self._OnStart)
self.startbtnsizer.AddStretchSpacer()
self.startbtnsizer.Add(self.btn, 0, wx.CENTER)
self.startbtnsizer.AddStretchSpacer()
self.startvsizer.AddStretchSpacer()
self.startvsizer.Add(self.startbtnsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.startvsizer.AddStretchSpacer()
self.startpnl.SetSizerAndFit(self.startvsizer)
def _OnStart(self,event):
frm2 = ParaFrame(None)
frm2.Show()
self.Destroy()
class ParaFrame(wx.Frame):
"""Data load frame"""
FRAME_MIN_SIZE = (950,800)
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="Load Calculator", size=wx.Size(900,600))
self.mainpnl = wx.Panel(self)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.analyzebtn = wx.Button(self.mainpnl, wx.ID_ANY, u"Analyze",\
wx.DefaultPosition, wx.DefaultSize, 0)
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.analyzebtn, 0, wx.ALL, 10)
self.vsizer.AddStretchSpacer()
self.mainsizer.AddStretchSpacer()
self.mainsizer.Add(self.vsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.mainsizer.AddStretchSpacer()
self.mainpnl.SetSizerAndFit(self.mainsizer)
self.analyzebtn.Bind(wx.EVT_BUTTON, self._analyze)
def _analyze(self, event):
dhandler = DataHandler(self)
dhandler.data = "a bunch"
dhandler.data2 = " of data"
dhandler.data3 = " goes here"
dhandler._analyzeData()
resfrm = ResultFrame(None, dhandler.data, dhandler.data2)
resfrm.Show()
self.Destroy()
class DataHandler:
def __init__(self,parent):
self.data = ''
self.data2 = ''
self.data3 = ''
def _analyzeData(self):
anastr = self.data + self.data2 + " gets analyzed"
print(anastr)
class ResultFrame(wx.Frame):
def __init__(self, parent, targets, dftables):
super(ResultFrame, self).__init__(parent, title="Results", size=(900,600))
self.targets = targets
self.dftables = dftables
self.InitUI()
self.Layout()
def InitUI(self):
self.mainpnl = wx.Panel(self)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.restartbtn = wx.Button(self.mainpnl, wx.ID_ANY, u"Start Over",\
wx.DefaultPosition, wx.DefaultSize, 0)
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.restartbtn, 0, wx.ALL, 10)
self.vsizer.AddStretchSpacer()
self.mainsizer.AddStretchSpacer()
self.mainsizer.Add(self.vsizer, wx.SizerFlags().Expand().Border(wx.ALL, 25))
self.mainsizer.AddStretchSpacer()
self.mainpnl.SetSizerAndFit(self.mainsizer)
self.restartbtn.Bind(wx.EVT_BUTTON, self.OnRestart)
### this is where I try to destroy the ParaFrame instance and everything spawned by it, unsuccessfully
def OnRestart(self, event):
frm = StartFrame(None)
frm.Show()
self.Destroy()
def main():
app = wx.App()
frm = StartFrame(None)
frm.Show()
app.MainLoop()
if __name__ == "__main__":
main()

pub.sendMessage is not working in my code

I am getting error after running code with below class. Clueless right now, please help
from __future__ import division
from threading import Thread
from pubsub import pub
import os
import glob
########################################################################
class process_csv_thread(Thread):
"""Test Worker Thread Class."""
An answer, just for the record. (Bare in mind, I'm no Thread wizard.)
If the Thread is not stopped and you don't unsubscribe from the messages, there is the possibility of messages being received from a still running Thread, which will attempt to update a widget that no longer exists.
I've adapted your code to include a Stop method for the Thread.
I also join the thread to ensure that it has terminated, before destroying the GUI.
Obviously, this Thread is simply spinning, you would need to ensure that a working Thread has a means of checking the running variable.
Thread code process_csv_class.py
from __future__ import division
from threading import Thread
from pubsub import pub
import os
import glob
import time
########################################################################
class process_csv_thread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self,csv_path,rnti_value):
"""Init Worker Thread Class."""
self.csv_path = csv_path
self.rnti_value = rnti_value
Thread.__init__(self)
self.running = True
self.start() # start the thread i.e. run()
#----------------------------------------------------------------------
def run(self):
all_files_list = glob.glob(self.csv_path+'/*.csv')
output_excel_file='Parsed_output.xlsx'
os.chdir(self.csv_path)
pub.sendMessage("update", msg="Currently loading")
while self.running == True:
time.sleep(1)
pub.sendMessage("update", msg="Processing files")
pub.sendMessage("finish", msg="Thread Stopped")
pub.sendMessage("finish", msg="Processing Terminated")
def stop(self):
self.running = False
Main program:
import time
import wx
import os
import configparser
from pubsub import pub
#from extract_class import TestThread
from process_csv_class import process_csv_thread
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Nokia 5G TTI Parser",size=(560,460))
# Add a panel so it looks the correct on all platforms
self.config = configparser.ConfigParser()
self.config.read("config_setup.ini")
panel = wx.Panel(self, wx.ID_ANY)
self.load_csv_btn=load_csv_btn= wx.Button(panel, label="Load 5G TTI CSV")
load_csv_btn.Bind(wx.EVT_BUTTON, self.selectLogsFolder)
self.def_opening_text = wx.TextCtrl ( panel, size = (122, 17),value = "Default Load Location:",style = wx.TE_READONLY | wx.NO_BORDER | wx.TE_CENTRE )
self.def_opening_location = wx.TextCtrl ( panel, size = (300, -1),value = self.config.get('PrevConfig','defaultLoad') )
self.process_btn=process_btn= wx.Button(panel, label="Process CSV Files")
process_btn.Bind(wx.EVT_BUTTON, self.processCSVs)
self.chart_btn=chart_btn= wx.Button(panel, label="Generate Chart Slides")
chart_btn.Bind(wx.EVT_BUTTON, self.generateSlides)
self.rnti_value = wx.TextCtrl ( panel, size = (45, -1),value = "RNTI:",style = wx.TE_READONLY | wx.NO_BORDER | wx.TE_LEFT )
self.rnti_value_var = wx.TextCtrl(panel,value=self.config.get('PrevConfig','rnti_value_default'))
self.outputMessages = wx.TextCtrl(parent = panel, id = -1, size = (530, 200), style = wx.TE_MULTILINE)
self.load_fold_btn=load_fold_btn= wx.Button(panel, label="Results Folder")
load_fold_btn.Bind(wx.EVT_BUTTON, self.LoadFold)
self.exit_btn=exit_btn= wx.Button(panel, label="Exit")
exit_btn.Bind(wx.EVT_BUTTON, self.onExit)
self.about_btn=about_btn= wx.Button(panel, label="About")
about_btn.Bind(wx.EVT_BUTTON, self.onAbout)
topSizer = wx.BoxSizer(wx.VERTICAL)
load_sizer = wx.BoxSizer(wx.HORIZONTAL)
load_sizer.Add(load_csv_btn, 0, wx.ALL, 5)
load_sizer.Add(self.def_opening_text, 0, wx.ALL, 8)
load_sizer.Add(self.def_opening_location, 0, wx.ALL, 5)
process_csv_sizer = wx.BoxSizer(wx.HORIZONTAL)
process_csv_sizer.Add(process_btn, 0, wx.ALL|wx.EXPAND, 5)
process_csv_sizer.Add(chart_btn, 0, wx.ALL|wx.EXPAND, 5)
rnti_val_sizer = wx.BoxSizer(wx.HORIZONTAL)
rnti_val_sizer.Add(self.rnti_value, 0, wx.ALL|wx.EXPAND, 5)
rnti_val_sizer.Add(self.rnti_value_var, 0, wx.ALL|wx.EXPAND, 5)
display_msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
display_msg_sizer.Add(self.outputMessages, 0, wx.ALL, 5)
exit_row_sizer = wx.BoxSizer(wx.HORIZONTAL)
exit_row_sizer.Add(self.load_fold_btn, 0, wx.ALL, 5)
exit_row_sizer.Add(self.exit_btn, 0, wx.ALL, 5)
exit_row_sizer.Add(self.about_btn, 0, wx.ALL, 5)
topSizer.Add(load_sizer, 0, wx.LEFT)
#topSizer.Add(extract_sizer, 0, wx.RIGHT)
topSizer.Add(process_csv_sizer, 0, wx.RIGHT)
topSizer.Add(rnti_val_sizer, 0, wx.RIGHT)
topSizer.Add(display_msg_sizer, 0, wx.CENTER)
topSizer.Add(exit_row_sizer, 0, wx.RIGHT)
panel.SetSizer(topSizer)
# create a pubsub receiver
pub.subscribe(self.updateDisplay, "update")
pub.subscribe(self.ThreadStopped, "finish")
self.updateDisplay("Please load csv file")
#----------------------------------------------------------------------
def updateDisplay(self, msg):
"""
Receives data from thread and updates the display
"""
self.outputMessages.write("\n>>>%s" % str(msg))
print("\n>>>%s" % str(msg))
#----------------------------------------------------------------------
def ThreadStopped(self, msg):
"""
Receives finish from thread and updates the display
"""
self.outputMessages.write("\n>>>%s" % str(msg))
print("\n>>>%s" % str(msg))
def selectLogsFolder(self, event):
openDirDialog = wx.DirDialog(self,"Choose a directory:",size=(300,400),style=wx.DD_DEFAULT_STYLE)
openDirDialog.SetPath(self.def_opening_location.GetValue())
openDirDialog.ShowModal()
self.csv_folder = openDirDialog.GetPath()
self.updateDisplay("File Location: "+self.csv_folder)
self.def_opening_location.SetValue(self.csv_folder)
openDirDialog.Destroy()
def processCSVs(self, event):
try:
self.pst = process_csv_thread(self.csv_folder,self.rnti_value_var.GetValue())
except:
self.updateDisplay("Error: Please check if CSV File is loaded")
def generateSlides(self, event):
try:
cqi_sinr_thread(self.csv_folder,self.cells_list_input.GetValue(),self.rnti_value_var.GetValue())
except:
self.updateDisplay("Error: Please check if CSV File is loaded")
def LoadFold(self, event):
try:
os.startfile(os.path.dirname(self.csv_folder))
except:
self.updateDisplay("Error: First Load File ")
def onExit(self, event):
try:
self.pst.stop()
self.pst.join()
except:
pass
pub.unsubscribe(self.updateDisplay, "update")
pub.unsubscribe(self.ThreadStopped, "finish")
wx.GetApp().Yield()
time.sleep(4)
#self.config.set('PrevConfig', 'CellsList', self.cells_list_input.GetValue())
self.config.set('PrevConfig', 'rnti_value_default', self.rnti_value_var.GetValue())
self.config.set('PrevConfig', 'defaultLoad', self.def_opening_location.GetValue())
with open('config_setup.ini', 'w') as configfile:
self.config.write(configfile)
configfile.close()
self.Close()
def onAbout(self, event):
info = wx.AboutDialogInfo()
info.Name = "KPI Parser"
info.Version = "v13.0"
info.AddDeveloper("***an \n**nan.**sir#*****.com")
wx.AboutBox(info)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
config_setup.ini file
[PrevConfig]
defaultload = /home/rolf
rnti_value_default = abc

how to stabilize the speed of my wx.Gauge

the speed of my gauge varies according to the instructions of the thread that is running with it. when the instructions are simple my gauge goes very fast but when it is necessary to read a file my gauge is very slow. I want to stabilize it at the same pace.
Even worse when you have to extract text from an image file the gauge crashes. and plant the GUI
wx.Gauge(self, -1, 20, pos=(150, 300), size=(250, 25), style = wx.GA_HORIZONTAL)
I have already changed Range with more value (now Range=20) but nothing
Your example didn't include anything that showed how you were reading the files, but I'll assume based on the description of your problem that they were called from the main thread. There are many examples of how and why you need to run blocking functions from another thread to keep the UI responsive. If you run a function from the main thread it blocks the event loop, meaning none of your normal events (like EVT_TIMER) get called until the function finishes. Here is your example with a simulated long running task in its own thread.
import time
import datetime
import wx
import threading
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.gspeed = 20
self.timer = wx.Timer(self)
self.timer.Start(self.gspeed)
self.star = True
self.start_time = time.time()
self.thread = None
self.InitUI()
self.start_long_running_task()
def start_long_running_task(self):
"""
:return: starts the long running task in its own thread to keep the UI responsive
:rtype:
"""
self.thread = threading.Thread(target=self.long_running_task)
self.thread.start()
def long_running_task(self):
"""
:return: simulated long running task
:rtype:
"""
print("long running task has started")
# checks if the window has been closed and if the timer is still running
while bool(self) and self.timer.IsRunning():
# do something
time.sleep(1)
# the timer was stopped or the window was closed
print("long running task is exiting")
def InitUI(self):
pnl = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
hbox3 = wx.BoxSizer(wx.HORIZONTAL)
self.btn2 = wx.Button(pnl, wx.ID_STOP)
self.text = wx.StaticText(pnl)
self.count = wx.StaticText(pnl)
self.Bind(wx.EVT_BUTTON, self.OnStop, self.btn2)
self.gauge = wx.Gauge(pnl, 20, size=(250, -1), style=wx.GA_HORIZONTAL)
hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
hbox2.Add(self.btn2, proportion=1)
hbox3.Add(self.text, proportion=1, flag=wx.RIGHT, border=50)
hbox3.Add(self.count, proportion=1)
vbox.Add((0, 30))
vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
vbox.Add((0, 20))
vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
vbox.Add(hbox3, proportion=1, flag=wx.ALIGN_CENTRE)
pnl.SetSizer(vbox)
self.SetTitle('Gauge')
self.Centre()
def OnStop(self, e):
self.timer.Stop()
self.text.SetLabel('Task Interrupted')
def OnTimer(self, e):
self.gauge.Pulse()
self.SetTimeLabel()
def get_elapsed_time(self):
val = round(time.time() - self.start_time, 1)
hours = val / 3600
minutes = (val % 3600) / 60
seconds = val % 60
strs = ("%lu:%02lu:%02lu") % (hours, minutes, seconds)
return strs
def SetTimeLabel(self):
self.text.SetLabel("{elapsed} seconds elapsed".format(elapsed=self.get_elapsed_time()))
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
This is an example based on the code I recommended in my comment.
The Start/Stop button will start or stop the file loading.
The Other task button simply prints the contents of the textctrl boxes as proof that the program is not locked by the file loading thread.
import wx
import time
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
load_status=["File Loading","File Loaded","Cancelled"]
class Model(Thread):
def __init__(self,parent):
Thread.__init__(self)
self.stopthread = 0
self.target = parent
#self.start()
def run(self):
line_counter = 0
with open('../xxx.html', 'r') as f:
while not self.stopthread:
line = f.readline()
if not line:
break
line_counter += 1
print(line_counter)
if self.stopthread:
break
time.sleep(0.05)
evt = progress_event(count=line_counter, status=self.stopthread)
wx.PostEvent(self.target, evt)
if self.stopthread == 0:
self.stopthread = 1
evt = progress_event(count=line_counter, status=self.stopthread)
wx.PostEvent(self.target, evt)
def terminate(self):
self.stopthread = 2
class View(wx.Frame):
def __init__(self, parent, title):
super(View, self).__init__(parent, title=title, size=(400, 400))
self.InitUI()
def InitUI(self):
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
id = wx.StaticText(self, label="ID:")
firstName = wx.StaticText(self, label="First name:")
lastName = wx.StaticText(self, label="Last name:")
self.id = wx.TextCtrl(self)
self.firstName = wx.TextCtrl(self)
self.lastName = wx.TextCtrl(self)
self.stop = wx.Button(self, -1, "Start")
self.other = wx.Button(self, -1, "Other task")
self.fgs.AddMany([id, (self.id, 1, wx.EXPAND),
firstName, (self.firstName, 1, wx.EXPAND),
lastName, (self.lastName, 1, wx.EXPAND),
(self.stop,1,wx.EXPAND),
(self.other,1,wx.EXPAND)])
self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,border=15)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Stop button
self.stop.Bind(wx.EVT_BUTTON, self.OnStartStop)
#Bind to Other task button
self.other.Bind(wx.EVT_BUTTON, self.OnOther)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.SetSizer(self.vbox)
self.Layout()
self.statusbar = self.CreateStatusBar(2)
self.text = wx.StaticText(self.statusbar,-1,("No File loaded"))
self.progress = wx.Gauge(self.statusbar, range=20)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text, 0, wx.ALIGN_TOP|wx.ALL, 5)
sizer.Add(self.progress, 1, wx.ALIGN_TOP|wx.ALL, 5)
self.statusbar.SetSizer(sizer)
#wx.BeginBusyCursor()
self.loadthread = Model(self)
def OnProgress(self, event):
self.text.SetLabel(load_status[event.status])
#self.progress.SetValue(event.count)
#or for indeterminate progress
self.progress.Pulse()
if event.status != 0:
wx.EndBusyCursor()
self.Update()
time.sleep(1)
#self.statusbar.Hide()
#Re-set thread in case it needs to be restarted
self.loadthread = Model(self)
self.stop.SetLabel("Start")
self.progress.SetValue(0)
self.text.SetLabel("")
def OnStartStop(self, event):
if self.loadthread.isAlive():
self.loadthread.terminate() # Shutdown the thread
self.loadthread.join() # Wait for it to finish
#Re-set thread in case it needs to be restarted
self.loadthread = Model(self)
self.stop.SetLabel("Start")
self.progress.SetValue(0)
self.text.SetLabel("")
else:
wx.BeginBusyCursor()
self.loadthread.start()
self.stop.SetLabel("Stop")
def OnExit(self, event):
if self.loadthread.isAlive():
self.loadthread.terminate() # Shutdown the thread
self.loadthread.join() # Wait for it to finish
self.Destroy()
def OnOther(self, event):
print("Other Task")
print(self.id.GetValue())
print(self.firstName.GetValue())
print(self.lastName.GetValue())
class Controller:
def __init__(self):
self.view = View(None, title='Test')
self.view.Show()
def main():
app = wx.App()
controller = Controller()
app.MainLoop()
if __name__ == '__main__':
main()
This gauge runs with a Thread that reads the file. When you have to read a Txt file the gauge goes fast but when you have to read the Docx the gauge slows down worse when you have to read the texts of an image the gauge crashes the GUI. I want to avoid my GUI crashes. If possible stabilize the speed of the gauge.
import time
import datetime
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.gspeed = 20
self.timer = wx.Timer(self)
self.timer.Start(self.gspeed)
self.star = True
self.start_time = time.time()
self.InitUI()
def InitUI(self):
pnl = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
hbox3 = wx.BoxSizer(wx.HORIZONTAL)
self.btn2 = wx.Button(pnl, wx.ID_STOP)
self.text = wx.StaticText(pnl)
self.count = wx.StaticText(pnl)
self.Bind(wx.EVT_BUTTON, self.OnStop, self.btn2)
self.gauge = wx.Gauge(pnl,20, size=(250, -1), style = wx.GA_HORIZONTAL)
hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
hbox2.Add(self.btn2, proportion=1)
hbox3.Add(self.text, proportion=1, flag=wx.RIGHT, border=50)
hbox3.Add(self.count, proportion=1)
vbox.Add((0, 30))
vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
vbox.Add((0, 20))
vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
vbox.Add(hbox3, proportion=1, flag=wx.ALIGN_CENTRE)
pnl.SetSizer(vbox)
self.SetTitle('Gauge')
self.Centre()
def OnStop(self, e):
self.timer.Stop()
self.text.SetLabel('Task Interrupted')
def OnTimer(self, e):
self.gauge.Pulse()
self.SetTimeLabel()
def get_elapsed_time(self):
val = round(time.time() - self.start_time, 1)
hours = val/3600
minutes = (val%3600)/60
seconds = val%60
strs = ("%lu:%02lu:%02lu")%(hours, minutes, seconds)
return strs
def SetTimeLabel(self):
self.text.SetLabel("{elapsed} seconds elapsed".format(elapsed=self.get_elapsed_time()))
def main():
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()

wxPython: How to make Dialog resize according to the content of the embedded StaticText

This is my small program for testing:
import wx
class Test(wx.Dialog):
def __init__(self, parent, title="", caption='', btnList=['OK']):
wx.Dialog.__init__(self, parent, -1, title=title)
self.btnList = btnList
self.parent = parent
sizer = wx.BoxSizer()
self.panel = wx.Panel(self, -1)
sizer.Add(self.panel)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.panel.SetSizer(self.mainSizer)
self.caption = caption
self.setCaption()
self.setButton()
self.SetSizerAndFit(sizer)
def setCaption(self):
caplb = wx.StaticText(self.panel, -1, self.caption)
self.mainSizer.Add(caplb, 1, wx.EXPAND | wx.ALL, 10)
capFont = caplb.GetFont()
capFont.SetPointSize(10)
capFont.SetWeight(wx.FONTWEIGHT_BOLD)
caplb.SetFont(capFont)
caplb.SetForegroundColour(wx.RED)
def setButton(self):
self.btnBox = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(self.btnBox, 0, wx.EXPAND | wx.BOTTOM, 10)
self.btnBox.Add((1, 1), 1, wx.EXPAND)
self.btns = []
for i in range(len(self.btnList)):
self.btns.append(wx.Button(self.panel, -1, self.btnList[i]))
self.btnBox.Add(self.btns[i])
self.btnBox.Add((1, 1), 1, wx.EXPAND)
if __name__ == "__main__":
app = wx.App()
msg = ""
for i in range(8):
msg += "%s: ATTENTION! ATTENTION! ATTENTION! ATTENTION!\n" % i
dlg = Test(None, caption=msg, title="WARNING", btnList=['Yes', 'No'])
dlg.CenterOnParent()
val = dlg.ShowModal()
app.MainLoop()
I want the dialog to spread so that all text in the caplb can be seen. In most of the systems I has worked on this piece of code work well. But when I move to the new system, there is something with the gtk limit to show only half of the caplb. I don't know how to force it to show all of the StaticText. Can you please have a look at that and show me the hint? Thanks ahead!!!

How to terminate thread using condition returned by wx.CallAfter()?

I am very new to wxPython and also not familiar with thread concept. I would appreciate a lot if anyone could provide info sources or suggestion to my question.
I had created a GUI using wxpython to allow users run my script with inputs. Since one step takes 20 min to run so I plan to create a Progress Dialog to show users progress and allow them to abort it. I had tested this using the sample code below.
However, I couldn't stop WorkThread even though I clicked the Stop Button in Progress Dialog. I tried
1. make if statement using return value from pb.sendMessage()
2. create the ProgressDialog object when WorkThread starts and call ProgressDialog.abort
but none of them work. I wonder if there's conceptual mistakes implementing code like this to achieve what I want to do? Or if this can work with correction? Any hint would be appreciated!!
class WorkThread(Thread):
def __init__(self):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.start() # start the thread
def run(self):
for i in range(10):
time.sleep(1)
val = 100 / 10
wx.CallAfter(pub.sendMessage, "update", step=val)
print 'Finish Run'
class ProgressDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None)
self.abort = False
self.progress = 0
bSizer2 = wx.BoxSizer(wx.VERTICAL)
self.gauge = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL)
self.gauge.SetValue(0)
self.m_button1 = wx.Button(self, wx.ID_ANY, u"Stop Training", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer2.Add(self.gauge, 0, 0, 5)
bSizer2.Add(self.m_button1, 0, 0, 5)
self.SetSizer(bSizer2)
self.Layout()
self.Centre(wx.BOTH)
## Connect Events
self.m_button1.Bind(wx.EVT_BUTTON, self.on_cancel)
pub.subscribe(self.updateProgress, "update")
def updateProgress(self, step):
self.progress += step
if self.abort:
self.Update()
self.Close()
elif self.progress >= 100:
self.gauge.SetValue(self.progress)
self.Update()
self.Close()
else:
self.gauge.SetValue(self.progress)
def on_cancel(self, event):
"""Cancels the conversion process"""
self.abort = True
print 'Click'
# pub.unsubscribe(self.if_abort, 'cancel')
def __del__(self):
pass
########################################################################################
class MainFrame(wx.Frame):
# ----------------------------------------------------------------------
def __init__(self,parent):
wx.Frame.__init__(self,parent)
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL | wx.CENTER, 5)
panel.SetSizer(sizer)
# ----------------------------------------------------------------------
def onButton(self, event):
btn = event.GetEventObject()
btn.Disable()
WorkThread()
self.dlg = ProgressDialog()
self.dlg.ShowModal()
btn.Enable()
app = wx.App()
frame = MainFrame(None)
frame.Show(True)
# start the applications
app.MainLoop()
You need a method within the thread to stop it.
You should also wait for it to stop.
Here is an option using your code:
from threading import Thread
import wx
from wx.lib.pubsub import pub
import time
class WorkThread(Thread):
def __init__(self):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.stop_work_thread = 0
self.start() # start the thread
def run(self):
for i in range(10):
if self.stop_work_thread == 1:
break
time.sleep(1)
val = 100 / 10
wx.CallAfter(pub.sendMessage, "update", step=val)
wx.CallAfter(pub.sendMessage, "finish")
return
def stop(self):
self.stop_work_thread = 1
class ProgressDialog(wx.Dialog):
def __init__(self,parent):
wx.Dialog.__init__(self, parent)
self.parent = parent
self.abort = False
self.progress = 0
bSizer2 = wx.BoxSizer(wx.VERTICAL)
self.gauge = wx.Gauge(self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL)
self.gauge.SetValue(0)
self.m_button1 = wx.Button(self, wx.ID_ANY, u"Stop Training", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer2.Add(self.gauge, 0, 0, 5)
bSizer2.Add(self.m_button1, 0, 0, 5)
self.SetSizer(bSizer2)
self.Layout()
self.Centre(wx.BOTH)
## Connect Events
self.m_button1.Bind(wx.EVT_BUTTON, self.on_cancel)
pub.subscribe(self.updateProgress, "update")
pub.subscribe(self.on_finish, "finish")
def updateProgress(self, step):
self.progress += step
self.gauge.SetValue(self.progress)
def on_cancel(self, event):
"""Cancels the conversion process"""
self.parent.work.stop()
self.parent.work.join()
pub.unsubscribe(self.updateProgress, "update")
pub.unsubscribe(self.on_finish, "finish")
self.Destroy()
# pub.unsubscribe(self.if_abort, 'cancel')
def on_finish(self):
"""conversion process finished"""
pub.unsubscribe(self.updateProgress, "update")
pub.unsubscribe(self.on_finish, "finish")
self.Close()
def __del__(self):
pass
########################################################################################
class MainFrame(wx.Frame):
# ----------------------------------------------------------------------
def __init__(self,parent):
wx.Frame.__init__(self,parent)
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
self.btn = btn = wx.Button(self.panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL | wx.CENTER, 5)
self.panel.SetSizer(sizer)
# ----------------------------------------------------------------------
def onButton(self, event):
btn = event.GetEventObject()
btn.Disable()
self.panel.work = WorkThread()
self.dlg = ProgressDialog(self.panel)
self.dlg.ShowModal()
btn.Enable()
app = wx.App()
frame = MainFrame(None)
frame.Show(True)
# start the applications
app.MainLoop()

Resources