wxPython threading - GUI freezes while doing an animation in GLCanvas - multithreading

What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()

Related

wxPython class doesnt show in window

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

Open another wx.python frame cause the main UI to be blocked

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.

PyQt, QThread: GUI freeze with large amount of SIGNAL from another thread (QListWidget massive update)

I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.
There are a massive "signaling" from this thread to the GUI and it "freeze".
Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?
Thanks
EDIT:
This is the thread that execute the heavy logic
class YourThreadName(QThread):
def __init__(self, some variables):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# Here there is a for cycle that emits a SIGNAL
for ... :
...
self.emit(SIGNAL("needed_variable"), needed_variable)
...
In the GUI Class there are some methods, particularly:
class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
def ... (self):
...
def start_main_code(self):
self.new_thread = YourThreadName(some variables)
self.connect(self.new_thread, SIGNAL("finished()"), self.done)
self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
self.new_thread.start()
def show_variable(self, data):
self.QListWidget_object.addItem(data)
def ... (self):
...
The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.
UPDATE:
The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.
import sys
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QThread):
message = QtCore.pyqtSignal(object)
def run(self):
batch = []
for index in range(50000):
if len(batch) < 200:
batch.append(index)
continue
self.message.emit(batch)
batch = []
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.listWidget = QtGui.QListWidget()
self.listWidget.setUniformItemSizes(True)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.listWidget)
layout.addWidget(self.button)
self.worker = Worker()
self.worker.message.connect(self.handleMessages)
def handleMessages(self, batch):
for message in batch:
self.listWidget.addItem('Item (%s)' % message)
def handleButton(self):
if not self.worker.isRunning():
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 200, 400)
window.show()
sys.exit(app.exec_())

Pause execution until button press

I have a QStackedWidget. In the logic (not the UI) I am trying to change pages and wait there until a button on that page is pressed (basically an OK/Cancel). I pass the UI to the function in the class.
Something like this:
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
while True:
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
do_something
window.stackedWidget.setCurrentIndex(3)
break
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)
break
for i in range(5):
#stuff
func1(window) #this is where I want to pause
#other stuff
Now I know that I can't break with the function like that or pass the window variable through connect, but I hope that makes my point clearly enough.
A simple way to do this is to process pending events inside the loop (so the UI remains responsive), and set/unset an internal flag to control starting and stopping of the loop.
The following demo script shows a basic implementation of this idea:
import time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
layout.addWidget(self.label)
self.buttonStart = QtGui.QPushButton('Start', self)
self.buttonStart.clicked.connect(self.handleStart)
layout.addWidget(self.buttonStart)
self.buttonStop = QtGui.QPushButton('Stop', self)
self.buttonStop.clicked.connect(self.handleStop)
layout.addWidget(self.buttonStop)
self._running = False
def handleStart(self):
self.buttonStart.setDisabled(True)
self._running = True
while self._running:
self.label.setText(str(time.clock()))
QtGui.qApp.processEvents()
time.sleep(0.05)
self.buttonStart.setDisabled(False)
def handleStop(self):
self._running = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 100)
window.show()
sys.exit(app.exec_())
Just remove while and break.
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
# do_something
window.stackedWidget.setCurrentIndex(3)
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)

How Can I Safely Manage wxPython Progress Dialog Threading?

I have several calculations to run without any user input and without the user worrying about the program having frozen, so I am trying to show a progress bar popup to keep them aware of what's happening.
Looking around has lead me to believe that I need to use a separate thread to do this, and I came up with this example.
import threading, wx, time
MAX_INT = 10
TEST_TUPLE = [[11, 22],[33,44]]
class mainFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title)
bt = wx.Button(self, wx.ID_OK)
self.Bind(wx.EVT_BUTTON, self.onBt, bt)
def onBt(self,event):
self.dlg = wx.ProgressDialog("title", "message", maximum=MAX_INT)
workThread = threading.Thread(target=self.doWork, args=(TEST_TUPLE,) )
workThread.start()
self.dlg.ShowModal()
def doWork(self, testArg):
# time consuming stuff that affects main GUI
print testArg
for i in range(1, MAX_INT+1):
self.SetPosition((i*4*MAX_INT, i*2*MAX_INT))
time.sleep(1)
print str(i)+" of "+str(MAX_INT)
wx.CallAfter(self.dlg.Update, i, "%i of %i"%(i, MAX_INT))
self.dlg.Destroy()
app = wx.App(False)
fr = mainFrame(None, -1, "Title")
fr.Show()
app.MainLoop()
It seems to work as intended, but is there some housekeeping that I am skipping here?
EDIT: I replaced the dialog with a miniframe so the main window would not freeze, disabled the button so there wouldn't be several frames spawned, and added a crude cancelling method.
import threading, wx, time
MAX_INT = 10
TEST_TUPLE = [[11, 22],[33,44]]
class GaugeFrame(wx.MiniFrame):
def __init__(self, parent, title, maximum):
wx.MiniFrame.__init__(self, parent, title=title, size=(200, 60) )
self.bar = wx.Gauge(self, range=maximum)
self.buCancel = wx.Button(self, label="Cancel")
self.SetBackgroundColour("LTGRAY")
siMainV = wx.BoxSizer(wx.VERTICAL)
siMainV.Add(self.bar)
siMainV.Add(self.buCancel, flag=wx.CENTER)
self.SetSizer(siMainV)
self.Fit()
self.Bind(wx.EVT_BUTTON, self.onCancel, self.buCancel)
def updateGauge(self, value, message=""):
self.bar.SetValue(value)
if message!="":
self.SetTitle(message)
def onCancel(self, e):
self.SetTitle("Cancelling...")
class MainFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title)
self.bt = wx.Button(self, wx.ID_OK)
self.Bind(wx.EVT_BUTTON, self.onBt, self.bt)
def onBt(self, event):
self.gFr = GaugeFrame(self, title="0 of "+str(MAX_INT), maximum=MAX_INT)
self.gFr.Show()
self.gFr.Center()
self.bt.Disable()
workThread = threading.Thread(target=self.doWork, args=(TEST_TUPLE,) )
workThread.start()
def doWork(self, testArg):
# time consuming stuff that affects main GUI
print testArg
for i in range(1, MAX_INT+1):
time.sleep(1)
if self.gFr.GetTitle()=="Cancelling...":
break
print str(i)+" of "+str(MAX_INT)
wx.CallAfter(self.gFr.updateGauge, i, "%i of %i"%(i, MAX_INT))
wx.CallAfter(self.gFr.Destroy)
wx.CallAfter(self.bt.Enable)
app = wx.App(False)
fr = MainFrame(None, -1, "Title")
fr.Show()
app.MainLoop()
Looks pretty good, just a couple of observations.
You should not call ANY window functions on the worker thread. This includes SetPosition and Destroy. You can use wx.CallAfter to invoke these on the main thread just like you are for Update.
You probably should allow the user to cancel the processing.

Resources