Recently, i’ve been working on a school project (code name: Cute Collage), which consisted in designing a software that creates photo collages. We were asked to reproduce the exact same interface and functionalities as the famous Shape Collage, using the best C++ framework ever: Qt! Qt is a cross-platform application and UI framework. It includes a cross-platform class library, integrated development tools and a cross-platform IDE. Using Qt, you can write web-enabled applications once and deploy them across many desktop and embedded operating systems without rewriting the source code.

The Qt framework SQK
The Qt framework SQK

As you can see on the figure above, Qt SDK comes with a set of useful development tools that help you in the design process. In Particular, a form designer called Qt Designer. The tool allows you to easily and quickly create your graphical user interface. Indeed with simple Drag & Drop, you can:

  • add and lay out all widgets proposed by Qt (QPushButton, QLabel, QRadioButton, …),
  • set their graphic properties,
  • and even connect them to basic events, like when a widget is clicked.

Widgets are put in a *.ui file, and you can still access and modify them through your C++ code.

It works just fine as long as you only use standard widgets in your application. But what if you need to integrate in the interface, some that must be customized so that they match exactly your needs?

For example, you might want to re-implement certain event handling methods of the widget, like when a Drag & Drop operation is performed, when a key is pressed, or when a mouse button is released. Or, you might just want to add to it new properties, signals or slots. Anyway, all this requires that you specialize the widget by inheritance, and that is where the shoe pinches. In fact, standard widgets added via Qt Designer can not be inherited :(.

But fear not anymore my friends! :)

There is actually a way to bypass this limitation: the solution consists of using your personalized widgets right directly in the form designer!

Before you can do that, you must make the designer aware of them. And there are two techniques for doing this: the “promotion” and the plugin technique.

In this tutorial, i will explain the first approach.

What is widget promotion?

Widget promotion is a technique that involves using the Qt’s standard widget that matches the most your custom one, and let the form designer know that this widget, must in fact be replaced by the personalized widget that you provide him. For example, we might represent instances of a custom push button class, MyPushButton, with instances of QPushButton and promote these to MyPushButton so that uic (user interface compiler – the tool that turns the *.ui file into the corresponding C++ code)  generates suitable code for this missing class.

When choosing a widget to use as a placeholder, it is useful to compare the API of the missing widget with those of standard Qt widgets. For specialized widgets that subclass standard classes, the obvious choice of placeholder is the base class of the custom widget; for example, QListWidget might be used for specialized QListWidget subclasses.

For specialized widgets that do not share a common API with standard Qt widgets, it is worth considering adapting a custom widget for use in Qt Designer. If this is not possible then QWidget is the obvious choice for a placeholder widget since it is the lowest common denominator for all widgets.

The great thing about this technique is that as you add the custom widget from the form designer, you can easily set all its graphic properties from the designer. The properties will be saved in the ui generated file, and will not polluates the code of the custom class. Thus, the custom class will only contains the application logic and nothing else.

Working Example: Creating a basic image editor with Qt Designer

In this example, we will use the widget promotion technique to create a basic image editor that looks like this:

A basic image editor created with Qt Designer
A basic image editor created with Qt Designer

In this interface, all widgets except the one in the central area, are standard widgets (several QPushButtons, a QSlider, and a QComboBox). So they don’t need to be customized anymore and can be directly added via Qt Designer. However, the widget in the main area, is more sophisticated. We must indeed re-implement the mousePressEvent()mouseMoveEvent() and mouseReleaseEvent() events handlers to implement the drawing. We also need to reimplement the paintEvent() event handler to update the painting area. This widget is really a basic one, so we can use the generic QWidget as placeholder. Let us put all this code in a C++ class that we will name PaintingWidget.

Here is the needed code:

PaintingWidget.h:

#ifndef PAINTINGWIDGET_H
#define PAINTINGWIDGET_H

#include <QWidget>
#include <QColor>
#include <QImage>
#include <QPoint>

class PaintingWidget : public QWidget
{
    Q_OBJECT
public:
    PaintingWidget(QWidget *parent = 0);

protected:
    //We reimplement the mouse/paint events handlers, inherited from QWidget
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void paintEvent(QPaintEvent *event);

private:
    //Utility function that draws a line between the last point and the given end point
    void drawLineTo(const QPoint &endPoint);

    //Useful variables for drawing on the widget
    int     _penWidth;
    QColor  _penColor;
    QImage  _image;
    QPoint  _lastPoint;

};

#endif // PAINTINGWIDGET_H

PaintingWidget.cpp:

#include "paintingwidget.h"
#include <QtGui>

PaintingWidget::PaintingWidget(QWidget *parent) :
    QWidget(parent)
{
    setAttribute(Qt::WA_StaticContents);//for optimizing painting events
    _modified = false;
    _isDrawing = false;
    _penWidth = 10;
    _penColor = Qt::black;
}

void PaintingWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        _lastPoint = event->pos();
        _isDrawing = true;
    }
}

void PaintingWidget::mouseMoveEvent(QMouseEvent *event)
{
    if ((event->buttons() & Qt::LeftButton) && _isDrawing)
        drawLineTo(event->pos());
}

void PaintingWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && _isDrawing) {
        drawLineTo(event->pos());
        _isDrawing = false;
    }
}

void PaintingWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QRect dirtyRect = event->rect();
    painter.drawImage(dirtyRect, _image, dirtyRect);
}

void PaintingWidget::drawLineTo(const QPoint &endPoint)
{
    QPainter painter(&_image);
    painter.setPen(QPen(_penColor, _penWidth, Qt::SolidLine, Qt::RoundCap,
                        Qt::RoundJoin));
    painter.drawLine(_lastPoint, endPoint);
    _modified = true;

    int rad = (_penWidth / 2) + 2;
    update(QRect(_lastPoint, endPoint).normalized()
                                     .adjusted(-rad, -rad, +rad, +rad));
    _lastPoint = endPoint;
}

Now the custom class is ready, let us associate it with the QWidget that represents it  in Qt Designer.

To promote the widget, Right Click on it, and choose Promote to… in the context menu that appears.

Promoting a widget – Part one

After entering the class name and header file in the lower part of the dialog, choose Add.

Promoting a widget : Part two

The placeholder class will now appear along with the base class in the upper list. Click the Promote button to accept this choice.

Promoting a widget – Part three

Now, when the form’s context menu is opened over objects of the base class, the placeholder class will appear in the Promote to submenu, allowing for convenient promotion of other objects to that class.

Important Notes:

  • The custom class must have at least one constructor that takes a QWidget * (either optional or not) as first parameter. It is a pointer to the parent widget, an it’s mandatory because when Qt Designer instantiates widgets, it automatically passes them a pointer to the top level widget as their first and only parameter. If you don’t provide such a constructor, you’ll get a compilation error.
  • if you preview your interface right now (Ctr + R in Qt Designer, Ctrl + Alt + R in Qt Creator), the promoted widget will look and behave like its base class. It’s only at runtime that your custom widget enters in action if i can say.
  • A promoted widget can be reverted to its base class by choosing Demote to from the form’s context menu.
Demoting a widget

There! I hope you enjoyed this tutorial!

PS:

This tip was part of the experience i have acquired thanks to my projet named Cute Collage. Soon, other tips Qt-related will come. Besides, the whole source code of the project will soon be available in the Projects section.

So stay tuned! ;)