Python is well suited for rapid development of cross-platform applications of all sorts, and that includes desktop GUI apps. There are choices to be made when starting to develop a GUI application in Python, however, and this article provides the information you need to set out on the right path. We will discuss what serious GUI toolkits are available for Python, their strengths and weaknesses, and provide a simple demo application for each one.

Demo “hello world” application overview

The demo application is simple: a window containing a combo box that presents the choices “hello”, “goodbye”, and “heyo”; a text box in which free text can be entered; and a button which, when clicked, prints a greeting assembled from the values of the combo box and text box — e.g. “Hello, world!” — to the console. Here is a mockup of the example app:

Demo hello world application overview

Note that all the toolkits discussed here lend themselves to a similar object-oriented approach to our simple application; it is an object that inherits from a top-level GUI object, such as an application, window, or frame. The application window is filled with a vertical layout widget which manages the arrangement of the control widgets. Other GUI toolkits for Python, such as the long-obsolete pyFLTK or the simplistic easygui, take different approaches, but the toolkits discussed here represent the mainstream of Python GUI library development.

I have attempted to provide idiomatic code for each toolkit, but I am not expert in a couple of them; please let me know in the comments if I’ve done anything that might be misleading to new developers, and I’ll do my best to update the examples.

Choosing a Python GUI Toolkit

There are several toolkits available for GUI programming in Python; several are good, none are perfect, and they are not equivalent; there are situations where one or more otherwise excellent toolkits will not be appropriate. But the choice is not difficult, and for many applications, any GUI toolkit you choose will work. In the toolkit sections below, I’ve tried to honestly lay out the drawbacks and advantages of each, to help you make an informed choice.

A caution: Much of the information available online — for example, in the Python wiki’s GUI article or the Python 2.7 FAQ — is out-of-date and misleading. Many of the toolkits on those pages have been unmaintained for five years or more, and others are too immature or undocumented for serious use. Also, do not use the Choose Your GUI Toolkit page I’ve seen bandied about here and there; its weighting code makes it almost impossible to choose anything but wxPython.

We will look at the four most popular Python GUI toolkits: TkInter, wxPython, pyGTK/PyGobject, and PyQt/PySide.

TkInter

TkInter is the grand old man of the Python GUI world, having been distributed with Python almost since the language was founded. It is cross-platform and omnipresent, stable and reliable, and easy to learn, it has an object-oriented, brilliantly Pythonic API, and it works with all Python versions, but it has some drawbacks as well:

Limited support for theming

Before Python 2.7/3.1, TkInter had no support for theming, so on every platform its custom-drawn widgets looked like Motif circa 1985. Since, the ttk module has introduced themed widgets, which improve the look somewhat but leave some unorthodox controls, such as OptionMenus, in place.

Small selection of widgets

Tkinter lacks some widgets other toolkits provide, such as non-textual listboxes, real combo-boxes, scrolled windows, and others. Some additional controls have been supplied by third parties, e.g. Tix to supply the lack. Mock up your application in advance if you consider using TkInter, and make sure it can provide every control you need, or you could get burned later.

Demo App/Hello World in Tkinter

import Tkinter as tk
 
class ExampleApp(tk.Frame):
    ''' An example application for TkInter.  Instantiate
        and call the run method to run. '''
    def __init__(self, master):
        # Initialize window using the parent's constructor
        tk.Frame.__init__(self,
                          master,
                          width=300,
                          height=200)
        # Set the title
        self.master.title('TkInter Example')
 
        # This allows the size specification to take effect
        self.pack_propagate(0)
 
        # We'll use the flexible pack layout manager
        self.pack()
 
        # The greeting selector
        # Use a StringVar to access the selector's value
        self.greeting_var = tk.StringVar()
        self.greeting = tk.OptionMenu(self,
                                      self.greeting_var,
                                      'hello',
                                      'goodbye',
                                      'heyo')
        self.greeting_var.set('hello')
 
        # The recipient text entry control and its StringVar
        self.recipient_var = tk.StringVar()
        self.recipient = tk.Entry(self,
                                  textvariable=self.recipient_var)
        self.recipient_var.set('world')
 
        # The go button
        self.go_button = tk.Button(self,
                                   text='Go',
                                   command=self.print_out)
 
        # Put the controls on the form
        self.go_button.pack(fill=tk.X, side=tk.BOTTOM)
        self.greeting.pack(fill=tk.X, side=tk.TOP)
        self.recipient.pack(fill=tk.X, side=tk.TOP)
 
    def print_out(self):
        ''' Print a greeting constructed
            from the selections made by
            the user. '''
        print('%s, %s!' % (self.greeting_var.get().title(),
                           self.recipient_var.get()))
    def run(self):
        ''' Run the app '''
        self.mainloop()
 
app = ExampleApp(tk.Tk())
app.run()

wxPython

Note that at the time of writing, wxPython is only available for Python 2.x

Guido van Rossum, the creator of Python, says of wxPython:

wxPython is the best and most mature cross-platform GUI toolkit, given a number of constraints. The only reason wxPython isn’t the standard Python GUI toolkit is that Tkinter was there first.

At least, he said that once, a long time ago; and how much GUI programming does Guido van Rossum do, anyway?

At any rate, wxPython does have a lot of good points. It has a vast set of widgets, native look-and-feel on Windows, Linux, and OS X, and a large-enough user base that at least the obvious bugs have been caught. It’s not included with Python, but installation is easy, and it’s available for all recent Python 2.x releases; wxPython is probably now the most popular GUI library for Python.

It’s not perfect, though; its documentation is atrocious, and though the exemplary demo app is priceless documentation itself, it’s about all the help you’re going to get. I personally find the API unPythonic and unpleasant, but others disagree. Still, you’ll almost never get in the middle of a project using wxPython and find it can’t do what you need, which is a big recommendation for it.

Demo App/Hello World in wxPython

Python 2.x

import  wx
 
class ExampleApp(wx.Frame):
    def __init__(self):
        # Every wx app must create one App object
        # before it does anything else using wx.
        self.app = wx.App()
 
        # Set up the main window
        wx.Frame.__init__(self,
                          parent=None,
                          title='wxPython Example',
                          size=(300, 200))
 
        # The greetings available
        self.greetings = ['hello', 'goodbye', 'heyo']
 
        # Layout panel and hbox
        self.panel = wx.Panel(self, size=(300, 200))
        self.box = wx.BoxSizer(wx.VERTICAL)
 
        # Greeting combobox
        self.greeting = wx.ComboBox(parent=self.panel,
                                    value='hello',
                                    size=(280, -1),
                                    choices=self.greetings)
 
        # Add the greeting combo to the hbox
        self.box.Add(self.greeting, 0, wx.TOP)
        self.box.Add((-1, 10))
 
        # Recipient entry
        self.recipient = wx.TextCtrl(parent=self.panel,
                                     size=(280, -1),
                                     value='world')
 
        # Add the greeting combo to the hbox
        self.box.Add(self.recipient, 0, wx.TOP)
 
        # Add padding to lower the button position
        self.box.Add((-1, 100))
 
        # The go button
        self.go_button = wx.Button(self.panel, 10, '&Go')
 
        # Bind an event for the button
        self.Bind(wx.EVT_BUTTON, self.print_result, self.go_button)
 
        # Make the button the default action for the form
        self.go_button.SetDefault()
 
        # Add the button to the hbox
        self.box.Add(self.go_button, 0, flag=wx.ALIGN_RIGHT|wx.BOTTOM)
 
        # Tell the panel to use the hbox
        self.panel.SetSizer(self.box)
 
    def print_result(self, *args):
        ''' Print a greeting constructed from
            the selections made by the user. '''
        print('%s, %s!' % (self.greeting.GetValue().title(),
                           self.recipient.GetValue()))
 
    def run(self):
        ''' Run the app '''
        self.Show()
        self.app.MainLoop()
 
# Instantiate and run
app = ExampleApp()
app.run()

pyGTK/pyGobject

pyGTK is a cross-platform widget toolkit based on GTK+, a broadly-used GUI toolkit developed originally for the image manipulation program GIMP. pyGobject is a new revision of pyGTK that uses GObject introspection and supports features of GTK3. Both versions’ APIs are closely related, and their capabilities and drawbacks are similar, so they can be considered as one for our purposes. The developers of pyGTK recommend using pyGobject for new development.

pyGTK only uses native widgets on Windows and Linux. Support for native widgets and windowing on OS X, though available in GTK+, is still under development in pyGTK; right now its look and feel on Mac emulates that of Linux. The widgets are, however, themeable, and there are a large number of very attractive themes. pyGTK is also most successful with Python 2.x; new versions begin to support Python 3 but are not complete, especially on Windows. Installation for Windows previously required downloading multiple packages from different sources, but recently all-in-one binary installers have been made available.

On the positive side, pyGTK is stable and well-tested and has a full selection of widgets. It also has excellent documentation and, if you like that sort of thing, an excellent visual GUI generator called Glade. Its API is straightforward and mostly simple, with only a few exceptions, and there is fairly good example code available. The bottom line for pyGTK: it is a solid and capable GUI toolkit that suffers from cross-platform inconsistency and the effects of being less popular than other equally good alternatives.

A special note about choosing pyGTK: if your application involves a variety of simple, text-based listboxes, pyGTK’s design will cause problems. Its strict separation of concerns and model-view-controller division make listboxes unusually complicated to develop compared to other toolkits. Failing to observe this point put me way behind on an important project when I was new to Python GUI development.

Demo App/Hello World in pyGTK/pyGobject

import gtk
 
class ExampleApp(gtk.Window):
    ''' An example application for pyGTK.  Instantiate
        and call the run method to run. '''
    def __init__(self):
        # Initialize window
        gtk.Window.__init__(self)
        self.set_title('pyGTK Example')
        self.set_size_request(300, 200)
        self.connect('destroy', gtk.main_quit)
 
        # Structure the layout vertically
        self.vbox = gtk.VBox()
 
        # The greeting selector --- note the use of the convenience
        # type ComboBoxText if available, otherwise convenience
        # function combo_box_new_text, which is deprecated
        if (gtk.gtk_version[1] > 24 or
            (gtk.gtk_version[1] == 24 and gtk.gtk_version[2] > 10)):
            self.greeting = gtk.ComboBoxText()
        else:
            self.greeting = gtk.combo_box_new_text()
            # fix method name to match gtk.ComboBoxText
            self.greeting.append = self.greeting.append_text
 
        # Add greetings
        map(self.greeting.append, ['hello', 'goodbye', 'heyo'])
 
        # The recipient text entry control
        self.recipient = gtk.Entry()
        self.recipient.set_text('world')
 
        # The go button
        self.go_button = gtk.Button('_Go')
        # Connect the go button's callback
        self.go_button.connect('clicked', self.print_out)
 
        # Put the controls in the vertical layout
        self.vbox.pack_start(self.greeting, False)
        self.vbox.pack_start(self.recipient, False)
        self.vbox.pack_end(self.go_button, False)
 
        # Add the vertical layout to the main window
        self.add(self.vbox)
 
    def print_out(self, *args):
        ''' Print a greeting constructed from
        the selections made by the user. '''
        print('%s, %s!' % (self.greeting.get_active_text().title(),
                           self.recipient.get_text()))
    def run(self):
        ''' Run the app. '''
        self.show_all()
        gtk.main()
 
app = ExampleApp()
app.run()

PyQt/PySide

Qt is more than a widget toolkit; it is a cross-platform application framework. PyQt, its Python interface, has been around for years, and is stable and mature; it has gained some cruft over the years, with two APIs available, known as API 1 and API 2, and a large number of deprecated features. Furthermore, even though Qt is available under the LGPL, PyQt is licensed under the GNU GPL versions 2 and 3 or in a fairly expensive commercial version, limiting your licensing options for your code.

PySide is a response to the drawbacks of PyQt. It is released under the LGPL and omits all deprecated features prior to PyQt 4.5 as well as the entire API 1, but otherwise is almost fully compatible with PyQt API 2. It is slightly less mature than PyQt, but is more actively developed.

No matter which wrapper you choose, Python and Qt work beautifully together. The more modern API 2 is fairly Pythonic and is clear, there is very good documentation available (although its roots in the C++ Qt documents are obvious), and the resulting applications look smashing in recent versions, if not quite native. There is every GUI widget you could possibly desire, and Qt supplies more — interesting classes to deal with XML, multimedia, database integration, and networking — though you’re probably better off using the equivalent Python libraries for most of the extra functionality.

The biggest downside of using a Qt wrapper is that the underlying Qt framework is huge. If it’s important to you that your distributable package be small, choose TkInter, which is not only small, but already a part of Python. Also, while Qt employs operating system APIs to adapt its widgets to your environment, not all are strictly native; wxPython would be a better choice if native look-and-feel is a major consideration for you. But PyQt has a lot going for it, and is certainly worth consideration.

Demo App/Hello World in PyQt/PySide

Note: For Qt, I’ve used PySide; the example code would run using PyQt with only a few lines’ difference.

import sys
from PySide.QtCore import *
from PySide.QtGui import *
 
class ExampleApp(QDialog):
    ''' An example application for PyQt. Instantiate
        and call the run method to run. '''
    def __init__(self):
        # create a Qt application --- every PyQt app needs one
        self.qt_app = QApplication(sys.argv)
 
        # The available greetings
        self.greetings = ['hello', 'goodbye', 'heyo']
 
        # Call the parent constructor on the current object
        QDialog.__init__(self, None)
 
        # Set up the window
        self.setWindowTitle('PyQt Example')
        self.setMinimumSize(300, 200)
 
        # Add a vertical layout
        self.vbox = QVBoxLayout()
 
        # The greeting combo box
        self.greeting = QComboBox(self)
        # Add the greetings
        list(map(self.greeting.addItem, self.greetings))
 
        # The recipient textbox
        self.recipient = QLineEdit('world', self)
 
        # The Go button
        self.go_button = QPushButton('&Go')
        # Connect the Go button to its callback
        self.go_button.clicked.connect(self.print_out)
 
        # Add the controls to the vertical layout
        self.vbox.addWidget(self.greeting)
        self.vbox.addWidget(self.recipient)
        # A very stretchy spacer to force the button to the bottom
        self.vbox.addStretch(100)
        self.vbox.addWidget(self.go_button)
 
        # Use the vertical layout for the current window
        self.setLayout(self.vbox)
 
    def print_out(self):
        ''' Print a greeting constructed from
            the selections made by the user. '''
        print('%s, %s!' % (self.greetings[self.greeting.currentIndex()].title(),
                           self.recipient.displayText()))
 
    def run(self):
        ''' Run the app and show the main form. '''
        self.show()
        self.qt_app.exec_()
 
app = ExampleApp()
app.run()

 

Leave a Reply

Your email address will not be published. Required fields are marked *