Layout managers

One of the great strengths of PyQt is the use of layout managers. Formerly, gui designers had to position and size every element in their dialogs with pixel precision. Of course, this meant that enlarging a window wouldn't show the user more data, just a vast desert of boring grey pixels. Worse, when making a window smaller, data would be obscured. Even worse, there are still applications being made where you cannot resize the windows at all.

Too large...

Too small.

It's easy to write applications as badly behaved as this in PyQt— but where a Visual Basic developer has to write a complex resize routine that recalculates the size and position of each element, PyQt developers can use Qt's advanced layout management facilities.

Basically, this means that you create several containers that hold your widgets, and those widgets will resize together with the containers. The easiest way to create a pleasing layout is by using the BlackAdder or Qt forms designer, as this automatically uses sensible defaults.

There are three fundamental approaches to layout management in PyQt: by stacking widgets or grouping them in frames, by using the simple layout management provided by QFrame and children, or by using the advanced layout management QLayout provides. In Qt 3.0 QLayout is even smart enough to reverse the order of labels and entry widgets in dialog boxes for right-to-left scripts.

Note: QMainWindow provides its own layout management— it manages the size and position of the menubar, toolbar or toolbars, statusbar and the widget in the middle. If that widget is not composed of several widgets, the management will be quite sufficient. If there are several widgets constrained by a QSplitter, the management will likewise be sufficient, because in that case, the QSplitter will be the central widget. If you have a more complex assembly of widgets, you will have to create a dummy central QWidget that contains a layoutmanager that manages those widgets in a pleasing way. You can also directly add a layout manager to QMainWindow, but PyQt will natter about a layout manager being added to a widget that already had one. It's not dangerous, though. See Example 10-12 for an example of such a dummy central widget.

Widget sizing: QSizePolicy

QWidget based classes provide the layout management system with size hints. This is a subtle system based on a class named QSizePolicy. A widget's size policy determines how small a widget can shrink, how big it can grow and how big it really wants to be. Then the layout manager negotiates with the widget though the use of the sizeHint() about the size it will get.

A widget can thus indicate whether it prefers to stay a fixed horizontal or vertical size, or would like to grow to occupy all available space. QSizePolicy contains a horizontal size policy record and a vertical size policy record. You can set the size policy programmatically, but the setting is also available in the BlackAdder forms creator.

The following size policies exist:

Groups and frames

One way of getting automatic layout management is by using QFrame, and its children, like QGroupBox. We have already seen such a frame in the radiobuttons example, Example 10-9. The contents of the frame will be managed automatically.

QFrame has three interesting child classes: QGrid, QHBox and QGroupBox. There's also QVBox, which descends from QHBox.

Adding widgets to one of the frame-based layout managers is simply a matter of creating the widget with the layout manager as parent. Those widgets will be resized according to the value their sizeHint() returns.

QHBox

This a very, very simple class. A QHBox aligns its children horizontally, with a settable spacing between them.

QVBox

A QVBox layout is possibly even simpler than the QHBox layout: as the name implies, it aligns its children vertically.

QGrid

It will come as no surprise that the QGrid is a simple grid layout manager — for more complicated layouts, with differently sized columns, you really need QGridLayout.

QGroupBox

A QGroupBox gives you a frame (which can be drawn in as many flavors as QFrame supports) and with a title text which will appear on top of the frame. A QGroupBox can hold child widgets. Those widgets will be aligned horizontally, vertically or in a grid. The grid can also be filled in columns (for vertically oriented frames), or in strips (for horizontally oriented frames).

QLayout

QLayout is foundation of all complex Qt layout managers. Built on QLayout, there are three layoutmanagers: one for horizontal layouts, one for vertical layouts and one for grids. It's also quite possible to build a new layoutmanager on QLayout, one, for instance, that manages playing cards on a stack, or perhaps items in circle.

You can not only add widgets to layouts; but also layouts. In this way, quite complex layouts can achieved with very little pain.

All layout managers work by maintaining a list of layoutitems. Those items can be QLayoutItems, QLayoutWidgets or QSpacerItems. It's interesting to note that QLayout is a QLayoutItem, and can thus be managed by another layoutmanager.

Every layout item proxies for a widget. A QSpacerItem is rather special, since it doesn't represent a widget, but rather space. A QSpacerItem ‘pushes' other widgets, either horizontally or vertically. You can use them to push all pushbuttons to the top of the dialog, instead of having them spread out over the whole height by the layout manager.

QBoxLayout and children

QBoxLayout is the parent class of the horizontal and vertical box layout managers — you will never use this class on its own, but its useful to look at the methods it offers, because those are inherited by QHBoxLayout and QVBoxLayout

QGridLayout

While you can handle many layout problems with combinations of horizontal and vertical box layout managers, other problems are more suited for a grid-based layout. The QGridLayout class provides a flexible layout manager.

The grid managed by a QGridLayout consists of cells laid out in rows and columns: the grid cannot be as complicated as a html table, but you can add widgets (or sub-layouts) that span multiple rows and columns. Rows (or columns) can be given a stretch factor and spacing.

Example 10-12. layout.py - two box layouts and adding and removing buttons dynamically to a layout

#
# layout.py - adding and removing widgets to a layout
#
import sys
from qt import *


class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        self.setCaption("Adding and deleting widgets")
        self.setName("main window")
        self.mainWidget=QWidget(self) # dummy widget to contain the
                                      # layout manager
        self.setCentralWidget(self.mainWidget)
        self.mainLayout=QVBoxLayout(self.mainWidget, 5, 5, "main")
        self.buttonLayout=QHBoxLayout(self.mainLayout, 5, "button")
        self.widgetLayout=QVBoxLayout(self.mainLayout, 5, "widget")

        self.bnAdd=QPushButton("Add widget", self.mainWidget, "add")
        self.connect(self.bnAdd, SIGNAL("clicked()"),
                     self.slotAddWidget)

        self.bnRemove=QPushButton("Remove widget",
                                  self.mainWidget, "remove")
        self.connect(self.bnRemove, SIGNAL("clicked()"),
                     self.slotRemoveWidget)

        self.buttonLayout.addWidget(self.bnAdd)
        self.buttonLayout.addWidget(self.bnRemove)

        self.buttons = []

    def slotAddWidget(self):
        widget=QPushButton("test", self.mainWidget)
        self.widgetLayout.addWidget(widget)
        self.buttons.append(widget)
        widget.show()

    def slotRemoveWidget(self):
        self.widgetLayout.parent().removeChild(self.widgetLayout)
        self.widgetLayout=QVBoxLayout(self.mainLayout, 5, "widget")
        self.buttons[-1].parent().removeChild(self.buttons[-1])
        del self.buttons[-1:]


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)
          

This example shows that it is not only possible to dynamically add widgets to a layout, but also to remove them again. Removing means first severing the link from the widget to the parent, and then deleting (using del) all Python references to the widget. When the last reference has been removed, the widget disappears.

layout.py

setGeometry

You can use setGeometry to set the size of every individual widget yourself. There's another useful application of setGeometry(), too: if you save the size of the application window when the last window closes in a configuration file, you can bring the window back to its last size and position the next time the user starts opens it.

Example 10-13. geometry.py - setting the initial size of an application

#
# geometry.py
#
import sys
from qt import *

class MainWindow(QMainWindow):
        
    def __init__(self, *args):
        apply(QMainWindow.__init__, (self,) + args)
        self.editor=QMultiLineEdit(self)
        self.setCentralWidget(self.editor)
        
def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.setGeometry(100,100,300,300)
    win.show()
        
    app.connect(app, SIGNAL("lastWindowClosed()")
                           , app
                           , SLOT("quit()")
                           )
    app.exec_loop()
    
if __name__=="__main__":
    main(sys.argv)