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.
#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.
#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.
#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.
#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.
#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.
In this part of the Qt5 tutorial, we have created a custom Burning widget.