Сегодня мы создадим свой собственный виджет. Обычно, большинство инструментариев содержат лишь самые распространенные наборы виджетов, например, стандартные кнопки, слайдеры и т.п. Так как при этом ни один инструментарий не может обеспечить пользователя каким-нибудь универсальным набором виджетов на все случаи жизни, то перед программистами возникает вопрос о необходимости создания своего собственного инструментария. Исходя из этого, можно выделить два основных пути решения: программист может изменить/улучшить существующий виджет или он может создать свой собственный виджет с нуля в Qt5.
Виджет записи данных на диск
В следующем примере мы с нуля создадим пользовательский виджет, имитирующий процесс записи данных на диск. Подобный виджет можно встретить в таких программах для записи дисков, как Nero Burning ROM, BurnAware, ImgBurn или K3b.
Заголовочный файл — burning.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#pragma once #include <QWidget> #include <QSlider> #include <QFrame> #include "widget.h" class Burning : public QFrame { Q_OBJECT public: Burning(QWidget *parent = 0); int getCurrentWidth(); public slots: void valueChanged(int); private: QSlider *slider; Widget *widget; int cur_width; void initUI(); }; |
Метод getCurrentWidth() используется для определения значений ползунка:
1 2 3 |
public: Burning(QWidget *parent = 0); int getCurrentWidth(); |
В клиентской области окна у нас будет два виджета: встроенный виджет слайдера и пользовательский виджет. Переменная cur_width
будет содержать текущее значение ползунка слайдера. Это значение используется при рисовании пользовательского виджета:
1 2 3 4 5 6 |
private: QSlider *slider; Widget *widget; int cur_width; void initUI(); |
Файл реализации — burning.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <QVBoxLayout> #include <QHBoxLayout> #include "burning.h" Burning::Burning(QWidget *parent) : QFrame(parent) { initUI(); } void Burning::initUI() { const int MAX_VALUE = 750; cur_width = 0; slider = new QSlider(Qt::Horizontal , this); slider->setMaximum(MAX_VALUE); slider->setGeometry(50, 50, 130, 30); connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged); QVBoxLayout *vbox = new QVBoxLayout(this); QHBoxLayout *hbox = new QHBoxLayout(); vbox->addStretch(1); widget = new Widget(this); hbox->addWidget(widget, 0); vbox->addLayout(hbox); setLayout(vbox); } void Burning::valueChanged(int val) { cur_width = val; widget->repaint(); } int Burning::getCurrentWidth() { return cur_width; } |
При перемещении ползунка вызывается слот valueChanged():
1 |
connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged); |
Когда мы изменяем значение ползунка слайдера, то сначала сохраняем новое значение, а затем перерисовываем пользовательский виджет:
1 2 3 4 5 |
void Burning::valueChanged(int val) { cur_width = val; widget->repaint(); } |
Заголовочный файл — widget.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#pragma once #include <QFrame> class Burning; class Widget : public QFrame { public: Widget(QWidget *parent = 0); protected: void paintEvent(QPaintEvent *e); void drawWidget(QPainter &qp); private: QWidget *m_parent; Burning *burn; static const int DISTANCE = 19; static const int LINE_WIDTH = 5; static const int DIVISIONS = 10; constexpr static const float FULL_CAPACITY = 700; constexpr static const float MAX_CAPACITY = 750; }; |
Мы храним указатель на родительский виджет, т.к. через него мы будем обращаться к переменной cur_width
:
1 2 3 |
private: QWidget *m_parent; Burning *burn; |
Ниже следуют несколько важных констант:
DISTANCE
— это расстояние между значениями шкалы.
LINE_WIDTH
— это длина вертикальных линий-насечек шкалы.
DIVISIONS
— это количество частей шкалы.
FULL_CAPACITY
— это ёмкость «диска». После его достижения происходит т.н. перепрожиг. Перепрожиг (англ. «Overburning») — это процесс записи информации на диск объемом большим, чем определено изначально. В этом случае для записи дополнительных данных используются так называемые резервные дорожки диска. Данный этап визуализируется красным цветом.
MAX_CAPACITY
— это максимальная ёмкость носителя.
Код:
1 2 3 4 5 |
const int DISTANCE = 19; const int LINE_WIDTH = 5; const int DIVISIONS = 10; constexpr const float FULL_CAPACITY = 700; constexpr const float MAX_CAPACITY = 750; |
Теперь мы отрисовываем пользовательский виджет: сначала прямоугольник, а затем вертикальные линии и цифры.
Файл реализации — widget.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#include <QtGui> #include "widget.h" #include "burning.h" const int PANEL_HEIGHT = 30; Widget::Widget(QWidget *parent) : QFrame(parent) { m_parent = parent; setMinimumHeight(PANEL_HEIGHT); } 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(); Burning *burn = (Burning *) m_parent; int cur_width = burn->getCurrentWidth(); 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(7); setFont(newFont); QFontMetrics metrics(font()); int w = metrics.width(num[i-1]); qp.drawText(i*step-w/2, DISTANCE, num[i-1]); } } |
Рисование пользовательского виджета делегируется методу drawWidget():
1 2 3 4 5 6 7 |
void Widget::paintEvent(QPaintEvent *e) { QPainter qp(this); drawWidget(qp); QFrame::paintEvent(e); } |
Данные числа используются для отображения масштаба виджета:
1 2 |
QString num[] = { "75", "150", "225", "300", "375", "450", "525", "600", "675" }; |
Получаем ширину виджета. Стоит отметить, что ширина пользовательского виджета является динамической. Она может быть изменена пользователем (например, при изменении размеров окна):
1 |
int width = size().width(); |
Получаем значение переменной cur_width
:
1 2 |
Burning *burn = (Burning *) m_parent; int cur_width = burn->getCurrentWidth(); |
Используем переменную width
для выполнения преобразований между значениями шкалы и размерами пользовательского виджета:
1 2 |
int till = (int) ((width / MAX_CAPACITY) * cur_width); int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY); |
Данные линии рисуют красный прямоугольник, указывающий на перепрожиг:
1 2 3 |
qp.setPen(redColor); qp.setBrush(redColor); qp.drawRect(full, 0, till-full, PANEL_HEIGHT); |
Рисуем небольшие вертикальные линии:
1 |
qp.drawLine(i*step, 0, i*step, LINE_WIDTH); |
Рисуем числа шкалы. Чтобы точно расположить числа, мы должны получить ширину строки:
1 2 3 4 |
QFontMetrics metrics(font()); int w = metrics.width(num[i-1]); qp.drawText(i*step-w/2, DISTANCE, num[i-1]); |
Основной файл программы — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <QApplication> #include "burning.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Burning window; window.resize(370, 200); window.setWindowTitle("The Burning widget"); window.show(); return app.exec(); } |
Результат выполнения программы:
Заключение
На этом всё. На следующем уроке мы с вами попробуем создать свой аналог игры «Змейка».
Осторожно, Qt6! 🙂
Cделал аналогичный виджет, который, на мой взгляд, получился более универсальным.
Qt6 в Microsoft Visual Studio 2022 Enterprise Edition:
ProgressLine.h
ProgressLine.cpp
Burning.h
Burning.cpp
main.cpp
Присоединяюсь к вопросу. Хотелось бы видеть свои виджеты слева в колонкe дизайнера форм. Как их туда вставить?
Как сделать, чтобы этот виджет показывался в редакторе форм?