Evgenii Legotckoi
Сәуір 16, 2016, 10:23 Т.Қ.

QML - 024-сабақ. Custom QQuickItem - C ++ тілінен QML-ге нысан қосу

QML объекты в Qt весьма замечательны, с ними удобно работать, но что если стандартных объектов нам становится недостаточно? Тогда можно сделать собственный объект, запрограммировать его в C++ и внедрить в логику QML слоя. В данном уроке предлагаю сделать небольшой импровизированный таймер, который можно запустить, остановить и очистить, но при этом дизайн таймера будет разработан в C++ слое и по сути большая часть работы будет осуществляться в C++.

А для разработки кастомизированного QuickItem понадобится использовать QQuickPaintedItem, в котором будет таймер, представленный на рисунке ниже, который будет нарисован наподобие обычного QGraphicsItem , но он будет обладать рядом свойств, которыми можно будет управлять из QML слоя.


Структура проекта Custom QQuickItem

  • CustomQuickItem.pro - профайл проекта;
  • deployment.pri - профайл деплоя проекта под различные архитектуры;
  • clockcircle.h - заголовочный файл самого таймера проекта;
  • clockcircle.cpp - файл исходных кодов таймера проекта;
  • main.cpp - файл исходных кодов главной функции проекта;
  • main.qml - файл исходных кодов qml.

CustomQuickItem.pro

Для того, чтобы регистрировать в QML слое кастомизированные QQuickItem классы, необходимо подключить модуль quickwidgets, как это сделано в данном файле.

  1. TEMPLATE = app
  2.  
  3. QT += qml quick quickwidgets
  4. CONFIG += c++11
  5.  
  6. SOURCES += main.cpp \
  7. clockcircle.cpp
  8.  
  9. RESOURCES += qml.qrc
  10.  
  11. # Additional import path used to resolve QML modules in Qt Creator's code model
  12. QML_IMPORT_PATH =
  13.  
  14. # Default rules for deployment.
  15. include(deployment.pri)
  16.  
  17. HEADERS += \
  18. clockcircle.h

main.cpp

  1. #include <QGuiApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QQuickWidget>
  4.  
  5. #include "clockcircle.h"
  6.  
  7. int main(int argc, char *argv[])
  8. {
  9. QGuiApplication app(argc, argv);
  10.  
  11. // Всё, что требуется в данном файле - это зарегистрировать новый класс (Тип объекта) для QML слоя
  12. qmlRegisterType<ClockCircle>("ClockCircle",1,0,"ClockCircle");
  13.  
  14. QQmlApplicationEngine engine;
  15. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  16.  
  17. return app.exec();
  18. }

clockCircle.h

Поскольку данный объект является объектом, который должен быть доступен из QML слоя, то необходимо все его свойства определить как Q_PROPERTY, в которых будут указаны все сеттеры, геттеры и соответственно сигналы об изменении данных свойств. Помимо этого в классе имеется несколько Q_INVOKABLE методов, которые также доступны будут из QML слоя. Это clear(), start(), stop(), ими из интерфейса будет производиться управление таймеров.

Свойств у таймера будет несколько:

  • m_name - Имя объекта;
  • m_backgroundColor - фоновый цвет таймера;
  • m_borderNonActiveColor - фоновый цвет ободка (круговой прогресс бар), в незаполненном состоянии;
  • m_borderActiveColor - фоновый цвет ободка, заполнение прогресс бара;
  • m_angle - угол поворота активной части прогресс бара;
  • m_circleTime - текущее время таймера.

  1. #ifndef CLOCKCIRCLE_H
  2. #define CLOCKCIRCLE_H
  3.  
  4. #include <QtQuick/QQuickPaintedItem>
  5. #include <QColor>
  6. #include <QBrush>
  7. #include <QPen>
  8. #include <QPainter>
  9. #include <QTime>
  10. #include <QTimer>
  11.  
  12. class ClockCircle : public QQuickPaintedItem
  13. {
  14. Q_OBJECT
  15. Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
  16. Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
  17. Q_PROPERTY(QColor borderActiveColor READ borderActiveColor WRITE setBorderActiveColor NOTIFY borderActiveColorChanged)
  18. Q_PROPERTY(QColor borderNonActiveColor READ borderNonActiveColor WRITE setBorderNonActiveColor NOTIFY borderNonActiveColorChanged)
  19. Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged)
  20. Q_PROPERTY(QTime circleTime READ circleTime WRITE setCircleTime NOTIFY circleTimeChanged)
  21.  
  22. public:
  23. explicit ClockCircle(QQuickItem *parent = 0);
  24.  
  25. void paint(QPainter *painter) override; // Переопределяем метод, в котором будет отрисовываться наш объект
  26.  
  27. // Методы, доступные из QML для ...
  28. Q_INVOKABLE void clear(); // ... очистки времени, ...
  29. Q_INVOKABLE void start(); // ... запуска таймера, ...
  30. Q_INVOKABLE void stop(); // ... остановки таймера, ...
  31.  
  32. QString name() const;
  33. QColor backgroundColor() const;
  34. QColor borderActiveColor() const;
  35. QColor borderNonActiveColor() const;
  36. qreal angle() const;
  37. QTime circleTime() const;
  38.  
  39. public slots:
  40. void setName(const QString name);
  41. void setBackgroundColor(const QColor backgroundColor);
  42. void setBorderActiveColor(const QColor borderActiveColor);
  43. void setBorderNonActiveColor(const QColor borderNonActiveColor);
  44. void setAngle(const qreal angle);
  45. void setCircleTime(const QTime circleTime);
  46.  
  47. signals:
  48. void cleared();
  49.  
  50. void nameChanged(const QString name);
  51. void backgroundColorChanged(const QColor backgroundColor);
  52. void borderActiveColorChanged(const QColor borderActiveColor);
  53. void borderNonActiveColorChanged(const QColor borderNonActiveColor);
  54. void angleChanged(const qreal angle);
  55. void circleTimeChanged(const QTime circleTime);
  56.  
  57. private:
  58. QString m_name; // Название объекта, по большей части до кучи добавлено
  59. QColor m_backgroundColor; // Основной цвет фона
  60. QColor m_borderActiveColor; // Цвет ободка, заполняющий при прогрессе ободок таймера
  61. QColor m_borderNonActiveColor; // Цвет ободка фоновый
  62. qreal m_angle; // Угол поворота графика типа пирог, будет формировать прогресс на ободке
  63. QTime m_circleTime; // Текущее время таймера
  64.  
  65. QTimer *internalTimer; // Таймер, по которому будет изменяться время
  66. };
  67.  
  68. #endif // CLOCKCIRCLE_H

clockcircle.cpp

Весь код, относящийся к сеттерам и геттерам свойств можно автоматически сгенерировать через контекстное меню, кликнув правой кнопкой мыши по написанным Q_PROPERTY. Обратите внимание только на setAngle(const qreal angle) , поскольку он модифицирован для обнуления угла поворота.

  1. #include "clockcircle.h"
  2.  
  3. ClockCircle::ClockCircle(QQuickItem *parent) :
  4. QQuickPaintedItem(parent),
  5. m_backgroundColor(Qt::white),
  6. m_borderActiveColor(Qt::blue),
  7. m_borderNonActiveColor(Qt::gray),
  8. m_angle(0),
  9. m_circleTime(QTime(0,0,0,0))
  10. {
  11. internalTimer = new QTimer(this); // Инициализируем таймер
  12. /* А также подключаем сигнал от таймера к лямбда функции
  13. * Структура лямбда-функции [объект](аргументы){тело}
  14. * */
  15. connect(internalTimer, &QTimer::timeout, [=](){
  16. setAngle(angle() - 0.3); // поворот определяется в градусах.
  17. setCircleTime(circleTime().addMSecs(50)); // Добавляем к текущему времени 50 милисекунд
  18. update(); // Перерисовываем объект
  19. });
  20. }
  21.  
  22. void ClockCircle::paint(QPainter *painter)
  23. {
  24. // Отрисовка объекта
  25. QBrush brush(m_backgroundColor); // выбираем цвет фона, ...
  26. QBrush brushActive(m_borderActiveColor); // активный цвет ободка, ...
  27. QBrush brushNonActive(m_borderNonActiveColor); // не активный цвет ободка
  28.  
  29. painter->setPen(Qt::NoPen); // Убираем абрис
  30. painter->setRenderHints(QPainter::Antialiasing, true); // Включаем сглаживание
  31.  
  32. painter->setBrush(brushNonActive); // Отрисовываем самый нижний фон в виде круга
  33. painter->drawEllipse(boundingRect().adjusted(1,1,-1,-1)); // с подгонкой под текущие размеры, которые
  34. // будут определяться в QML-слое.
  35. // Это будет не активный фон ободка
  36.  
  37. // Прогресс бар будет формироваться с помощью отрисовки Pie графика
  38. painter->setBrush(brushActive); // Отрисовываем активный фон ободка в зависимости от угла поворота
  39. painter->drawPie(boundingRect().adjusted(1,1,-1,-1), // с подгонкой под размеры в QML слое
  40. 90*16, // Стартовая точка
  41. m_angle*16); // угол поворота, до которого нужно отрисовать объект
  42.  
  43. painter->setBrush(brush); // основной фон таймера, перекрытием которого поверх остальных
  44. painter->drawEllipse(boundingRect().adjusted(10,10,-10,-10)); // будет сформирован ободок (он же прогресс бар)
  45. }
  46.  
  47. void ClockCircle::clear()
  48. {
  49. setCircleTime(QTime(0,0,0,0)); // Очищаем время
  50. setAngle(0); // Выставляем поворот на ноль
  51. update(); // Обновляем объект
  52. emit cleared(); // ИСпускаем сигнал очистки
  53. }
  54.  
  55. void ClockCircle::start()
  56. {
  57. internalTimer->start(50); // Запускаем таймер с шагом 50 мс
  58. }
  59.  
  60. void ClockCircle::stop()
  61. {
  62. internalTimer->stop(); // Останавливаем таймер
  63. }
  64.  
  65. QString ClockCircle::name() const
  66. {
  67. return m_name;
  68. }
  69.  
  70. QColor ClockCircle::backgroundColor() const
  71. {
  72. return m_backgroundColor;
  73. }
  74.  
  75. QColor ClockCircle::borderActiveColor() const
  76. {
  77. return m_borderActiveColor;
  78. }
  79.  
  80. QColor ClockCircle::borderNonActiveColor() const
  81. {
  82. return m_borderNonActiveColor;
  83. }
  84.  
  85. qreal ClockCircle::angle() const
  86. {
  87. return m_angle;
  88. }
  89.  
  90. QTime ClockCircle::circleTime() const
  91. {
  92. return m_circleTime;
  93. }
  94.  
  95. void ClockCircle::setName(const QString name)
  96. {
  97. if (m_name == name)
  98. return;
  99.  
  100. m_name = name;
  101. emit nameChanged(name);
  102. }
  103.  
  104. void ClockCircle::setBackgroundColor(const QColor backgroundColor)
  105. {
  106. if (m_backgroundColor == backgroundColor)
  107. return;
  108.  
  109. m_backgroundColor = backgroundColor;
  110. emit backgroundColorChanged(backgroundColor);
  111. }
  112.  
  113. void ClockCircle::setBorderActiveColor(const QColor borderActiveColor)
  114. {
  115. if (m_borderActiveColor == borderActiveColor)
  116. return;
  117.  
  118. m_borderActiveColor = borderActiveColor;
  119. emit borderActiveColorChanged(borderActiveColor);
  120. }
  121.  
  122. void ClockCircle::setBorderNonActiveColor(const QColor borderNonActiveColor)
  123. {
  124. if (m_borderNonActiveColor == borderNonActiveColor)
  125. return;
  126.  
  127. m_borderNonActiveColor = borderNonActiveColor;
  128. emit borderNonActiveColorChanged(borderNonActiveColor);
  129. }
  130.  
  131. void ClockCircle::setAngle(const qreal angle)
  132. {
  133. if (m_angle == angle)
  134. return;
  135.  
  136. m_angle = angle;
  137.  
  138. /* Данное добавление сделано для того,
  139. * чтобы обнулить поворот при достижении таймером
  140. * 60 секунд
  141. * */
  142. if(m_angle <= -360) m_angle = 0;
  143. emit angleChanged(m_angle);
  144. }
  145.  
  146. void ClockCircle::setCircleTime(const QTime circleTime)
  147. {
  148. if (m_circleTime == circleTime)
  149. return;
  150.  
  151. m_circleTime = circleTime;
  152. emit circleTimeChanged(circleTime);
  153. }

main.qml

Ну а теперь осталось только добавить новый объект в QML слой, настроить его и посмотреть результат. В данном случае будет три кнопки, которые будут управлять Q_INVOKABLE методами таймера, а также будет устанавливаться время в наш таймер во время его работы на основании работы сигналов и слотов .

  1. import QtQuick 2.6
  2. import QtQuick.Window 2.2
  3. import QtQuick.Controls 1.4
  4. import QtQml 2.2
  5. // После того, как объект зарегистрирован в C++ слое
  6. // его необходимо подключить в QML
  7. import ClockCircle 1.0
  8.  
  9. Window {
  10. visible: true
  11. width: 400
  12. height: 400
  13.  
  14. // А теперь добавляем объект
  15. ClockCircle {
  16. id: clockCircle
  17. // позиционируем его и задаём размеры
  18. anchors.top: parent.top
  19. anchors.topMargin: 50
  20. anchors.horizontalCenter: parent.horizontalCenter
  21. width: 200
  22. height: 200
  23.  
  24. // Определяем его свойства, которые Q_PROPERTY
  25. name: "clock"
  26. backgroundColor: "whiteSmoke"
  27. borderActiveColor: "LightSlateGray"
  28. borderNonActiveColor: "LightSteelBlue"
  29.  
  30. // Добавляем текст, на который будет выставляться время таймера
  31. Text {
  32. id: textTimer
  33. anchors.centerIn: parent
  34. font.bold: true
  35. font.pixelSize: 24
  36. }
  37.  
  38. // При изменении времени, помещаем это время на таймер
  39. onCircleTimeChanged: {
  40. textTimer.text = Qt.formatTime(circleTime, "mm:ss.zzz")
  41. }
  42. }
  43.  
  44. Button {
  45. id: start
  46. text: "Start"
  47. onClicked: clockCircle.start(); // Запуск таймера
  48. anchors {
  49. left: parent.left
  50. leftMargin: 20
  51. bottom: parent.bottom
  52. bottomMargin: 20
  53. }
  54. }
  55.  
  56. Button {
  57. id: stop
  58. text: "Stop"
  59. onClicked: clockCircle.stop(); // остановка таймера
  60. anchors {
  61. horizontalCenter: parent.horizontalCenter
  62. bottom: parent.bottom
  63. bottomMargin: 20
  64. }
  65. }
  66.  
  67. Button {
  68. id: clear
  69. text: "Clear"
  70. onClicked: clockCircle.clear(); // очистка таймера
  71. anchors {
  72. right: parent.right
  73. rightMargin: 20
  74. bottom: parent.bottom
  75. bottomMargin: 20
  76. }
  77. }
  78. }

Видеоурок

Скачать программный код урока - CustomQuickItem

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Docent
  • Қаз. 23, 2018, 7:57 Т.Қ.

Полгода назад искал как сделать такой объект, делал на QML - первое знакомство с ним было, по вашему "QML-004". А тут вот на блюдечке все готово) Попробуем потом переделать как в вашем 032 уроке, сравним кто шустрее.

Evgenii Legotckoi
  • Қаз. 24, 2018, 1:36 Т.Ж.

Самый шустрый будет как раз в уроке 032. Там же OpenGL!!!

Когда на работе стояла такая задача, я сначала реализовал в этом уроке 024 по QML, но понял, что при большом количестве объектов ой как плохо CPU тянет, в итоге разобрался с OpenGL и всё стало работать с пол пинка. Реально быстрее отрисовывает. Всё-таки CPU не должен графикой заниматься ))

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз