In Qt 2.x, Qt Designer was a pure graphical ui-file editor, in other words: a visual form designer. The goal was to turn the most tedious part of GUI programming - the dialog design - into a pleasant experience. We believe that goal was reached. From an architectural point of view, Qt Designer in 2.x is a fairly simple program. It reads and writes ui-files. Each ui-file contains an XML description of one single dialog form. A second utility - the user interface compiler uic - is used during the build process of an application to generate C++ code from those XML descriptions.
In Qt 3.0, Qt Designer goes beyond that. In addition to many new design features like the ability of creating main windows and actions, it introduces:
The purpose of this document is to explain the motivation for making these changes, describe the new concepts involved and show how these features work internally.
Note that despite the new concepts, the Qt Designer in 3.0 is still usage compatible with the Qt Designer in 2.x. This means you can use it exactly the way you used Qt Designer before. However, by utilizing its new concepts you gain additional benefits - if you want to.
One word in advance: Qt Designer still is not a complete integrated development environment, and neither does it have to be. Our policy is to make GUI development as easy and powerful as possible and let our users continue using the development tools they are most familiar and productive with. If you want to create or edit a form, use Qt Designer. If you want edit code for that form, you can use the new C++ editor plugin in Qt Designer as well. This built-in editor has certain benefits stemming from its tight integration with the visual form design process that we will explain later. However, if you feel uncomfortable using yet another editor, nothing stops you from using your favorite development environment together with Qt Designer, may it be vi, emacs, notepad or Microsoft Visual Studio.
Reading and writing single, non-connected ui-files is conceptually simple and worked fairly well in Qt 2.x. However, it lacked certain features that made us introduce project management for the GUI part of an application in Qt Designer. The main benefits of project management are:
The following is a discussion of these benefits and why project management is required to achieve them.
Grouping of forms is an obvious advantage. By keeping a list of ui-files that belong to the same project, the user of Qt Designer has all forms available at a single click in the forms list.
Without project management, images were duplicated in each form they were used within. Images had to be stored in the XML file and thus were added to the generated code later. This resulted not only in very large ui-files, but also made sharing of images across form boundaries impossible. If you had an arrow-up pixmap in three different forms, the same pixmap was stored three times in the executable code.
As a workaround, we introduced a pixmap-loading function that you could define in Qt Designer. It then was your responsibility to provide the implementation of this function in your application code. The big disadvantage of this approach was that you couldn't see the images during the design process in Qt Designer. This not only makes designing a form more boring, but also has a noticeable impact on geometry management.
In Qt 3.0, we added the concept of a project based image collection to Qt Designer. During the design process of a graphical user interface, you can add new images to this collection. Those images are stored as PNGs (portable network graphics) in a subdirectory images inside the project directory. Whenever you modify the image collection, Qt Designer creates a source file images.cpp which contains both the image data in binary format and a function to instantiate the images. The Images are accessible by all forms in the project and the data will be shared.
A further benefit is that images.cpp adds the images to the default QMimeSourceFactory. This way they are accessible from rich-text labels, What's This? context help and even tooltips through standard HTML image tags. The source argument of the image tag is simply the image's name in the image collection. This also works during the design process in Qt Designer.
Qt 3.0 introduces a brand new database module Qt Sql. In order to be able to design database forms, Qt Designer needs to know about the available databases and their tables. To get this information the first time, Qt Designer needs to connect to the databases. For later usage, it stores the information in a db-file. Via invoking Reconnect in the database connections dialog, the cached information gets updated.
In almost all non-trivial database applications you will have to access the database from more than one form. This is why the db-file is part of a project, not just part of a single form.
As shown in this chapter, there is a certain amount of information that needs to be stored per GUI project in order for Qt Designer to function properly. This is a list of all forms, the image collection and information about available databases and how to access them.
Instead of adding yet another designer.cfg file that is only usable within Qt Designer, we decided to take a slightly different approach.
The majority of Qt users were already using a project-file format to create cross-platform makefiles: tmake project files, or *.pro. Among other things those project files already featured a list of all ui-files for the project. This was necessary in order to invoke uic during the build process.
In Qt 3.0, tmake was enhanced to be qmake, and is now an integral part of Qt itself. This is why it was natural to us, to use qmake project files in Qt Designer as well. When you add a form to your project in Qt Designer, it is automatically added to the INTERFACES section of the project file, and thus qmake generates the required build rules without any further work. Similarly, the image collection file images.cpp is added to the SOURCES section and thus gets automatically compiled into your executable.
If you are not using qmake as your build system, but something different (e.g. automake/autoconf or jam), your life has not changed. See the .pro file as a file describing the GUI part of your application. All you need to do is - as previously - add the ui-files and the images collection to your own Makefiles.
Qt Designer reads and writes the ui-file form.ui. The user interface compiler uic creates both a header file form.h and an implementation file form.cpp from the ui-file. The application code in main.cpp includes form.h, instantiates the form, shows it, modifies it, connects to its signals and slots or does whatever else it wants with it.
While the concept is easy, it did not prove to be sufficient for more complex dialogs. Complex dialogs tend to have quite some logic attached to the widgets of a form, more logic than what usually can be expressed with predefined signals and slots. One way of handling this extra logic is to write a controller class in the application code that adds functionality to the form. This is possible because uic generated classes expose a form's controls and their signals to the public space. The big disadvantage of this method is that it's not exactly Qt-style. If you were not using Qt Designer, you would almost always add the logic to the form itself, where it belongs.
This is why Qt Designer very soon got the capability of adding custom slots and member variables to a form. The big additional benefit with this method is that you can use Qt Designer to connect signals to those custom slots, in the same elegant graphical way that is used to connect signals to predefined slots. The uic then adds an empty stub for each custom slot to the generated form.cpp implementation file.
The big question now is how to add custom implementation code to those custom slots. Adding code to the generated form.cpp is not an option, as this gets recreated by the uic whenever the form changes - and we definitely do not want to advocate a combination of generated and handwritten code.
A very clean way to implement custom slots for generated forms is via C++ inheritance as shown in the next figure:
Here the user wrote an additional class FormImpl, which is split into the header file formimpl.h and the implementation file formimpl.cpp. The header file includes the uic-generated form.h and reimplements all custom slots. This is possible because uic generated custom slots are virtual. In addition to implementing custom slots, this approach gives the user a way to do extra initialization work in the constructor of the subclass, and respectively extra cleanups in the destructor.
Because of these benefits and its flexibility, this approach became the main way of using Qt Designer in Qt 2.x.
As a side note: To keep the namespace clean, most users did not follow the Form and FormImpl naming scheme shown in the figure, but instead named their Qt Designer forms FormBase and their subclasses Form. This made a lot of sense, because they always subclassed and were using those subclasses in application code.
Despite its flexibility and cleanness, the subclassing approach has some disadvantages:
There may be more disadvantages, but those were reason enough for us to investigate alternative solutions. For Qt 3.0, we came up with a new concept, the ui.h-extension.
This is how it works:
In addition to the ui-file form.ui, Qt Designer reads and writes another associated file form.ui.h. This ui.h-file is an ordinary C++ source file that contains implementations of custom slots. The file gets included from the generated form implementation file form.cpp and thus can be totally ignored by other user code.
The form.ui.h file has a special position among all other files. It is a shared source file that gets written and read by both the user and Qt Designer. As such it is an ordinary revision controlled source file and not generated by uic. Qt Designer's responsibility is to keep the file in sync with the custom slot definitions of the associated form:
This way integrity is guaranteed, there is no more need for subclassing and no more danger of forgotten or misspelled slots in subclasses.
You can edit ui.h-files either directly in Qt Designer with the built-in C++ editor plugin, or use whatever favorite development environment you have.
Historic remark: Early beta releases of Qt 3.0 did not yet implement the entire ui.h-concept but kept the C++ code inside the XML ui-files. This has shown to be less useful than we hoped it would be, as it made editing with custom editors and debugging more difficult.
The ui.h-extension approach has one disadvantage compared to subclassing. The ui.h file only contains custom slot implementations or other code the user might want to add, but the objects are still entirely constructed and destructed inside the generated form.cpp code.
This leaves the user without the possibility of doing further form initializations or cleanups that you normally would do within the constructor and destructor functions of a C++ class.
To work around this limitation, we created the init/destroy convention. If you add a slot void Form::init() to your form, this slot will be called automatically at the end of the generated form constructor. Respectively, if you add a slot void Form::destroy() to your form, the slot will automatically be invoked by the destructor before any form controls get deleted.
We extracted the part of Qt Designer that is responsible for loading and previewing a form into a library of its own, libqui. A new class QWidgetFactory makes it possible to load ui-files at runtime and instantiate forms from them.
This dynamic approach 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. Ultimately, you can give users of your application a choice to modify the graphical user interface without needing a complete C++ development environment.
Since the .ui file is not compiled it cannot include any C++ code, (e.g. custom slot implementations). We provide a way of adding those implementations via a controlling QObject subclass that you pass as receiver to the widget factory.
This concept and its usage is explained in detail in the chapter Subclassing and Dynamic Dialogs in this manual.