Running on Python 3.x on Windows 10
I'm working on a script to help automate compiling a .tiff image sequence into a video. I'm using wxPython to build the GUI. First I create the window class and set a global variable for the window.
global main_window
main_window = self
Then I have a function I use to write to the statusbar and also print the values to console(occasionally I also add code to write a log file from the text I send to this function).
def update_status_bar(window, text):
status = str(text)
window.statusbar.SetStatusText(status)
print(status)
window.Refresh()
window.Update()
wx.SafeYield(win=None, onlyIfNeeded=False)
This is the moviePy function I wrote to convert the image sequence into ta video.
def video_from_sequence(image_sequence, video, fps):
img = []
update_status_bar(main_window, 'Getting Image Directory')
path = os.path.dirname(os.path.realpath(image_sequence))
print(path)
update_status_bar(main_window, 'Getting List of Image Files')
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith('.tiff'):
img.append(file)
os.chdir(path)
update_status_bar(main_window, 'Creating Video From Image Sequence')
clip = ImageSequenceClip(img, fps=fps)
update_status_bar(main_window, clip.write_videofile(video, fps=fps))
The print to console does show the progress, however the statusbar is not populating with the current progress of the process. Because I'd like to eventually have this run as a .pyw, where the statusbar shows the progress, it's important to me that I have the progressbar show up in the statusbar, however I'm having trouble finding out a way to do this.
UPDATE (06/01/2020):
I've managed to use 2 functions to start and stop the progress bar, and created the status bar with 2 panels instead of one.
My code within the MainWindow class for the status bar has been changed to:
self.statusbar = self.CreateStatusBar(2)
self.progress_bar = wx.Gauge(self.statusbar, -1, size=(280,25), style=wx.GA_PROGRESS)
self.progress_bar_active = False
self.Show()
self.progress_bar.SetRange(50)
self.progress_bar.SetValue(0)
My function to start the animation:
def start_busy_statusbar(window):
window.count = 0
window.proc = subprocess.Popen(['ping', '127.0.0.1', '-i', '0.2'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while True:
wx.Yield()
try:
list_data = window.proc.stdout.readline()
wx.Yield()
except:
break
if len(list_data) == 0:
break
window.progress_bar.Pulse()
wx.Yield()
window.count += 1
And my function to stop the animation:
def stop_busy_statusbar(window):
window.progress_bar.Destroy()
window.progress_bar = wx.Gauge(window.statusbar, -1, size=(280, 25), style=wx.GA_PROGRESS)
Which does feel a bit crude, but it works.
So my convert() function that calls the video_from_sequence() function looks like this:
def convert(self, event):
start_busy_statusbar(main_window)
image_sequence = str(self.text_image_sequence_dir.GetValue())
original_video = str(self.text_original_video_dir.GetValue())
if image_sequence.endswith('.tiff') and original_video.endswith('.mkv'):
try:
new_video = str(original_video)[:-4] + '_1080p.mkv'
temp_video = str(original_video)[:-4] + '_temp.mkv'
print(image_sequence)
print(original_video)
print(new_video)
fps = get_frame_rate(original_video)
print(fps)
video_from_sequence(image_sequence, temp_video, fps)
except FileNotFoundError as e:
e = str(e).replace('Errno 2] ', '')
e = e.replace('directory:', 'directory:\n')
warning(e)
update_status_bar(self, 'Finished')
else:
warning('You must enter valid paths for both a tiff sequence and original video.')
stop_busy_statusbar(main_window)
What I'm running into now, is that the Window locks up while it's processing the image sequence. It's been suggested that I use a seperate thread or utilize the wx.Yield. I'm not quite sure how to execute these things. I've tried CallAfter(function(vars)) but that doesn't seem to work.
I've copied code snippets demonstrating how this works in wxPython, but I don't really understand how it works, and can't get it to work in my script.
Here is a very old example of implementing a progress bar in the statusbar.
It should give you enough to work with.
import wx
import subprocess
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbar progress')
panel = wx.Panel(self)
self.start_btn = wx.Button(panel, label='Start')
self.start_btn.Bind(wx.EVT_BUTTON, self.OnStart)
self.stop_btn = wx.Button(panel, label='Stop')
self.stop_btn.Bind(wx.EVT_BUTTON, self.OnStop)
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.text = wx.TextCtrl(panel, -1, 'Type text here')
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
btn_sizer.Add(self.start_btn)
btn_sizer.Add(self.stop_btn)
btn_sizer.Add(self.text)
panel.SetSizer(btn_sizer)
self.statusbar = self.CreateStatusBar(3)
self.text1 = wx.StaticText(self.statusbar,-1,("Static text"))
self.text2 = wx.StaticText(self.statusbar,-1,("Count Text"))
self.progress_bar = wx.Gauge(self.statusbar, -1, style=wx.GA_HORIZONTAL|wx.GA_SMOOTH)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text1, 0, wx.ALL, 5)
sizer.Add(self.text2, 1, wx.ALL, 5)
sizer.Add(self.progress_bar, 2, wx.ALL, 5)
self.statusbar.SetSizer(sizer)
self.Show()
self.progress_bar.SetRange(50)
self.progress_bar.SetValue(0)
def OnStart(self,event):
self.start_btn.Enable(False)
self.text1.SetLabelText("Started")
self.count = 0
self.proc = subprocess.Popen(['ping','127.0.0.1','-i','0.2'],stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
while True:
try:
list_data = self.proc.stdout.readline()
except:
break
if len(list_data) == 0:
break
self.progress_bar.Pulse()
self.count+=1
self.text2.SetLabelText("Count "+str(self.count))
wx.GetApp().Yield()
def OnStop(self,event):
self.start_btn.Enable(True)
self.text1.SetLabelText("Stopped")
try:
self.proc.kill()
except:
pass
def OnExit(self,event):
try:
self.proc.kill()
except:
pass
self.Destroy()
if __name__ == '__main__':
app = wx.App()
frame = MainFrame()
app.MainLoop()
Related
I am trying to troubleshoot/de-bug an issue I came across with my application using wxPython 4.0.7.
I re-wrote my entire program that was functioning with Python 2.7 and wxPython 2.8 on a Windows 7 32-bit system to now work with 64 bit Python 3.7.4 and wxPython 4.0.7 on a 64 bit Windows 10 system.
The problem I am having is that my program requires that it iterate multiple times based on the number of loops specified by the user, and it calls an instance of wx.App() from two different python scripts utilized.
I have read that calling multiple instances of wx.App() is a "no-no" (see creating multiple instances of wx.App)
Clearly this is a problem with this version of wxPython as my application crashes after the first iteration now, when it worked fine before.
Okay, so I understand this now, but I am not certain what the "fix" is for my particular issue.
The basic outline of my application is this:
A "runner.py" script is launched which contains the main wx.frame() gui and the following code is appended to the end of the script:
app = wx.App()
frame = Runner(parent=None, foo=Foo)
frame.Show()
app.MainLoop()
When the user clicks on the "execute" button in the wxPython GUI, I have a progress dialog that initiates using this code:
pd = wx.ProgressDialog(title = "Runner.py", message= "Starting Model", parent=self, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
The runner.py script executes a "for loop" that does a bunch of stuff (actually reads in some inputs from R scripts) and then once it's done, it opens up a second python script ("looping.py") and iterates through a set of processes based on the number of loops the user specifies in the GUI launched from runner.py.
As the user needs to visually see what loop process the model run is going through, I have inside this second "looping.py" script, yet another instance of wx.App() that calls up another wx.ProgressDialog(), And the script looks like this:
#Progress Bar to user to start model
app = wx.App()
pd = wx.ProgressDialog("looping.py", "Setup Iteration", parent=None, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
My specific question is this: How do I initiate the wx.ProgressDialog() successfully within the "looping.py" script without it crashing my application past the first iteration?
You will probably have to sub-class wx.ProgressDialog, arguably it may be easier to write your own progress bar display.
Something like this, may give you some ideas.
I've included the ability to run multiple threads doing different things, with pause and stop buttons. The main frame has a button to test whether the Gui is still active, whilst running the threads.
Updates from the thread are driven by an event
You may wish to reduce or increase its options.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(400,400))
panel = MyPanel(self)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
self.btn_start = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(180,30), pos=(10,10))
btn_test = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(180,30), pos=(10,50))
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(300,100))
self.btn_start.Bind(wx.EVT_BUTTON, self.Start_Process)
btn_test.Bind(wx.EVT_BUTTON, self.ActiveText)
def Start_Process(self, event):
process1 = ProcessingFrame(title='Threaded Task 1', parent=self, job_no=1)
process2 = ProcessingFrame(title='Threaded Task 2', parent=self, job_no=2)
self.btn_start.Enable(False)
def ActiveText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
class ProcessingFrame(wx.Frame):
def __init__(self, title, parent=None,job_no=1):
wx.Frame.__init__(self, parent=parent, title=title, size=(400,400))
panel = wx.Panel(self)
self.parent = parent
self.job_no = job_no
self.btn = wx.Button(panel,label='Stop processing', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.btn_pause = wx.Button(panel,label='Pause processing', size=(200,30), pos=(10,50))
self.btn_pause.Bind(wx.EVT_BUTTON, self.OnPause)
self.progress = wx.Gauge(panel,size=(200,10), pos=(10,90), range=60)
self.process = wx.TextCtrl(panel,size = (200,250), pos=(10,120), style = wx.TE_MULTILINE)
#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)
def OnProgress(self, event):
self.progress.SetValue(event.count)
self.process.write(event.process+"\n")
self.Refresh()
if event.count >= 60:
self.OnExit(None)
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.parent.btn_start.Enable(True)
self.Destroy()
def OnPause(self, event):
if self.mythread.isAlive():
self.mythread.pause() # Pause the thread
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.target = parent_target
self.stopthread = False
self.process = 1 # Testing only - mock process id
self.start() # start the thread
def run(self):
# A selectable test loop that will run for 60 loops then terminate
if self.target.job_no == 1:
self.run1()
else:
self.run2()
def run1(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 10 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Envoking process "+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def run2(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 100 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Checking process"+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def terminate(self):
self.stopthread = True
def pause(self):
if self.stopthread == "Pause":
self.stopthread = False
self.target.btn_pause.SetLabel('Pause processing')
else:
self.stopthread = "Pause"
self.target.btn_pause.SetLabel('Continue processing')
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
Can any one please tell me whats wrong with this piece of code.
When I press button 1 - everything is good. I want to press button2 - to stop the process started by button 1 and do an another process. I am unable to do it - MY GUI is going irresponsive.
You are welcome to edit the serial communication with PRINT statements if you like in doit and doit2 functions.
Please dont comment about how I made the GUI - it is just a quick example. Please comment on why I am unable to pass the pill2kill - when I press the button 2. And why my GUI is going to irresponsive state.
import threading
import time
import numpy as np
import serial
from Transmit import Write
from Receive import Read
import struct
import time
import serial.tools.list_ports
import wx
class windowClass(wx.Frame):
def __init__(self, parent, title):
appSize_x = 1100
appSize_y = 800
super(windowClass, self).__init__(parent, title = title, style = wx.MINIMIZE_BOX | wx.SYSTEM_MENU | wx.CLOSE_BOX |wx.CAPTION, size = (appSize_x, appSize_y))
self.basicGUI()
self.Centre()
self.Show()
def basicGUI(self):
# Main Panel
panel1 = wx.Panel(self)
panel1.SetBackgroundColour('#D3D3D3')
firmware_version = wx.StaticText(panel1, -1, "RANDOM1", pos = (70, 10) )
firmware_version_text_control = wx.TextCtrl(panel1, -1, size = (70,25), pos = (105,40))
pump_model_serial_number = wx.StaticText(panel1, -1, "RANDOM2", pos=(25, 75))
pump_model_serial_number.SetBackgroundColour('yellow')
model = wx.StaticText(panel1, -1, "RANDOM3", pos=(110, 100))
self.listbox = wx.ListBox(panel1, -1, size = (300,250), pos = (20,135))
clear_history = wx.Button(panel1, -1, 'BUTTON1', size = (225,30), pos = (40, 400))
clear_history.SetBackgroundColour('RED')
clear_history.Bind(wx.EVT_BUTTON, self.OnClearHistory)
clear_history2 = wx.Button(panel1, -1, 'BUTTON2', size=(225, 30), pos=(40, 500))
clear_history2.SetBackgroundColour('GREEN')
clear_history2.Bind(wx.EVT_BUTTON, self.OnClearHistory2)
def OnClearHistory(self, event):
self.pill2kill = threading.Event()
self.t = threading.Thread(target=self.doit, args=(self.pill2kill, "task"))
self.t.start()
self.t.join()
def OnClearHistory2(self, event):
self.pill2kill.set()
self.t1 = threading.Thread(target=self.doit2)
self.t1.start()
time.sleep(5)
self.t1.join()
def doit(self, stop_event, arg):
while not stop_event.wait(1):
print ("working on %s" % arg)
ser = serial.Serial(3, 115200)
c = ser.write('\x5A\x03\x02\x02\x02\x09')
print c
d = ser.read(7)
print d.encode('hex')
ser.close()
print("Stopping as you wish.")
def doit2(self):
#print ("working on %s" % arg)
ser = serial.Serial(3, 115200)
c = ser.write('\x5A\x03\x02\x08\x02\x0F') # Writing to an MCU
print c
d = ser.read(7)
print d.encode('hex')
ser.close()
def random():
app = wx.App()
windowClass(None, title='random')
app.MainLoop()
random()
Don't use the .join commands. They are blocking the main thread.
See this SO question for an in depth description:
what is the use of join() in python threading
Thread.join will block until the thread terminates. If your GUI's event handlers are blocked and unable to return to the MainLoop then other events can not be received and dispatched, and so the application appears to be frozen.
A long running shell script produces stdout and stderr, which I would like to show on a textctrl in a GUI. This is possible using threading and separating the GUI thread from the shell script's thread. However when I implement multiprocessing, I hit a roadblock. Here's my -stripped down- code:
#!/usr/bin/env python
import wx
import sys, subprocess
from multiprocessing import Process, Queue
from Queue import Empty
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.button = wx.Button(self, -1 , "Run")
self.output = wx.TextCtrl(self, -1, '', style=wx.TE_MULTILINE|\
wx.TE_READONLY|wx.HSCROLL)
self.Bind(wx.EVT_BUTTON, self.OnButton, self.button)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.output, -1, wx.EXPAND, 0)
sizer.Add(self.button)
self.SetSizerAndFit(sizer)
self.Centre()
def OnButton(self, event):
numtasks = 4 # total number of tasks to run
numprocs = 2 # number of processors = number of parallel tasks
work_queue = Queue()
for i in xrange(numtasks):
work_queue.put(i)
processes = [Process(target=self.doWork, args=(work_queue, ))
for i in range(numprocs)]
for p in processes:
p.daemon = True
p.start()
def doWork(self, work_queue):
while True:
try:
x = work_queue.get(block=False)
self.runScript(x)
except Empty:
print "Queue Empty"
break
def runScript(self, taskno):
print '## Now Running: ', taskno
command = ['./script.sh']
proc = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
while True:
stdout = proc.stdout.readline()
if not stdout:
break
#sys.stdout.flush() #no need for flush, apparently it is embedded in the multiprocessing module
self.output.AppendText(stdout.rstrip()) #this is the part that doesn't work.
if __name__ == "__main__":
app = wx.App(0)
frame = MyFrame(None, title="shell2textctrl", size=(500,500))
frame.Show(True)
app.MainLoop()
I've tried many solutions upon others' suggestions. Among these are: using wx.CallAfter or wx.lib.delayedresult to make the GUI responsive; several threading recipes (eg. wxApplication Development Cookbook, threadpool, others...) and have the multiprocess() run from a separate thread; redefining sys.stdout.write to write to the textctrl; and whatnot. None of them were successfull. Can anyone please provide a solution together with working code? You can use this script written somewhere here by I forgot who:
#!/bin/tcsh -f
# i = 1
while ($i <= 20)
echo $i
# i += 1
sleep 0.2
end
I don't know if this will help you or not, but I have an example of something like this using subprocess here:
http://www.blog.pythonlibrary.org/2010/06/05/python-running-ping-traceroute-and-more/
See the pingIP and tracertIP methods. If your shell script is written in Python, you can probably add some kind of generator to it like the ones I am using in that article to post back to the GUI. You probably already read the LongRunningProcess wiki article, but I took that and made my own tutorial here:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
I found a solution which implements a modified output Queue on top of Roger Stuckey's Simpler wxPython Multiprocessing Example framework. The code below is even Simpler than his; it could also be cleaned out a little. Hope it helps someone else. I still have a nagging feeling that there should be a more straightforward way of doing this though.
import getopt, math, random, sys, time, types, wx, subprocess
from multiprocessing import Process, Queue, cpu_count, current_process, freeze_support
from Queue import Empty
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.Point(700, 500), wx.Size(300, 200))
self.panel = wx.Panel(self, wx.ID_ANY)
#widgets
self.start_bt = wx.Button(self.panel, wx.ID_ANY, "Start")
self.Bind(wx.EVT_BUTTON, self.OnButton, self.start_bt)
self.output_tc = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_MULTILINE|wx.TE_READONLY)
# sizer
self.sizer = wx.GridBagSizer(5, 5)
self.sizer.Add(self.start_bt, (0, 0), flag=wx.ALIGN_CENTER|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
self.sizer.Add(self.output_tc, (1, 0), flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=5)
self.sizer.AddGrowableCol(0)
self.sizer.AddGrowableRow(1)
self.panel.SetSizer(self.sizer)
# Set some program flags
self.keepgoing = True
self.i = 0
self.j = 0
def OnButton(self, event):
self.start_bt.Enable(False)
self.numtasks = 4
self.numproc = 2
#self.numproc = cpu_count()
self.output_tc.AppendText('Number of processes = %d\n' % self.numproc)
# Create the queues
self.taskQueue = Queue()
self.outputQueue = Queue()
# Create the task list
self.Tasks = range(self.numtasks)
# The worker processes...
for n in range(self.numproc):
process = Process(target=self.worker, args=(self.taskQueue, self.outputQueue))
process.start()
# Start processing tasks
self.processTasks(self.update)
if (self.keepgoing):
self.start_bt.Enable(True)
def processTasks(self, resfunc=None):
self.keepgoing = True
# Submit first set of tasks
numprocstart = min(self.numproc, self.numtasks)
for self.i in range(numprocstart):
self.taskQueue.put(self.Tasks[self.i])
self.j = -1 # done queue index
self.i = numprocstart - 1 # task queue index
while (self.j < self.i):
# Get and print results
self.j += 1
output = None
while output != 'STOP!':
try:
output = self.outputQueue.get()
if output != 'STOP!':
resfunc(output)
except Empty:
break
if ((self.keepgoing) and (self.i + 1 < self.numtasks)):
# Submit another task
self.i += 1
self.taskQueue.put(self.Tasks[self.i])
def update(self, output):
self.output_tc.AppendText('%s PID=%d Task=%d : %s\n' % output)
wx.YieldIfNeeded()
def worker(self, inputq, outputq):
while True:
try:
tasknum = inputq.get()
print '## Now Running: ', tasknum #this goes to terminal/console. Add it to outputq if you'd like it on the TextCtrl.
command = ['./script.sh']
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
r = p.stdout.readline()
if not r:
outputq.put('STOP!')
break
outputq.put(( current_process().name, current_process().pid, tasknum, r.rstrip()))
except Empty:
break
# The worker must not require any existing object for execution!
worker = classmethod(worker)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1, 'stdout to GUI using multiprocessing')
self.frame.Show(True)
self.frame.Center()
return True
if __name__ == '__main__':
freeze_support()
app = MyApp(0)
app.MainLoop()
I just came out with my noob way of ending a thread, but I don't know why it's not working. Would somebody please help me out?
Here's my sample code:
import wx
import thread
import time
import threading
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, id = -1, title = "Testing", pos=(350, 110), size=(490, 200), style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX)
self.panel = wx.Panel(self)
self.stop = False
self.StartButton = wx.Button(parent = self.panel, id = -1, label = "Start", pos = (110, 17), size = (50, 20))
self.MultiLine = wx.TextCtrl(parent = self.panel, id = -1, pos = (38, 70), size = (410, 90), style = wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_AUTO_URL)
self.Bind(wx.EVT_BUTTON, self.OnStart, self.StartButton)
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnStart(self, event):
self.StartButton.Disable()
self.NewThread = threading.Thread(target = self.LongRunning)
self.NewThread.start()
def OnClose(self, event):
self.stop = True
BusyBox = wx.BusyInfo("Just a moment please!", self)
wx.Yield()
while True:
try:
if not self.NewThread.isAlive():
self.Destroy()
break
time.sleep(0.5)
except:
self.Destroy()
break
def LongRunning(self):
Counter = 1
while True:
time.sleep(2)
print "Hello, ", Counter
self.MultiLine.AppendText("hello, " + str(Counter) + "\n") #If you comment out this line, everything works fine. Why can't I update the fame after I hit the close button?
Counter = Counter + 1
if self.stop:
break
class TestApp(wx.App):
def OnInit(self):
self.TestFrame = TestFrame()
self.TestFrame.Show()
self.SetTopWindow(self.TestFrame)
return True
def main():
App = TestApp(redirect = False)
App.MainLoop()
if __name__ == "__main__":
main()
As you can see in my code, there's a infinite loop in the thread, what I tell the thread to do is break out of the loop once I click the close button. But the problem is, every time when I hit the close button, it seems the code stuck at self.MultiLine.AppendText("hello, " + str(Counter) + "\n") line, I don't know why. Anybody can help?
Try using a thread safe method such as wx.CallAfter when updating your multiline.
def LongRunning(self):
Counter = 1
while True:
time.sleep(2)
print "Hello, ", Counter
wx.CallAfter(self.updateMultiLine, "hello, " + str(Counter) + "\n")
Counter = Counter + 1
if self.stop:
break
def updateMultiLine(self, data):
self.MultiLine.AppendText(data)
In general with GUI toolkits, only one thread should access GUI functions. An exception is wx.CallAfter
As you (should) know, software defects can be classified into three groups:
Your bugs.
Their bugs.
Threads.
;)
I'm working on a media player and am able to load in a single .wav and play it. As seen in the code below.
foo = wx.FileDialog(self, message="Open a .wav file...", defaultDir=os.getcwd(), defaultFile="", style=wx.FD_MULTIPLE)
foo.ShowModal()
queue = foo.GetPaths()
self.playing_thread = threading.Thread(target=self.playFile, args=(queue[0], 'msg'))
self.playing_thread.start()
But the problem is, when I try to make the above code into a loop for multiple .wav files. Such that while playing_thread.isActive == True, create and .start() the thread. Then if .isActive == False, pop queue[0] and load the next .wav file. Problem is, my UI will lock up and I'll have to terminate the program. Any ideas would be appreciated.
Since is using wx.python, use a Delayedresult, look at wx demos for a complete example.
Full minimal example:
import wx
import wx.lib.delayedresult as inbg
import time
class Player(wx.Frame):
def __init__(self):
self.titulo = "Music Player"
wx.Frame.__init__(self, None, -1, self.titulo,)
self.jobID = 0
self.Vb = wx.BoxSizer(wx.VERTICAL)
self.panel = wx.Panel(self,-1)
self.playlist = ['one','two']
self.abortEvent = inbg.AbortEvent()
self.msg = wx.StaticText(self.panel, -1, "...",pos=(30,-1))
self.msg.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD))
self.action = wx.Button(self.panel, -1,"Play Playlist")
self.Bind(wx.EVT_BUTTON, self.StartPlaying,self.action)
self.Vb.Add(self.msg, 0, wx.EXPAND|wx.ALL, 3)
self.Vb.Add(self.action, 0, wx.EXPAND|wx.ALL, 3)
self.panel.SetSizer(self.Vb)
self.Show()
def StartPlaying(self,evt):
self.BgProcess(self.Playme)
def Playme(self,jobID, abortEvent):
print "in bg"
list = self.getPlayList()
print list
for music in list:
self.msg.SetLabel('Playing: %s' % music)
stop = 100
while stop > 0:
print stop
stop -=1
self.msg.SetLabel('Playing: %s [%s ]' % (music,stop))
def _resultConsumer(self, inbg):
jobID = inbg.getJobID()
try:
result = inbg.get()
return result
except Exception, exc:
return False
def getPlayList(self):
return self.playlist
def setPlayList(self,music):
self.playlist.appdend(music)
def BgProcess(self,executar):
self.abortEvent.clear()
self.jobID += 1
inbg.startWorker(self._resultConsumer, executar, wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
app = wx.App(False)
demo = Player()
app.MainLoop()