From: www.itworld.com
April 13, 2001 —
In principle, the X Window system provides a programming interface for all Unix and many other operating systems. However, X Window is too low level to be practical for most projects, which is why dozens of higher-level abstractions have been defined to simplify GUI construction.
Most GUI toolkits for Unix are presented through their C programming interfaces or, occasionally, with Java. But the number of toolkits available to Python programmers is remarkable, which is one of the reasons we believe Python is a far better vehicle for exposition and most development. One of the most interesting of these toolkits, PyQt, has just become even more interesting.
What Qt 2.x gives
Version 0.12 of PyQt supports Qt 2.x. Qt is the full-featured, Unicode-aware, cross-platform, theme-configurable toolkit from Troll Tech that's the basis for the upcoming KDE2 desktop for Unix. Qt provides an abundance of widgets, customizable features, high-quality documentation, and extensibility. We detail its capabilities below.
Conventional programs operate best with the Roman alphabet and the English language. Unicode, however, is a relatively new encoding standard that makes it practical for programmers to manage almost any human language. Qt supports Unicode on all platforms.
While much of Qt's early buzz came from Linux practitioners, the toolkit is equally at home with any Unix or Windows OS. Variations are also available for QNX and a few other specialized OSs.
You can customize Qt with colors and fonts, and you can customize the shape of each widget. Your application can, at the runtime request of the user, look like a Windows application, a Motif, or a CDE, and if you want to imitate NextStep, Mac OS, or Swing, it's straightforward to create code that does just that.
Several important products already rely on Qt, including the KDE and KDE2 desktops, Kylix, YAST 2, QCad, and the Opera browser.
On a technical level Qt is a C++ library with support for interesting features including signals and slots. Slots, which can be thought of as a kind of weak reference, do the work in Qt that callbacks accomplish for other GUI toolkits. Slots promote better type safety than do callbacks, and they are especially handy for model-view-controller (MVC) designs. MVC is widely recognized as a clean, rational, object model that naturally expresses sound GUI design.
Qt for Python
C++ programmers aren't the only ones enjoying Qt's benefits. Thanks to the work of independent consultant Phil Thompson, all of its power is now available for Python developers too, including signals and slots, Unicode strings, themes, C++ class variables, and operator overloading.
The documentation available from Troll Tech for Qt is of equal benefit to Python developers. PyQt binds Qt to Python in such a natural way that PyQt programmers read the C++ documentation with little more than a mental regular expression for translation. The PyQt distribution also includes many example programs, PyQt's mailing list is helpful, and its tutorial has a record of success.
PyQt is free for Windows and Unix, while Qt itself is free for Unix. The Qt libraries for Windows are available only under a commercial license from Troll Tech.
Qt2 is richer than most alternative GUI toolkits, and the PyQt binding is correspondingly weighty, at about 5 megabytes for common platforms. However, it is a shared library, so running two PyQt applications generally fills up little more than 5 megabytes instead of 10.
Because KDE2 isn't even in alpha (as of mid-May 2000), no bindings are available for it. Your current choice is between either Qt 1.x with KDE 1.1.2 desktop support, or Qt 2.x support without KDE. KDE desktop integration offers common resources like icons, toolbar images, session management, and desktop-wide look-and-feel customization. KDE2 will also offer component embedding (KParts) and a distribution object model (DCOP). Work has already started on preliminary KDE2 bindings.
A quick tutorial
To deepen your understanding of PyQt's key concepts, you'll want to work through a small PyQt demonstration. For this purpose, one of us (Rempt) has selected a useful little project: a character selection window with Unicode support. You can download its source at: http://www.valdyas.org/python/qchm.tgz. In just 200 lines it exhibits Qt 2.1's Unicode support, themability, and a common dialog, as well as the signals/slot mechanism.
One of Qt's great benefits is the ease with which you can achieve standard functionality. As we construct the main window in the next section you'll see how straightforward it is to create common dialogs and themes, and how naturally the application-specific meat supports Unicode. To properly appreciate the productivity of those features you first need to understand PyQt architectural principles.
We organize PyQt projects in three main files with supporting files for classes as needed. They are the main.py application starter, a mainwin.py main window definition, and a view.py file that implements the code for the heart of the application, which graphically occupies the central area between the toolbar and the status bar.
A Qt application always consists of a QApplication object that shows a main window. All widget objects in a Qt application form a hierarchical parent-child tree, starting with the main window. The application object isn't visible because it's an abstract object that manages the visible parts of the application.
This is the code for main.py:
import sys
from qt import *
from mainwin import QCharMap
def main(argv):
app=QApplication(sys.argv)
win=QCharMap()
win.show()
app.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()'))
app.exec_loop()
if __name__=="__main__":
main(sys.argv)
Most interesting here is the line
app.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()'))
That is our first example of the signals/slot mechanism of Qt. The experienced Qt eye understands that when the user closes the last window of the application, the application fires an event called a signal. The signal tells all listeners that the last window is closed: 'lastWindowClosed'. In this case, the application listens to its own signals.
PyQt's QApplication class has a function, quit(), that quits the main event loop. By connecting the lastWindowClosed() signal to the quit() method (slot), we ensure that when the last window is closed the quit() method is called.
The classes in the C++ Qt library accept functions specially marked as slots only in such uses as the one above. The Python binding is less demanding: every Python-class method subclassed from QObject can be used as a slot, with no other restrictions.
The main window
Qt applications appear as one or more windows, each with a menu bar, one or more toolbars, and a status bar. Qt 2.1 also supports multiple document interface (MDI) windows on all platforms.
The main window object, an instance of the QMainWindow class, not only creates the application window, menu bar, toolbar, and status bar, but also acts as a clearinghouse for such events as menu or toolbar choices. The main window thus functions like a central controller or director of the application.
The main window contains one main view: the area between the toolbar and the status bar. You can change the main view during runtime, which can be very useful. For instance, if you are writing an HTML editor, you can switch between a view that shows the code and a view that shows the rendered HTML.
The code necessary to create menus, toolbars, and a status bar is fairly tedious. KDE2 offers a system of desktop-wide standard items and application-specific XML files that define the menu and toolbar. Presumably this will simplify coding with future releases of Qt. For Qt 2.1, however, the developer must still code those items. For example, to show a toolbar button we create an instance of the class QToolButton, which combines:
self.buttons.append(QToolButton( Icon("text.xpm")
, "Font"
, "Change the font"
, self.slotEditFont
, self.toolbar
)
)
Without the assignment of that return value to a list, Python's reference-counting scheme would be free to destroy the QToolButton immediately after creation.
Likewise, a menu item is simple to implement.
self.edit_menu.insertItem(i18n("&Options"), self.slotEditOptions)
You can create a pop-up menu for each menu in the menu bar and add items to it. Menu items can also have icons or check boxes.
Creation of the main view is straightforward:
def initView(self):
self.view = QCharView(self, "QCharView")
self.connect(self, PYSIGNAL("sigFontChanged"), self.view.setFont)
self.setCentralWidget(self.view)
self.setCaption("Qt Unicode script viewer")
self.view.setFocus()
QCharView, a fairly simple widget, is created with the main window as parent (this is the self parameter in self.view = QCharView(self, "QCharView")). Completing that task makes self.view central widget the main window.
This is a good place to connect the various signals to slots in the main view. We also set a nice caption and give the view focus. If we don't, the keyboard focus will be on the menu bar when the application is started, a place no user will expect to find it.
Common dialogs
We call up the Qt 2.1 font dialog from a menu option in the main window. In PyQt, the font dialog returns a tuple, which consists of the font object chosen, and a Boolean, which indicates whether the OK button has been clicked. Contrast this with the C++ font dialog, which only returns the font and has a by-reference parameter set to true when the OK button is clicked. We find the Python approach much more pleasant.
If the OK button has been clicked, the slotEditFont() method emits a signal to tell the world which new font the user has chosen.
def slotEditFont(self):
font, OK=QFontDialog.getFont(self)
if OK:
self.emit(PYSIGNAL("sigFontChanged"),(font,))
Themes
Qt 2.1 and PyQt are fully themable, which means you can write your own custom widget styles in Python. Qt 2.1 ships with four built-in styles: Motif, CDE, Windows, and Platinum. Setting a style is simply a matter of creating a style object and assigning it. The qApp object, which refers to the QApplication object, is in every class in the application.
def slotEditMotif(self): self.style=QMotifStyle(TRUE) qApp.setStyle(self.style)
Creating your own widget style is a bit laborious but can be very rewarding. An example in the PyQt distribution -- themes.py -- includes a custom Norwegian wood style.
The main view
The main view of the application is very simple. Derived from QWidget, the view shows a combo box and a table of Unicode characters. The layout is handled by an easy-to-use QVBoxLayout manager. The QGridLayout is more flexible but also more complicated.
class QCharView(QWidget):
def __init__(self, *args):
apply(QWidget.__init__,(self,)+args)
self.box=QVBoxLayout(self)
self.comboCharset=uniCharsetSelector(FALSE, self)
self.box.addWidget(self.comboCharset)
self.charTable=uniCharsetTable(self)
self.box.addWidget(self.charTable)
A combo box has a fixed height of one line, so the layout manager fills all remaining space with the character table.
Qt has three simple and effective layout managers that include horizontal and vertical boxes and a grid layout manager. While Motif is the industry standard, its layout management is recognized as both primitive and cranky. Qt is a welcome contrast because its layout managers are comparatively simple yet robust. Automatic layout management is increasingly important for modern applications that depend on localized strings that may vary in length, user-selected font sizes, and the resizing of windows, among other features. Even with Qt's well-written, built-in layout managers, complex layouts with many constituent widgets can be hard to get right.
The next source code fragment shows how both widgets are subclassed within Python from Qt widgets. To the surprise of even the good folks at Troll Tech, PyQt makes it perfectly feasible to subclass Qt widgets in Python. Phil Thompson's avowed goal is to make everything possible in PyQt that the C++ binding of Qt exposes.
class uniCharsetSelector(QComboBox):
...
class uniCharsetTable(QTable):
...
Unicode support
PyQt also offers access to the Qt QString class, which is fully Unicode enabled. Unfortunately there is no easy way to move Unicode data from QString to the Python 1.6 Unicode strings, or vice versa. For now, you need to use either single Unicode characters or byte arrays. Keep in mind, however, that Python 1.6 is only in its second alpha stage and Unicode support in Python is still in development.
In this case we want single Unicode characters added to our character table:
def slotShowCharset(self, start, end):
qstring=QString("<TABLE>")
counter=0
for wch in range(start, end + 1):
if counter==0:
qstring.append(QString("<TR>"))
qstring.append(QString("<TD>"))
qstring.append(QString(QChar(wch)))
qstring.append(QString("</TD>"))
counter=counter + 1
if counter==10:
counter=0
qstring.append(QString("</TR>"))
qstring.append(QString("</TABLE>"))
self.setText(qstring)
That small function loops through a range of Unicode code points (as defined in the Unicode table of character blocks), then creates a QString filled with an HTML table of the Unicode character and displays the table through a rich text view.
Tying widgets together with signals and slots
The main view has two widgets: a combo box from which the user can select a Unicode character block, and a rich text where we show the characters. Qt's signals and slots mechanism ties the two widgets together.
The choice of a new character block causes the combo box to emit a signal:
def sigActivated(self, index):
begin, start=self.charsets[index]
self.emit(PYSIGNAL("sigActivated"),(begin, start))
The edit box has a function to change the character block:
def slotShowCharset(self, start, end):
self.setText(QString(""))
for wch in range(start, end + 1):
ustring=QString(QChar(wch))
self.append(ustring)
We connect the signal and slot on creation of the two widgets:
def __init__(self, *args):
...
self.comboCharset=uniCharsetSelector(FALSE, self)
...
self.charTable=uniCharsetTable(self)
...
self.connect(self.comboCharset, PYSIGNAL("sigActivated")
, self.charTable.slotShowCharset
)
We have used the sigXXX() formula for naming methods that emit signals, and the slotXXX() formula for slots. This is a convention, not a necessity, that PyQt imposes. You can connect C++ signals to Python slots, Python signals to C++ slots, C++ signals to C++ slots, or Python signals to Python slots.
Finishing touches for delivery
To protect users from having to launch our application as python main.py, we wrap it into the application-starter shell script qchartable:
#!/usr/bin/env bash python ./main.py
To turn our application into a package we can deliver to unsophisticated end users, we need an installation script. This might create a shared, systemwide directory for common data, such as icon images and data tables, or it might create a systemwide library directory for compiled python files. Finally, it might copy the start-up script to a directory in the path.
Conclusion
Well, that's all. As you've seen, PyQt is an easy way to quickly create good-looking, full-featured, and flexible GUI applications that perform quite well. For a look at the complete source code, please see the Resources below.
Resources and Related Links
Other Unix Insider resources
Unix Insider