I've got a GUI script with all my wxPython code in it, and a separate testSequences module that has a bunch of tasks that I run based on input from the GUI. The tasks take a long time to complete (from 20 seconds to 3 minutes), so I want to thread them, otherwise the GUI locks up while they're running. I also need them to run one after another, since they all use the same hardware. (My rationale behind threading is simply to prevent the GUI from locking up.) I'd like to have a "Running" message (with varying number of periods after it, i.e. "Running", "Running.", "Running..", etc.) so the user knows that progress is occurring, even though it isn't visible. I'd like this script to run the test sequences in separate threads, but sequentially, so that the second thread won't be created and run until the first is complete. Since this is kind of the opposite of the purpose of threads, I can't really find any information on how to do this... Any help would be greatly appreciated.
Thanks in advance!
gui.py
import testSequences
from threading import Thread
#wxPython code for setting everything up here...
for j in range(5):
testThread = Thread(target=testSequences.test1)
testThread.start()
while testThread.isAlive():
#wait until the previous thread is complete
time.sleep(0.5)
i = (i+1) % 4
self.status.SetStatusText("Running"+'.'*i)
testSequences.py
import time
def test1():
for i in range(10):
print i
time.sleep(1)
(Obviously this isn't the actual test code, but the idea is the same.)
You cannot wait in the GUI-thread with a while loop because you block the processing of the event-queue. One solution is to poll the state of the thread with a timer:
import wx
import time
from threading import Thread
def test1():
for i in range(10):
print i
time.sleep(1)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test")
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(sizer)
self.button = wx.Button(panel, 0, "Start")
sizer.Add(self.button, 0, wx.ALIGN_LEFT)
self.button.Bind(wx.EVT_BUTTON, self.OnButton)
self.text = wx.StaticText(panel, 0, "No test is running")
sizer.Add(self.text, 0, wx.ALIGN_LEFT)
self.timer = wx.Timer(self)
def OnButton(self, event):
self.testThread = Thread(target=test1)
self.testThread.start()
self.text.SetLabel("Running")
self.button.Disable()
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(20, oneShot=True)
event.Skip()
def PollThread(self, event):
if self.testThread.isAlive():
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(200, oneShot=True)
self.text.SetLabel(self.text.GetLabel() + ".")
else:
self.button.Enable()
self.text.SetLabel("Test completed")
app = wx.PySimpleApp()
TestFrame().Show()
app.MainLoop()
Figured out a way to do this. Instead of creating threads in my gui.py, I created a class that inherits from Thread, and runs all the tests in that class, then posts wxPython events when one test is done (so I can update the status bar) and when all tests are done (so I can inform the user that all tests are complete.
myEVT_TESTDONE = wx.NewEventType()
EVT_TESTDONE = wx.PyEventBinder(myEVT_TESTDONE , 1)
myEVT_ALLDONE = wx.NewEventType()
EVT_ALLDONE = wx.PyEventBinder(myEVT_ALLDONE, 1)
class TestDone(wx.PyCommandEvent):
def __init__(self, etype, eid, val=None):
wx.PyCommandEvent.__init__(self, etype, eid)
self._val = val
def GetValue(self):
return self._val
class AllDone(wx.PyCommandEvent):
def __init__(self, etype, eid):
wx.PyCommandEvent.__init__(self, etype, eid)
class TestSequence(Thread):
def __init__(self, parent, queue):
Thread.__init__(self)
self._queue = queue
self._parent = parent
self.start()
def run(self):
testCount = 0
for test in self._queue:
#Time-intensive task goes here
for i in range(10):
print i
sleep(1)
evt = TestDone(myEVT_TESTDONE, -1, i)
wx.PostEvent(self._parent, evt)
evt = AllDone(myEVT_ALLDONE, -1)
wx.PostEvent(self._parent, evt)
class MainSequence(wx.Frame):
def __init__(self, parent, id, title):
self.Bind(EVT_TESTDONE, self.testDoneEvt)
self.Bind(EVT_ALLDONE, self.allDoneEvt)
#...the rest of the wxPython code
def testDoneEvt(self, event):
#Set what to be done after every test, e.g. update progress bar
step = event.GetValue()
def allDoneEvt(self, event):
#Set what to be done after all tests, e.g. display "Tests complete"
program = wx.App()
window = MainSequence(None, -1, 'App title')
program.MainLoop()
Related
I am using:
wxPython 4.0.7.post2
Pypubsub 4.0.3
Python 3.8.1
I have the following example program I have written:
import wx
import time
from threading import Thread
from pubsub import pub
TIME_UPDATED = "time.updated"
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title="Example")
self.text = wx.StaticText(self, label="I will display seconds elapsed!")
self.othertext = wx.StaticText(self, label="I will Update")
sizer = wx.BoxSizer(orient=wx.VERTICAL)
sizer.Add(self.text)
sizer.Add(self.othertext)
self.SetSizer(sizer)
self.timer = wx.Timer(self)
pub.subscribe(self.UpdateTime, TIME_UPDATED)
self.Bind(wx.EVT_TIMER, self.OnTime, self.timer)
self.Show()
self.i = 0
self.timer.Start(500)
def OnTime(self, _):
self.i += 1
self.othertext.SetLabel(str(self.i))
def UpdateTime(self, seconds):
self.text.SetLabel("{seconds} seconds have elapsed".format(seconds=seconds))
self.text.Refresh()
class BackgroundThread(Thread):
def run(self):
time_elapsed = 0
while True:
# Lets sleep 1 second
time.sleep(1)
time_elapsed += 1
# <<<<---- This line is what I am worried about.
pub.sendMessage(TIME_UPDATED, seconds=time_elapsed)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
background = BackgroundThread(daemon=True)
background.start()
app.MainLoop()
I am performing a pub.sendMessage(TIME_UPDATED, seconds=time_elapsed) without a wx.CallAfter and it seems to be working fine. I am not sure why.
Could someone please explain if wx.CallAfter is necessary anymore?
If it is can you explain why that is? Is it that some wx methods put something onto the dispatch queue whereas others do not?
Yes, you should still ensure that UI operations occur on the UI thread. Just because something is not safe to do does not mean that it doesn't happen to work okay (or apprear to work okay) in some cases.
I am trying to figure out how to add a progress bar to a GUI Installer I am making. The problem is actually making the progress bar work. I have it implemented but it freezes the entire program half way though.
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define variables
url = "Enter any dropbox link .zip file here"
r = requests.get(url, stream = True)
# Button definitions
ID_START = wx.NewId()
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
# Checks for old files
def Check():
if os.path.exists("Folder"):
print('\n\nRemoving old files...')
subprocess.check_call(('attrib -R ' + 'Folder' + '\\* /S').split())
shutil.rmtree('Folder')
print('\nRemoved old files.')
else:
pass
# Downloads new file
def Download():
print('\n\nDownloading:')
urllib.request.urlretrieve(url, 'temp.zip')
print('\nDownload Complete.')
# Extracts new file
def Extract():
print('\n\nExtracting...')
zip_ref = zipfile.ZipFile("temp.zip", 'r')
zip_ref.extractall("Folder")
zip_ref.close()
print('\nExtraction Complete')
# Deletes the .zip file but leave the folder
def Clean():
print('\n\nCleaning up...')
os.remove("temp.zip")
print('\nDone!')
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
Check()
Download()
Extract()
Clean()
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.SetSize(400, 350)
wx.Button(self, ID_START, 'Download', size=(300,50), pos=(42,250))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.status = wx.StaticText(self, -1, '', pos=(7,200))
self.gauge = wx.Gauge(self, range = 1000, size = (370, 30), pos=(7,217),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
def OnStart(self, event):
"""Start Computation."""
self.count = 0
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('Downloading...')
self.worker = WorkerThread(self)
while self.count <= 10000:
time.sleep(.001);
self.count = self.count + 1
self.gauge.SetValue(self.count)
self.status.SetLabel('Done!')
def OnResult(self, event):
"""Show Result status."""
# The worker is done
self.worker = None
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Also, if you see any way to improve or simplify my code without minimizing efficiency, feel free.
Ok, firstly the freeze is due to the use of the sleep method.
You're spawning the new thread on the 'Download' button's click event, that's good. But, you have to make this thread somehow communicate back to the main thread/frame instead of sleeping in the main thread.
This is where an wx Event can be used. A good tutorial is here. Add something like this after the Clean() method:
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # bind specific events to event handlers
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
Now the worker thread becomes a bit more complex since it has to notify the main thread on any progress:
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.sendEvent('started')
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
# This is the code executing in the new thread.
self.sendEvent('checking', 0)
# Check() # this method isn't working for me...?
self.sendEvent('Downloading...', 100)
Download()
self.sendEvent('Downloading complete', 400)
# ... same pattern as above for other methods...
Extract()
Clean()
def sendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
Now, all that's left is to receive the events on the main thread now.
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
# ...same as before...
self.Bind(EVT_PROGRESS, self.OnResult) # Bind our new custom event to a function
def OnStart(self, event):
# Trigger the worker thread unless it's already busy
if not self.worker:
self.status.SetLabel('')
self.worker = WorkerThread(self)
def OnResult(self, event):
"""Our handler for our custom progress event."""
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
Hope that makes sense.
I have put together your code, the excellent answer from Sree and added the use of the urllib.request.urlretrieve() reporthook option to display download completion.
Note: Any credit should be given to Sree this is just a personal exercise on my part.
# Import Libraries
import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
from threading import *
# Define variables
url = "Enter any dropbox link .zip file here"
#r = requests.get(url, stream = True)
# Button definitions
ID_START = wx.NewId()
myEVT_PROGRESS = wx.NewEventType() # Custom Event Type
EVT_PROGRESS = wx.PyEventBinder(myEVT_PROGRESS, 1) # bind specific events to event handlers
class ProgressEvent(wx.PyCommandEvent):
"""Event to signal that a status or progress changed"""
def __init__(self, etype, eid, status=None, progress=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)
self._status = status # field to update label
self._progress = progress # field to update progress bar
def GetValue(self):
"""Returns the value from the event.
#return: the tuple of status and progress
"""
return (self._status, self._progress)
# Checks for old files
def Check():
if os.path.exists("Folder"):
print('\n\nRemoving old files...')
#subprocess.check_call(('attrib -R ' + 'Folder' + '\\* /S').split())
#shutil.rmtree('Folder')
print('\nRemoved old files.')
else:
pass
# Extracts new file
def Extract():
print('\n\nExtracting...')
#zip_ref = zipfile.ZipFile("temp.zip", 'r')
#zip_ref.extractall("Folder")
#zip_ref.close()
time.sleep(5)
print('\nExtraction Complete')
# Deletes the .zip file but leave the folder
def Clean():
print('\n\nCleaning up...')
#os.remove("temp.zip")
print('\nDone!')
# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
# This starts the thread running on creation.
self.start()
# This is what runs on a separate thread when you click the download button
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
self.SendEvent('Checking...', 50)
Check()
self.SendEvent('Connecting to download...', 0)
#Perform download
urllib.request.urlretrieve(url, 'temp.zip', reporthook=self.Download_Progress)
self.SendEvent('Extracting...', 800)
Extract()
self.SendEvent('Cleaning...', 900)
Clean()
self.SendEvent('Finished...', 1000)
def Download_Progress(self, block_num, block_size, total_size):
downloaded = block_num * block_size
progress = int((downloaded/total_size)*1000)
if progress > 1000:
progress = 1000
self.SendEvent("Download active...",progress)
def SendEvent(self, status=None, progress=None):
# Send event to main frame, first param (str) is for label, second (int) for the progress bar
evt = ProgressEvent(myEVT_PROGRESS, -1, status, progress)
wx.PostEvent(self._notify_window, evt)
# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame):
"""Class MainFrame."""
def __init__(self, parent, id):
"""Create the MainFrame."""
wx.Frame.__init__(self, parent, id, 'RFMP GUInstaller',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.SetSize(400, 350)
wx.Button(self, ID_START, 'Download', size=(300,50), pos=(42,250))
self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.status = wx.StaticText(self, -1, '', pos=(7,200))
self.gauge = wx.Gauge(self, range = 1000, size = (370, 30), pos=(7,217),
style = wx.GA_HORIZONTAL)
# And indicate we don't have a worker thread yet
self.worker = None
self.Bind(EVT_PROGRESS, self.OnResult) # Bind our new custom event to a function
def OnStart(self, event):
"""Start Computation."""
self.count = 0
# Trigger the worker thread unless it's already busy
if not self.worker:
self.worker = WorkerThread(self)
def OnResult(self, event):
"""Show Result status."""
# The worker is done
self.worker = None
status, progress = event.GetValue()
self.status.SetLabel(status)
if progress:
self.gauge.SetValue(progress)
class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
# Main Loop
if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Based on Classes, i have window which contain a button and progressbar, whenever the button is clicked there two things should happen :
1 - should entried value from dialog pass to class ABCD
2 - While our class ABCD() do his stuff, should our progressbar do regular pulsing untill the class ABCD() finish process.
So the problem is that the progressbar pulse only one time,then stucked there till the class ABCD() finished, then its start pulsing regulary later.
Here is my try:
import gi,time
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
self.Myinput = Gtk.Entry()
box = self.get_content_area()
box.add(self.Myinput)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
Hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.add(Hbox)
self.button = Gtk.Button("Open dialog")
self.button.connect("clicked", self.on_button_clicked)
Hbox.pack_start(self.button, True, True, 0)
self.progressbar = Gtk.ProgressBar()
Hbox.pack_start(self.progressbar, True, True, 0)
#~~~~~~ Progress Bar
def on_timeout(self, user_data):
"""
Update value on the progress bar
"""
if self.activity_mode:
self.progressbar.pulse()
else:
new_value = self.progressbar.get_fraction() + 0.01
if new_value > 1:
new_value = 0
self.progressbar.set_fraction(new_value)
# As this is a timeout function, return True so that it
# continues to get called
return True
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
variable = dialog.Myinput.get_text()
print("start")
dialog.destroy()
#ProgressBar time function
self.timeout_id = GObject.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
self.progressbar.pulse()
#this for Updating the Windows and make the progressbar pulsing while waiting
# the class ABCD finish his stuff, finally should stop pulsing.
while Gtk.events_pending():
Gtk.main_iteration_do(False)
passing_instance = ABCD(variable)
class ABCD(object):
def __init__(self,value_of_dialog):
self.get_value = value_of_dialog
self.for_add = "______ add was done"
self.final_value = self.get_value+self.for_add
time.sleep(10)
print("gonna be finished")
print(self.final_value)
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
As we can see here i already try to make pulse and refresh the windows in this part of code
self.timeout_id = GObject.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
self.progressbar.pulse()
#this for Updating the Windows and make the progressbar pulsing while waiting
# the class ABCD finish his stuff, finally should stop pulsing.
while Gtk.events_pending():
Gtk.main_iteration_do(False)
Otherwise because in my class ABCD() i have time.sleep(10) should
the progress bar pulse only for that time 10 seconds later only then
stop.
How should this code gonna be, i need someone provide me the correct code, with little explain.
The issue with using sleep in order to emulate the passing of time is that sleep will stop everything that is happening in the thread which in this case prevents the thread to reach Gtk.main() which is needed to make your progressbar pulse or update.
So in order to do this properly there are 2 options:
Run ABCD in a separate thread such that the main thread can reach Gtk.main(). Which than will make sure that the progressbar moves as expected. A quick example of this looks like this:
self.abcd_thread = ABCD(variable)
self.abcd_thread.start()
class ABCD(Thread):
def __init__(self, value_of_dialog):
super(ABCD, self).__init__()
self.get_value = value_of_dialog
self.for_add = "______ add was done"
self.final_value = self.get_value+self.for_add
def run(self):
print "Starting " + self.name
time.sleep(10)
print("gonna be finished")
print(self.final_value)
print "Exiting " + self.name
When using this you can use self.abcd_thread.isAlive() to see whether the thread is still computing things. The way to return information heavily depends on the job placed in the thread.
Replace the time.sleep with the following fragment:
now = time.time()
while time.time() - now < 10:
# Insert any code here
Gtk.main_iteration_do(False)
This will still emulate ABCD doing stuff for 10 seconds but because we call Gtk.main_iteration_do(False) in each iteration of the loop GTK is able to update the interface during the loop.
In general the second option is the easiest as it only involves making Gtk.main_iteration_do(False) call during whatever your doing. The first option on the other hand is more useful when dealing with complex computations where adding Gtk calls doesn't fit in easily.
This only happens on Linux (possible OS X also, can't test atm), works fine on Windows.
I have a wx.ProgressDialog that is spawned with the main thread. I send the work off to another thread, and it periodically calls back to a callback function in the main thread that will update the ProgressDialog or, at the end of the work, destroy it. However, I get an interesting message on Linux when this happens:
(python:12728): Gtk-CRITICAL **: IA__gtk_window_set_modal: assertion 'GTK_IS_WINDOW (window)' failed
The dialog does close, but if I try to spawn it again it looks like it's already almost finished. Sometimes a seg fault will follow this message as well.
I've tried to simulate it with a stripped down version here:
import wxversion
wxversion.select("2.8")
import wx
import sys
import threading
MAX_COUNT = 100
## This class is in a different area of the codebase and
class WorkerThread(threading.Thread):
def __init__(self, callback):
threading.Thread.__init__(self)
self.callback = callback
def run(self):
# simulate work done. IRL, this calls another function in another
# area of the codebase. This function would generate an XML document,
# which loops through a list of items and creates a set of elements for
# each item, calling back after each item. Here, we simply set up a for
# loop and simulate work with wx.MilliSleep
for i in xrange(MAX_COUNT):
print i
wx.MilliSleep(30)
wx.CallAfter(self.callback, i)
# Send done signal to GUI
wx.CallAfter(self.callback, -1)
class Frame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff")
m_btn.Bind(wx.EVT_BUTTON, self.OnRunButton)
box.Add(m_btn, 0, wx.ALL, 10)
panel.SetSizer(box)
panel.Layout()
def OnRunButton(self, event):
self.progressDialog = wx.ProgressDialog("Doing work",
"Doing Work",
maximum=MAX_COUNT, parent=self,
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
self.worker(self.threadCallback)
self.progressDialog.ShowModal()
def worker(self, callback):
# This bit is in another part of the codebase originally. In the test,
# I could have added it to OnRunButton, but I wanted function calls to
# be similar between test and actual code
thread = WorkerThread(callback)
thread.start()
def threadCallback(self, info):
# We update based on position, or destroy if we get a -1
if info == -1:
self.progressDialog.Destroy()
else:
self.progressDialog.Update(info)
app = wx.App(redirect=False)
top = Frame("ProgressDialog Test")
top.Show()
app.MainLoop()
(we select 2.8, but ideally any fix should work in both 2.8 and 3.0. I actually haven't been able to test it in 3.0 in linux due to a bad 3.0 build)
This does a good job at representing the issue: works fine in Windows, but seg fault when it tries to destroy the progress dialog. However, I can't get the example to show the GTK_IS_WINDOW
Ive tried searching for solutions. I've read that it might be due to the fact that the worker thread finishes too quickly, and thus leaves the GUI with some messages in it's queue. I'm not sure I completely understand this (never got the hang of Yields and messages, etc), but what I believe this to mean is that when the worker is at 100%, the ProgressDialog (being slower), might only be at 75%, and still has the extra 25% of messages to use to "Update" the GUI, but instead gets destroyed.
I'd like some clarification on if I'm understanding that correctly or not.
Also, I believe .Hide() works as a work around, but I'd like to Destroy it instead because that's the proper thing to do.
Regardless, any help would be greatly appreciated. =)
I've tried your code, also many modifications been tried to overcome this issue, but failed. Anyway, I've created the following wxPython script to fulfill your purpose, see below:
import wxversion
wxversion.select("2.8") # version 3.0 works, too.
import wx
import sys
import threading
import time
MAX_COUNT = 200
class WorkerThread(threading.Thread):
def __init__(self, target, countNum):
threading.Thread.__init__(self, target = target)
self.setDaemon(True)
self.cnt = countNum
self.target = target
self.pb = self.target.pb
def run(self):
for i in xrange(self.cnt):
print i+1
wx.MilliSleep(50)
wx.CallAfter(self.pb.SetValue, i+1)
wx.CallAfter(self.target.MakeModal, False)
wx.CallAfter(self.target.Close)
class ProgressBarFrame(wx.Frame):
def __init__(self, parent, title, range = 100) :
wx.Frame.__init__(self, parent = parent, title = title)
self.range = range
self.createProgressbar()
self.SetMinSize((400, 10))
self.Centre()
self.Show()
self.t0 = time.time()
self.elapsed_time_timer.Start(1000)
def createProgressbar(self):
self.pb = wx.Gauge(self)
self.pb.SetRange(range = self.range)
self.elapsed_time_st = wx.StaticText(self, label = 'Elapsed Time:')
self.elapsed_time_val = wx.StaticText(self, label = '00:00:00')
vbox_main = wx.BoxSizer(wx.VERTICAL)
hbox_time = wx.BoxSizer(wx.HORIZONTAL)
hbox_time.Add(self.elapsed_time_st, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5)
hbox_time.Add(self.elapsed_time_val, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5)
vbox_main.Add(self.pb, 0, wx.EXPAND | wx.ALL, 5)
vbox_main.Add(hbox_time, 0, wx.EXPAND | wx.ALL, 5)
self.SetSizerAndFit(vbox_main)
self.elapsed_time_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTickTimer, self.elapsed_time_timer)
def onTickTimer(self, event):
fmt='%H:%M:%S'
self.elapsed_time_val.SetLabel(time.strftime(fmt, time.gmtime(time.time()-self.t0)))
class Frame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff")
self.Bind(wx.EVT_BUTTON, self.OnRunButton, m_btn)
box.Add(m_btn, 0, wx.ALL, 10)
panel.SetSizer(box)
def OnRunButton(self, event):
self.progressbar = ProgressBarFrame(self, 'Working Processing', MAX_COUNT)
self.progressbar.MakeModal(True)
worker = WorkerThread(self.progressbar, MAX_COUNT)
worker.start()
app = wx.App(redirect=False)
top = Frame("ProgressDialog Test")
top.Show()
app.MainLoop()
I'm using wx.Gauge to do what wx.ProgressDialog does, as well as an additional wx.Timer to show the elapsed time. MakeModal() method is used to mimic the ShowModal effect which is the default style that Dialog shows, do not forget to release the Modal status by MakeModal(False) or the frame would be freezed. You can add more stuff in the ProgressBarFrame class.
I'm thinking the segment fault error may arise from the events calling, especially when multithreading issue is involved, maybe carefully inspect into the wx.ProgressDialog class would show some clue.
I'm trying to create a gui application using wxpython and I got some issues with the TextCtrl element. The effect that I am trying to achieve is that the user will enter a command to a text field (command) and the command might pop a message that appears in the `(out) field. After some time (0.7 seconds on this example) the message will get back to a default message ("OutPut"). I have two problems:
The message doesn't always appear.
The program sometime crashes due to a segmentation fault and I don't get any Error message to handle that.
I guess that the two related in some way, but I don't know why. In the following example, I only type "test" and wait until the original message appear. Both problems happen on that scenario.
I post here two files that serve as smallest working example. File number 1, creates the GUI,
import wx
import os.path
import os
from threading import Thread
from time import sleep
from MsgEvent import *
class MainWindow(wx.Frame):
def __init__(self):
super(MainWindow, self).__init__(None, size=(400,200),)
#style=wx.MAXIMIZE)
self.CreateInteriorWindowComponents()
self.CreateKeyBinding()
self.command.SetFocus()
self.Layout()
def Test(self):
self.command.SetValue('open')
self.ParseCommand(None)
def PostMessage(self,msg):
'''For its some reason, this function is called twice,
the second time without any input. I could'nt understand why.
For that, the test :if msg == None'''
if msg == None: return
worker = MessageThread(self,msg,0.7,'OutPut')
worker.start()
def CreateKeyBinding(self):
self.command.Bind(wx.EVT_CHAR,self.KeyPressed)
def KeyPressed(self,event):
char = event.GetUniChar()
if char == 13 and not event.ControlDown(): #Enter
if wx.Window.FindFocus() == self.command:
self.ParseCommand(event)
else:
event.Skip()
def ParseCommand(self,event):
com = self.command.GetValue().lower() #The input in the command field
self.PostMessage(com)
def CreateInteriorWindowComponents(self):
''' Create "interior" window components. In this case it is just a
simple multiline text control. '''
self.panel = wx.Panel(self)
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(12)
self.vbox = wx.BoxSizer(wx.VERTICAL)
#Out put field
self.outBox = wx.BoxSizer(wx.HORIZONTAL)
self.out = wx.TextCtrl(self.panel, style=wx.TE_READONLY|wx.BORDER_NONE)
self.out.SetValue('OutPut')
self.out.SetFont(font)
self.outBox.Add(self.out,proportion=1,flag=wx.EXPAND,border=0)
self.vbox.Add(self.outBox,proportion=0,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=0)
#Setting the backgroudn colour to window colour
self.out.SetBackgroundColour(self.GetBackgroundColour())
#Commands field
self.commandBox = wx.BoxSizer(wx.HORIZONTAL)
self.command = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER)
self.command.SetFont(font)
self.commandBox.Add(self.command, proportion=1, flag=wx.EXPAND)
self.vbox.Add(self.commandBox, proportion=0, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=0)
self.panel.SetSizer(self.vbox)
return
#Close the window
def OnExit(self, event):
self.Close() # Close the main window.
app = wx.App()
frame = MainWindow()
frame.Center()
frame.Show()
app.MainLoop()
And file number 2, called MsgThread.py handle the events.
import wx
import threading
import time
myEVT_MSG = wx.NewEventType()
EVT_MSG = wx.PyEventBinder(myEVT_MSG,1)
class MsgEvent(wx.PyCommandEvent):
""" event to signal that a message is ready """
def __init__(self,etype,eid,msg='',wait=0,msg0=''):
""" create the event object """
wx.PyCommandEvent.__init__(self,etype,eid)
self._msg = unicode(msg)
self._wait_time = wait
self._reset_message = unicode(msg0)
def GetValue(self):
""" return the value from the event """
return self._msg
class MessageThread(threading.Thread):
def __init__(self,parent,msg='',wait=0,msg0=''):
"""
parent - The gui object that shuold recive the value
value - value to handle
"""
threading.Thread.__init__(self)
if type(msg) == int:
msg = unicode(msg)
self._msg = msg
self._wait_time = wait
self._reset_message = msg0
self._parent = parent
print self._msg
def run(self):
""" overide thread.run Don't call this directly, its called internally when you call Thread.start()"""
self._parent.out.SetValue(unicode(self._msg))
time.sleep(self._wait_time)
self._parent.out.SetValue(self._reset_message)
self._parent.MessageFlag = False
event = MsgEvent(myEVT_MSG,-1,self._msg)
wx.PostEvent(self._parent,event)
What is faulty?
WX Python is not thread safe except for 3 functions (wx.CallAfter, wx.CallLater, wx.PostEvent)
So basically what you have to do is ensure you never call any widget directly from within the subthread. You may use events or CallAfter.
You also might want to check this:
Nice writeup about threads and wxpython
This might help as well
Lokla