Qt provides three basic ways of managing the layout of child widgets on a form: absolute positioning, manual layout, and layout managers. We will look at each of these approaches in turn, using the Find File dialog shown in Figure 6.1 as our example.
Figure 6.1. The Find File dialog
Absolute positioning is the crudest way of laying out widgets. It is achieved by assigning hard-coded sizes and positions (geometries) to the form's child widgets and a fixed size to the form. Here's what the FindFileDialog constructor looks like using absolute positioning:
FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... namedLable->setGeometry(10, 10, 50, 20); namedLineEdit->setGeometry(70, 10, 200, 20); lookInLabel->setGeometry(10, 35, 50, 20); lookInLineEdit->setGeometry(70, 35, 200, 20); subfoldersCheckBox->setGeometry(10, 60, 260, 20); listView->setGeometry(10, 85, 260, 100); messageLabel->setGeometry(10, 190, 260, 20); findButton->setGeometry(275, 10, 80, 25); stopButton->setGeometry(275, 40, 80, 25); closeButton->setGeometry(275, 70, 80, 25); helpButton->setGeometry(275, 185, 80, 25); setFixedSize(365, 220); }
Absolute positioning has many disadvantages. The foremost problem is that the user cannot resize the window. Another problem is that some text may be truncated if the user chooses an unusually large font or if the application is translated into another language. And this approach also requires us to perform tedious position and size calculations.
An alternative to absolute positioning is manual layout. With manual layout, the widgets are still given absolute positions, but their sizes are made propor tional to the size of the window rather than being entirely hard-coded. This can be achieved by reimplementing the form's resizeEvent() function to set its child widget's geometries:
FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... setMinimumSize(215, 170); resize(365, 220); } void FindFileDialog::resizeEvent(QResizeEvent *) { int extraWidth = width() - minimumWidth(); int extraHeight = height() - minimumHeight(); namedLabel->setGeometry(10, 10, 50, 20); namedLineEdit->setGeometry(70, 10, 50 + extraWidth, 20); lookInLabel->setGeometry(10, 35, 50, 20); lookInLineEdit->setGeometry(70, 35, 50 + extraWidth, 20); subfoldersCheckBox->setGeometry(10, 60, 110 + extraWidth, 20); listView->setGeometry(10, 85, 110 + extraWidth, 50 + extraHeight); messageLabel->setGeometry(10, 140 + extraHeight, 110 + extraWidth, 20); findButton->setGeometry(125 + extraWidth, 10, 80, 25); stopButton->setGeometry(125 + extraWidth, 40, 80, 25); closeButton->setGeometry(125 + extraWidth, 70, 80, 25); helpButton->setGeometry(125 + extraWidth, 135 + extraHeight, 80, 25); }
We set the form's minimum size to 215 x 170 in the FindFileDialog constructor and its initial size to 365 x 220. In the resizeEvent() function, we give any extra space to the widgets that we want to grow.
Just like absolute positioning, manual layout requires a lot of hard-coded constands to be calculated by the programmer. Writing code like this is tiresome, especially if the design changes. And there is still the risk of text truncation. The risk can be avoided by taking account of the child widgets' size hints, but that would complicate the code even further.
Figure 6.2. Resizing a resizable dialog
The best solution for laying out widgets on a form is to use Q's layout managers. The layout managers provide sensible defaults for every type of widget and take into account each widget's size hint, which in turn typically depends on the widget's font, style, and contents. Layout managers also respect minimum and maximum sizes, and automatically adjust the layout in response to font changes, text changes, and window resizing.
Qt provides three layout managers: QHBoxLayout, QVBoxLayout, and QGridLayout. These classes inherit QLayout, which provides the basic framework for layouts. All three classes are fully supported by Qt Designer and can also be used in code. Chapter 2 presented examples of both approaches.
Here's the FindFileDialog code using layout managers:
FindFileDialog::FindFileDialog(QWidget *parent, const char *name) : QDialog(parent, name) { ... QGridLayout *leftLayout = new QGridLayout; leftLayout->addWidget(namedLabel, 0, 0); leftLayout->addWidget(namedLineEdit, 0, 1); leftLayout->addWidget(lookInLabel, 1, 0); leftLayout->addWidget(lookInLineEdit, 1, 1); leftLayout->addMultiCellWidget(subfoldersCheckBox, 2, 2, 0, 1); leftLayout->addMultiCellWidget(listView, 3, 3, 0, 1); leftLayout->addMultiCellWidget(messageLabel, 4, 4, 0, 1); QVBoxLayout *rightLayout = new QVBoxLayout; rightLayout->addWidget(findButton); rightLayout->addWidget(stopButton); rightLayout->addWidget(closeButton); rightLayout->addStretch(1); rightLayout->addWidget(helpButton); QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setMargin(11); mainLayout->setSpacing(6); mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout); }
The layout is handled by one QHBoxLayout, one QGridLayout, and one QVBoxLayout The QGridLayout on the left and the QVBoxLayout on the right are placed side by side by the outer QHBoxLayout. The margin around the dialog is 11 pixels and the spacing between the child widgets is 6 pixels.
Figure 6.3. The Find File dialog's layout
QGridLayout works on a two-dimensional grid of cells. The QLabel at the top-left corner of the layout is at position (0, 0), and the corresponding QLineEdit is at position (0, 1). The QCheckBox spans two columns; it occupies the cells in positions (2, 0) and (2, 1). The QListView and the QLabel beneath it also span two columns. The calls to addMultiCellWidget() have the following syntax:
leftLayout->addMultiCellWidget(widget, row1, row2, col1, col2);
where widget is the child widget to insert into the layout, (row1, col1) is the top-left cell occupied by the widget, and (row2, col2) is the bottom-right cell occupied by the widget.
The same dialog could be created visually in Qt Designer by placing the child widgets in their approximate positions, selecting those that need to be laid out together, and clicking Layout|Lay Out Horizontally, Layout|Lay Out Vertically, or Layout|Lay Out in a Grid. We used this approach in Chapter 2 for creating the Spreadsheet application's Go-to-Cell and Sort dialogs.
Using layout managers provides additional benefits to those we have discussed so far. If we add a widget to a layout or remove a widget from a layout, the layout will automatically adapt to the new situation. The same applies if we call hide() or show() on a child widget. If a child widget's size hint changes, the layout managers automatically redone, taking into account the new size hint. Also, layout managers automatically set a minimum size for the form as a whole, based on the form's child widgets' minimum sizes and size hints.
In every example presented so far, we have simply put the widgets in layouts, with spacer items to consume any excess space. Sometimes this isn't sufficient to make the layout look exactly the way we want. In such situations, we can adjust the layout by changing the size policies and size hints of the widgets being laid out.
A widget's size policy tells the layout system how it should stretch or shrink. Qt provides sensible default size policy values for all its built-in widgets, but since no single default can account for every possible layout, it is still common for developers to change the size policies for one or two widgets on a form. A size policy has both a horizontal and a vertical component. The most useful values for each component are Fixed, Minimum, Maximum, Preferred, and Expanding:
Figure 6.4 summarizes the meaning of the different size policies, using a QLabel showing the text "Some Text" as an example.
Figure 6.4. The meaning of the different size policies
When a form that contains both Preferred and Expanding widgets is resized, extra space is given to the Expanding widgets, while the Preferred widgets stay at their size hint.
There are two other size policies: MinimumExpanding and Ignored. The former was necessary in a few rare cases in older versions of Qt, but it isn't useful any more; a better approach is to use Expanding and reimplement minimumSizeHint() appropriately. The latter is similar to Expanding, except that it ignores the widget's size hint.
In addition to the size policy's horizontal and vertical components, the QSizePolicy class stores both a horizontal and a vertical stretch factor. These stretch factors can be used to indicate that different child widgets should grow at different rates when the form expands. For example, if we have a QListView above a QTextEdit and we want the QTextEdit to be twice as tall as the QListView, we can set the QTextEdit's vertical stretch factor to 2 and the QListView's vertical stretch factor to 1.
Another way of influencing a layout is to set a minimum size, a maximum size, or a fixed size on the child widgets. The layout manager will respect these constraints when laying out the widgets. And if this isn't sufficient, we can always derive from the child widget's class and reimplement sizeHint() to obtain the size hint we need.
Part I: Basic Qt
Getting Started
Creating Dialogs
Creating Main Windows
Implementing Application Functionality
Creating Custom Widgets
Part II: Intermediate Qt
Layout Management
Event Processing
2D and 3D Graphics
Drag and Drop
Input/Output
Container Classes
Databases
Networking
XML
Internationalization
Providing Online Help
Multithreading
Platform-Specific Features