Original article at Dr. Dobb's Journal
Among the many GUI programming libraries available, both free and licensed, the Qt C++ library by Trolltech (http://www.trolltech.com) has a very strong following. With the advent of Qt Designer 3.3.1, Qt developers now have a feature-rich IDE with which to design and code their GUI applications. The lack of compiler support is the only significant drawback in an otherwise polished and useful tool.
The original Qt Designer which appeared in Qt 2.0 did one thing, and did it well: WYSIWYG interface design. This filled a void in the world of Qt development which at the time required most of this code to be written by hand. The latest Designer (available in Qt 3.3.1) now goes above and beyond simple GUI building. It supports full project management, source code editing, resource management for images and database connections, and a full suite of controls to design with.
Designer produces tight, reliable code, and it does so for all platforms supported by Qt (Windows, Linux and other *nix platforms, and Mac OSX). Qt is a C++ library, so all code produced by Designer is in C++. In order to support the advanced event handling which Qt employs, there are some additional preprocessing steps necessary when compiling applications based on Qt. This may seem like unwanted overhead, but in practice these steps are integrated neatly into the compilation process, and you are rarely bothered by any of the details.
Designer works with Qt project files (.pro) directly, so that it can be used on an as-needed basis to design and implement the GUI portions of your application, and it does so in a straightforward and understandable way. Designer's project management gives you control over application include paths, linking with other libraries and platform specific settings.
[qt-designer-screenshot1.pcx] Figure 1: Qt Designer 3.3.1
Designing
The original method for designing user interfaces is still available in Designer 3.3.1, and it is often the fastest and most straightforward way of using Designer in your C++ GUI projects. In order to fully appreciate what is going on while designing interfaces, an understanding of the Qt object model is necessary.
In Qt, all classes inherit from QObject and all visual classes inherit from QWidget. QObject provides the framework for inter-object communication and also provides memory management for child objects. QWidget (which is derived from QObject) provides event handling, painting routines and window functionality. Designer exploits the meta information available in the Qt object system and, through limited introspection, can make the method names and parameters of Qt classes available to the designer. Interfaces are saved in .ui files which are preprocessed by a User Interface Compiler (uic) during compilation. The uic generates regular C++ classes that correspond to the interface you designed. You can extend the behavior of these generated classes by inheriting from them normally. Example 1 implements a simple 'Address Book' application using this method of design. The 'Address Book' application displays a list of contacts, and allows you to alter and insert contact information using QLineEdit widgets.
[example1.zip] Example 1: A simple "Address Book" application built with Designer
The latest Designer improves upon this basic method of design by now providing integrated source code editing of your interfaces (see 'The Integrated Editor' below) which avoids the extra step of inheriting from Designer-generated classes and significantly speeds up development time.
The actual process of building interfaces in Designer is what you would expect from a modern IDE, and Designer supports all widgets provided by Qt (see Figure 2). One interesting feature is the automatic 'layout' tools which allow you to drop a bunch of controls onto a window and then be able to quickly organize and manage layouts with a minimum of fuss (as shown in Figure 3). In addition, widget properties can be edited in groups with the property editor after selecting them in Designer (Figure 4).
[qt-designer-screenshot2.pcx] Figure 2: Populating a window with widgets
[qt-designer-screenshot3.pcx] Figure 3: Using layouts to organize widgets
Those familiar with modern GUI builders will be comfortable using Designer to build their interfaces. You can select different widget types and place them on your windows, grouping and organizing them in any way. You can also set widget properties, define tool tips, adjust layouts, initialize list boxes, etc... There are a few annoyances though, including the lack of a default action when placing new widgets: pressing return usually does nothing, and occasionally double-clicking a new widget also does nothing. While right-clicking always gives you access to editing basic widget properties, it is annoying to constantly have to resort to this when a reasonable default action should be made available in the main design window.
[qt-designer-screenshot4.pcx] Figure 4: Editing widgets in a group
Signals and Slots
At the heart of Qt is the signal/slot mechanism, which provides a clean abstraction of event handling. As shown in Figure 5, Designer provides visual signal/slot editing which makes this mechanism very intuitive while designing. Even if you have never programmed with Qt before, Designer exposes the most powerful parts of the Qt GUI library to the developer in an organized fashion.
[qt-designer-screenshot5.pcx] Editing signals and slots
In Qt, 'signals' (which loosely correspond to 'events') are generated by objects at runtime. These signals are regular class methods which are marshaled by the Qt meta object system and connected to other widgets via 'slots' (which are also regular class methods). This is a flexible and powerful system for event handling, and it is used extensively throughout the Qt library. An extra pre-compilation step is necessary using the Meta Object Compiler (moc) which generates the meta object code used internally by Qt when connecting 'signals' to 'slots'. There are advantages and disadvantages to implementing event handling in this way versus, for example, a template and function object-based approach (see for example http://libsigc.sourceforge.net). However, the approach taken by Qt has proven itself over time to be flexible, portable and 'fast enough', especially when it is considered that the cost of emitting signals as implemented by Qt is extremely minor when compared to the cost of other code within GUI programs.
Creating new slots for your interface can be done in a number of different ways within Designer. While creating signal/slot connections, new slots can be defined for the window to handle them. Also, using the 'Signal Handler' property editor which displays all the signals generated by the selected widget, you can quickly define new slots that are automatically given reasonable names by Designer. In either case, empty virtual methods are automatically generated by Designer in the corresponding window's source code. While previous versions of Designer stopped at that point, the latest Designer allows you to edit the source code for these new slots while you are designing.
For example, the bulk of the behavior in the 'Address Book' example is handled by connecting various 'signals' with 'slots' implemented in the AddressBookImpl class, which is the topmost widget in the application in Example 1. As a developer, you need not worry about all of the internal plumbing implemented by Qt to support signals and slots. Once you understand the concept behind signals/slots, this abstraction really speeds up development.
The Integrated Editor
Designer's integrated source code editor is pretty good. It provides syntax highlighting, automatic indentation, tab-completion and limited method name completion. Designer does its best to maintain synchronization between source code and GUI design by adding new methods in the Object Explorer when source code is edited, and automatically creating skeleton code when new methods are added via the IDE. There are a few situations where source code can fall out of sync with the visual design, most notably when you change the class name of a top-level widget. Since Designer cannot keep up with these types of changes, you will get errors during compilation and have to go back and figure out exactly where Designer lost track. It's best to design new windows, name controls and finish the layout before getting into source code editing.
Familiarity with Qt programming is helpful but not necessary when editing source code from within Designer. The Qt library is known for its intuitive class hierarchy and method names, so it's usually easy to accomplish what you want even without a good working knowledge of the library. For example, in the "Address Book" example application, automatically updating line edit controls whenever a new list item is selected is simple to accomplish in the AddressBook::newItemSelected() slot (see Listing 1, which is part of the code included in Example 2).
Listing 1: void AddressBook::newItemSelected( QListViewItem * item ) { // update line edits if ( item ) { firstLineEdit->setText( item->text(0) ); lastLineEdit->setText( item->text(1) ); emailLineEdit->setText( item->text(2) ); } }
It is important to note that the code you are editing for a window is actually a file included by the generated implementation of the widget. The window definition for the "Address Book" application is contained in addressbook.ui. At compile time, uic generates the corresponding .h and .cpp file that correspond to the design. The custom source code that was added to the AddressBook class is actually contained in addressbook.ui.h, which is included by the implementation of AddressBook as shown in Figure 5.
/--------------------\ |GUI design: | | addressbook.ui | \--------------------/ | | \- Generated by uic -> addressbook.h | \- Generated by uic -> addressbook.cpp | \- #include -> addressbook.ui.h
Figure 5: Files generated by Designer
The name of the file containing the custom code for the AddressBook class is a bit of a misnomer, since this file is actually included directly in the implementation file, addressbook.cpp. It would have been more clear to name this file addressbook.ui.cpp. In any case, this included file relies on the class declaration generated by Designer, therefore it is important to make sure that any methods added by hand into addressbook.ui.h are also recognized by Designer and added to the list of methods for the AddressBook class (otherwise, it will not compile). Normally, the source code editor within Designer does a good job parsing the code and recognizing both new methods and changes to existing methods. Occasionally, though, the source code can get out of sync with Designer's definition, so it pays to keep an eye on the 'Object Explorer' while editing source code to make sure it stays up-to-date. Hopefully, the next version of Designer will address some of these synchronization issues, since editing the source code for slots directly within Designer is by far the most useful feature of this tool.
The entire 'Address Book' application, written completely within Designer instead of using inheritance, is available as Example 2.
[example2.zip] Example 2: The "Address Book" application written entirely within Designer
Resource Management
Designer not only manages multiple window designs for a project, but also image and database resources. Consolidating all images from a project makes accessing image resources simple. Images are added to the project's 'Image Collection', and behind the scenes Designer generates all the code necessary to include the images into the compiled application, as well as registering the names of the images into the application's MIME factory so that they can be retrieved with a single function call (see Listing 2).
Listing 2: addButton->setPixmap( QPixmap::fromMimeSource( "logo.xpm" ) );
Internally, this static method searches the internal list of registered MIME-types until it finds the "logo.xpm" resource which Designer generated. This could all be done by hand, of course, but Designer relieves the developer of the burden and generates very reasonable code.
An important new feature in Qt 3.x is the SQL module, and Designer has full support for not only adding database connection information to your projects, but also interactively allows you to design with data-aware controls including data tables, browsers and views. The Qt SQL module provides uniform access to a variety of databases by presenting a single API which is implemented by a variety of database-specific plugins for back-ends such as Oracle, MySQL, PostgreSQL and ODBC. The data-aware widgets in Qt (including QDataTable, QDataView and QDataBrowser) give you access to efficient data displays as well as inline-editing of data directly from the database. Designer supports these widgets as fully as any other control, which makes development of non-trivial database applications with Designer just as easy as those created with database RAD tools, such as Microsoft Visual Basic. The main difference being, with Qt, the generated applications run natively on a wide variety of platforms with a simple recompile.
Custom Widgets and Templates
Adding your own widgets to Designer is usually necessary in any non-trivial program. Designer supports this by maintaining a list of 'custom widgets' which can be added to windows like any other control, although it appears in the design window as an anonymous grey box (see Example 3 which is yet another implementation of the "Address Book" application using a custom widget). Just adding your widget's class name, and its relevant signals, slots and properties to the 'Custom Widget' dialog is enough to let you visually design with it. Designer will generate the proper code to include your widget in the interface, and all you need to do is add your custom widget's implementation to the project.
[example3.zip] Example 3: The "Address Book" application using a custom widget
Designer even supports custom widget 'plugins'. This requires more coding on your part, but once completed your custom widget will appear normally in the design window instead of as a grey box. This may be helpful for those who have separate developers working on GUI design as opposed to non-GUI application functionality, since they will be able to work with custom controls just as if it were any other control within Designer. In practice, it is usually sufficient to give Designer the top-level description of your custom widgets and skip the whole plugin process.
Another helpful tool Designer supports are 'templates'. These can be created for entire window designs, or can be created to provide base class functionality for entire groups of windows or other container widgets. As the name implies, templates provide pre-configured widget layouts which save you the trouble of recreating common layouts (Designer ships with some sample templates including 'Dialog' and 'Main Window').
Conclusion: Everything But the Compiler
The environment in which C++ developers work has a big impact on productivity. Many are quite happy using emacs and a few terminals to develop their applications. Others prefer the bells and whistles of, for example, MS Visual Studio. Between these two extremes lies Qt Designer.
Using Designer, C++ GUI application development with Qt is fast, maintainable and flexible. You have access to all that the Qt library offers including sophisticated widgets, intuitive event handling and database support. Designer manages entire C++ projects, and it can be customized and extended with user-defined widgets and templates. Designer can even be used on larger projects in such a way that GUI design is a separate and distinct task from developing application functionality, which may be useful in some development shops.
The resulting code generated by Designer will compile and run natively on almost any platform. Where Designer falls short is its lack of support for compilation. All other major IDEs (including emacs and MS Studio) support the compile/edit cycle extremely well, but Designer is remarkable in that it completely skirts this issue. Those comfortable with compiling from the command line may appreciate this, since Designer can essentially stay out of your way. However, it would be a major advantage to new developers if the option existed to launch a compile from within Designer, with the ability to track down compile errors and fix them from within the Designer IDE. Hard core developers would still have the option to skip this step if they choose, but having the option to compile from within Designer would fill the last remaining hole in an otherwise useful and powerful GUI design tool.