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.
We'll start with a general description of how to subclass a form and follow with a short example.
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 and add
SOURCES += main.cpp |
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.
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 |
INTERFACES = settingsformbase.ui |
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.
We'll begin by creating a new project. Click File|New Project and type in a project name of 'credit' and a project file name of 'credit.pro' then click OK. Now we'll add a form to the project. Click File|New to invoke the New Form 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 'CustomerFormBase' and the caption to 'Credit Rating'. Save the form as 'customerformbase.ui'.
We'll now add the widgets we need.
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'.
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. Click the Pointer (arrow) toolbar button to stop clicking the form from adding any 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.
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.
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 lay out the widgets and connect up the slots we need.
Click the form so that no widgets are selected. Ctrl+Click the Standard radio button and drag the rubber band so that it touches the other two radio buttons, then release. Press Ctrl+L (lay out vertically). Click the Credit Rating button group, then press Ctrl+H (lay out horizontally).
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.
Click the form, then press Ctrl+L.
The buttons expand to take up the full width of the form. It might look more attractive to use spacers with them. 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).
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. We'll create a standard main.cpp as follows:
#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(); } |
Add main.cpp to the credit.pro project file by adding the following line at the end:
SOURCES += main.cpp |
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/eg/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(); }; |
The implementation in qt/tools/designer/eg/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() ); } |
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(); } |
HEADERS += creditform.h SOURCES += creditform.cpp |
The subclassing example we've used is simple, but this reflects subclassing forms in Qt: it is easy to do.