Every now and then, I see someone wondering how to create a progress bar and update it. So I decided to whip up an example application that updates a progress bar (technically a wx.Gauge widget) from a thread. In this tutorial, we will create a frame with a button. When the button is pushed, it will launch a dialog that contains our progress bar and it will start a thread. The thread is a dummy thread in that it doesn’t do anything in particular except send an update back to the dialog once a second for twenty seconds. Then the dialog is destroyed. Let’s take a look!
import time import wx from threading import Thread from wx.lib.pubsub import Publisher ######################################################################## class TestThread(Thread): """Test Worker Thread Class.""" #---------------------------------------------------------------------- def __init__(self): """Init Worker Thread Class.""" Thread.__init__(self) self.start() # start the thread #---------------------------------------------------------------------- def run(self): """Run Worker Thread.""" # This is the code executing in the new thread. for i in range(20): time.sleep(1) wx.CallAfter(Publisher().sendMessage, "update", "") ######################################################################## class MyProgressDialog(wx.Dialog): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" wx.Dialog.__init__(self, None, title="Progress") self.count = 0 self.progress = wx.Gauge(self, range=20) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.progress, 0, wx.EXPAND) self.SetSizer(sizer) # create a pubsub listener Publisher().subscribe(self.updateProgress, "update") #---------------------------------------------------------------------- def updateProgress(self, msg): """ Update the progress bar """ self.count += 1 if self.count >= 20: self.Destroy() self.progress.SetValue(self.count) ######################################################################## class MyFrame(wx.Frame): #---------------------------------------------------------------------- def __init__(self): wx.Frame.__init__(self, None, title="Progress Bar Tutorial") # Add a panel so it looks the correct on all platforms panel = wx.Panel(self, wx.ID_ANY) self.btn = btn = wx.Button(panel, label="Start Thread") btn.Bind(wx.EVT_BUTTON, self.onButton) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) panel.SetSizer(sizer) #---------------------------------------------------------------------- def onButton(self, event): """ Runs the thread """ btn = event.GetEventObject() btn.Disable() TestThread() dlg = MyProgressDialog() dlg.ShowModal() btn.Enable() #---------------------------------------------------------------------- # Run the program if __name__ == "__main__": app = wx.App(False) frame = MyFrame() frame.Show() app.MainLoop()
Let’s spend a few minutes breaking this down. We’ll start at the bottom. The MyFrame class is what gets run first. When you run this script you should see something like this:
As you can see, all this code does is create a simple frame with a button on it. If you press the button, the following dialog will be created and a new thread will start:
Let’s look at the portion of the code that makes the dialog:
######################################################################## class MyProgressDialog(wx.Dialog): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" wx.Dialog.__init__(self, None, title="Progress") self.count = 0 self.progress = wx.Gauge(self, range=20) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.progress, 0, wx.EXPAND) self.SetSizer(sizer) # create a pubsub listener Publisher().subscribe(self.updateProgress, "update") #---------------------------------------------------------------------- def updateProgress(self, msg): """ Update the progress bar """ self.count += 1 if self.count >= 20: self.Destroy() self.progress.SetValue(self.count)
This code just creates a dialog with a wx.Gauge widget. The Gauge is the actual widget behind the progress bar. Anyway, we create a pubsub listener at the very end of the dialog’s __init__. This listener accepts messages that will fire off the updateProgress method. We will see the messages get sent in the thread class. In the updateProgress method, we increment the counter and update the wx.Gauge by setting its value. We also check to see if the count is greater than or equal to 20, which is the range of the gauge. If it is, then we destroy the dialog.
Now we’re ready to look at the threading code:
######################################################################## class TestThread(Thread): """Test Worker Thread Class.""" #---------------------------------------------------------------------- def __init__(self): """Init Worker Thread Class.""" Thread.__init__(self) self.start() # start the thread #---------------------------------------------------------------------- def run(self): """Run Worker Thread.""" # This is the code executing in the new thread. for i in range(20): time.sleep(1) wx.CallAfter(Publisher().sendMessage, "update", "")
Here we create a thread and immediately start it. The thread loops over a range of 20 and uses the time module to sleep for a second in each iteration. After each sleep, it sends a message to the dialog to tell it to update the progress bar.
Updating the Code for wxPython 2.9
The code in the previous section was written using pubsub’s old API which has been tossed out the window with the advent of wxPython 2.9. So if you try to run the code above in 2.9, you will likely run into issues. Thus for completeness, here is a version of the code that uses the new pubsub API:
import time import wx from threading import Thread from wx.lib.pubsub import pub ######################################################################## class TestThread(Thread): """Test Worker Thread Class.""" #---------------------------------------------------------------------- def __init__(self): """Init Worker Thread Class.""" Thread.__init__(self) self.start() # start the thread #---------------------------------------------------------------------- def run(self): """Run Worker Thread.""" # This is the code executing in the new thread. for i in range(20): time.sleep(1) wx.CallAfter(pub.sendMessage, "update", msg="") ######################################################################## class MyProgressDialog(wx.Dialog): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" wx.Dialog.__init__(self, None, title="Progress") self.count = 0 self.progress = wx.Gauge(self, range=20) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.progress, 0, wx.EXPAND) self.SetSizer(sizer) # create a pubsub receiver pub.subscribe(self.updateProgress, "update") #---------------------------------------------------------------------- def updateProgress(self, msg): """""" self.count += 1 if self.count >= 20: self.Destroy() self.progress.SetValue(self.count) ######################################################################## class MyForm(wx.Frame): #---------------------------------------------------------------------- def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial") # Add a panel so it looks the correct on all platforms panel = wx.Panel(self, wx.ID_ANY) self.btn = btn = wx.Button(panel, label="Start Thread") btn.Bind(wx.EVT_BUTTON, self.onButton) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) panel.SetSizer(sizer) #---------------------------------------------------------------------- def onButton(self, event): """ Runs the thread """ btn = event.GetEventObject() btn.Disable() TestThread() dlg = MyProgressDialog() dlg.ShowModal() btn.Enable() #---------------------------------------------------------------------- # Run the program if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop()
Note that now you import the pub module rather than the Publisher module. Also note that you have to use keyword arguments. See the pubsub documentation for additional information.
Wrapping Up
At this point, you should know how to create your own progress dialog and update it from a thread. You can use a variation of this code to create a file downloader. If you do that, you would need to check the size of the file you are downloading and download it in chunks so you can create the wx.Gauge with the appropriate range and update it as each chunk is downloaded. I hope this give you some ideas for how to use this widget in your own projects.
Additional Reading
- wxPython and Threads
- wxPython wiki: Long Running Tasks
Pingback: Mike Driscoll: wxPython: How to Update a Progress Bar from a Thread | The Black Velvet Room
Pingback: Mike Driscoll: wxPython 2.9 and the Newer Pubsub API: A Simple Tutorial | The Black Velvet Room
Your way sounds like a good approach too. I just happen to enjoy using pubsub and it was the first thing to occur to me. The main difference is that I am telling my GUI when to update and you are polling to see if you can update.
Pingback: wxPython 2.9 and the Newer Pubsub API: A Simple Tutorial | Hello Linux
Pingback: wxPython: Creating a File Downloading App | Hello Linux
Hello,
I have been experimenting with different ways to update progress from thread. I notice that when the updates are too fast, I get a recurssion limit exceeded error for my update function.
Ever faced it?
No, I haven’t seen that one before. I would just limit your updates a bit. There’s no reason to update the progress bar faster than the eye can see anyway.
Pingback: Python:wxPython threads blocking – IT Sprite
Are you tired of seeking loans and Mortgages,have you been turned down constantly By your banks and other financial institutions,We offer any form of loan to individuals and corporate bodies at low interest rate.If you are interested in taking a loan,feel free to contact us today,we promise to offer you the best services ever.Just give us a try,because a trial will convince you.What are your Financial needs?Do you need a business loan?Do you need a personal loan?Do you want to buy a car?Do you want to refinance?Do you need a mortgage loan?Do you need a huge capital to start off your business proposal or expansion? Have you lost hope and you think there is no way out, and your financial burdens still persists? Contact us (gaincreditloan1@gmail.com)
Your Name:……………
Your Country:……………
Your Occupation:……………
Loan Amount Needed:……………
Loan Duration……………
Monthly Income:……………
Your Telephone Number:…………………
Business Plan/Use Of Your Loan:……………
Contact Us At : gaincreditloan1@gmail.com
Phone number :+44-75967-81743 (WhatsApp Only)