SoFunction
Updated on 2024-12-20

Getting Started with Python GUI Programming with PyQt.

In general, choosing a GUI toolkit for your application can be tricky. Programmers using Python (and many languages as well) have a wide variety of GUI toolkits to choose from, each with its own advantages and disadvantages. Some are faster than others, some are smaller; some are easy to install, and some are better suited for cross-platform use (for which, it should be noted, some support specific features you need to fulfill). Of course, the various libraries come with various licenses.

For Python programmers, the default GUI choice is Tk (via the Tkinter binding)-for obvious reasons.Tkinter and the idle IDE were written by Python's founders, and they appear as the default choice for most Python distributions. The standard Python documentation discusses Tkinter, but not any of the other GUI bindings. This is intentional! At the very least, it can be assumed that if Tk and Tkinter weren't so bad, there would be no reason for programmers to look for alternatives. To induce Python programmers to abandon the default choices, a toolkit must provide something extra, and PyQt is such a toolkit.

The advantages of PyQt far outweigh those of Tkinter (which also has several disadvantages.) Both Qt and PyQt are fast; Qt and PyQt are designed to be completely object-oriented; and Qt provides a well-designed collection of windowing artifacts, which is much larger than that provided by Tk. On the downside, Qt's license is more restrictive than many toolkits (at least on non-Linux platforms); installing Qt and PyQt correctly is often complicated; and, since Qt is a fairly large library, users of PyQt applications will need to try to complete the installation of Qt and PyQt, which makes distribution difficult. (Read about Qt bindings for other languages later in this article.)

PyQt is strictly under the Qt distribution license. In particular, it is available under the GPL on UNIX/X11 platforms, and can be used in the Qt Palmtop Environment environment on the Zaurus, and free-as-in-free-beer Windows packages exist for older versions of Qt. PyQt is commercially licensed for Windows.

For the purposes of this article, there is one aspect of PyQt that outperforms many other toolkits and deserves special attention: Qt uses a mechanism called signals/slots to pass events and messages between window artifacts (and other objects). This mechanism is completely different from the callback mechanism used by most toolkits, including Tkinter. It is much easier to use signals/slots to control inter-object communication in a flexible and maintainable way than it is to use the fragile callback style. The larger the application, the more important this advantage of Qt becomes.

Boudewijn Rempt, one of the authors of this article, has published a book on application development using PyQt. GUI Programming with Python: QT Edition (see references) shows how to design and develop complete GUI applications, from initial conception to distribution.
Sample applications

To show the contrast between signals/slots and callbacks, we've provided a write-for-fun application that uses Tkinter and PyQt. Although the PyQt version is actually not simpler for this basic program, it has demonstrated the better modularity and maintainability of PyQt applications.

The application includes four window building blocks:

  1. "Quit" button (to communicate with the entire application)
  2. "Log Timestamp" button (for messages between window components)
  3. Text area showing a scrollable list of time stamps for logged logs
  4. Message window component showing timestamps of logged logs

In Tkinter, we can implement the application like this:

Listing 1. Tkinter application

  #!/usr/bin/python
import sys, time
from Tkinter import *
class Logger(Frame):
  def __init__(self):
    Frame.__init__(self)
    (expand=YES, fill=BOTH)
    ("Timestamp logging application")
     = []
     = Text(height=6, width=25)
     = StringVar()
     = Message(font=('Sans',24),
                textvariable=)
     = Button(text="Log Timestamp",
             command=self.log_timestamp)
     = Button(text="Quit", command=)
    (side=LEFT)
    ()
    (side=TOP, expand=YES, fill=BOTH)
    (side=BOTTOM, fill=BOTH)
  def log_timestamp(self):
    stamp = ()
    (END, stamp+"\n")
    (END)
    (stamp)
    ("% 3d" % len())
if __name__=='__main__':
  Logger().mainloop()

This version of Tk uses the log_timestamp() method as the command= argument to the button. This method needs to operate individually on all the window artifacts it wants to affect in turn. This style is vulnerable if we want to change the effect of a button press (e.g. also logging the timestamp). You can do this through inheritance:

Listing 2. Tkinter enhancements

    class StdOutLogger(Logger):
  def log_timestamp(self):
    Logger.log_timestamp(self)
    print [-1]

But the author of this subclass needs to be fairly precise about what Logger.log_timestamp() already does; and there's no way to get rid of the message except by completely overriding the .log_timestamp() method in the subclass and not calling the parent method.

A very basic PyQt application always has some sample code that is the same everywhere, as does the Tkinter code. However, when we look further into the code needed to set up the application, as well as the code to display the window artifacts, the differences become apparent.

Listing 3. PyQt application

 

  #!/usr/bin/env python
import sys, time
from qt import * # Generally advertised as safe
class Logger(QWidget):
  def __init__(self, *args):
    QWidget.__init__(self, *args)
    ("Timestamp logging application")
     = QGridLayout(self, 3, 2, 5, 10)
     = QTextEdit(self)
    (250, 300)
    ()
     = QLabel("", self)
    (QFont("Sans", 24))
     = QPushButton("&Log Timestamp", self)
     = QPushButton("&Quit", self)
    (, 0, 2, 0, 0)
    (, 0, 1)
    (, 1, 1)
    (, 2, 1)
    (, SIGNAL("clicked()"),
           self.log_timestamp)
    (, SIGNAL("clicked()"),
           )
  def log_timestamp(self):
    stamp = ()
    (stamp)
    (str(()))
if __name__ == "__main__":
  app = QApplication()
  (app, SIGNAL('lastWindowClosed()'), app,
         SLOT('quit()'))
  logger = Logger()
  ()
  (logger)
  app.exec_loop()

By creating a layout manager, the Logger class gets to work. Layout managers are a complex subject in any GUI system, but Qt's implementation makes it easy. In most cases, you'll use Qt Designer to create a general GUI design, which you can then use to generate Python or C++ code. You can then subclass the generated code to add functionality.

In this example, however, we chose to create the layout manager manually. Window artifacts are placed in individual cells of the grid, or can be placed across multiple cells. Where Tkinter requires named parameters, PyQt does not allow them. This is an important difference that often confuses people working in both environments.

All Qt window artifacts work naturally with QString objects, but not with Python strings or Unicode objects. Fortunately, the conversion is automatic. If you use a string or Unicode argument in a Qt method, it's automatically converted to a QString. there's no reverse conversion: if you call a method that returns a QString, you get a QString.

The most interesting part of the application is where we connect the clicked signals to functions. One button is connected to the log_timestamp method; the other is connected to the close method of the QWidget class.
Figure 1. A snapshot of logger-qt's screen.
A snapshot of logger-qt's screen

 (385×336)

Now we want to add logging to the standard output of this application. This is very easy. We can either subclass the Logger class or, for demonstration purposes, create simple standalone functions:

Listing 4. PyQt enhancements

  def logwrite():
  print(())
if __name__ == "__main__":
  app = QApplication()
  (app, SIGNAL('lastWindowClosed()'), app,
        SLOT('quit()'))
  logger = Logger()
  (, SIGNAL("clicked()"), logwrite)
  ()
  (logger)
  app.exec_loop()

As we can see from the code above, this is what happens when you connect the log QPushButton's clicked() signal to the new function. Note: Signals can also deliver any data to the slot they are connected to, although we don't show an example of this here.

If you do not want to call the original method, then you can signal disconnect from the slot, for example by adding the following line before the () line:

Listing 5. PyQt enhancements

          (, SIGNAL("clicked()"),
          logger.log_timestamp)

The GUI will no longer be updated.

Other GUI bindings for Python

PyQt may not be very useful in a given instance, either because of license status issues or platform availability issues (or, perhaps, because redistribution is difficult, e.g., the size is large). For this reason (and for comparison), we would like to point out some other popular GUI toolkits for Python.

    Anygui
Anygui is not actually a GUI toolkit, but an abstract wrapper around a large number of toolkits (even amazing ones like curses and Java/Jython Swing). In terms of programming style, using Anygui is similar to using Tkinter, but checking off the underlying toolkit is either automatic or a configuration call. Anygui is great because it allows applications to run on widely varying platforms unchanged (but therefore it supports the "lowest common denominator" of supported toolkits). ").
    PyGTK
The PyGTK binding wraps the GTK toolkit used under the GPL, which is the basis for the popular Gnome environment.GTK is fundamentally an X Window toolkit, but it also has beta-level support for Win32 and alpha-level support for BeOS. In the normal paradigm, PyGTK uses callbacks for window artifacts. Bindings exist between GTK and a large number of programming languages, not just Qt, or even Tk.
    FXPy
The Python binding FXPy wraps the FOX toolkit, which has been ported to most UNIX-like platforms, as well as Win32. Like most toolkits, both FOX and FXPy use the callback paradigm; FOX is licensed under the LGPL.
    wxPython
This bundle wraps the wxWindows toolkit. Like FOX or GTK, wxWindows was ported to Win32 and UNIX-like platforms (but not to MacOS, OS/2, BeOS, or other "minor" platforms - although it has alpha support for MacOSX). In terms of paradigms, wxP In terms of paradigms, wxPython is close to callback style. wxPython pays more attention to inheritance structures than most other toolkits, and it uses "events" rather than callbacks. But essentially, events are still attached to a single method, which may then need to act on various window artifacts.
    win32ui
win32ui is part of the win32all package, which wraps the MFC classes. Obviously, this toolkit is a Win32-specific library. mfc is actually more than just a GUI toolkit, it uses a mix of paradigms. For those who want to create Windows applications, win32ui will get you "closer to the essence" than other toolkits.

Using Qt from other languages

As with Python, it is possible to use the Qt toolkit from a large number of other programming languages. If we had a free choice, we would prefer Python to any other language, and external constraints such as company policies and connections to other codebases can dictate the choice of programming language. External constraints such as company policies and connections to other codebases can dictate the choice of programming language; the original language of Qt is C++, but there are also bindings for C, Java, Perl, and Ruby. For comparison with the Python example, let's discuss an application written in Ruby and Java for fun.

Ruby/Qt is very similar in usage to PyQt. The two languages have similar dynamics and simplicity, so the code is similar except for spelling differences:

Listing 6. Qt2 application

#!/usr/local/bin/ruby
require 'qt2'
include Qt2
a = ([$0] + ARGV)
hello = ('Hello world!')
(100, 30)
( hello, QSIGNAL('clicked()'), a, QSLOT('quit()'))
(hello)



Java is always a bit longer than a scripting language, but the basic parts are the same. A minimal qtjava application with the same functionality is similar:

Listing 7. Qt2 application

import .*;
public class HelloWorld {
 public static void main(String[] args)
 {
  QApplication myapp = new QApplication(args);
  QPushButton hello = new QPushButton("Hello World", null);
  (100,30);
  (hello, SIGNAL("clicked"),
         this, SLOT("quit()"));
  (hello);
  ();
  ();
  return;
 }
 static {
  ("qtjava");
  try {
    Class c = ("");
  } catch (Exception e) {
    ("Can't load qtjava class");
  }
 }
}

PyQt is an attractive and fast interface that integrates the Qt toolkit with the Python programming language. In addition to the wide variety of windowing artifacts provided by the toolkit, the signal/slot programming style used by Qt is more productive and maintainable than the callback style used by most other GUI toolkits.