All basic screen components are available in PyQt: buttons, frames, edit controls, listboxes and comboboxes. All these widgets can be drawn in any number of styles, and you can even define your own style. Note that the window titlebar and borders are not defined by the widget style, but by the system you are running. The borders in these screenshots are from the KDE System++ style.
Basic widgets in the CDE style
Basic widgets in the motif style
Basic widgets in the motif plus style
Basic widgets in the platinum style
Basic widgets in the SGI style
Basic widgets in the Windows style
Frames are used to group other widgets — either visibly (for instance by drawing a nice bevel around them), or invisibly (by managing the geometry of those widgets. PyQt offers all the usual options, from panels to ridges to bevels, with horizontal and vertical lines thrown in for good measure.
Pushbuttons are the mainstay of gui programming. They can be adorned with text or with a picture, but not both (you need a QToolButton for that). QPushButtons are based on an abstraction of all button functionality, namely QButton, which is also the parent of QCheckBox, QRadioButton and QToolButton. In honor of QPushButton's central importance, I want to present a ‘Hello' application with four buttons, each in a different style. This also shows a frame.
Example 10-7. buttons.py - Four pushbuttons saying ‘hello'.
# # buttons.py # from qt import * import sys class MainWindow(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__, (self,) + args) self.setCaption("Buttons") self.grid=QGrid(2, self) self.grid.setFrameShape(QFrame.StyledPanel) self.bn1=QPushButton("Hello World", self.grid) self.bn1.setFlat(1) self.bn2=QPushButton("Hello World", self.grid) self.bn2.setDefault(1) self.bn3=QPushButton("Hello World", self.grid) self.bn3.setToggleButton(1) self.bn3.setDown(1) self.bn4=QPushButton("Hello", self.grid) self.setCentralWidget(self.grid) def main(args): app=QApplication(args) win=MainWindow() win.show() app.connect(app, SIGNAL("lastWindowClosed()") , app , SLOT("quit()") ) app.exec_loop() if __name__=="__main__": main(sys.argv)
buttons.py
Labels are ubiquitous in a gui application — and the PyQt QLabel offers much more than just plain-text labels for use in dialog boxes. PyQt labels can also contain rich text or a QMovie, such as an animated GIF or PNG. Through the setBuddy method, a QLabel can be associated with another control. If any character in the label text is prefixed by an ampersand — & — that character will be shown underlined, and by pressing alt-character, the user can jump to the control associated with the label through the buddy property.
Example 10-8. label.py - a label associated with an edit control
# # label.py # import sys from qt import * class dlgLabel(QDialog): def __init__(self,parent = None,name = None,modal = 0,fl = 0): QDialog.__init__(self,parent,name,modal,fl) self.setCaption("label dialog") if name == None: self.setName("dlgLabel") self.layout=QHBoxLayout(self) self.layout.setSpacing(6) self.layout.setMargin(11) self.label=QLabel("&Enter some text", self) self.edit=QLineEdit(self) self.label.setBuddy(self.edit) self.layout.addWidget(self.label) self.layout.addWidget(self.edit) if __name__ == '__main__': app = QApplication(sys.argv) QObject.connect(app, SIGNAL('lastWindowClosed()') , app , SLOT('quit()') ) win = dlgLabel() app.setMainWidget(win) win.show() app.exec_loop()
label.py
If you press alt-e after starting the label.py script (which is on the CD-ROM), you'll see the cursor appearing in the edit field.
You might wonder why the cursor is not the control that accepts user input when you start the script — this is a property of PyQt. On starting an application, the main window has the focus, not the controls associated with it. If you want to make the user's life easier, call setFocus() on the main widget in the __init__ of the main window:
# # label2.py # import sys from qt import * class dlgLabel(QDialog): def __init__(self,parent = None,name = None,modal = 0,fl = 0): QDialog.__init__(self,parent,name,modal,fl) self.setCaption("label dialog") if name == None: self.setName("dlgLabel") self.layout=QHBoxLayout(self) self.layout.setSpacing(6) self.layout.setMargin(11) self.label=QLabel("&Enter some text", self) self.edit=QLineEdit(self) self.label.setBuddy(self.edit) self.layout.addWidget(self.label) self.layout.addWidget(self.edit) self.edit.setFocus() if __name__ == '__main__': app = QApplication(sys.argv) QObject.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) win = dlgLabel() app.setMainWidget(win) win.show() app.exec_loop()
A label is a QWidget; it is thus quite possible to handle key and mouse events in a label. You might, for instance, want to make a clickable label that looks like an URL.
Radio buttons always remind of my tax return forms - check one and only one out of a certain number of choices. Radio buttons should not be used if there are more than five choices at most, and you would be well advised to limit yourself to no more than three. A constellation of radio buttons has the advantage that all options are visible at the same time, but it takes a lot of screen space. Do not use checkboxes instead of radio buttons for exclusive choices. People will get confused.
Radio buttons include their labels — and these labels can again be marked with an ampersand (&) for easy selection. In order to force the ‘one and only one' choice, combine the mutually exclusive radio buttons in a QButtonGroup, or one of the descendants: QVButtonGroup for vertical layouts (recommended) or QHButtonGroup for horizontal layouts (these look rather weird). A radiobutton can be initialized with setChecked().
Example 10-9. radio.py - a group of mutually exclusive options
# # label.py # import sys from qt import * class dlgRadio(QDialog): def __init__(self,parent = None,name = None,modal = 0,fl = 0): QDialog.__init__(self,parent,name,modal,fl) self.setCaption("radiobutton dialog") if name == None: self.setName("dlgRadio") self.layout=QVBoxLayout(self) self.buttonGroup=QVButtonGroup("Choose your favourite", self) self.radio1=QRadioButton("&Blackadder I", self.buttonGroup) self.radio2=QRadioButton("B&lackadder II", self.buttonGroup) self.radio3=QRadioButton("Bl&ackadder III", self.buttonGroup) self.radio4=QRadioButton("Bla&ckadder Goes Forth", self.buttonGroup) self.radio1.setChecked(1) self.layout.addWidget(self.buttonGroup) if __name__ == '__main__': app = QApplication(sys.argv) QObject.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) win = dlgRadio() app.setMainWidget(win) win.show() app.exec_loop()
radio.py
Checkboxes are another of those gui features that makes one think of bureaucratic forms. Check any that apply... Checkboxes are as easy to use as radiobuttons, and come with a label attached, like radiobuttons. A variation which makes for instant user confusion is the tri-state checkbox, in which there's a checked, unchecked and a doesn't apply state — the doesn't apply state is usually rendered to look just like a completely disabled checkbox. However, it is sometimes necessary to introduce this behavior. Imagine a dialog box that allows the user to set a filter:
A dialog with a tri-state checkbox in the ‘doesn't apply' state.
Now the user has three choices: either look for solvent persons, for insolvent persons or for both. The Platinum style makes it very clear which state the checkbox is in, compared to, for instance, the Windows style.
Listboxes are simple containers where a variable number of strings can be added. You can allow the user to select no item, one item, a range of items or a discontinuous set of items. You can enter texts or pixmaps in a listbox, but the listbox, like the listview and the combobox, doesn't let you associate arbitrary data with the items inside it.
This is something you may often want — you let the user select a certain object by clicking on an item in the listbox, and you want that object to be available in your application, not the string or picture that represents the object, or the index of the item in the listbox. You can achieve this by coding a small associative listbox:
Example 10-10. listbox.py - A listbox where data can be associated with an entry
# # listbox.py # # listbox with key-to-index and index-to-key mapping # import sys from qt import * class AssociativeListBox(QListBox): def __init__(self, *args): apply(QListBox.__init__,(self,)+args) self.text2key = {} self.key2text = {} self.connect(self, SIGNAL("selected(int)"), self.slotItemSelected) def insertItem(self, text, key): QListBox.insertItem(self, text) self.text2key [self.count() - 1] = key self.key2text [key]=self.count() - 1 def currentKey(self): return self.text2key[self.currentItem()] def setCurrentItem(self, key): if self.key2text.has_key(key): QListBox.setCurrentItem(self, self.key2text[key]) def slotItemSelected(self, index): key=self.currentKey() self.emit(PYSIGNAL("itemSelected"), (key, self.currentText()) ) def removeItem(self, index): del self.text2key[self.currentItem()] del self.key2text[index] QListView.removeItem(self, index) class MainWindow(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__, (self,) + args) self.listbox=AssociativeListBox(self) self.listbox.insertItem("Visible text 1", "key1") self.listbox.insertItem("Visible text 2", "key2") self.listbox.insertItem("Visible text 3", "key3") self.setCentralWidget(self.listbox) self.connect(self.listbox,PYSIGNAL( "itemSelected"), self.printSelection) def printSelection(self, key, text): print "Associated with", key, "is", text def main(args): app=QApplication(args) win=MainWindow() win.show() app.connect(app, SIGNAL("lastWindowClosed()") , app , SLOT("quit()") ) app.exec_loop() if __name__=="__main__": main(sys.argv)
listbox.py
Of course, the same trick is needed to get something useful out of a QComboBox or a QListView.
A QComboBox offers almost the same functionality as a QListBox, but folds out and in, preserving screen space. The contents of a combobox can be read-only or editable, and you can check the correctness of user input using a QValdidator object.
This is a simple one-line edit control, familiar to gui users everywhere. It supports copy, cut and paste and redo, too. There's a special mode for password boxes.
QMultiLineEdit provides a very simple multi-line editor. This class does not, in any way, support rich text — all text is in the same font, same size and same color. You can enable word-wrap, but that's about it. There are no limits on the amount of text it can handle (as was the case with the old Windows edit control, which had a limit of about 32 kb), but with megabytes of data, it will become decidedly slow.
With Qt3 this class has become obsolete. You're supposed to use the new, advanced QTextEdit, instead. After creating a QTextEdit object, you'd set the textformat to plain with QTextEdit.setFormat(Qt.PlainText); no user would notice the difference. QTextEdit and QMultiLineEdit are quite different to the programmer, though, and you can still use QMultiLineEdit if you need compatibility with older versions of Qt.
One of the most useful things you can offer a user in any document-based application is a context menu — press the mouse-button anywhere in the document and a list of useful options pop up. PyQt's QPopupMenu can be used both as a stand-alone popup menu, and within a menu bar. Menu items can have a shortcut key associated with them, an accelerator and a small icon. The most useful way of adding items to a menu is by defining a QAction. You can nest menus, and make ‘tear-off' menus, where the user can click on a ‘tear-off handle' which puts the menu in a window of its own.
QProgressBar gives you a horizontal progressbar — it's quite simple, even though it can be set to use one of several different styles. There's also QProgressDialog which can be used for lengthy actions that completely block access to the application. Since PyQt doesn't really support multi-threading, it's probably best to stick with the blocking dialog.
If you want to get fancy, you can, with a bit of juggling, get an approximation to threading by using a QTimer. Then it's best to place the progress bar in the statusbar of your application, instead of a separate non-modal progress dialog. the Section called A painting example in Chapter 21 gives an example of the use of a timer.
There are several other, simple user-interface widgets: QDial, QLCDNumber, QScrollBar, QSizeGrip, QSpinBox and QToolButton. These widgets are seldom used, mostly because they are rather overspecialized.
QDial is a potentio-meter like knob. Twiddling it demands a fair proficiency with the mouse, and the keyboard interface isn't immediately obvious. See Example 7-5 for an example of using QDial.
QLCDNumber is a kind of label which can display numbers in an lcd-like format. It's mostly interesting for historical reasons — the first version was written for the Sinclair ZX-Spectrum, a 1.9 MHz Z80 computer with a rubber keyboard and 48 Kb of ram.
QScrollBar looks to be quite useful, because, on the face of it, any gui application is full of scrollbars. But those scrollbars come automatically with the edit-controls, listboxes and other scrollable widgets, and the QScrollBar is seldom used in isolation, and then mostly for the same purpose as QDial— as a range control. If you want to use it for scrolling a section on screen, use the QScrollView class instead.
The QSizeGrip is extremely obscure, being at its peak form only in statusbars of resizable windows. And those QStatusBars can take care of their sizegrips themselves.
QSpinBox is another range control. It's often used to let the user select a size for a font — she can either type the size directly, or use the little arrows to choose a larger or smaller size. Spinboxes are often quite fiddly to use.
QToolButton is a special button that carries more often a picture than a text — it's mostly used in toolbars, where you can add buttons without explicitly creating instances of this class.