Qt is known as a cross platform graphical user interface toolkit. This is true, it is a great cross platform GUI toolkit. But! It is also a cross platform toolkit concerning databases, file access, sockets and much more. This article concerns the Qt object model and why this is an improvement over the classic C++ model.
The classic C++ object model has data and methods that either can be private, protected of public. When designing a GUI (or any other event driven task) we want to tie a method implemented by us to a specific event. To do this, we inherit the event generating class and overload the virtual method and re-implement it to do what we want it to. This gives us two problems: a) we will have a new class for each action and b) if there were any functionality in the original, virtual, method, we will have to re-implement it or make an explicit call to the original function.
Qt solves this problem by introducing signals and slots. A method can be defined as a slot that is either private, protected or public. Slots are ordinary methods, but they are listed in the slot table of the meta object. Each class utilizing the Qt object model has an automatically generated meta object containing type information, inheritance information and a list of signals, slots and properties.
A signal is declared as if it was a usual method, but it may not be implemented. Signals cannot be called but emitted. Let us go back to the event driven GUI example. Instead of inheriting the event generating class we implement a slot that fits the signal emitted at the interesting event.
There are more packages based on signals and slots such as GTK+ and Boost.Signals. These are not dealt with in this text. If you intend to work with this concept in your projects I recommend you check these alternatives out too. My personal opinion is that GTK+ fails by being implemented in C and Boost.Signals do not offer GUI components and thus falls outside my current application.
Let us demonstrate this by looking at a small example. We will put two buttons in a dialog and let them alter the text of the title bar. We start with a version using the inheritance method, after that, the Qt version is discussed.
We start of by looking at our imaginary button class. It holds a lot more than the stuff that is shown here, but this shows the idea.
class Button : public Widget
{
public:
Button( char *label , Widget *parent=0 );
Button( Widget *parent=0 );
~Button();
...
protected:
// Replace these methods to respond to events
virtual void eventClicked( void );
...
};
This class if fairly straightforward to use. Simply inherit it and replace the virtual methods of your choice to respond to events. We can also assume that the layout management of our imaginary toolkit uses a Qt-like approach, so that we simply supply a parent and things work by them selfs. We also assume that the toolkit takes care of memory management just as Qt, i.e. when the parent is deleted, any children are deleted too.
Now we create our dialog though inhertitance from our imaginary toolkit.
class Example1Dialog : public Dialog
{
public:
Example1Dialog();
};
Before we can implement the constructor we will need to create our buttons. These buttons contain the actions that is needed to change the title of the dialog.
class FooButton : public Button
{
public:
FooButton( Dialog *parent ) : Button( "Foo", parent ) {}
protected:
void eventClicked( void ) { parent->setCaption( "Foo" ); }
};
class BarButton : public Button
{
public:
BarButton( Dialog *parent ) : Button( "Bar", parent ) {}
protected:
void eventClicked( void ) { parent->setCaption( "Bar" ); }
};
I changed the parent type to only accept dialogs, just to be on the safe side as we call the parent when we recieve an event. Nothing too complex this far and the implementation of the dialog constructor is not too hard either.
Example1Dialog() : Dialog()
{
setCaption( "Click!" );
FooButton *fb = new FooButton( this );
BarButton *bb = new BarButton( this );
}
Notice that I have left out any layout related code. This version is a bit simplified, but so is all code throughout this example.
So what are the drawbacks of this version you may ask? One point is that we will end up with lots of classes. Another, more serious, is that the functionality for the dialog (changing the title from user actions) is spread though two classes (not counting the dialog itself). This makes it harder to overview the code and thus it is harder to maintain it. Before drawing any final conclusions, let us continue with the Qt solution.
As opposed to the previous example we will let the functionality, i.e. the slots, be a part of our dialog and simply connect the "clicked" signals from the buttons to the appropriate slots.
This example covers a fully working example. Remember this when comparing it to the previous example which just showed the highlights.
We begin by looking at the main function that simply initializes the application. It is very simple and should not pose any problems.
#include "example1dialog.h"
int main( int argc, char** argv )
{
// Initialize Qt
QApplication app( argc, argv );
// Create an instance of our dialog
example1Dialog dialog( 0, 0, TRUE );
// Set it as the main widget (as we do not have a main window)
app.setMainWidget(&dialog);
// Execute
dialog.exec();
return 0;
}
Notice that Qt is accompanied by a great documentation set that you can refer if you want to know any details. It is available on-line from http://doc.trolltech.com and contains reference material, examples and in-depth tutorials. In this tutorial I use the free 2.3 version for win32 just to demonstrate that the Windows version is freely available too. The code I present here is fully compatible with any UNIX or Mac version of Qt (it is, as I mentioned, a cross platform toolkit).
Now, we can continue by looking at the class declaration of the example1Dialog class. This is where the Qt object model enters.
class example1Dialog : public QDialog
{
Q_OBJECT
public:
example1Dialog( QWidget* parent = 0, const char* name = 0, bool modal = FALSE, WFlags f = 0 );
protected slots:
void fooClicked( void );
void barClicked( void );
};
First, notice that we inherit a descendant of QObject. This is necessary as the QObject class holds much of the object model functionality. The first line in the actual body of the class declaration must be Q_OBJECT. This macro has two purposes. It declares the things needed to get the object model running and works as a marker that the meta object compiler triggers on (more about this later).
The slots are declared in a pretty intuit way. Signals are declared in a similar way but in the signals: section of the declaration.
We continue by looking at the implementation of the slots:
void example1Dialog::fooClicked()
{
setCaption( "Foo" );
}
void example1Dialog::barClicked()
{
setCaption( "Bar" );
}
This is pretty straight forward as slots are simply ordinary methods that can be connected to signals. As these calls are made from inside a descendant of a QDialog we can set the caption as easily as shown in the code. We can also activate these slots from any signal, not just the buttons as we intend to do in this example.
We will now show the method that takes care of the set up; the implementation of the creator of our example1Dialog class. This method creates a layout, the buttons and sets up the connections between the signals and the slots.
example1Dialog::example1Dialog( QWidget* parent, const char* name, bool modal, WFlags f ) : QDialog( parent, name, modal, f )
{
setCaption( "Click!" );
// The layout
QVBoxLayout *vb = new QVBoxLayout( this, 8 );
vb->setAutoAdd( vb );
// The buttons
QPushButton *pbFoo = new QPushButton( "Foo", this );
QPushButton *pbBar = new QPushButton( "Bar", this );
// The connections
connect( pbFoo, SIGNAL(clicked()), this, SLOT(fooClicked()) );
connect( pbBar, SIGNAL(clicked()), this, SLOT(barClicked()) );
}
The layout is created with a spacing of eight pixels margin between the edges and each widget. The call to setAutoAdd means that all widgets created with example1Dialog as a parent automatically will be inserted into the layout.
The buttons are created with a text and the dialog as the parent. This means that Qt will delete them from memory the moment that the dialog is deleted. Thus we do not need to care about any complex memory management issues. Each parent will take care of its children through the QObject heritage.
The last thing that we do is setting up the connections. This is where the magic takes place. Here we connect each of the button's clicked signals to the slots that we created. Notice that one can send parameters between signals and slots without (almost) any restrictions. No need to declare special POD (plain old data) classes to carry any parameters, just emit the signals and do not bother.
This version embedds the functionality into the dialog. The signals activating the slots can come from anywhere (think toolbars, menus, keyboard short-cuts, etc. doing the same thing). This does not only have the advantage that all code that affects the dialog is in the dialog class, but the code is more reuseable. Just imagine adding the ability to activate the same functions from a menu. In the Qt code, we just add and connect two menu items while in the inheritance example we will need (at least) two more classes.
If you try to compile the code above you will run into problems. The tool that is needed is the meta object compiler, the moc. It creates code for signals and the meta object of the class. You might think that this is an "ugly" solution. This is actually the most common objection to using Qt. However, I have never, during three years of usage run into any serious problems caused by the moc or the object model. If I encounter any problems due to the moc or object model they are always easily solved thanks to good documentation and good debugging support. The biggest benefit, however, is the syntactical clarity that the moc provides. Anyone who knows C++ will be able to read and understand code with signals and slots after just 1-2 minutes of introduction.
The output from the moc will be stored in the file moc_example1dialog.cpp. Simply include this file in your make file and you are ready to go!
From version 3.0 Qt is shipped with the qmake tool. This completely removes the Makefile hell that Qt could lead to in earlier version. A project file (.pro) is translated into a proper Makefile by qmake. For our example the project file would look something like the file below.
SOURCES = main.cpp example1dialog.cpp HEADERS = example1dialog.h CONFIG += qt want_on release
As you can see, the need to explicitly handle the moc_*.cpp files is removed. Do not let the simplicity of this example fool you; qmake is a powerful tool that can handle pretty much all the things that a Makefile can do. By doing fine tuning qmake can even handle platform differences in, for example, third part support libraries.
If you choose to develop your applications using the Qt Designer, you will end up getting the project file for free too. You simply draw the interface (as in Kylix or VB), declare and implement the slots, graphically connect the signals to the slots and compile. Using Designer is really the way to design and implement applications. The example above is just an example focusing on the object model behind.
Qt offers an extended object model that makes it easier to reuse code. The object model introduces new syntax elements that and a new compilation stage (the moc). To remove the introduced complexity Qt also supply the qmake tool that makes it easier than before to build projects.
The syntax of Qt extended code integrates nicely with C++ and makes the code readable and easy to understand. The extensions are transparent to the programmer and does not introduce any new things to think about more than inheriting QObject or any of its descendants and including the Q_OBJECT macro first in the class declaration.
This text does not cover much of the capabilities of the Qt object model or Qt. Worth mentioning is that the object model introduces supports for named properties and lots of run-time information. By using Qt Scripting for Application (QSA) Qt objects can be scripted in JavaScript.
As always when discussing Qt, there is the licensing issue. Qt is available as GPL for the X11/UNIX target. For Windows (win32) the older version 2.3 is available for free software. This is a decision made by Trolltech and it helps them saving money.
Johan Thelin has a degree in electrical engineering and has worked as a software developer since 1995. He lives in a town just outside Gothenburg, Sweden. His interests spread the whole software spectrum from database design to low level operating system hacking and he has professional experience from both areas and many in between. He has also an interest in hardware design and has implemented a 32-bit pipelined RISC CPU in an FPGA.
The original version of this document can be found at version of this document can be found at http://www.digitalfanatics.org/e8johan.