Creating custom widgets

Painting with a custom widget is, at its heart, no different than offscreen painting; all you need is a widget subclass and a painter pointing to the widget, and you're all set. Yet, how do you know when to paint?

Qt's QWidget class defines an interface used by the rendering system to pass events to your widget: Qt defines the QEvent class to encapsulate the data about an event, and the QWidget class defines an interface that Qt's rendering system uses to pass events to your widget for processing. Qt uses this event system not just to indicate things such as mouse movements and keyboard input, but also for requests to paint the screen as well.

Let's look at painting first. QWidget defines the paintEvent method, which Qt's rendering system invokes, passing a QPaintEvent pointer. The QPaintEvent pointer includes the region that needs to be repainted and the bounding rectangle of the region because it's often faster to repaint an entire rectangle than a complex region. When you draw a widget's content with QPainter, Qt performs the necessary clipping to the region; however, you can use the information as a hint to what needs to be redrawn, if it's helpful.

Let's look at another painting example, this time, an analog clock widget. This example is from the sample code that comes with Qt; you can see it at https://doc.qt.io/qt-5/qtwidgets-widgets-analogclock-example.html.

I've included the whole QWidget subclass implementing an analog clock here. We'll pick through it in several pieces; first come the obligatory header inclusions, as follows:

#include "analogclock.h" 

The constructor comes after the header inclusions, as follows:

AnalogClock::AnalogClock(QWidget *parent) : QWidget(parent) 
{ 
    QTimer *timer = new QTimer(this); 
    connect(timer, &QTimer::timeout, this, &AnalogClock::update); 
    timer->start(1000); 
    resize(200, 200); 
} 

The constructor creates a timer object that emits a timeout signal every 1000 milliseconds and connects that timer to the widget's update slot. The update slot forces the widget to repaint; this is how the widget will update itself every second. Finally, it resizes the widget itself to be 200 pixels on a side.

The timeout signal triggers the update slot function, which basically tells the parent QWidget class to refresh the screen, subsequently triggering the paintEvent function, as follows:

void AnalogClock::update()
{
QWidget::update();
}

The next part is the paint event handler. This is a long method, so we'll look at it in pieces. The method can be seen in the following code block:

void AnalogClock::paintEvent(QPaintEvent *) 
{ 
    static const QPoint hourHand[3] = { 
        QPoint(7, 8), 
        QPoint(-7, 8), 
        QPoint(0, -40) 
    }; 
    static const QPoint minuteHand[3] = { 
        QPoint(7, 8), 
        QPoint(-7, 8), 
        QPoint(0, -70) 
    }; 
 
    QColor hourColor(127, 0, 127); 
    QColor minuteColor(0, 127, 127, 191); 
    int side = qMin(width(), height()); 
    QTime time = QTime::currentTime(); 
 
    QPainter painter(this); 

Before this is a declaration of stack variables, including the coordinate arrays and colors for the hour and minute hands, and getting a QPainter instance with which to paint.

Next is the code that sets up the painter itself. We request an antialiased drawing and use Qt's support to scale and translate the view, to make our coordinate math a little easier, shown as follows:

    painter.setRenderHint(QPainter::Antialiasing); 
    painter.translate(width() / 2, height() / 2); 
    painter.scale(side / 200.0, side / 200.0); 
 
    painter.setPen(Qt::NoPen); 
    painter.setBrush(hourColor); 

We translate the origin to the middle of the widget. Finally, we set the pen and brush; we choose NoPen for the pen, so the only things drawn are fills, and we set the brush initially to the hour brush color.

After that, we draw the hour hand. This code uses Qt's support for rotation in rendering, to rotate the viewport by the right amount to place the hour hand (each hour takes 30 degrees), and draws a single convex polygon for the hand itself. The following code snippet shows this:

    painter.save(); 
    painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0))); 
    painter.drawConvexPolygon(hourHand, 3); 
    painter.restore(); 

The code saves the configured state of the painter before doing the rotation and then restores the (unrotated) state after drawing the hour hand.

Of course, hour hands are better read with hour markings, so we loop through 12 rotations, drawing a line for each hour mark, as follows:

    painter.setPen(hourColor); 
 
    for (int i = 0; i < 12; ++i) { 
        painter.drawLine(88, 0, 96, 0); 
        painter.rotate(30.0); 
    } 

With the hour hand out of the way, it's time to draw the minute hand. We use the same trick with rotation to rotate the minute hand to the correct position, drawing another convex polygon for the minute hand, as follows:

    painter.setPen(Qt::NoPen); 
    painter.setBrush(minuteColor); 
    painter.save(); 
    painter.rotate(6.0 * (time.minute() + time.second() / 60.0)); 
    painter.drawConvexPolygon(minuteHand, 3); 
    painter.restore(); 

Finally, we draw 60 tick marks around the face of the clock, one for each minute, as follows:

    painter.setPen(minuteColor); 
 
    for (int j = 0; j < 60; ++j) { 
        if ((j % 5) != 0) 
            painter.drawLine(92, 0, 96, 0); 
        painter.rotate(6.0); 
    } 
}

As I hinted at earlier, custom widgets can also accept events; the mousePressEvent, mouseReleaseEvent, and mouseDoubleClick events indicate when the user presses, releases, or double-clicks on the mouse within the bounds of the widget. There's also the mouseMoveEventmouseMoveEvent, which the Qt system invokes whenever the mouse moves in a widget and a mouse button is pressed down. The interface also specifies events for key presses: there's the keyPressEvent that tells you when the user has pressed a key, along with the focusInEvent and focusOut events that indicate when the widget gains and loses keyboard focus, respectively.

The header file looks much simpler, as we can see in the following code block:

#include <QWidget>
#include <QTimer>
#include <QTime>
#include <QPainter>

class AnalogClock : public QWidget
{
Q_OBJECT
public:
explicit AnalogClock(QWidget *parent = nullptr);
void paintEvent(QPaintEvent *);
signals:
public slots:
void update();
};

The following screenshot shows the clock face in action:

For more information about the QWidget interface and creating custom widgets, see the QWidget documentation at https://doc.qt.io/qt-5/qwidget.html and the Qt event system documentation at https://doc.qt.io/qt-5/eventsandfilters.html.

In this section, we have learned how to create a real-time analog clock display, using Qt's paint event. Let's move on to the next section, and learn how to create a simple 2D game, using Qt's Graphics View framework!