I am using wxPython for the GUI, and I have a different thread that interacts with some I/O. Is there a way to update the GUI to show the I/O status when the thread receives an event from the I/O device?
To be clearer: every 3 seconds the IOthread asks to a parking bar if it is up or down, and I want the GUI to show a red led if it is down or a green led when it is up. So basically I need to communicate the information received by the IOthread to the GUI, which is running in the main thread
You could have a peek at pubsub but as you are using wxPython with a thread, it's easier to use a user defined event.
This adapted code, simply alternates between True and False, obviously you would change that and only post the event, if it changes.
from threading import Thread
import wx
import time
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class WorkThread(Thread):
def __init__(self,parent_target):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.target = parent_target
self.stop_work_thread = 0
self.start() # start the thread
def run(self):
curr_loop = True
while True:
if self.stop_work_thread == 1: # Exit
break
time.sleep(1)
if self.stop_work_thread == 2: # Paused
continue
curr_loop = not curr_loop
evt = progress_event(active=curr_loop,name=self.name)
#Send back current bar position up True, Down False
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.stop()
return
def stop(self):
self.stop_work_thread = 1
def pause(self):
if self.stop_work_thread == 2:
self.stop_work_thread = 0
self.target.pause_btn.SetLabel('Pause')
else:
self.stop_work_thread = 2
self.target.pause_btn.SetLabel('Paused')
class Progress(wx.Frame):
def __init__(self, parent, title):
super(Progress, self).__init__(parent, title = title,size = (500,300))
left_sizer = wx.BoxSizer(wx.VERTICAL)
middle_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.panel = wx.Panel(self)
self.start_btn = wx.Button(self.panel, label="Start")
self.stop_btn =wx.Button(self.panel, label="Stop")
self.pause_btn =wx.Button(self.panel, label="Pause")
self.quit_btn =wx.Button(self.panel, label="Quit")
self.logger = wx.TextCtrl(self.panel, size=(200,200), style=wx.TE_MULTILINE | wx.TE_READONLY)
self.stop_btn.Disable()
self.pause_btn.Disable()
left_sizer.Add(self.start_btn,0,wx.EXPAND)
left_sizer.Add(self.stop_btn,0,wx.EXPAND)
left_sizer.Add(self.pause_btn,0,wx.EXPAND)
left_sizer.Add(self.quit_btn,0,wx.EXPAND)
middle_sizer.Add(self.logger,0,wx.EXPAND)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainsizer.Add(left_sizer)
self.mainsizer.Add(middle_sizer)
self.panel.SetSizer(self.mainsizer)
self.Layout()
self.start_btn.Bind(wx.EVT_BUTTON, self.onStart)
self.stop_btn.Bind(wx.EVT_BUTTON, self.onCancel)
self.pause_btn.Bind(wx.EVT_BUTTON, self.onPause)
self.quit_btn.Bind(wx.EVT_BUTTON, self.onExit)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnActive)
self.Bind(wx.EVT_CLOSE, self.onExit)
def OnActive(self, event):
if not self.work.is_alive():
return
active = event.active
ident = event.name
if active:
self.logger.Clear()
self.logger.SetBackgroundColour("green")
self.logger.write('\n\n\n\n\n\t\tBar is Up')
else:
self.logger.Clear()
self.logger.SetBackgroundColour("red")
self.logger.write('\n\n\n\n\n\t\tBar is Down')
def onStart(self, event):
self.start_btn.Disable()
self.work = WorkThread(parent_target=self)
self.pause_btn.Enable()
self.stop_btn.Enable()
def onPause(self, event):
if self.work.is_alive():
self.work.pause() # Pause the thread
def onCancel(self, event):
"""Cancel thread process"""
try:
self.work.stop()
self.work.join()
except:
pass
self.onFinish()
def onFinish(self):
"""thread process finished - clean up"""
self.start_btn.Enable()
self.stop_btn.Disable()
self.pause_btn.Disable()
self.pause_btn.SetLabel("Pause")
self.logger.Clear()
self.logger.SetBackgroundColour("white")
def onExit(self, event):
self.onCancel(None)
self.onFinish()
self.Destroy()
app = wx.App()
frame = Progress(None,'Bar Up/Down Display')
frame.Show()
app.MainLoop()
Related
I have 2 threads going on which work fine but thread 2 is giving me an error "QObject: Cannot create children for a parent that is in a different thread."
How can I display the return of thread 2 into "self.deviceInformation.setText"
Also, is there a way to clear the previous return and only display the current return? As it stands now, all returns get appended to the previous return
class ListBox(QWidget):
def __init__(self):
super().__init__()
self.Button()
def Button(self):
clear_btn = QPushButton('Start Threads', self)
clear_btn.clicked.connect(self.thread)
self.setFixedSize(1400, 900)
self.deviceInformation = QTextEdit(self)
self.deviceInformation.move(860,335)
self.deviceInformation.resize(500,250)
self.deviceInformation.setReadOnly(True)
self.show()
def thread(self):
t1=Thread(target=self.Operation)
t1.daemon = True
t1.start()
t2=Thread(target=self.Operation2)
t2.daemon = True
t2.start()
def Operation(self):
z=0
while True:
print(z)
z=z+1
time.sleep(1)
def Operation2(self):
zz=1
while True:
print(zz)
self.deviceInformation.setText(str(zz))
zz=zz+1
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ListBox()
sys.exit(app.exec_())
So i created a class that runs a big calculation in a seperet thread.
This is what i expected to see:
And this is what i do see:
This is the frame class
class ThreadFrame(wx.Frame):
def __init__(self,parent=None):
wx.Frame.__init__(self,parent=parent)
self.frame = wx.Frame(None, title='Bitte Warten',style=wx.FRAME_NO_TASKBAR)
self.frame.SetSize(500,100)
self.panel=wx.Panel(self.frame)
self.parent=parent
self.WaitLbl=wx.StaticText(self.panel,-1)
self.WaitLbl.SetLabel('Geotags werden ausgelesen und abgerufen.')
self.progress = wx.Gauge(self.panel,size=(500,30), range=self.parent.list_ctrl.GetItemCount())
self.btn = wx.Button(self.panel,label='Abbrechen')
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.Sizer=wx.BoxSizer(wx.VERTICAL)
#Add Widgets LeftSizer
self.Sizer.Add(self.WaitLbl,0,wx.ALL|wx.CENTER,5)
self.Sizer.Add(self.progress,0,wx.ALL,5)
self.Sizer.Add(self.btn,0,wx.ALL|wx.CENTER,5)
self.panel.SetSizer(self.Sizer)
self.Sizer.Fit(self.panel)
self.panel.Layout()
self.Centre()
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
#self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self.frame, self.parent)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
print('Thread lebt noch')
self.mythread.terminate() # Shutdown the thread
print('Thread wird beendet')
self.mythread.join() # Wait for it to finish
self.Close()
And this is the thread where the calculation is running
class TestThread(Thread):
def __init__(self,parent_target,toplevel):
Thread.__init__(self)
self.parent = toplevel
self.ownparent=parent_target
self.stopthread = False
self.start() # start the thread
def run(self):
print('Thread gestartet')
i=0
while self.stopthread == False:
#if calculation is not finished:
#do calculation and count i one up
evt = progress_event(count=i)
#Send back current count for the progress bar
try:
wx.PostEvent(self.ownparent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
i=i+1
else:
print('Thread Terminated')
self.terminate()
def terminate(self):
self.stopthread = True
And this is how i call the class from my main programm:
frame=ThreadFrame(self)
The main program also has a frame open. So this is a frame which is opend and then starts a thread which does calculation and then stops.
I think thats all that is to know. I replaced the caluclation with speudo code because my brain hurts and i cant come up with a plachold right now. But i feel like between all the fiting and sizers and panels and frames i went somewhere wrong. And i totaly dont look through all of this stuff at the moment.
The code you show doesn't seem to correspond to the screenshots. Whatever problem with the layout you might have, you should still have "Bitte warten" in the frame title, but your screenshot doesn't even show this. Either you're not executing the code you show at all, or you create some other frame elsewhere in your code which you see here. Or, of course, you've uploaded a wrong screenshot or code version. But something just doesn't fit here.
So im not sure what caused the problem. I started from scratch and now it works. This time i didnt use a new frame because the class itself is allready a frame.
class GeoThreadFrame(wx.Frame):
def __init__(self, radsteuer):
wx.Frame.__init__(self,parent=radsteuer.frame,style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP|wx.FRAME_NO_TASKBAR)
panel = wx.Panel(self)
self.SetWindowStyle(wx.FRAME_NO_TASKBAR|wx.STAY_ON_TOP)
self.progress = wx.Gauge(panel,size=(300,30), pos=(10,50), range=radsteuer.list_ctrl.GetItemCount())
self.btn = wx.Button(panel,label='Abbrechen', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
panel.Center()
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Center()
self.Show()
self.mythread = GeoLocationThread(self, radsteuer)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
#time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
#self.mythread.join(3) # Wait for it to finish
self.Destroy()
class GeoLocationThread(Thread):
def __init__(self,parent_target,mainparent):
Thread.__init__(self)
self.parent = parent_target
self.mainparent=mainparent
self.stopthread = False
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
i=0
while self.stopthread == False:
if i < self.mainparent.list_ctrl.GetItemCount():
self.calculation(i)
evt = progress_event(count=i)
i=i+1
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
def calculate(self,i):
#your calculation here
I would like to play with 3 buttons. I have to pause the execution of my function and restart the execution where it stopped(unpause). I must also put stop the execution of my function and restart the execution from the start.
the cancel button must also stop the execution like stop. PyprogressDialog must disappear after a button (any button) set press.thank
import wx
import time
class PyProgressDemo(wx.Frame):
def __init__(self, parent):
super().__init__(parent)
self.panel = wx.Panel(self, -1)
self.startbutton = wx.Button(self.panel, -1, "Start with PyProgress!")
self.pausebutton = wx.Button(self.panel, -1, "Pause/Unpause PyProgress!")
self.stopbutton = wx.Button(self.panel, -1, "stop all thing")
self.startbutton.Bind(wx.EVT_BUTTON, self.onButton)
self.pausebutton.Bind(wx.EVT_BUTTON, self.onPause)
self.stopbutton.Bind(wx.EVT_BUTTON, self.onStop)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(self.startbutton)
vbox.Add(self.pausebutton)
vbox.Add(self.stopbutton)
self.panel.SetSizer(vbox)
self.Show()
import threading
self.shutdown_event = threading.Event()
def activity(self):
while not self.shutdown_event.is_set():
for i in range(10):
print (i)
time.sleep(1)
if self.shutdown_event.is_set():
break
print("stop")
self.keepGoing = True
self.shutdown_event.set()
def onButton(self, event):
import threading
threading.Thread(target = self.activity).start()
self.dlg = wx.ProgressDialog('title', 'Some thing in progresse...',
style= wx.PD_ELAPSED_TIME
| wx.PD_CAN_ABORT)
self.keepGoing = False
while self.keepGoing == False:
wx.MilliSleep(30)
keepGoing = self.dlg.Pulse()
self.dlg.Destroy()
def onPause(self, event):
#pause/unpause
pass
def onStop(self, event):
#and wx.PD_CAN_ABORT
self.shutdown_event.set()
app = wx.App()
prog = PyProgressDemo(None)
app.MainLoop()
Here is a simple example that will open a progress dialog and start a simulated task when the button is clicked. When the cancel button is clicked the progress dialog will close and the long running task will stop. When the resume button is clicked it will re-open the progress dialog and re-start the long running task.
import wx, traceback
class Mainframe(wx.Frame):
def __init__(self):
super().__init__(None)
self.btn = wx.Button(self, label="Start")
self.btn.Bind(wx.EVT_BUTTON, self.on_btn)
# progress tracks the completion percent
self._progress = 0
self._task_running = False
self.dialog = None # type: wx.ProgressDialog
self.CenterOnScreen(wx.BOTH)
self.Show()
self.poll()
def on_btn(self, evt):
# is there a dialog already opened?
if not self.dialog:
# create a progress dialog with a cancel button
self.dialog = wx.ProgressDialog("title", "message", 100, self, wx.PD_CAN_ABORT)
self.dialog.Show()
self.btn.SetLabel("Running")
self.start_long_running_task()
def start_long_running_task(self):
if not self._task_running:
print("starting long running task")
self._task_running = True
self.long_running_task()
def long_running_task(self):
""" this method simulates a long-running task,
normally it would be run in a separate thread so as not to block the UI"""
if not self:
return # the frame was closed
if self.dialog:
# increment the progress percent
self._progress += 1
if self._progress > 100:
self._progress = 0
wx.CallLater(300, self.long_running_task)
else:
# the progress dialog was closed
print("task stopped")
self._task_running = False
def poll(self):
""" this method runs every 0.3 seconds to update the progress dialog, in an actual implementation
self._progress would be updated by the method doing the long-running task"""
if not self:
return # the frame was closed
if self.dialog:
# the cancel button was pressed, close the dialog and set the button label to 'Resume'
if self.dialog.WasCancelled():
self.btn.SetLabel("Resume")
self.dialog.Destroy()
else:
# update the progress dialog with the current percentage
self.dialog.Update(self._progress, "%s%% complete" % self._progress)
wx.CallLater(300, self.poll)
try:
app = wx.App()
frame = Mainframe()
app.MainLoop()
except:
input(traceback.format_exc())
I am trying to open another wx frame from a main frame UI. I followed the example shown here: https://wiki.wxpython.org/CallAfter but my main UI is still blocked.
Here is the event listener on the main UI:
def testShowGUI(self):
# This process is a long one
# It uses the vtk to read point cloud file and reconstruct the surface
file = "cache/d9c5e8ef-7b7f-485e-8fc8-23098c32afcb.ply"
reader = vtk.vtkPLYReader()
reader.SetFileName(file)
reader.Update()
delaunay = vtk.vtkDelaunay2D()
delaunay.SetAlpha(0.1)
delaunay.SetTolerance(0.0001)
delaunay.SetOffset(1.25)
delaunay.BoundingTriangulationOff()
delaunay.SetInputData(reader.GetOutput())
delaunay.Update()
#Once finish reading and processing the point cloud, pass to the next function for rendering
wx.CallAfter(self.AfterProcess, delaunay)
def AfterProcess(self, data):
meshVisGui = MesVisGUI.MeshVisGui(data)
meshVisGui.Show()
def OnEnter(self, event):
#Event listener when user click on Enter button
my_thread = threading.Thread(target=self.testShowGUI)
my_thread.start()
The code for the separate frame is as below:
class MeshVisGui(wx.Frame):
SPACING = 4
def __init__(self, delaunay, parent=None):
self.delaunayData = delaunay
self.title = "Mesh Visualization"
wx.Frame.__init__(self, None, -1, self.title)
self.Initialize()
def Initialize(self):
self.panel = wx.Panel(self, -1, size=(600, 400), style=wx.BORDER_RAISED)
self.widget3d = wxVTKRenderWindowInteractor(self.panel, -1)
self.widget3d.Enable()
self.render = vtk.vtkRenderer()
self.render.SetBackground(params.BackgroundColor)
self.widget3d.GetRenderWindow().AddRenderer(self.render)
self.interactor = self.widget3d.GetRenderWindow().GetInteractor()
self.interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
self.axesWidget = utils.createAxes(self.interactor)
self.meshActor = utils.build_actor(self.delaunayData)
self.render.AddActor(self.meshActor)
self.render.ResetCamera()
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.widget3d, 1, wx.EXPAND, self.SPACING)
self.panel.SetSizer(box)
self.Layout()
However, on my main UI, it is still showing a spinning icon and block the UI while it is trying to process the point cloud data. Can someone help me spot what I have done wrong?
Wxpython version: 4.0.1
Python version: 3.6.5
Following on from Mike Driscoll's answer and comment, here is a sample of a threaded task running from another panel.
The thread reports back to the 2nd panel (it's parent) using an event. This allows a progress bar to be updated.
The 2nd panel includes a "Cancel" option for the threaded task, whilst the main frame has a button to test if it is not frozen.
The use of wx.GetApp().Yield() may be slightly old fashioned but I have always found it to be reliable.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(300,10), pos=(10,50), range=300)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 300:
time.sleep(1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
frame = ThreadFrame(title='Threaded Task', parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = self.txt.GetValue()
txt += "More text " + str(self.text_count)+"\n"
self.txt.SetValue(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
You do not need threads to open new frames / windows in your wxPython application. You just need to create a sub-class of wx.Frame to hold the code of your other frame. Then from your main application's frame, you can instantiate the other frame and show it. You use the same concept when you create a wx.Dialog or a wx.MessageDialog.
Here is a simple example:
import wx
class OtherFrame(wx.Frame):
"""
Class used for creating frames other than the main one
"""
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
def on_new_frame(self, event):
frame = OtherFrame(title='SubFrame',
parent=wx.GetTopLevelParent(self))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
In this example, I set the other frame's parent to the MainFrame instance by using wx.GetTopLevelParent(self). The benefit of setting a parent for the sub-frames is that if I close the main frame, it will cause the other frames to also be closed.
I have successfully created a threading example of a thread which can update a Progressbar as it goes. However doing the same thing with multiprocessing has so far eluded me.
I'm beginning to wonder if it is possible to use tkinter in this way. Has anyone done this?
I am running on OS X 10.7. I know from looking around that different OS's may behave very differently, especially with multiprocessing and tkinter.
I have tried a producer which talks directly to the widget, through both namespaces and event.wait, and event.set. I have done the same thing with a producer talking to a consumer which is either a method or function which talks to the widget. All of these things successfully run, but do not update the widget visually. Although I have done a get() on the IntVar the widget is bound to and seen it change, both when using widget.step() and/or widget.set(). I have even tried running a separate tk() instance inside the sub process. Nothing updates the Progressbar.
Here is one of the simpler versions. The sub process is a method on an object that is a wrapper for the Progressbar widget. The tk GUI runs as the main process. I also find it a little odd that the widget does not get destroyed at the end of the loop, which is probably a clue I'm not understanding the implications of.
import multiprocessing
from tkinter import *
from tkinter import ttk
import time
root = Tk()
class main_window:
def __init__(self):
self.dialog_count = 0
self.parent = root
self.parent.title('multiprocessing progess bar')
frame = ttk.Labelframe(self.parent)
frame.pack(pady=10, padx=10)
btn = ttk.Button(frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=1, pady=10)
btn = ttk.Button(frame, text="progress_bar")
btn.bind("<Button-1>", self.pbar)
btn.grid(row=0, column=2, pady=10)
self.parent.mainloop()
def pbar(self, event):
name="producer %d" % self.dialog_count
self.dialog_count += 1
pbar = pbar_dialog(self.parent, title=name)
event = multiprocessing.Event()
p = multiprocessing.Process(target=pbar.consumer, args=(None, event))
p.start()
def cancel(self, event):
self.parent.destroy()
class pbar_dialog:
toplevel=None
pbar_count = 0
def __init__(self, parent, ns=None, event=None, title=None, max=100):
self.ns = ns
self.pbar_value = IntVar()
self.max = max
pbar_dialog.pbar_count += 1
self.pbar_value.set(0)
if not pbar_dialog.toplevel:
pbar_dialog.toplevel= Toplevel(parent)
self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
#self.frame.pack()
self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)
btn = ttk.Button(self.frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=3, pady=10)
self.frame.pack()
def set(self,value):
self.pbar_value.set(value)
def step(self,increment=1):
self.pbar.step(increment)
print ("Current", self.pbar_value.get())
def cancel(self, event):
self.destroy()
def destroy(self):
self.frame.destroy()
pbar_dialog.pbar_count -= 1
if pbar_dialog.pbar_count == 0:
pbar_dialog.toplevel.destroy()
def consumer(self, ns, event):
for i in range(21):
#event.wait(2)
self.step(5)
#self.set(i)
print("Consumer", i)
self.destroy()
if __name__ == '__main__':
main_window()
For contrast, here is the threading version which works perfectly.
import threading
from tkinter import *
from tkinter import ttk
import time
root = Tk()
class main_window:
def __init__(self):
self.dialog_count = 0
self.parent = root
self.parent.title('multiprocessing progess bar')
frame = ttk.Labelframe(self.parent)
frame.pack(pady=10, padx=10)
btn = ttk.Button(frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=1, pady=10)
btn = ttk.Button(frame, text="progress_bar")
btn.bind("<Button-1>", self.pbar)
btn.grid(row=0, column=2, pady=10)
self.parent.mainloop()
def producer(self, pbar):
i=0
while i < 101:
time.sleep(1)
pbar.step(1)
i += 1
pbar.destroy()
def pbar(self, event):
name="producer %d" % self.dialog_count
self.dialog_count += 1
pbar = pbar_dialog(self.parent, title=name)
p = threading.Thread(name=name, target=self.producer, args=(pbar,))
p.start()
#p.join()
def cancel(self, event):
self.parent.destroy()
class pbar_dialog:
toplevel=None
pbar_count = 0
def __init__(self, parent, ns=None, event=None, title=None, max=100):
self.ns = ns
self.pbar_value = IntVar()
self.title = title
self.max = max
pbar_dialog.pbar_count += 1
if not pbar_dialog.toplevel:
pbar_dialog.toplevel= Toplevel(parent)
self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
#self.frame.pack()
self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)
btn = ttk.Button(self.frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=3, pady=10)
self.frame.pack()
self.set(0)
def set(self,value):
self.pbar_value.set(value)
def step(self,increment=1):
self.pbar.step(increment)
def cancel(self, event):
self.destroy()
def destroy(self):
self.frame.destroy()
pbar_dialog.pbar_count -= 1
if pbar_dialog.pbar_count == 0:
pbar_dialog.toplevel.destroy()
pbar_dialog.toplevel = None
def automatic(self, ns, event):
for i in range(1,100):
self.step()
if __name__ == '__main__':
main_window()
Doing something similar, I ended up having to use a combination of threads and processes - the GUI front end had two threads: one for tkinter, and one reading from a multiprocessing.Queue and calling gui.update() - then the back-end processes would write updates into that Queue
This might be a strange approach, but it works for me. Copy and paste this code to a file and run it to see the result. It's ready to run.
I don't have the patience to explain my code right now, I might edit it another day.
Oh, and this is in Python 2.7 I started programming two months ago, so I have not idea if the difference is relevant.
# -*- coding: utf-8 -*-
# threadsandprocesses.py
# Importing modules
import time
import threading
import multiprocessing
import Tkinter as tki
import ttk
class Master(object):
def __init__(self):
self.mainw = tki.Tk()
self.mainw.protocol("WM_DELETE_WINDOW", self.myclose)
self.mainw.title("Progressbar")
self.mainw.geometry('300x100+300+300')
self.main = tki.Frame(self.mainw)
self.RunButton = ttk.Button(self.main, text='Run',
command=self.dostuff)
self.EntryBox = ttk.Entry(self.main)
self.EntryBox.insert(0, "Enter a number")
self.progress = ttk.Progressbar(self.main,
mode='determinate', value=0)
self.main.pack(fill=tki.BOTH, expand=tki.YES)
self.progress.pack(expand=tki.YES)
self.EntryBox.pack(expand=tki.YES)
self.RunButton.pack()
print "The Master was created"
def dostuff(self):
print "The Master does no work himself"
data = range(int(self.EntryBox.get()))
S = Slave(self, data)
print "The Master created a Slave to do his stuff"
print "The Slave gets told to start his work"
S.start()
def myclose(self):
self.mainw.destroy()
return
def nextstep(self):
print "Good job, Slave, I see the result is"
print Master.results.get()
class Slave(threading.Thread):
def __init__(self, guest, data):
print "This is the Slave."
print "Nowdays, Work is outsourced!"
self.data = data
self.guest = guest
threading.Thread.__init__(self)
def run(self):
print "The Slave is outsourcing his work to Calcualte inc."
time.sleep(1)
Outsourcing = Calculate()
Results = Outsourcing.run(self.guest, self.data)
return Results
# unwrapping outside a class
def calc(arg, **kwarg):
return Calculate.calculate(*arg, **kwarg)
class Calculate(object):
def run(self, guest, data):
print"This is Calculate inc. ... how can I help you?"
time.sleep(1)
maximum = int(guest.EntryBox.get())
guest.progress.configure(maximum=maximum, value=0)
manager = multiprocessing.Manager()
queue = manager.Queue()
lock = manager.Lock()
print "Things are setup and good to go"
# Counting the number of available CPUs in System
pool_size = multiprocessing.cpu_count()
print "Your system has %d CPUs" % (pool_size)
# Creating a pool of processes with the maximal number of CPUs possible
pool = multiprocessing.Pool(processes=pool_size)
Master.results = pool.map_async(calc, (zip([self]*len(data), [lock]*len(data),
[queue]*len(data), data)))
for job in range(1, maximum+1):
queue.get() # this is an abuse I think, but works for me
guest.progress.configure(value=job)
# Properly close and end all processes, once we're done
pool.close()
pool.join()
print "All done"
guest.nextstep()
return
def calculate(self, lock, queue, indata):
lock.acquire()
print 'Reading values and starting work'
lock.release()
time.sleep(3) # some work
results = indata # The works results
lock.acquire()
print 'Done'
lock.release()
queue.put("Finished!")
return results
if __name__ == '__main__':
TheMaster = Master()
TheMaster.mainw.mainloop()