You are on page 1of 6

Stacked Layouts

The QStackedLayout class lays out a set of child widgets, or "pages", and shows only one at a time, hiding the others from the
user. The QStackedLayout itself is invisible and provides no intrinsic means for the user to change the page. The small arrows
and the dark gray frame in Figure 6.5 are provided by Qt Designer to make the layout easier to design with. For convenience, Qt
also includes QStackedWidget, which provides a QWidget with a built-in QStackedLayout.
Figure 6.5. QStackedLayout

The pages are numbered from 0. To make a specific child widget visible, we can call setCurrentIndex() with a page
number. The page number for a child widget is available using indexOf().
The Preferences dialog shown in Figure 6.6 is an example that uses QStackedLayout. The dialog consists of a
QListWidget on the left and a QStackedLayout on the right. Each item in the QListWidget corresponds to a different
page in the QStackedLayout. Here's the relevant code from the dialog's constructor:
PreferenceDialog::PreferenceDialog(QWidget *parent)
: QDialog(parent)
{
...
listWidget = new QListWidget;
listWidget->addItem(tr("Appearance"));
listWidget->addItem(tr("Web Browser"));
listWidget->addItem(tr("Mail & News"));
listWidget->addItem(tr("Advanced"));
stackedLayout = new QStackedLayout;
stackedLayout->addWidget(appearancePage);
stackedLayout->addWidget(webBrowserPage);
stackedLayout->addWidget(mailAndNewsPage);
stackedLayout->addWidget(advancedPage);
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
...
listWidget->setCurrentRow(0);
}

Figure 6.6. Two pages of the Preferences dialog

We create a QListWidget and populate it with the page names. Then we create a QStackedLayout and call
addWidget() for each page. We connect the list widget's currentRowChanged(int) signal to the stacked layout's
setCurrentIndex(int) to implement the page switching and call setCurrentRow() on the list widget at the end of the
constructor to start on page 0.
150

Forms such as this are also very easy to create using Qt Designer:
1.

Create a new form based on one of the "Dialog" templates or on the "Widget" template.

2.

Add a QListWidget and a QStackedWidget to the form.

3.

Fill each page with child widgets and layouts.


(To create a new page, right-click and choose Insert Page; to switch pages, click the tiny left or right arrow located at the top
right of the QStackedWidget.)

4.

Lay out the widgets side by side using a horizontal layout.

5.

Connect the list widget's currentRowChanged(int) signal to the stacked widget's setCurrentIndex(int) slot.

6.

Set the value of the list widget's currentRow property to 0.

Since we have implemented page switching using predefined signals and slots, the dialog will exhibit the correct behavior when
previewed in Qt Designer.
For cases where the number of pages is small and likely to remain small, a simpler alternative to using a QStackedWidget and
QListWidget is to use a QTabWidget.

151

Splitters
A QSplitter is a widget that contains other widgets. The widgets in a splitter are separated by splitter handles. Users can
change the sizes of a splitter's child widgets by dragging the handles. Splitters can often be used as an alternative to layout
managers, to give more control to the user.
The child widgets of a QSplitter are automatically placed side by side (or one below the other) in the order in which they are
created, with splitter bars between adjacent widgets. Here's the code for creating the window depicted in Figure 6.7:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextEdit *editor1 = new QTextEdit;
QTextEdit *editor2 = new QTextEdit;
QTextEdit *editor3 = new QTextEdit;
QSplitter splitter(Qt::Horizontal);
splitter.addWidget(editor1);
splitter.addWidget(editor2);
splitter.addWidget(editor3);
...
splitter.show();
return app.exec();
}

Figure 6.7. The Splitter application

The example consists of three QTextEdits laid out horizontally by a QSplitter widget this is shown schematically in
Figure 6.8. Unlike layout managers, which simply lay out a form's child widgets and have no visual representation, QSplitter
is derived from QWidget and can be used like any other widget.
Figure 6.8. The Splitter application's widgets

Complex layouts can be achieved by nesting horizontal and vertical QSplitters. For example, the Mail Client application
shown in Figure 6.9, consists of a horizontal QSplitter that contains a vertical QSplitter on its right side. The layout is
shown schematically in Figure 6.10.
Figure 6.9. The Mail Client application

152

Figure 6.10. The Mail Client's splitter layout

Here's the code in the constructor of the Mail Client application's QMainWindow subclass:
MailClient::MailClient()
{
...
rightSplitter = new QSplitter(Qt::Vertical);
rightSplitter->addWidget(messagesTreeWidget);
rightSplitter->addWidget(textEdit);
rightSplitter->setStretchFactor(1, 1);
mainSplitter = new QSplitter(Qt::Horizontal);
mainSplitter->addWidget(foldersTreeWidget);
mainSplitter->addWidget(rightSplitter);
mainSplitter->setStretchFactor(1, 1);
setCentralWidget(mainSplitter);
setWindowTitle(tr("Mail Client"));
readSettings();
}

After creating the three widgets that we want to display, we create a vertical splitter, rightSplitter, and add the two widgets
we want on the right. Then we create a horizontal splitter, mainSplitter, and add the widget we want it to display on the left
and rightSplitter whose widgets we want shown on the right. We make mainSplitter the QMainWindow's central
widget.

153

When the user resizes a window, QSplitter normally distributes the space so that the relative sizes of the child widgets stay
the same. In the Mail Client example, we don't want this behavior; instead, we want the QTreeWidget and the
QTableWidget to keep their sizes and we want to give any extra space to the QTextEdit. This is achieved by the two
setStretchFactor() calls. The first argument is the 0-based index of the splitter's child widget, and the second argument is
the stretch factor we want to set; the default is 0.
The first setStretchFactor() call is on rightSplitter, and it sets the widget at position 1 (textEdit) to have a
stretch factor of 1. The second setStretchFactor() call is on mainSplitter, and it sets the widget at position 1
(rightSplitter) to have a stretch factor of 1. This ensures that the textEdit will get any additional space that is available.
When the application is started, QSplitter gives the child widgets appropriate sizes based on their initial sizes (or based on
their size hint if no initial size is specified). We can move the splitter handles programmatically by calling
QSplitter::setSizes(). The QSplitter class also provides a means of saving and restoring its state the next time the
application is run. Here's the writeSettings() function that saves the Mail Client's settings:
void MailClient::writeSettings()
{
QSettings settings("Software Inc.", "Mail Client");
settings.beginGroup("mainWindow");
settings.setValue("geometry", saveGeometry());
settings.setValue("mainSplitter", mainSplitter->saveState());
settings.setValue("rightSplitter", rightSplitter->saveState());
settings.endGroup();
}

Here's the corresponding readSettings() function:


void MailClient::readSettings()
{
QSettings settings("Software Inc.", "Mail Client");
settings.beginGroup("mainWindow");
restoreGeometry(settings.value("geometry").toByteArray());
mainSplitter->restoreState(
settings.value("mainSplitter").toByteArray());
rightSplitter->restoreState(
settings.value("rightSplitter").toByteArray());
settings.endGroup();
}

Qt Designer fully supports QSplitter. To put widgets into a splitter, place the child widgets approximately in their desired
positions, select them, and click Form|Lay Out Horizontally in Splitter or Form|Lay Out Vertically in Splitter.

154

Scrolling Areas
The QScrollArea class provides a scrollable viewport and two scroll bars. If we want to add scroll bars to a widget, it is much
simpler to use a QScrollArea than to instantiate our own QScrollBars and implement the scrolling functionality ourselves.
The way to use QScrollArea is to call setWidget() with the widget to which we want to add scroll bars. QScrollArea
automatically reparents the widget to make it a child of the viewport (accessible through QScrollArea::viewport()) if it
isn't already. For example, if we want scroll bars around the IconEditor widget we developed in Chapter 5 (as shown in
Figure 6.11), we can write this:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
IconEditor *iconEditor = new IconEditor;
iconEditor->setIconImage(QImage(":/images/mouse.png"));
QScrollArea scrollArea;
scrollArea.setWidget(iconEditor);
scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
scrollArea.viewport()->setAutoFillBackground(true);
scrollArea.setWindowTitle(QObject::tr("Icon Editor"));
scrollArea.show();
return app.exec();
}

Figure 6.11. Resizing a QScrollArea

The QScrollArea (shown schematically in Figure 6.12) presents the widget at its current size or uses the size hint if the widget
hasn't been resized yet. By calling setWidgetResizable(true), we can tell QScrollArea to automatically resize the
widget to take advantage of any extra space beyond its size hint.
Figure 6.12. QScrollArea's constituent widgets

155

You might also like