Trolltech | Documentation | Qt Quarterly | « Glimpsing the Third Dimension

Multi-Page Dialogs
by Jasmin Blanchette
Multi-page dialogs present information on pages that are superimposed on screen, so that only one page is visible at any one time. This articles demonstrates two kinds of multi-page dialog and explains how to achieve automatic dialog resizing when a new page is made visible.


QTabWidget with Automatic Resizing

The standard way to present lots of information in one window is to use QTabWidget. Most users are familiar with tab dialogs, and there's probably no better way to hide advanced options from casual users.

By default, QTabWidget::minimumSizeHint() returns the maximum of all the pages it contains, not just the visible page. For example, in the "Find & Replace" dialog below, the "Replace" page dictates the size of the whole dialog:

Find-Wrong

When screen-space is at a premium, it may be preferable for the dialog to resize itself automatically to occupy as little space as possible:

Find-Right

Qt requires very little code to implement automatic resizing. First we'll need the "ignored" and "preferred" size policies a few times, so let's declare them as global constants right away:

    const QSizePolicy ignored(QSizePolicy::Ignored, QSizePolicy::Ignored);
    const QSizePolicy preferred(QSizePolicy::Preferred, QSizePolicy::Preferred);
    

Then we implement a currentChanged() slot in our dialog and connect it to the QTabWidget's currentChanged() signal:

    void FindDialog::currentChanged(QWidget *newPage)
    {
        if (newPage == pageFind) {
            pageFind->setSizePolicy(preferred);
            pageReplace->setSizePolicy(ignored);
        } else {
            pageFind->setSizePolicy(ignored);
            pageReplace->setSizePolicy(preferred);
        }
        layout()->activate();
        setFixedSize(minimumSizeHint());
    }
    

If the new page is "Find," we set its size policy to "preferred" and the size policy of "Replace" to "ignored"; otherwise, we do the opposite. By setting the hidden page's size policy to "ignored," we ensure that the hidden page doesn't participate in the minimum size hint of the whole QTabWidget.

At the end of the function, we call QLayout::activate() to recompute the layout, and we fix the size of the widget to its minimum size hint. The minimum size hint of a layout-managed widget is computed by the layout; without the call to QLayout::activate(), minimumSizeHint() would return a value based on the previously shown page.

Another approach would be to subclass QTabWidget and reimplement minimumSizeHint(), to avoid fiddling with the pages' size policies. This is probably the best approach if you often need multi-page dialogs. You could then make your subclass into a custom widget plugin so that you can easily use it in Qt Designer.

LayoutHint and updateGeometry()
Whenever a widget's size policy, size hint, or minimum size hint changes, the widget calls QWidget::updateGeometry() on itself, which posts a LayoutHint event to the widget's parent. QLayout intercepts such events and calls activate() on itself. All of this happens automatically, so we rarely need to think about it.

In our FindDialog example above, we called activate() explicitly because we wanted the layout to recompute itself straight away. We could have achieved the same result by calling QApplication::sendPostedEvents(0, QEvent::LayoutHint), which would ensure that the LayoutHint events are processed immediately and that activate() is called.


QWidgetStack with Buttons

One variant of the resizing issue discussed above occurs in connection with a button-controlled QWidgetStack. The widget combines toggle buttons and a QWidgetStack. Clicking a button brings the associated page in the QWidgetStack to the front and resizes the dialog to the minimum required by the new page. Unlike QTabWidget-based dialogs, these dialogs are usually used all the time by the user and need to be small and fast to use.

Here are screenshots from a Tool3D dialog with three pages (A, B, and C):

Tool3D

The dialog is made up of a QButtonGroup on the left and a QWidgetStack on the right. It was designed using Qt Designer, which introduced native support for QWidgetStack in Qt 3.1.

Designer

Again, we solve the tricky automatic dialog resizing using the same solution we applied to the QTabWidget: Set the size policies of hidden pages to "ignored."

Here's the init() function called by the widget's constructor. The names buttonA, pageA, etc., were entered in Qt Designer:

    void Tool3D::init()
    {
        connect(buttonA, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool)));
        connect(buttonB, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool)));
        connect(buttonC, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool)));
        pageA->setSizePolicy(ignored);
        pageB->setSizePolicy(ignored);
        pageC->setSizePolicy(ignored);
        buttonA->toggle();
    }
    

We connect the three QToolButtons' toggled(bool) signals to the dialog's buttonToggled(bool) slot; we set the size policy of all pages to "ignored"; and we programmatically "click" the first button to bring page A to the front.

Here's the buttonToggled() slot called by each button's toggled() signal:

    void Tool3D::buttonToggled(bool on)
    {
        if (on) {
            QToolButton *buttonX = (QToolButton *)sender();
            QWidget *newPage;
            if (buttonX == buttonA)
                newPage = pageA;
            else if (buttonX == buttonB)
                newPage = pageB;
            else
                newPage = pageC;
            QWidget *oldPage = widgetStack->visibleWidget();
            if (oldPage != 0)
                oldPage->setSizePolicy(ignored);
            newPage->setSizePolicy(preferred);
            widgetStack->raiseWidget(newPage);
            layout()->activate();
            setFixedSize(minimumSizeHint());
        }
    }
    

We find out which page corresponds to the button that was pressed (using QObject::sender()), we update the size policies, and we raise the new page on top of the QWidgetStack to make it visible. The calls to activate() and to setFixedSize() are identical to what we did for QTabWidget earlier.

The QTabWidget and QWidgetStack examples have a lot in common -- and, in fact, QWizard could have been another example. Qt tries to make programming as uniform as possible, so that the knowledge you accumulate using one class can serve you again in other classes.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech. Trademarks Qt Quarterly