Of course, you will never write a script like the previous one in earnest. While it works, it doesn't even show the correct way of setting up a PyQt application. A far superior structure is as follows:
Example 6-2. hello2.py — a better hello world
import sys from qt import * class HelloButton(QPushButton): def __init__(self, *args): apply(QPushButton.__init__, (self,) + args) self.setText("Hello World") class HelloWindow(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__, (self,) + args) self.button=HelloButton(self) self.setCentralWidget(self.button) def main(args): app=QApplication(args) win=HelloWindow() win.show() app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()")) app.exec_loop() if __name__=="__main__": main(sys.argv)
This is more like it! While still boring and trivial, this small program shows several important aspects of programming with Python and Qt: the subclassing of Qt classes in Python, the use of windows and widgets, and the use of signals and slots.
In most PyQt applications you will create a custom main window class, based on QMainWindow, and at least one custom main view widget, based on any Qt widget — it could be a listview, an editor window or a canvas, or, as in this case, a simple button. Although PyQt allows you to subclass almost any Qt class, you can't base a Python class on more than one Qt class at a time.
That is, multiple inheritance of Qt classes is not supported. This is seldom (if ever) a problem—try to imagine what a widget that looks like a checkbox and a radiobutton at the same time. Using two widgets in one custom widgets is another matter, called delegation, and is fully supported.
In this script we have subclassed QMainWindow to create a custom window that contains a pushbutton as its central widget. Almost always, a window will have the usual frills around the borders — menus, toolbars and statusbars. This is what QMainWindow is designed for. We didn't define any menu items, so the window is still a bit bare.
The central part of a window—the letterbox, so to speak—is where the application-specific functionality appears. This is, of course, our button. QMainWindow manages the resizing of its central widget automatically, as you might have noticed when dragging the borders of the window. Also, note the difference in geometry between this version of Hello World and the previous one: this is caused by the automatic layout handling that QMainWindow provides.
A better hello world
You set the central part of the window with the setCentralWidget() method:
self.setCentralWidget(self.button)
An application can have zero, one, or more windows — and an application shouldn't close down until the last window is closed. QApplication keeps count of the number of windows still open and will try to notify the world when the last one is closed. This is done through the signals/slots system. While this system will be discussed in depth in a later chapter, it's sufficiently important to warrant some consideration here.
Basically, objects can register an interest in each other, and when something interesting happens, all interested objects are notified. In this case, the QApplication object wants to know when the last window is closed, so it can quit.
app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
Let's analyze this line: the app object makes a connection between a signal lastWindowClosed() (which is sent by the application object itself), and its own quit() function. Using signals and slots from Python is extremely convenient, both for gui work and in more abstract situations where a decoupling between objects is desirable.
Another example of using signals and slots is in the following rewrite of the HelloWindow class:
Example 6-3. fragment from hello3.py
... class HelloWindow(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__, (self,) + args) self.button=HelloButton(self) self.setCentralWidget(self.button) self.connect(self.button, SIGNAL("clicked()"), self, SLOT("close()"))
We have added a line where the clicked() signal, which is emitted by the QPushButton when it is clicked, is connected to the close() slot of the HelloWindow class. Since HelloWindow inherits QMainWindow, it also inherits all its slot functions.
Now, if you click on the button, the window closes—and we have our first interactive PyQt application!
An interesting exercise is to create more than one window by rewriting the main function:
Example 6-4. Fragment from hello5.py
... def main(args): app=QApplication(args) winlist=[] for i in range(10): win=HelloWindow() win.show() winlist.append(win) app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()")) app.exec_loop() ...
If you run this version of the script, ten windows will rapidly pop up on your desktop. You can close each window by pressing either the button or using the window controls — the application will only stop when the last one is closed.
Try commenting out the line winlist.append(win):
Example 6-5. Fragment from hello4.py
... def main(args): app=QApplication(args) winlist=[] for i in range(10): win=HelloWindow() win.show() #winlist.append(win) app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()")) app.exec_loop() ...
and see what happens...
This is one of the interesting features in Python: in contrast to C++, Python has a garbage collector. (Actually, you can choose between a garbage collector and a reference counter, but I don't want to get that technical yet). This virtual garbage-man will remove unreferenced objects as soon as possible. That means that any object that doesn't have a Python variable name associated with it will disappear. (Unless the object is the child of a QObject; see Chapter 9 for all the details). If you were to try this trick in C++, keeping references would make no difference, as C++ does not delete unused objects for you, which can easily lead to nasty memory leaks.