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()
Related
My program uses a recursive function that takes a few seconds. This function is used several times in a loop.
I want to show the progress of the loop with a gauge. But after a few passes through the loop, the gauge freezes and the program seems to stop working: "don't answer" appears in the title while the program is really running.
Once the main loop is finished, the gauge fills up to 100% at once.
import wx
class Mywin(wx.Frame):
def __init__(self, parent, title):
super(Mywin, self).__init__(parent, title=title, size=(300, 200))
self.InitUI()
def InitUI(self):
self.count = 0
pnl = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
self.gauge = wx.Gauge(pnl, range=20, size=(250, 25), style=wx.GA_HORIZONTAL)
self.btn1 = wx.Button(pnl, label="Start")
self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn1)
hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10)
vbox.Add((0, 30))
vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
vbox.Add((0, 20))
vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
pnl.SetSizer(vbox)
self.SetSize((300, 200))
self.Centre()
self.Show(True)
def Recursive(self, param1, param2):
for i in range(1000): # complex work simulated by for loop in my recursive function
print(i)
# list1 is modified in my function
return
def OnStart(self, e):
nb_elements = 150
for n in range(nb_elements):
list1 = []
list2 = []
self.Recursive(list1, list2)
self.gauge.SetValue(int((n/nb_elements)*100))
ex = wx.App()
Mywin(None, 'wx.Gauge')
ex.MainLoop()
First be careful with that range value on the gauge.
Use wx.GetApp().Yield() to momentarily pass control back to the main loop, so the update to the gauge can occur.
If you don't want to unnecessarily pass back control, moderate the yields by only yielding occasionally.
Here I do that with divmod.
import wx
import time
class Mywin(wx.Frame):
def __init__(self, parent, title):
super(Mywin, self).__init__(parent, title=title, size=(300, 200))
self.InitUI()
def InitUI(self):
self.count = 0
pnl = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
self.gauge = wx.Gauge(pnl, range=1000, size=(250, 25), style=wx.GA_HORIZONTAL)
self.btn1 = wx.Button(pnl, label="Start")
self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn1)
hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10)
vbox.Add((0, 30))
vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)
vbox.Add((0, 20))
vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
pnl.SetSizer(vbox)
self.SetSize((300, 200))
self.Centre()
self.Show(True)
def Recursive(self, param1, param2):
for i in range(1000): # complex work simulated by for loop in my recursive function
time.sleep(0.001)
quo,rem = divmod(i,50)
if rem == 0:
self.gauge.SetValue(i)
wx.GetApp().Yield()
# list1 is modified in my function
return
def OnStart(self, e):
nb_elements = 10
for n in range(nb_elements):
list1 = []
list2 = []
self.Recursive(list1, list2)
print("Finished")
ex = wx.App()
Mywin(None, 'wx.Gauge')
ex.MainLoop()
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()
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()
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()
I'm trying to learn about threading with wxPython, so I tried to make this simple example work, but it seems that the progress bar updates happen after the "long process" is done. Could anyone tell me why this is not working ?
#!/usr/bin/env python
import time
import wx
from wx.lib.pubsub import Publisher as pub
class Window(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="wxGauge")
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.gauge = wx.Gauge(self, range=10)
self.btn = wx.Button(self, label="Start process")
self.sizer.Add(self.gauge)
self.sizer.Add(self.btn)
self.SetSizerAndFit(self.sizer)
pub.subscribe(self.update, "update")
self.btn.Bind(wx.EVT_BUTTON, self.OnClick)
self.Show()
def update(self, msg):
self.gauge.SetValue(msg.data)
def OnClick(self, evt):
for i in range(10):
wx.CallAfter(pub.sendMessage, "update", i + 1)
time.sleep(1)
if __name__ == '__main__':
app = wx.App()
Window()
app.MainLoop()
It is because of the usage of "CallAfter". If call "sendMessage" in this way, it is actually called after the event handler finished.
You can try it in this way:
for i in range(10):
pub.sendMessage("update", i+1)
time.sleep(1)
EDIT:
put this "sendMessage" out of the event handler.
import time
import wx
from wx.lib.pubsub import Publisher as pub
class Window(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="wxGauge")
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.gauge = wx.Gauge(self, range=10)
self.btn = wx.Button(self, label="Start process")
self.sizer.Add(self.gauge)
self.sizer.Add(self.btn)
self.SetSizerAndFit(self.sizer)
pub.subscribe(self.update, "update")
self.btn.Bind(wx.EVT_BUTTON, self.OnClick)
self.Bind(wx.EVT_TIMER, self.TimerHandler)
self.timer = wx.Timer(self)
self.gaugeIndex = 0
self.Show()
def TimerHandler(self, event):
self.gaugeIndex += 1
pub.sendMessage("update", self.gaugeIndex)
if self.gaugeIndex == 10:
self.timer.Stop()
def update(self, msg):
self.gauge.SetValue(msg.data)
def OnClick(self, evt):
self.timer.Start(1000)
if __name__ == '__main__':
app = wx.App(redirect=False)
Window()
app.MainLoop()