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
Related
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()
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 writing a GUI to display a few images (book covers from amazon) from a defined list of URLs, I have been able to load them in a panel successfully, but in spite of using threads, the GUI seems to wait till all the loop is over and then all the images show up at once, how can I achieve the GUI to display each images as they are fetched run time..the GUI is basically frozen till the images are fetched...Thanks!
the question again is to ensure I get to display each image on the GUI as they are pulled and not all at once...
import wx
import os
import sys
import urllib2
import cStringIO
import threading
import time
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.makeButtons()
def makeButtons(self):
def _update_data(data):
time.sleep(2)
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage( wx.ImageFromStream( stream ) )
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetBitmapMargins((4,4))
button.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD, False))
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.Show(True)
self.panel.Layout()
def f():
f = urllib2.urlopen(url)
data = f.read()
wx.CallAfter(_update_data, data)
for url in urls:
threading.Thread(target=f).start()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
It would also be possible to spin off all threads at once, as has been tried in the code sample in the question. The problem in the original code was that the GUI was blocking due to the time delay in processing the request for the bitmaps.
Bear in mind that in the sample below the order of the bitmaps will depend on the order the threads are finishing (which is random in this case).
import wx
import urllib2
import cStringIO
import threading
import time
from random import random
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def url2bmp(url, callback):
f = urllib2.urlopen(url)
data = f.read()
# to simulate random read delay
time.sleep(2 * random())
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
wx.CallAfter(callback, bmp)
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
self.makeButtons()
def makeButtons(self):
for url in urls:
threading.Thread(target=url2bmp, args=(url, self.update_ui)).start()
def update_ui(self, bmp):
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
EDIT: Rewrote the example to generate the empty buttons first, then apply the bitmap as soon as they arrive. This is uglier now and it would be time to refactor to have a more safe mapping button to url than (mis)using the label.
import wx
import urllib2
import cStringIO
import threading
import time
from random import random
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def url2bmp(url, callback):
f = urllib2.urlopen(url)
data = f.read()
# to simulate random read delay
time.sleep(2 * random())
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
wx.CallAfter(callback, url, bmp)
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
self.makeButtons()
def makeButtons(self):
for url in urls:
self.update_ui(url)
threading.Thread(target=url2bmp, args=(url, self.update_ui)).start()
def update_ui(self, url, bmp=None):
if bmp is None:
# create button, but not bitmap
button = wx.Button(self.panel, -1, url, style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
self.wrapSizer.Add(button, 1, wx.EXPAND)
else:
children = self.wrapSizer.GetChildren()
# http://www.blog.pythonlibrary.org/2012/08/24/wxpython-how-to-get-children-widgets-from-a-sizer/
for widget in children:
button = widget.GetWindow()
if isinstance(button, wx.Button):
if button.GetLabel() == url:
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetLabel('')
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
Have allowed myself to rewrite. Important is to get out of the __init__ method as soon as possible (calling Show before starting the thread) and letting the thread working asynchronuosly. You also started all your threads at once, while this example uses one thread to get one bitmap after the other.
import wx
import os
import sys
import urllib2
import cStringIO
import threading
import time
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def gen_urls():
"""Gives back one bitmap after another."""
for url in urls:
f = urllib2.urlopen(url)
data = f.read()
time.sleep(2)
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage( wx.ImageFromStream( stream ) )
yield bmp
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
threading.Thread(target=self.makeButtons).start()
def makeButtons(self):
for bmp in gen_urls():
wx.CallAfter(self.update_ui, bmp)
def update_ui(self, bmp):
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetBitmapMargins((4,4))
button.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD, False))
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
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 need to download a list of urls in a wxPython application, but I'm really new to threading, could anyone show me a working example of how to download links and put the output in a wx control.
It would have been nice to see what you had done to at least try to accomplish this. Anyway, there are a couple of good resources for learning how to do threads in wxPython. Here they are:
wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
I wrote up a tutorial on downloading files using urllib/urllib2 and requests here:
http://www.blog.pythonlibrary.org/2012/06/07/python-101-how-to-download-a-file/
If you combine these three articles together, you can come up with your answer. However, I went ahead and wrote up a super simple downloader:
import requests
import os
import wx
import wx.lib.scrolledpanel as scrolled
from threading import Thread
from wx.lib.pubsub import pub
########################################################################
class DownloadThread(Thread):
"""Downloading thread"""
#----------------------------------------------------------------------
def __init__(self, gnum, url, fsize):
"""Constructor"""
Thread.__init__(self)
self.fsize = fsize
self.gnum = gnum
self.url = url
self.start()
#----------------------------------------------------------------------
def run(self):
"""
Run the worker thread
"""
local_fname = os.path.basename(self.url)
count = 1
while True:
if os.path.exists(local_fname):
tmp, ext = os.path.splitext(local_fname)
cnt = "(%s)" % count
local_fname = tmp + cnt + ext
count += 1
else:
break
req = requests.get(self.url, stream=True)
total_size = 0
print local_fname
with open(local_fname, "wb") as fh:
for byte in req.iter_content(chunk_size=1024):
if byte:
fh.write(byte)
fh.flush()
total_size += 1024
if total_size < self.fsize:
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=total_size)
print "DONE!"
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=self.fsize)
########################################################################
class MyGauge(wx.Gauge):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, range, num):
"""Constructor"""
wx.Gauge.__init__(self, parent, range=range)
pub.subscribe(self.updateProgress, "update_%s" % num)
#----------------------------------------------------------------------
def updateProgress(self, msg):
""""""
self.SetValue(msg)
########################################################################
class MyPanel(scrolled.ScrolledPanel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
scrolled.ScrolledPanel.__init__(self, parent)
self.data = []
self.download_number = 1
# create the sizers
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
dl_sizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
lbl = wx.StaticText(self, label="Download URL:")
self.dl_txt = wx.TextCtrl(self)
btn = wx.Button(self, label="Download")
btn.Bind(wx.EVT_BUTTON, self.onDownload)
# layout the widgets
dl_sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
dl_sizer.Add(self.dl_txt, 1, wx.EXPAND|wx.ALL, 5)
dl_sizer.Add(btn, 0, wx.ALL, 5)
self.main_sizer.Add(dl_sizer, 0, wx.EXPAND)
self.SetSizer(self.main_sizer)
self.SetAutoLayout(1)
self.SetupScrolling()
#----------------------------------------------------------------------
def onDownload(self, event):
"""
Update display with downloading gauges
"""
url = self.dl_txt.GetValue()
try:
header = requests.head(url)
fsize = int(header.headers["content-length"]) / 1024
sizer = wx.BoxSizer(wx.HORIZONTAL)
fname = os.path.basename(url)
lbl = wx.StaticText(self, label="Downloading %s" % fname)
gauge = MyGauge(self, fsize, self.download_number)
sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(gauge, 0, wx.ALL|wx.EXPAND, 5)
self.main_sizer.Add(sizer, 0, wx.EXPAND)
self.Layout()
# start thread
DownloadThread(self.download_number, url, fsize)
self.download_number += 1
except Exception, e:
print "Error: ", e
########################################################################
class DownloaderFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Downloader", size=(800, 400))
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DownloaderFrame()
app.MainLoop()
Note: You will need requests to use this script.