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/eg/receiver1/receiver.pro looks like this:
TEMPLATE    = app
CONFIG      = qt warn_on release
TARGET      = receiver
SOURCES    += main.cpp
unix:LIBS  += -lqresource
win32:LIBS += $(QTDIR)/lib/qresource.lib
INTERFACES  = mainform.ui 
PROJECTNAME = receiver
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 Form 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 Return. 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 Return.

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/eg/receiver1/mainform.cpp which is generated from mainform.ui.
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/eg/receiver2/mainform.cpp file generated by mainform.ui.
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/eg/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 look at the implementation of each function 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.