Designing forms
One important feature of BlackAdder and
PyQt is the visual gui designer. You can use the designer to
easily create all kinds of dialog windows, and even custom
widgets.
The definition of these user-interface
designs is saved in files in an XML format, and you can easily
translate those to Python code. The beauty of the system is that
you can just as easily translate your designs to valid C++ code,
making it easy to develop your prototype in Python and, when
satisfied, port the whole gui to fast, compiled C++. (C++ code,
I might add, that compiles just as well on Windows, Unix/X11, OS
X and embedded systems).
The version of Qt Designer that is included
with Qt3 can also create complete main windows with menus and
toolbars. Once pyuic has been updated
to include those elements, you can use this in your Python
projects, too.
Let's use Designer to design a small form
that would be useful for connecting to some system, and hook it
up in a small PyQt program.
It's quite easy to work with designer
— just keep in mind that you never
have to place items pixel-perfect. Just bang widgets of roughly
the right size in roughly the right places, add the Qt layout
managers, and see them work their magic.
You add a layout manager by selecting the
widgets you want to be managed, and then selecting the right
layout manager from the toolbar.
In the above design, there are three layout
managers: the buttons on the right are stacked, the widgets
inside the bevel are in a grid, and everything in the form is in
another grid. Try making the dialog larger and smaller —
it will always look good. Even better, if a visually impaired
user chooses a large system font, say Arial 24 points bold, the
form will still look good.
You can either compile the .ui file to
Python code from BlackAdder or from the command-line. The
result will be something like this:
Example 6-6. frmconnect.py
# Form implementation generated from reading ui file 'frmconnect.ui'
#
# Created: Wed Feb 28 21:34:40 2001
# by: The Python User Interface Compiler (pyuic)
#
# WARNING! All changes made in this file will be lost!
from qt import *
class frmConnect(QDialog):
def __init__(self,parent = None,name = None,modal = 0,fl = 0):
QDialog.__init__(self,parent,name,modal,fl)
if name == None:
self.setName('frmConnect')
self.resize(547,140)
self.setCaption(self.tr('Connecting'))
self.setSizeGripEnabled(1)
frmConnectLayout = QGridLayout(self)
frmConnectLayout.setSpacing(6)
frmConnectLayout.setMargin(11)
Layout5 = QVBoxLayout()
Layout5.setSpacing(6)
Layout5.setMargin(0)
self.buttonOk = QPushButton(self,'buttonOk')
self.buttonOk.setText(self.tr('&OK'))
self.buttonOk.setAutoDefault(1)
self.buttonOk.setDefault(1)
Layout5.addWidget(self.buttonOk)
self.buttonCancel = QPushButton(self,'buttonCancel')
self.buttonCancel.setText(self.tr('&Cancel'))
self.buttonCancel.setAutoDefault(1)
Layout5.addWidget(self.buttonCancel)
self.buttonHelp = QPushButton(self,'buttonHelp')
self.buttonHelp.setText(self.tr('&Help'))
self.buttonHelp.setAutoDefault(1)
Layout5.addWidget(self.buttonHelp)
spacer = QSpacerItem(20,20,QSizePolicy.Minimum,QSizePolicy.Expanding)
Layout5.addItem(spacer)
frmConnectLayout.addLayout(Layout5,0,1)
self.grpConnection = QGroupBox(self,'grpConnection')
self.grpConnection.setSizePolicy(QSizePolicy(5,7,self.grpConnection. \
sizePolicy().hasHeightForWidth()))
self.grpConnection.setTitle(self.tr(''))
self.grpConnection.setColumnLayout(0,Qt.Vertical)
self.grpConnection.layout().setSpacing(0)
self.grpConnection.layout().setMargin(0)
grpConnectionLayout = QGridLayout(self.grpConnection.layout())
grpConnectionLayout.setAlignment(Qt.AlignTop)
grpConnectionLayout.setSpacing(6)
grpConnectionLayout.setMargin(11)
self.lblName = QLabel(self.grpConnection,'lblName')
self.lblName.setText(self.tr('&Name'))
grpConnectionLayout.addWidget(self.lblName,0,0)
self.lblHost = QLabel(self.grpConnection,'lblHost')
self.lblHost.setText(self.tr('&Host'))
grpConnectionLayout.addWidget(self.lblHost,2,0)
self.lblPasswd = QLabel(self.grpConnection,'lblPasswd')
self.lblPasswd.setText(self.tr('&Password'))
grpConnectionLayout.addWidget(self.lblPasswd,1,0)
self.txtPasswd = QLineEdit(self.grpConnection,'txtPasswd')
self.txtPasswd.setMaxLength(8)
self.txtPasswd.setEchoMode(QLineEdit.Password)
grpConnectionLayout.addWidget(self.txtPasswd,1,1)
self.cmbHostnames = QComboBox(0,self.grpConnection,'cmbHostnames')
grpConnectionLayout.addWidget(self.cmbHostnames,2,1)
self.txtName = QLineEdit(self.grpConnection,'txtName')
self.txtName.setMaxLength(8)
grpConnectionLayout.addWidget(self.txtName,0,1)
frmConnectLayout.addWidget(self.grpConnection,0,0)
self.connect(self.buttonOk,SIGNAL('clicked()'),self,SLOT('accept()'))
self.connect(self.buttonCancel,SIGNAL('clicked()'), \
self,SLOT('reject()'))
self.setTabOrder(self.txtName,self.txtPasswd)
self.setTabOrder(self.txtPasswd,self.cmbHostnames)
self.setTabOrder(self.cmbHostnames,self.buttonOk)
self.setTabOrder(self.buttonOk,self.buttonCancel)
self.setTabOrder(self.buttonCancel,self.buttonHelp)
self.lblName.setBuddy(self.txtName)
self.lblPasswd.setBuddy(self.txtName)
Now this looks pretty hideous — but fortunately you'll
never have to hack it. You would lose all your changes anyway, the
next time you make a change to your design and regenerate the Python
code. The best thing to do is to subclass this form with
code that actually fills the dialog with data and perfoms an
action upon closing it. I like to keep the names of the
generated form and the subclassed form related, and I tend to
refer to the first as a form, and the second as dialog —
hence the prefix frmXXX for generated forms and dlgXXX for the
dialogs.
For example:
Example 6-7. dlgconnect.py — the subclass of the generated form
import sys
from qt import *
from frmconnect import frmConnect
class dlgConnect(frmConnect):
def __init__(self, parent=None):
frmConnect.__init__(self, parent)
self.txtName.setText("Baldrick")
for host in ["elizabeth","george", "melchett"]:
self.cmbHostnames.insertItem(host)
def accept(self):
print self.txtName.text()
print self.txtPasswd.text()
print self.cmbHostnames.currentText()
frmConnect.accept(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
QObject.connect(app, SIGNAL('lastWindowClosed()'),
app, SLOT('quit()'))
win = dlgConnect()
app.setMainWidget(win)
win.show()
app.exec_loop()
As you can see, we have subclassed the
generated form. In the constructor, the various fields are
filled with a bit of data. Note that we can simply use Python
string objects in setText() methods. Qt uses a special string
object, QString for all its textual data,
but PyQt automatically translates both Python strings and Python
unicode strings to these QString objects.
There are some complications, which we deal with in
Chapter 8, but the translation is mostly
transparent.
When you press the OK button, Qt calls the
accept() method of the dialog class, in
this case dlgConnect, which inherits
frmConnect, which inherits
QDialog. The
accept() method prints out the contents of
the fields. Then the accept() method of the
parent class — ultimately QDialog
— is called, and the dialog is closed.