A better Hello World
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.
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.