Creating Python GUI Applications with wxPython

Introduction

In this tutorial, we're going to learn how to use wxPython library for developing Graphical User Interfaces (GUI) for desktop applications in Python. GUI is the part of your application which allows the user to interact with your application without having to type in commands, they can do pretty much everything with a click of the mouse.

Some of the popular Python alternatives for developing a GUI include Tkinter, and pyqt. However, in this tutorial, we will learn about wxPython.

Before we move further, there are a few prerequisites for this tutorial. You should have a basic understanding of Python's syntax, and/or have done at least beginner level programming in some other language. Although you can follow it, even if you do not meet these criterias, but you might find some parts to be a bit complex. If you do, feel free to ask for clarifications in the comments.

Installation

The installation process for wxPython is fairly straight forward, although it differs slightly depending on the system you're using.

Mac and Windows

WxPython is quite easy to install on Mac and Windows using pip package manager. If you have pip installed in your system, run the following command to download to install wxPython:

$ pip install wxpython

Linux

For Linux, the procedure could be a bit of a pain, as it has a lot of prerequisite libraries that need to be installed. I would recommend to try running the following two commands in a sequence:

# Command 1
$ sudo apt-get install dpkg-dev build-essential python2.7-dev python3.5-dev python3.6-dev libgstreamer-plugins-base1.0-dev libnotify-dev libwebkitgtk-3.0-dev libwebkit-dev libwebkitgtk-dev libjpeg-dev libtiff-dev libgtk2.0-dev libsdl1.2-dev libgstreamer-plugins-base0.10-dev freeglut3 freeglut3-dev

# Command 2
$ pip install --upgrade --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython

However, if these do not work then you will have to manually install these libraries, a list of which is mentioned in the "Prerequisites" section of WxPython's Github repo.

Examples of Creating GUIs with wxPython

In this section, we will get our hands dirty with wxPython and create a basic string manipulation application with some basic functionalities, like counting the number of words, displaying the frequency of each word, most repeated word, etc.

Before moving forward, we will create a very simple skeleton application, which we will use as a starting point in the upcoming examples to implement more advanced GUI functionalities.

Without further ado, let's start. Below is the basic skeleton or structure of a GUI application built using wxPython. We will change it further in the next section to make it object oriented for additional functionality.

import wx

# Creates an App object which runs a loop to display the
# GUI on the screen
myapp = wx.App()

# Initialises a frame that the user would be able to
# interact with
init_frame = wx.Frame(parent=None, title='Word Play')

# Display the initialised frame on screen
init_frame.Show()

# Run a loop on the app object
myapp.MainLoop()

If the loop is not run (i.e. the app.MainLoop() call), then the frame will appear on the screen for a split second, and even before you could see it, it will disappear. This function ensures that the frame remains visible on the screen, until the user exits the program, and it does so by running the frame in a loop.

Note: While running this on a Mac, I got the following error when I ran my code using python filename.py command in the terminal:

This program needs access to the screen. Please run with a Framework build of python, and only when you are logged in on the main display of your Mac.

To get rid of this, simply use pythonw instead of python in the above command.

Once the program runs, you should see the following blank window on your screen:

Object Oriented Code

Before we add functionality to our code, let's modularize it first by making classes and functions, so that it looks cleaner and its easier to extend it. The functionality of the following code is same as before, however, it has been refactored to implement object oriented programming concepts.

import wx
import operator

# We make a class for frame, so that each time we
# create a new frame, we can simply create a new
# object for it

class WordPlay(wx.Frame):
    def __init__(self, parent, title):
        super(WordPlay, self).__init__(parent, title=title)
        self.Show()

def main():
    myapp = wx.App()
    WordPlay(None, title='Word Play')
    myapp.MainLoop()

main()

In the script above, we create a class WordPlay that inherits the wxFrame class. The constructor of the WordPlay class accepts two parameters: parent and title. Inside the child constructor, the parent class constructor for the wxPython class is called and the parent and title attributes are passed to it. Finally the show method is called to display the frame. In the main() method, the object of WordPlay class is created.

The code now looks a lot more structured and cleaner; it is easier to understand and more functionalities can be seamlessly added to the above code.

Adding Functionalities

We will be adding functionalities one at a time in order to avoid confusion regarding which code part is added for which particular functionality. What we want in our basic application is a text box where we can add text, and then a few buttons to perform different functions on that text, like calculating the number of words in it, frequency of each word, etc., followed by the output being displayed on our app screen.

Let's start by adding a text box to our app in which we can add our text.

# Some of the code will be the same as the one above,
# so make sure that you understand that before moving
# to this part

import wx
import operator

# We make a class for frame, so that each time we create a new frame,
# we can simply create a new object for it

class WordPlay(wx.Frame):
    def __init__(self, parent, title):
        super(WordPlay, self).__init__(parent, title=title)
        self.widgets()
        self.Show()

    # Declare a function to add new buttons, icons, etc. to our app
    def widgets(self):
        text_box = wx.BoxSizer(wx.VERTICAL) # Vertical orientation

        self.textbox = wx.TextCtrl(self, style=wx.TE_RIGHT)
        text_box.Add(self.textbox, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=5)

        grid = wx.GridSizer(5, 5, 10, 10) # Rows, columns, vertical gap, horizontal gap
        text_box.Add(grid, proportion=2, flag=wx.EXPAND)

        self.SetSizer(text_box)

def main():
    myapp = wx.App()
    WordPlay(None, title='Word Play')
    myapp.MainLoop()

main()

As you can see, we have added a new function named widgets() above, and it has been called in the WordPlay class's constructor. Its purpose is to add new widgets to our screen; however, in our case we are only interested in adding one widget, i.e. a text box where we can add some text.

Let's now understand some important things that are going on inside this widgets() function. The BoxSizer() method, as the name suggests, controls the widgets' size, as well as its position (relative or absolute). The wx.VERTICAL specifies that we want a vertical orientation for this widget. TextCtrl basically adds a small text box in our current from, where the user can enter in a text input. The GridSizer() method helps us create a table-like structure for our window.

Alright, let's see what our application looks like now.

A text box can now be seen in our application window.

Let's move further and add two buttons to our application, one for counting the number of words in the text, and the second to display the most repeated word. We will accomplish that in two steps, first we will add two new buttons, and then we will add event handlers to our program which will tell us which button the user has clicked, along with the text entered in the text box, so that a specific action can be performed on the input.

Adding buttons is rather simple, it only requires adding some additional code to our "widgets" function. In the code block below, we will only be displaying the updated widgets function; the rest of the code stays the same.

# Adding buttons to our main window

def widgets(self):
    text_box = wx.BoxSizer(wx.VERTICAL)

    self.textbox = wx.TextCtrl(self, style=wx.TE_RIGHT)
    text_box.Add(self.textbox, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=5)

    grid = wx.GridSizer(2, 5, 5) # Values have changed to make adjustments to button positions
    button_list = ['Count Words', 'Most Repeated Word'] # List of button labels

    for lab in button_list:
        button = wx.Button(self, -1, lab) # Initialise a button object
        grid.Add(button, 0, wx.EXPAND) # Add a new button to the grid with the label from button_list

    text_box.Add(grid, proportion=2, flag=wx.EXPAND)

    self.SetSizer(text_box)

As you can see, two new buttons have now been added to our main window as well.

Adding an Event Handler

Our application's interface is now ready, all we need to do now is add event handlers to perform specific actions upon button clicks. For that we will have to create a new function and add an additional line of code in the widgets function. Let's start by writing our function.

# Declare an event handler function

def event_handler(self, event):
    # Get label of the button clicked
    btn_label = event.GetEventObject().GetLabel()

    # Get the text entered by user
    text_entered = self.textbox.GetValue()

    # Split the sentence into words
    words_list = text_entered.split()

    # Perform different actions based on different button clicks
    if btn_label == "Count Words":
        result = len(words_list)
    elif btn_label == "Most Repeated Word":
        # Declare an empty dictionary to store all words and
        # the number of times they occur in the text
        word_dict = {}

        for word in words_list:
            # Track count of each word in our dict
            if word in word_dict:
                word_dict[word] += 1
            else:
                word_dict[word] = 1

            # Sort the dict in descending order so that the
            # most repeated word is at the top
            sorted_dict = sorted(word_dict.items(),
                                key=operator.itemgetter(1),
                                reverse=True)

            # First value in the dict would be the most repeated word
            result = sorted_dict[0]

    # Set the value of the text box as the result of our computation
    self.textbox.SetValue(str(result))

The logic behind the "Most Repeated Word" feature is that we first run a loop that iterates through word from the list of all words. Then it checks if that particular word already exists in the dictionary or not; if it does, then that means it is being repeated, and its value is incremented by one each time the word reappears. Otherwise, if it doesn't exist in the dictionary, then that means it has appeared in the sentence for the first time, and its 'occurrence' value should be set to 1. Lastly, we sort the dictionary (similar to Python list sorting) in descending order so that the word with the highest value (frequency) comes out on top, which we can then display.

Alright, so now that we have written the computation/action that needs to be performed when a specific button is clicked, let's "bind" that action to that particular button. For that, we'll have to slightly modify our widgets function.

# Only one line needs to be added in the "for loop" of
# our widgets function, so that's all we're showing
for lab in button_list:
    button = wx.Button(self, -1, lab)
    self.Bind(wx.EVT_BUTTON, self.event_handler, button)
    grid.Add(button, 0, wx.EXPAND)

In the code above, the self.Bind call is where the binding occurs. What it does is that it links a particular action to a specific button, so that when you click that button, a specific action linked to it will be performed. In our particular case, we only have one event handler function, which handles both actions by checking at runtime which button was clicked through the 'label' property and then performing the linked action. So in the self.Bind call we bind all our buttons to the single 'event_handler' function.

Alright, so our code is now complete. Let's try both of our features and see if it all works as expected.

In the first step, as shown below, we enter a string in the text box:

Next, if we click the "Count Words" button you should see "7" in the text box since there were 7 words in the string.

So far so good!

Now let's write another string in the text box, as shown in the following figure:

Now, if we click the "Most Repeated Word" button you will see the most repeated words in the text box, along with its frequency of occurrence, as shown below:

Works perfectly!

We have only added two features, but the purpose was to show you how all these components are connected, you can add as many functionalities as you want by simply writing additional functions for them. Furthermore, this tutorial was not focused much on the aesthetics. There are many widgets available in wxPython to beautify your program now that you have grasped the basic knowledge of the toolkit.

Conclusion

To sum things up, we learned that wxPython is popularly used for developing GUI-based desktop applications in Python, and that Python also has some other cool alternatives for it. We went through the commands to download and install it on all popular Operating Systems. Lastly, we learned how to make a modularized application using wxPython which can be easily extended, as could be seen in this tutorial where we built up on a basic skeleton app and added more features step by step.