ZetCode

Custom widget in Qt5

last modified October 18, 2023

In this part of the Qt5 C++ programming tutorial, we create a custom widget.

Most toolkits usually provide only the most common widgets like buttons, text widgets, or sliders. No toolkit can provide all possible widgets. Programmers must create such widgets by themselves. They do it by using the drawing tools provided by the toolkit. There are two possibilities: a programmer can modify or enhance an existing widget or he can create a custom widget from scratch.

The Burning widget

In the next example we create a custom Burning widget. This widget can be seen in applications like Nero or K3B. The widget is created from scratch.

application.h
#pragma once

#include <QWidget>
#include <QSlider>
#include <QFrame>
#include "widget.h"

class Application : public QFrame {

  Q_OBJECT

  public:
    Application(QWidget *parent = nullptr);

  private:
    QSlider *slider;
    Widget *widget;

    void initUI();
};

This is the header file of the main window of the example.

application.cpp
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "application.h"

Application::Application(QWidget *parent)
    : QFrame(parent) {

  initUI();
}

void Application::initUI() {

  const int MAX_VALUE = 750;

  slider = new QSlider(Qt::Horizontal , this);
  slider->setMaximum(MAX_VALUE);
  slider->setGeometry(50, 50, 170, 30);

  widget = new Widget(this);
  connect(slider, &QSlider::valueChanged, widget, &Widget::setValue);

  auto *vbox = new QVBoxLayout(this);
  auto *hbox = new QHBoxLayout();

  vbox->addStretch(1);
  hbox->addWidget(widget, 0);
  vbox->addLayout(hbox);

  setLayout(vbox);
}

Here we build the main window of the example.

connect(slider, &QSlider::valueChanged, widget, &Widget::setValue);

When we move the slider, the valueChanged slot is executed.

widget.h
#pragma once

#include <QFrame>

class Widget : public QFrame {

  Q_OBJECT;

  public:
    Widget(QWidget *parent = nullptr);

  public slots:
    void setValue(int);

  protected:
    void paintEvent(QPaintEvent *e);
    void drawWidget(QPainter &qp);

  private:
    int cur_width;

    static constexpr int DISTANCE = 19;
    static constexpr int LINE_WIDTH = 5;
    static constexpr int DIVISIONS = 10;
    static constexpr float FULL_CAPACITY = 700;
    static constexpr float MAX_CAPACITY = 750;
};

This is the header file of the custom burning widget.

private:
  int cur_width;

The cur_width variable holds the current value from the slider. This value is used when painting the custom widget.

static constexpr int DISTANCE = 19;
static constexpr int LINE_WIDTH = 5;
static constexpr int DIVISIONS = 10;
static constexpr float FULL_CAPACITY = 700;
static constexpr float MAX_CAPACITY = 750;

These are the important constants. The DISTANCE is the distance of the numbers on the scale from the top of their parent border. The LINE_WIDTH is the vertical line width. The DIVISIONS is the number of parts of the scale. The FULL_CAPACITY is the capacity of the media. After it is reached, overburning happens. This is visualized by a red colour. The MAX_CAPACITY is the maximum capacity of a medium.

widget.cpp
#include <QtGui>
#include "widget.h"

const int PANEL_HEIGHT = 30;

Widget::Widget(QWidget *parent)
    : QFrame(parent), cur_width(0) {

  setMinimumHeight(PANEL_HEIGHT);
}

void Widget::setValue(int value)
{
  cur_width = value;
  repaint();
}

void Widget::paintEvent(QPaintEvent *e) {

  QPainter qp(this);
  drawWidget(qp);

  QFrame::paintEvent(e);
}

void Widget::drawWidget(QPainter &qp) {

  QString num[] = { "75", "150", "225", "300", "375", "450",
    "525", "600", "675" };

  int asize = sizeof(num)/sizeof(num[1]);

  QColor redColor(255, 175, 175);
  QColor yellowColor(255, 255, 184);

  int width = size().width();

  int step = (int) qRound((double)width / DIVISIONS);
  int till = (int) ((width / MAX_CAPACITY) * cur_width);
  int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);

  if (cur_width >= FULL_CAPACITY) {

    qp.setPen(yellowColor);
    qp.setBrush(yellowColor);
    qp.drawRect(0, 0, full, 30);
    qp.setPen(redColor);
    qp.setBrush(redColor);
    qp.drawRect(full, 0, till-full, PANEL_HEIGHT);

  } else if (till > 0) {

    qp.setPen(yellowColor);
    qp.setBrush(yellowColor);
    qp.drawRect(0, 0, till, PANEL_HEIGHT);
  }

  QColor grayColor(90, 80, 60);
  qp.setPen(grayColor);

  for (int i=1; i <=asize; i++) {

    qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
    QFont newFont = font();
    newFont.setPointSize(8);
    setFont(newFont);

    QFontMetrics metrics(font());

    int w = metrics.horizontalAdvance(num[i-1]);
    qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
  }
}

Here we paint the custom widget. We paint the rectangle, vertical lines, and the numbers.

void Widget::setValue(int value)
{
  cur_width = value;
  repaint();
}

We store the currently selected value into the cur_width variable and redraw the widget.

void Widget::paintEvent(QPaintEvent *e) {

  QPainter qp(this);
  drawWidget(qp);

  QFrame::paintEvent(e);
}

The drawing of the custom widget is delegated to the drawWidget method.

QString num[] = { "75", "150", "225", "300", "375", "450",
  "525", "600", "675" };

We use these numbers to build the scale of the Burning widget.

int width = size().width();

We get the width of the widget. The width of the custom widget is dynamic. It can be resized by the user.

int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);

We use the width variable to do the transformations, between the values of the scale and the custom widget's measures.

qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT);

These three lines draw the red rectangle, indicating the overburning.

qp.drawLine(i*step, 0, i*step, LINE_WIDTH);

Here we draw the small vertical lines.

QFontMetrics metrics(font());

int w = metrics.horizontalAdvance(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);

Here we draw the numbers of the scale. To precisely position the numbers, we must get the width of the string.

main.cpp
#include <QApplication>
#include "application.h"

int main(int argc, char *argv[]) {

  QApplication app(argc, argv);
  Application window;

  window.resize(370, 200);
  window.setWindowTitle("The Burning widget");
  window.show();

  return app.exec();
}

This is the main file.

The Burning widget
Figure: The Burning widget

In this part of the Qt5 tutorial, we have created a custom Burning widget.