Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions |

[Prev: The Designer Approach] [Home] [Next: Creating Custom Widgets]

## Subclassing and Dynamic Dialogs

This chapter describes two different approaches that you can take to creating forms with Qt Designer. Subclassing is used to extend the functionality of a form by creating your own class based upon a form you create in Qt Designer. Dynamic dialogs are `.ui` files which can be executed by a Qt application; this keeps the GUI design and the code separate and is useful in environments where the GUI may have to change more often than the underlying application logic.

### Subclassing

We'll start with a general description of how to subclass a form and follow with a short example. Note that subclassing has some disadvantages compared with putting your code into a form directly; see Extending the functionality of a form in The Designer Approach chapter for details.

#### Generating Source Code from Qt Designer .ui Files

Qt Designer reads and writes `qmake` `.pro` (project) files which are used to record the files used to build the application and from which Makefiles are generated. Qt Designer also reads and writes `.ui` (user interface) files. These are XML files that record the widgets, layouts, source code and settings you've used for a form. Every `.ui` file is converted by the `uic` (user interface compiler) into a C++ `.h` file and a C++ `.cpp` file. These C++ files are then read by `moc` (meta object compiler), and finally compiled by your compiler into a working application.

If you create applications wholly within Qt Designer you only need to create a `main.cpp`.

If you create the `main.cpp` file within Qt Designer, it will automatically be added to your project file by Qt Designer. If you create the `main.cpp` file outside of Qt Designer you must add it to the project file manually by adding the following line at the end of your project's `.pro` file:

    SOURCES += main.cpp

You can then use `qmake` to generate the Makefile. (For example `qmake -o Makefile myproject.pro`.) Running `make` (Linux, Unix or Borland compilers), or `nmake` (Visual C++), will then call `uic`, `moc` and your compiler as necessary to build your application.

If you use Qt Designer to create your main window and dialogs, but also add other C++ files, or if you subclass any of your forms you will need to add these files to the `.pro` file so that they are compiled with the rest of your application's source files. Each `.h` file that you create separately from Qt Designer should be added to the `HEADERS` line, and each `.cpp` file should be added to the `SOURCES` line, just as we've done for `main.cpp`. If you get undefined reference errors it is worth checking that you've added the names of all your header and implementation files to the `.pro` file.

#### Subclassing a Form

When subclassing a form it is helpful to use a naming convention to help us identify which files are generated from Qt Designer's `.ui` files and which are hand coded.

Suppose, for example, that we are developing a dialog and writing the code directly in Qt Designer. We might call our dialog 'OptionsForm' and the `.ui` file, `optionsform.ui`. The automatically generated files will be `optionsform.h` and `optionsform.cpp`.

If we were developing another dialog, but this time one that we intended to subclass, we want to make it easy to distinguish between the automatically generated files and our hand coded files. For example, we might call our dialog 'SettingsFormBase' and the `.ui` file `settingsformbase.ui`. The automatically generated files would then be called `settingsformbase.h` and `settingsformbase.cpp`. We would then call our subclass 'SettingsForm' and code it in the files `settingsform.h` and `settingsform.cpp`.

Any subclass of a form should include the `Q_OBJECT` macro so that slots and signals will work correctly. Once you've created your subclass be sure to add the `.h` and the `.cpp` files to the `.pro` project file. For example we would add the following lines for our subclassed 'SettingsForm' at the end of the `.pro` file:

    HEADERS += settingsform.h
    SOURCES += settingsform.cpp

The simplest way to create a new source file is by clicking File|New to invoke the 'New File' dialog, then click 'C++ Source' or 'C++ Header' as appropriate, then click OK. A new empty source window will appear. You don't need to manually edit the `.pro` file since Qt Designer will add them for you automatically.

Qt Designer will have added

    FORMS = settingsformbase.ui

to the project file. The `settingsformbase.h` and `settingsformbase.cpp` files will be generated from the `.ui` file automatically.

#### A Subclassing Example

We will write a small example dialog to show the use of subclassing in practice. The dialog will present a choice of customer credit ratings with an option of choosing a 'special' rating for which a specific amount must be given. We'll implement the functionality in a subclass. We'll start by creating the base form and connecting its signals and slots, then we'll create the subclass and a simple `main.cpp` so that we can test it.

##### Designing the Form

We'll begin by creating a new project. Click File|New, then click the 'C++ Project' icon to invoke the Project Settings dialog. Click the ellipsis button to invoke the Save As dialog; navigate to the project's directory (creating it if necessary). Make sure you're in the project's directory, then enter a project name of 'credit.pro'. Click the Save button to return to the Project Settings dialog, then click OK. Now we'll add a form to the project. Click File|New to invoke the New File dialog. The default form is Dialog which is what we want; click OK. Resize the form to make it smaller; it should be about 2 inches (5 cm) square. Change the form's name to 'CreditFormBase' and the caption to 'Credit Rating'. Save the form as `creditformbase.ui`.

We'll now add the widgets we need.

1. Click the Button Group toolbar button, then click near the top left of the form. Resize the button group so that it takes up approximately half the form. Change the button group's name to 'creditButtonGroup' and its title property to 'Credit Rating'.

2. We'll now add some radio buttons. Double click the Radio Button toolbar button. Click towards the top of the Credit Rating button group and a radio button will appear. Click below this button, to create a second radio button, then click below the second button to create a third. Now we will switch off the effect of the double click by clicking the Pointer (arrow) toolbar button. The pointer will now behave normally, i.e. clicking the form will no longer create more radio buttons. Change the first radio button's name to 'stdRadioButton' and its text to '&Standard'. Change its checked property to True. Change the second button's name to 'noneRadioButton' and its text to '&None'. Change the third radio button's properties to 'specialRadioButton' and 'Sp&ecial' respectively.

3. If the user chooses the special credit rating they must specify an amount. Click the SpinBox toolbar button and click the form just below the button group. Change the spin box's name to 'amountSpinBox'. Change its prefix to '$ ' (note the space), its maxValue to '100000' and its lineStep to '10000'. Change its enabled property to False.

4. Click the Push Button toolbar button and click the form below the spin box. Change the button's name to 'okPushButton', its text to 'OK' and its default property to 'True'. Add a second button to the right of the first. Change the second button's name to 'cancelPushButton' and its text to 'Cancel'.

We'll now lay out the widgets and connect up the slots we need.

1. Click the credit rating group box then press Ctrl+L (lay out vertically).

2. Click the form so that the button group is no longer selected. Ctrl+Click the OK button and drag the rubber band to touch the Cancel button, then release. Press Ctrl+H.

3. Click the form, then press Ctrl+L.

   The widgets will be laid out vertically, each one stretching to fill up the maximum space both vertically and horizontally. The buttons look rather large since they've expanded to take up the full width of the form. It might look more attractive to make the buttons smaller using spacers. Click the OK button, then press Ctrl+B (break layout). Resize both buttons to make them narrower leaving space on either side of them. Click the Spacer toolbar button then click to the left of the OK button; click Horizontal from the pop up spacer menu. Copy this spacer and place the copy between the two buttons. Copy the spacer again and place the copy to the right of the Cancel button. (For the second and third spacers, click on the first spacer, press Ctrl+C then Ctrl+V. Drag the new spacer to the desired position.) Ctrl+Click the left most spacer and drag the rubber band so that it touches the buttons and the spacers, then release. Press Ctrl+H. Click the form then press Ctrl+L.

We'll now connect the signals and slots. Press F3 (connect signals/slots), then click the OK button. Drag to the form and release. In the Edit Connections dialog that pops up connect the `clicked()` signal to the `accept()` slot. (Click the `clicked()` signal, click the `accept()` slot, then click OK.) Connect the Cancel button to the `reject()` slot using the same technique.

We want the amount spin box to be enabled only if the special radio button is checked. Press F3 (connect signals/slots), then click the special radio button. Drag to the spin box and release. In the Edit Connections dialog that pops up click the `toggled()` signal and the `setEnabled()` slot.

If the user checks the standard or none radio buttons we want to set the amount accordingly. Press F3, then click the credit rating button group. Drag to the form and release. Click the `clicked()` signal. We want to connect this signal to our own custom slot, but we haven't created one yet. Click the Edit Slots button and the Edit Slots dialog will pop up. Click New Slot and change the Slot's name to 'setAmount()'. Click OK. This new slot is now available in the list of slots. Click the `setAmount()` slot then click OK.

We'll subclass the form to set the amount in the spin box depending on which radio button is checked. Save the form as 'creditformbase.ui' (press Ctrl+S).

##### Creating the Test Harness

Although we intend our dialog to be used within an application it is useful to create a test harness so that we can develop and test it stand-alone. Click File|New to invoke the 'New File' dialog, then click 'C++ Source', then click OK. In the editor window that pops up, enter the following code:

    #include <qapplication.h>
    #include "creditformbase.h"

    int main( int argc, char *argv[] )
    {
        QApplication app( argc, argv );

        CreditFormBase creditForm;
        app.setMainWidget( &creditForm );
        creditForm.show();

        return app.exec();
    }

Note that we're including `creditformbase.h` and instantiating a CreditFormBase object; once we've written our subclass we'll replace the header with our subclass, `creditform.h`, and instantiate a CreditForm.

We can now generate the application with `qmake`, e.g. `qmake -o Makefile credit.pro`, make it and run it. The form should run fine, but doesn't yet have the behaviour we require.

##### Creating the Subclass

We need to create a header and an implementation file for our subclass. The code for our subclass is minimal. The header file is `qt/tools/designer/examples/credit/creditform.h`:

        #include "creditformbase.h"

        class CreditForm : public CreditFormBase
        {
            Q_OBJECT
        public:
            CreditForm( QWidget* parent = 0, const char* name = 0,
                        bool modal = FALSE, WFlags fl = 0 );
            ~CreditForm();
        public slots:
            void setAmount();
        };

We've declared the slot, `setAmount()`, that we created in Qt Designer. The `Q_OBJECT` macro is included because it is essential for classes that use signals and slots.

The implementation in `qt/tools/designer/examples/credit/creditform.cpp` is simple:

        #include <qradiobutton.h>
        #include <qspinbox.h>
        #include "creditform.h"

        CreditForm::CreditForm( QWidget* parent, const char* name,
                                bool modal, WFlags fl )
            : CreditFormBase( parent, name, modal, fl )
        {
            setAmount();
        }

        CreditForm::~CreditForm() { /* NOOP */ }

        void CreditForm::setAmount()
        {
            if ( stdRadioButton->isChecked() )
                amountSpinBox->setValue( amountSpinBox->maxValue() / 2 );
            else if ( noneRadioButton->isChecked() )
                amountSpinBox->setValue( amountSpinBox->minValue() );
        }

We call `setAmount()` in the constructor to ensure that the correct amount is shown when the form starts based on whichever radio button we checked in Qt Designer. In `setAmount()` we set the amount if the standard or none radio button is checked. If the user has checked the special radio button they are free to change the amount themselves.

To be able to test our subclass we change `main.cpp` to include `creditform.h` rather than `creditformbase.h` and change the instantiation of the creditForm object:

        #include <qapplication.h>
        #include "creditform.h"

        int main( int argc, char *argv[] )
        {
            QApplication app( argc, argv );

            CreditForm creditForm;
            app.setMainWidget( &creditForm );
            creditForm.show();

            return app.exec();
        }

If you created the `creditform.h` and `creditform.cpp` files in Qt Designer, they are already in the project file, but if you created them manually you must also update the project file by adding these two new lines at the end:

    HEADERS += creditform.h
    SOURCES += creditform.cpp

To test the form rerun `qmake` to regenerate the Makefile, then make and run.

The subclassing example we've used is simple, but this reflects subclassing forms in Qt: it is easy to do.

### Creating Dynamic Dialogs from .ui Files

Qt programs are capable of loading Qt Designer `.ui` files and instantiating the forms represented by the `.ui` files. Since the `.ui` file is not compiled it cannot include any C++ code, (e.g. slot implementations). In this section we will explain how to load a dynamic dialog and how to create a class that can be used to implement the dynamic dialog's custom slots.

We will use the credit form that we created in the subclassing section as our example form. We will start by simply instantiating and running the form and then we'll cover how to implement custom slots.

We'll create a `main.cpp` file to use as a test harness, and manually create a project file.

#### Creating the Project File

The project file `qt/tools/designer/examples/receiver1/receiver.pro` looks like this:

    TEMPLATE    = app
    CONFIG     += qt warn_on release
    TARGET      = receiver
    SOURCES    += main.cpp
    unix:LIBS  += -lqui
    win32:LIBS += $(QTDIR)/lib/qui.lib
    FORMS      = mainform.ui
    LANGUAGE    = C++
    INCLUDEPATH += $(QTDIR)/tools/designer/uilib

We do not include the `creditformbase.ui` file since this file will be read at runtime, as we'll see shortly. We must include the `qresource` library since the functionality we require is not part of the standard Qt library.

#### Creating main.cpp

The `main.cpp` is quite standard. It will invoke the form we're going to create in Qt Designer as its main form. This form will then load and execute the dynamic dialog.

        #include <qapplication.h>
        #include "mainform.h"

        int main( int argc, char *argv[] )
        {
            QApplication app( argc, argv );

            MainForm *mainForm = new MainForm;
            app.setMainWidget( mainForm );
            mainForm->show();

            return app.exec();
        }

We create a new instance of our MainForm class, set it to be the main widget, show it and enter the event loop in the `app.exec()` call.

#### Creating the Main Form

##### Designing the Form

1. Open the `receiver.pro` project file in Qt Designer. We'll create a dialog as our main window which we'll use to invoke the dynamic dialog. Press Ctrl+N to launch the New File dialog and click OK to get the default which is a dialog. Change the dialog's name to 'MainForm' and its caption to 'Main Form'. Add two buttons, one called 'creditPushButton' with the text '&Credit Dialog', and the other called 'quitPushButton' with the text '&Quit'. (For each button click the Push Button toolbar button, then click the form. Change the properties in the property window to those we've just described.)

2. We will now add a couple of labels so that we can show the settings the user chose in the dynamic dialog. Click the Text Label toolbar button, then click the form below the Credit Dialog button. Change the label's text to 'Credit Rating'. Add another text label below the Quit button. Change its name to 'ratingTextLabel' and its text to 'Unrated'.

3. We'll now lay out the widgets. Click the form then press Ctrl+G (lay out in a grid).

4. We'll now handle the signals and slots connections. Press F3 (connect signals/slots). Click the Credit Dialog button, drag to the form and release. Click the `clicked()` signal. We'll need to implement a custom slot. Click Edit Slots to invoke the Edit Slots dialog. Click New Slot and type in the Slot name 'creditDialog()'. Click OK. The new slot is now in the list of slots; click the `creditDialog()` slot to make the connection then click OK. Connect the Quit button's `clicked()` signal to the dialog's `accept()` function. (Press F3. Click the Quit button and drag to the form; release. Click the `clicked()` signal and the `accept()` slot, then click OK.)

Save the form and call it `mainform.ui`. (Press Ctrl+S and enter the filename.) In the next section we'll write the code for loading and launching the dynamic dialog directly in Qt Designer.

##### Loading and Executing a Dynamic Dialog

We'll now add the code to invoke the credit dialog. Before we can do this we need to add the widget factory's header file to the form. Click the Source tab in the Object Hierarchy. Right click Included (in Implementation), then click New. Type in '`<qwidgetfactory.h>`', then press Enter. Because we will need to access the spin box in the dynamic dialog we must add its header file. Right click Included (in Implmentation), then click New. Type in '`<qspinbox.h>`', then press Enter.

In our main form we created a slot called `creditDialog()`. We will implement this slot directly in Qt Designer and use it to load and execute the dynamic dialog. The code is taken from `qt/tools/designer/examples/receiver1/mainform.ui.h` which contains the C++ implementation of `mainform.ui`'s slots.

        void MainForm::creditDialog()
        {
            QDialog *creditForm = (QDialog *)
                QWidgetFactory::create( "../credit/creditformbase.ui" );
            // Set up the dynamic dialog here

            if ( creditForm->exec() ) {
                // The user accepted, act accordingly
                QSpinBox *amount = (QSpinBox *) creditForm->child( "amountSpinBox", "QSpinBox" );
                if ( amount )
                    ratingTextLabel->setText( amount->text() );
            }
            delete creditForm;
        }

The `create()` function is a static QWidgetFactory function. It loads the specified `.ui` file and returns a pointer to the toplevel QWidget created from the `.ui` file. We have cast the pointer to QDialog since we know that the `creditformbase.ui` file defines a QDialog. After creating the dialog we `exec()` it. If the user clicked OK the dialog returns Accepted and we enter the body of the `if` statement. We want to know the amount of credit that the user selected. We call the `child()` function on the dialog passing it the name of the widget we're interested in. The `child()` function returns a pointer to the widget with the name we passed, or returns 0 if no widget of that name was found. In the example we call `child()` to get a pointer to the 'amountSpinBox'. If the pointer we get back is not 0 we set the rating text to the amount in the dialog's spin box. At the end we delete the dynamic dialog. Deleting the dialog ensures that we free up its resources as soon as it is no longer required.

We used the `child()` to gain access to a widget within the dynamic dialog, passing it the name of the widget we were interested in. In some situations we might not know what a widget is called. We can access the first widget of a specified class by calling `child()` with a null widget name and a classname, e.g. `child(0,"QPushButton")`. This will return a pointer to the first QPushButton it finds (or 0 if there isn't one). If you want pointers to all the widgets of a given class you can call the `QObject::queryList()` function, passing it the name of the class. It returns a QObjectList pointer which points to every object in the dialog that is derived from the given class. See the online QObject documentation for further details.

##### Implementing Slots for Dynamic Dialogs

There is one outstanding issue that we haven't addressed: the dynamic dialog does not have the behaviour of the original credit dialog because we have not implemented the `setAmount()` slot. We can implement slots for dynamic dialogs by creating a QObject subclass. We then create an instance of this subclass and pass a pointer to it to the `QWidgetFactory::create()` function which will connect the dynamic dialog's signals to the slots implemented in our subclass.

We need to create a QObject subclass and change our `creditDialog()` to create an instance of our subclass that can be passed to the `QWidgetFactory::create()` function. Here is the modified `creditDialog()` function from the `qt/tools/designer/examples/receiver2/mainform.ui.h` file that contains the code for `mainform.ui`'s slots:

        void MainForm::creditDialog()
        {
            Receiver *receiver = new Receiver;
            QDialog *creditForm = (QDialog *)
            QWidgetFactory::create( "../credit/creditformbase.ui", receiver );
            receiver->setParent( creditForm );

            // Set up the dynamic dialog here

            if ( creditForm->exec() ) {
                // The user accepted, act accordingly
                QSpinBox *amount = (QSpinBox *) creditForm->child( "amountSpinBox", "QSpinBox" );
                if ( amount )
                    ratingTextLabel->setText( amount->text() );
            }

            delete receiver;
            delete creditForm;
        }

We create a new instance of our 'Receiver' subclass. (We'll write the code for this class shortly.) We then create the QDialog using `QWidgetFactory::create()`. This call differs from our previous example because we pass in the subclass object so that the `create()` function can set up the signals/slots connections automatically for us. Since our slot must access the widgets in the dynamic form we pass a pointer to the form to the receiver object through our `setParent()` function. The remainder of the function is the same as before except that we delete our receiver object.

We'll now look at the implementation of our 'Receiver' subclass. The code is taken from `qt/tools/designer/examples/receiver2/receiver.h` and the corresponding `receiver.cpp` file. We'll start with the header file.

    #include <qobject.h>
    #include <qdialog.h>

    class Receiver : public QObject
    {
        Q_OBJECT
    public:
        void setParent( QDialog *parent );
    public slots:
        void setAmount();
    private:
        QDialog *p;
    };

Our class must be a QObject subclass and because we're using signals and slots it must include the `Q_OBJECT` macro. We declare a function and the `setAmount()` slot that we wish to implement as well as a private QDialog pointer.

We'll discuss the implementation of each function in `receiver.cpp` separately.

        void Receiver::setParent( QDialog *parent )
        {
            p = parent;
            setAmount();
        }

The `setParent()` function assigns a pointer to the dynamic dialog to our private pointer. We could not do this in a constructor call because we have to construct our Receiver object before we call `QWidgetFactory::create()`, since we must pass the Receiver object to the `create()` function. Once we've called `create()` we then have a pointer to the dynamic dialog which we can then pass via `setParent()` to our Receiver class. In the subclass version of this example we called `setAmount()` in the constructor; but we cannot do that here because the implementation of `setAmount()` depends on knowledge of the dynamic dialog which is not available at construction time. Because of this we call `setAmount()` in the `setParent()` function.

        void Receiver::setAmount()
        {
            QSpinBox *amount =
                (QSpinBox *) p->child( "amountSpinBox", "QSpinBox" );

            QRadioButton *radio =
                (QRadioButton *) p->child( "stdRadioButton", "QRadioButton" );
            if ( radio && radio->isChecked() ) {
                if ( amount )
                    amount->setValue( amount->maxValue() / 2 );
                return;
            }

            radio =
                (QRadioButton *) p->child( "noneRadioButton", "QRadioButton" );
            if ( radio && radio->isChecked() )
                if ( amount )
                    amount->setValue( amount->minValue() );
        }

Since we may be updating the amount spin box we need to get a pointer to it. We call `child()` on the pointer `p` which points to the dynamic dialog assigned in the `setParent()` call. We cast the resulting pointer to the correct type so that we can call any functions relevant to that type. In the example we call `child()` to get a pointer to the amount spin box, and then call `child()` again to get a pointer to the 'stdRadioButton'. If we get a pointer to the radio button and the button is checked we set the amount providing we have a pointer to the amount spin box. If this radio button was checked we're finished so we return. If the 'stdRadioButton' isn't checked we get a pointer to the 'noneRadioButton' and set the amount if this button is checked. We do nothing if the 'specialRadioButton' is checked because the user is free to enter a value of their choice.

> Compiling vs Dynamically Loading Dialogs

> The differences between using a 'compiled in' `.ui` file and a dynamically loaded `.ui` file are these:

> - Dynamic dialogs cannot have any C++ code in the `.ui` file; any custom slots must be implemented via a QObject subclass. Compiled dialogs can contain code either in the `.ui` file or in a subclass.

> - Dynamic dialogs will load slower because the `.ui` file must be read and a QWidget instance instantiated based on the `.ui` file's parse tree. Compiled code will load much faster because no file reading or parsing is necessary. Note that the user may not notice any difference in speed since the difference may be mere fractions of a second.

> - Dynamic dialogs allow you to change the `.ui` file independently of the code so long as none of the changes impact the code. This means that you can change the appearance of the form, e.g. move widgets and lay them out differently. If you want to change a compiled dialog you must change the `.ui` file and recompile. If you are building an application and want your customers to be able to customize aspects of the user interface you can give them a copy of Qt Designer and use dynamic dialogs.

[Prev: The Designer Approach] [Home] [Next: Creating Custom Widgets]