wxPython is a powerful cross-platform GUI toolkit for the Python language. In this post you will learn how to start a long background task from a wxPython GUI with a progress-bar and a stop button.
GUI programming is almost always based on the event-driven paradigm. This is also true when using wxPython. In this paradigm, your responsibility as a programmer (in addition to building the layout of the GUI) is to write event handlers. Each event handler is a function which is responsible for handling some type of event. The event handlers are executed sequentially by wxPython (or any other GUI framework) in a single thread which means that while each event handler is running, the user can not interact with the program. So, in order to make a user friendly and responsive user interface your event handlers should do their job very quickly.
Sometimes we need to do some heavy processing in our GUI application. In such cases we should do the long processing in a separate thread.
In cases like these we will probably want to add the following features:
- Disabling the button that starts the long operation while it is running
- Allowing the user to stop the long operation with a stop button
- Showing the progress in a progress-bar
- Once the long operation is completed, updating the GUI and enabling the “start processing” button. These operations should not be done in the worker thread but in the main GUI thread, otherwise some bad thing can happen.
All of these features which are mentioned above adds some complexity into the code. Luckily you can find here a simple example which contains all of these feature and to copy-paste it into your project.
The Example
The following example demonstrate a GUI allowing computing the factorial of a number.
Factorial, in mathematics, the product of all positive integers less than or equal to a given positive integer and denoted by that integer and an exclamation point. Thus, factorial seven is written 7!, meaning 1 × 2 × 3 × 4 × 5 × 6 × 7. Factorial zero is defined as equal to 1.
https://www.britannica.com/science/factorial
Computing factorial in Python can be implemented as following:
def factorial(num: int) -> int: res = 1 for i in range(2, num): res *= i return res
Bad Implementation Without Threads

# import wxPython import wx # import IntCtrl which is a textbox that accepts only # integer number from wx.lib.intctrl import IntCtrl class MainFrame(wx.Frame): def __init__(self, *args, **kw): """Construct the frame""" # Ensure the parent's __init__ is called super(MainFrame, self).__init__(*args, **kw) # Create a panel in the frame pnl = wx.Panel(self) # Create a sizer to stack the child widgets vertically sizer = wx.BoxSizer(wx.VERTICAL) pnl.SetSizer(sizer) # Add a caption: Input Number label = wx.StaticText(pnl, label="Input Number:") sizer.Add(label, 0, wx.EXPAND) # Add a textbox that accepts only integer number self.input_number = IntCtrl(pnl) sizer.Add(self.input_number, 0, wx.EXPAND) # Add a "Compute Factorial" button # that calls on_compute_button when clicked self.compute_button = \ wx.Button(pnl, label='Compute Factorial') sizer.Add(self.compute_button, 0, wx.EXPAND) self.compute_button.Bind(wx.EVT_BUTTON, self.on_compute_button) # Add a textbox for displaying the result of the # computation self.output_textbox = \ wx.TextCtrl(pnl, style=wx.TE_MULTILINE) font1 = \ wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Consolas') self.output_textbox.SetFont(font1) sizer.Add(self.output_textbox, 1, wx.EXPAND) def on_compute_button(self, event): """ This function handles the click event of the "Compute Factorial" button. It computes the factorial of the input and display it """ # Compute the factorial of self.input_number res = 1 for i in range(2, self.input_number.GetValue()): res *= i # Convert the result into string line = str(res) # Divide the result into line of 50 characters digits_per_line = 50 res = '\r\n'.join( [line[i:i + digits_per_line] for i in range(0, len(line), digits_per_line)]) # Display the result in self.output_textbox self.output_textbox.SetValue(res) if __name__ == '__main__': # Create a wx application app = wx.App() # Create the demo window frm = MainFrame(None, title='Compute Factorial - No Threads ') # Show the window frm.Show() # Start wx main loop app.MainLoop()
Good Implementation With A Worker Thread

# import wxPython import wx # import IntCtrl which is a textbox that accepts only # integer number from wx.lib.intctrl import IntCtrl # for creating thread from threading import Thread class MainFrame(wx.Frame): def __init__(self, *args, **kw): """Construct the frame""" # Ensure the parent's __init__ is called super(MainFrame, self).__init__(*args, **kw) # Create a panel in the frame pnl = wx.Panel(self) # Create a sizer to stack the child widgets vertically self.sizer = wx.BoxSizer(wx.VERTICAL) pnl.SetSizer(self.sizer) # Add a caption: Input Number label = wx.StaticText(pnl, label="Input Number:") self.sizer.Add(label, 0, wx.EXPAND) # Add a textbox that accepts only integer number self.input_number = IntCtrl(pnl) self.sizer.Add(self.input_number, 0, wx.EXPAND) # Add a "Compute Factorial" button # that calls on_compute_button when clicked self.compute_button = \ wx.Button(pnl, label='Compute Factorial') self.sizer.Add(self.compute_button, 0, wx.EXPAND) self.compute_button.Bind(wx.EVT_BUTTON, self.on_compute_button) # Add an hidden stop button that calls # on_stop_button when clicked # It will be displayed during processing self.stop_button = wx.Button(pnl, label='Stop') self.sizer.Add(self.stop_button, 0, wx.EXPAND) self.stop_button.Hide() self.stop_button.Bind(wx.EVT_BUTTON, self.on_stop_button) self.stop_flag = False # Add an hidden progress bar # It will be displayed during processing self.progress_bar = wx.Gauge(pnl, style=wx.GA_HORIZONTAL) self.progress_bar.Hide() self.sizer.Add(self.progress_bar, 0, wx.EXPAND) # Add a textbox for displaying the result # of the computation self.output_textbox = \ wx.TextCtrl(pnl, style=wx.TE_MULTILINE) font1 = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Consolas') self.output_textbox.SetFont(font1) self.sizer.Add(self.output_textbox, 1, wx.EXPAND) # Bind event handler for EVT_COMPUTATION_COMPLETED self.EVT_COMPUTATION_COMPLETED = wx.Window.NewControlId() self.Connect(-1, -1, self.EVT_COMPUTATION_STOPPED, self.on_computation_completed) # Bind event handler for EVT_COMPUTATION_STOPPED self.EVT_COMPUTATION_STOPPED = wx.Window.NewControlId() self.Connect(-1, -1, self.EVT_COMPUTATION_STOPPED, self.on_computation_stopped) # Bind event handler for EVT_PROGRESS self.EVT_PROGRESS = wx.Window.NewControlId() self.Connect(-1, -1, self.EVT_PROGRESS, self.on_progress) # Create a timer fot updating the progress self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) self.update_progress = True # timer event handler def on_timer(self, event): # signal to the thread to update the progress bar self.update_progress = True def on_compute_button(self, event): """ This function handles the click event of the "Compute Factorial" button. It starts a thread that computes the factorial of the input and display it """ # update the GUI: # 1. hide the compute button # 2. display the stop button and the progress bar # 3. start a timer for updating the progress bar self.set_display_mode(is_running=True) # stop_flag is used for stopping the thread when # clicking on stop button self.stop_flag = False # get the input number from the text box input_number = self.input_number.GetValue() # initialize the progress bar self.progress_bar.SetRange(input_number) self.progress_bar.SetValue(0) # start the thread Thread(target=self.compute_factorial_thread, args=(input_number,)).start() def set_display_mode(self, is_running): """ Update the GUI according the current mode """ if is_running: self.compute_button.Hide() self.stop_button.Show() self.progress_bar.Show() self.timer.Start(500) else: self.compute_button.Show() self.stop_button.Hide() self.progress_bar.Hide() self.timer.Stop() self.sizer.Layout() # event handler for the stop button def on_stop_button(self, event): # signal to the thread to stop self.stop_flag = True def post_event(self, event_id, data=None): """ Post an event of a specific type with some data argument """ event = wx.PyEvent() event.SetEventType(event_id) event.data = data wx.PostEvent(self, event) def compute_factorial_thread(self, input_num): """ thread that compute factorial """ res = 1 # computation loop for i in range(2, input_num): res *= i # if the update progress flag is on if self.update_progress: # turn off the update progress flag self.update_progress = False # update the progress bar self.post_event(self.EVT_PROGRESS, i) # if the stop flag is on if self.stop_flag: # update the GUI thread that the # computation was stopped self.post_event(self.EVT_COMPUTATION_STOPPED) # quit the thread return # update the progress bar to 100% self.post_event(self.EVT_PROGRESS, input_num-1) # Convert the result into string line = str(res) # Divide the result into line of 50 characters digits_per_line = 50 res = '\r\n'.join( [line[i:i + digits_per_line] for i in range(0, len(line), digits_per_line)]) # update the GUI thread with the result of the # computation event = wx.PyEvent() event.SetEventType(self.EVT_COMPUTATION_COMPLETED) event.data = res wx.PostEvent(self, event) # this event handler is called when the thread is # completed def on_computation_completed(self, event): # Display the result in self.output_textbox self.output_textbox.SetValue(event.data) # return the GUI to the initial mode self.set_display_mode(is_running=False) # this event handler is called when the thread is # stopped def on_computation_stopped(self, event): # Write "Sopped" in self.output_textbox self.output_textbox.SetValue("Stopped") # return the GUI to the initial mode self.set_display_mode(is_running=False) # this event handler is called for updating the # progress bar def on_progress(self, event): self.progress_bar.SetValue(event.data) if __name__ == '__main__': # Create a wx application app = wx.App() # Create the demo window frm = MainFrame( None, title='Compute Factorial - Worker Thread ') # Show the window frm.Show() # Start wx main loop app.MainLoop()