wxPython: How to Update a Progress Bar from a Thread

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:

progressBarFrame

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:

progressBarDlg

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

9 thoughts on “wxPython: How to Update a Progress Bar from a Thread”

  1. Pingback: Mike Driscoll: wxPython: How to Update a Progress Bar from a Thread | The Black Velvet Room

  2. Pingback: Mike Driscoll: wxPython 2.9 and the Newer Pubsub API: A Simple Tutorial | The Black Velvet Room

  3. 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.

  4. Pingback: wxPython 2.9 and the Newer Pubsub API: A Simple Tutorial | Hello Linux

  5. Pingback: wxPython: Creating a File Downloading App | Hello Linux

  6. 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?

  7. 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.

  8. Pingback: Python:wxPython threads blocking – IT Sprite

  9. 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)

Comments are closed.