A cross-platform tool for C++ GUI development
Dave Berton is a professional programmer responsible for the original design of the Qt SQL database layer. He can be contacted at ---.
The Designer IDE, which comes with the latest Qt C++ library by Trolltech (http://www.trolltech.com/), has filled a gaping hole in the world of application development with Qt, namely the lack of a visual IDE. The latest Designer (available in Qt 3.3.1) provides a modern GUI development environment complete with code generation, code editing, and project management.
Early versions of Designer (in Qt Version 2.x) provided basic WYSIWYG interface building. Its sole function was to design visual interfaces, then generate corresponding C++ code that corresponded to the visual design. At the time, this had tremendous utility since the alternative was to either build your interfaces by hand in code, or use one of a number of limited freeware utilities. With Designer, almost the entire Qt class hierarchy was made available to design with, and the code that it generated was on par with hand-crafted code. The latest incarnation of Designer 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 generates sane and reliable code that can be compiled for all platforms supported by Qt including Windows, Linux and other UNIX platforms, and Mac OS X. Since it is primarily a GUI builder, Designer saves its work in separate .ui files that must be preprocessed by special utilities in order to utilize them in your projects. This may seem like unwanted overhead, but in practice these precompilation steps are integrated neatly into your builds by Qt's build system (qmake). Designer works with the same project files used by qmake (.pro) so that it can be used on an as-needed basis to design and implement the GUI portions of your application; see Figure 1.
The Qt Metaobject Model
Designer really shines in its handling of the Qt metaobject model. In Qt, metaobject information is managed with a signal/slot mechanism, which lets you perform limited introspection and manage runtime callbacks of object methods.
In Qt, signals are regular class methods that correspond loosely to events. Any object derived from QObject can generate a signal. The Qt metaobject system handles information about the object, the signal, and its parameters so that you can perform runtime introspection. However, the most important aspect of signals is that they can be connected, at runtime, to slots. A slot is a regular class method that is invoked at runtime by the Qt metaobject system whenever a signal that is connected to it is activated. This allows communication between disparate objects without any knowledge of each other beyond their integration into the Qt metaobject system. The only penalty is a common parent class (QObject) and the cost of runtime dispatching of events (which is hidden away within the Qt metaobject system).
To create a class that is part of this metaobject system, simply declare a class derived from QObject, as in Listing 1.
The important macro Q_OBJECT must be present in the declaration of the class since this is required by the Meta Object Compiler (MOC). The MOC must perform preprocessing on all files that declare classes derived from QObject to generate the metaobject information for your class including the class name, signal names, slot names, and other information for the metaobject system. The MOC preprocessor is invoked automatically when building Qt projects.
Listing 2 is the implementation of the Foo class. As you can see, the Foo::doIt() slot is defined, but the Foo::didIt() signal is not. The reason for this is because in Qt, a signal is implemented separately by the MOC in generated code. Once activated by the emit macro, the methods of any other objects that have been connected to the Foo::didIt() signal are invoked at runtime.
Qt is a GUI library, so most signal/slot methods are used by visual classes that derive from QWidget. QWidget (which is itself derived from QObject to take advantage of the metaobject system) provides general event handling, painting routines, and windowing functionality. The Qt library provides all the modern widgets including listboxes, push buttons, and scroll bars, as well as advanced controls such as spreadsheet-like tables and dockable toolbars.
Qt Widgets (such as buttons, listboxes, and sliders) provide all the expected behaviors, which manifest themselves primarily through signals and slots. For example, QListBox provides signals such as highlighted(const QString&), which is emitted whenever a new item is highlighted within a listbox. QLabel provides slots such as setText(const QString&), which allows the text of a label to be changed. The highlighted signal from the listbox can be connected to the setText slot of the label at runtime, as in Listing 3 (taken from the "signal-to-slot" example at http://www.cuj .com/code/). In this example, whenever a new listbox item is selected, the text of the label changes. Notice the SIGNAL() and SLOT() macros\u2014these actually transform their arguments into plain strings for internal use by the Qt metaobject system when connecting signals and slots.
In addition to runtime marshaling of methods, the Qt metaobject system also handles memory. All Qt objects can be created with a parent object that will assume ownership of objects created on the heap, which is the reason why widgets are generally created using new. Since creating GUI classes on the heap like this is a relatively rare event (compared with other operations inside a typical GUI application), this does not prove to be a performance problem at runtime.
The Qt metaobject system has many advantages, not the least of which is its portability to a variety of platforms. One of the original design goals for Qt was portability, which ruled out a template-based solution since early C++ compilers had poor template support. The Qt metaobject system is quite flexible, and provides a very clean syntax with which to express signals and slots, and their connections. This clean syntax is not to be overlooked, even though it is not Standard C++. A clean syntax opens the door to understanding and easy adoption. Access specifiers are supported so that you can declare, for example, private slots. And, of course, adding metaobject information also allows for runtime introspection of an object's properties, which can be handy in certain situations.
There are a few disadvantages to this approach, however. The connection of signals to slots relies on a string representation of the methods being connected, which is not type-safe. A small typo can result in a difficult-to-debug (mis)connection. The use of Designer for interface design, however, can practically eliminate this problem since all of the GUI code is automatically generated correctly. Further, it is not wise to rely on the efficiency of invoking signals. The implementation for this callback mechanism has been tweaked over the years and is quite fast; however, it still requires several function calls more than a template-based solution. For example, libsigc++ (http://libsigc.sourceforge.net/) provides a type-safe and extremely efficient implementation of callbacks that relies on templates and function objects. However, libsigc++ has a different design goal and in practice, the Qt metaobject system has proven itself to be more than sufficient for GUI application programming needs.
Other Qt Classes
In addition to a fleet of visual classes, Qt provides a fairly complete application development framework. This includes platform-independent file and directory classes, XML classes, full regex support, and database classes. Qt also supports its own template library, giving you access to STL-like containers even on platforms where the STL may not be fully supported. Qt's thread classes provide a cross-platform API for multithreaded development and the Qt network classes provide rudimentary but portable network support. For graphics, there are platform-independent image classes, 2D graphics classes, and an OpenGL layer. The key aspect of all these tools is their platform independence. Qt shields you from the details of any particular platform and presents a unified and consistent API.
Designing
Armed with the power of the Qt metaobject system, Designer exposes the entire Qt visual class hierarchy while visually building your interfaces. Class and method names, as well as method parameters, are available while designing, and Designer employs a nice interface to the signal/slot mechanism. Signals from any widget can be connected to compatible slots of any other widget, as in Figure 2 (see the "connect" example; http://www.cuj.com/code/).
During compilation, the User Interface Compiler (UIC) is invoked to transform the .ui file created in Designer into regular C++ classes that correspond to the interface you designed. Designer therefore relieves you of the burden of generating all the interface code of your applications by hand, which can be a significant amount. It also relieves you of writing custom code for all the connections between widgets. Instead, it is all done visually (and quickly) in Designer. Once the design is finished, classes generated by UIC can be extended by normal inheritance, which is a nice way of developing GUI programs because the visual portions of your application will remain separate from any application-specific functionality. Designer lets you define custom slots in the interface that will remain unhandled by the UIC-generated code. These unhandled slots are virtual so that you can fill in the desired functionality in your derived classes.
The latest Designer goes one step further than simply generating code for base classes. It contains an integrated source-code editor that allows you to write code for custom slots directly within Designer. This may present a bit of a maintenance problem because both the GUI design and the application code are now contained in the same place. However, for fast application development this is a nice feature. Also, the custom code for your interfaces is maintained by Designer in a separate source file, which allows you to continue working with your editor of choice.
Designer's integrated source-code editor provides syntax highlighting, automatic indentation, tab completion, and (by exploiting the Qt metaobject system) limited method name completion (including pop-up assistance). Those familiar with other advanced IDE editors will be comfortable using this one. 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. Again, since Designer's original goal was to provide rapid GUI design, you may choose to leave source-code editing to your favorite editor and let Designer generate suitable base classes from which to derive. However, for simple slots, it is often convenient and quick to simply embed custom code into slots while creating your GUI within Designer.
Custom Widgets
Most nontrivial applications will contain custom widgets. A custom widget is typically derived from one of the standard Qt widgets and contains extra domain- or application-specific code. The QListView class provides standard list functionality (including tree views) but can be customized, for example, into a general-purpose directory browser. Typical Qt developers will often have a large collection of general-purpose custom widgets such as this. Fortunately, Designer fully supports integrating your own custom widgets in two different ways.
The simplest way is through the Custom Widgets dialog. You enter information about your custom widget, including the class name, associated header file, signal names, and slot names. The custom widget is immediately available in the design window as an anonymous gray box; see Figure 3.
In most situations, this is sufficient for designing with custom widgets since the primary attributes of the custom widget (its visual layout and its signal/slot connections) are available to Designer. However, Designer also supports custom widget plug-ins. This requires a good deal more coding on your part because the custom widget needs to be registered and exported with Designer-specific plug-in classes. This immediately reduces the utility of designing with custom widgets since you must now start writing wrapper code to support your widget within Designer. The option to write Designer plug-ins for your custom widgets is there, though, for those who would like to have the full power of Designer applied to their custom widgets. In practice, it is usually sufficient to give Designer the top-level description of your custom widgets in the Custom Widget dialog, and just get down to the task of designing interfaces. A complete application that utilizes a custom widget is in Figure 4 (see the "custom" example; http://www.cuj.com/code/).
Designer contains other time-saving features as well. For example, interface templates allow you to package and save prebuilt designs for use in future projects. Designer also has full support for the Qt database classes, making the development of database applications straightforward. The Designer environment is pretty flexible, and you can customize toolbars and source-code editor settings. On Windows, there is limited integration with MS Visual Studio. In UNIX, Designer can import designs created with Qt Architect and Glade.
Conclusion
The Qt library provides tremendous cross-platform tools for C++ GUI application development. Using the Designer IDE, development time can be significantly reduced and interface design can be completely separated from developing application functionality. Designer gives you access to all the visual Qt classes, including advanced widgets and custom widgets. Event handling with signals and slots is fully supported, and custom code can be edited directly in Designer with a full-featured editor. Designer integrates well with qmake, the Qt build system, and it can be extended with user-defined templates or even (with some extra work) custom widget plug-ins.
One of the most compelling selling points of the Qt GUI library is that the code generated by Designer compiles and runs natively on almost any platform. Designer is not a complete IDE in that it does not support the normal compile/edit/debug cycle of many other development environments, such as Emacs or MS Visual Studio. However, its support for fast interface design, event handling, and custom code editing still makes it a worthy tool.