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, как это сделано в данном файле.
- TEMPLATE = app
- QT += qml quick quickwidgets
- CONFIG += c++11
- SOURCES += main.cpp \
- clockcircle.cpp
- RESOURCES += qml.qrc
- # Additional import path used to resolve QML modules in Qt Creator's code model
- QML_IMPORT_PATH =
- # Default rules for deployment.
- include(deployment.pri)
- HEADERS += \
- clockcircle.h
main.cpp
- #include <QGuiApplication>
- #include <QQmlApplicationEngine>
- #include <QQuickWidget>
- #include "clockcircle.h"
- int main(int argc, char *argv[])
- {
- QGuiApplication app(argc, argv);
- // Всё, что требуется в данном файле - это зарегистрировать новый класс (Тип объекта) для QML слоя
- qmlRegisterType<ClockCircle>("ClockCircle",1,0,"ClockCircle");
- QQmlApplicationEngine engine;
- engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
- return app.exec();
- }
clockCircle.h
Поскольку данный объект является объектом, который должен быть доступен из QML слоя, то необходимо все его свойства определить как Q_PROPERTY, в которых будут указаны все сеттеры, геттеры и соответственно сигналы об изменении данных свойств. Помимо этого в классе имеется несколько Q_INVOKABLE методов, которые также доступны будут из QML слоя. Это clear(), start(), stop(), ими из интерфейса будет производиться управление таймеров.
Свойств у таймера будет несколько:
- m_name - Имя объекта;
- m_backgroundColor - фоновый цвет таймера;
- m_borderNonActiveColor - фоновый цвет ободка (круговой прогресс бар), в незаполненном состоянии;
- m_borderActiveColor - фоновый цвет ободка, заполнение прогресс бара;
- m_angle - угол поворота активной части прогресс бара;
- m_circleTime - текущее время таймера.
- #ifndef CLOCKCIRCLE_H
- #define CLOCKCIRCLE_H
- #include <QtQuick/QQuickPaintedItem>
- #include <QColor>
- #include <QBrush>
- #include <QPen>
- #include <QPainter>
- #include <QTime>
- #include <QTimer>
- class ClockCircle : public QQuickPaintedItem
- {
- Q_OBJECT
- Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
- Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
- Q_PROPERTY(QColor borderActiveColor READ borderActiveColor WRITE setBorderActiveColor NOTIFY borderActiveColorChanged)
- Q_PROPERTY(QColor borderNonActiveColor READ borderNonActiveColor WRITE setBorderNonActiveColor NOTIFY borderNonActiveColorChanged)
- Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged)
- Q_PROPERTY(QTime circleTime READ circleTime WRITE setCircleTime NOTIFY circleTimeChanged)
- public:
- explicit ClockCircle(QQuickItem *parent = 0);
- void paint(QPainter *painter) override; // Переопределяем метод, в котором будет отрисовываться наш объект
- // Методы, доступные из QML для ...
- Q_INVOKABLE void clear(); // ... очистки времени, ...
- Q_INVOKABLE void start(); // ... запуска таймера, ...
- Q_INVOKABLE void stop(); // ... остановки таймера, ...
- QString name() const;
- QColor backgroundColor() const;
- QColor borderActiveColor() const;
- QColor borderNonActiveColor() const;
- qreal angle() const;
- QTime circleTime() const;
- public slots:
- void setName(const QString name);
- void setBackgroundColor(const QColor backgroundColor);
- void setBorderActiveColor(const QColor borderActiveColor);
- void setBorderNonActiveColor(const QColor borderNonActiveColor);
- void setAngle(const qreal angle);
- void setCircleTime(const QTime circleTime);
- signals:
- void cleared();
- void nameChanged(const QString name);
- void backgroundColorChanged(const QColor backgroundColor);
- void borderActiveColorChanged(const QColor borderActiveColor);
- void borderNonActiveColorChanged(const QColor borderNonActiveColor);
- void angleChanged(const qreal angle);
- void circleTimeChanged(const QTime circleTime);
- private:
- QString m_name; // Название объекта, по большей части до кучи добавлено
- QColor m_backgroundColor; // Основной цвет фона
- QColor m_borderActiveColor; // Цвет ободка, заполняющий при прогрессе ободок таймера
- QColor m_borderNonActiveColor; // Цвет ободка фоновый
- qreal m_angle; // Угол поворота графика типа пирог, будет формировать прогресс на ободке
- QTime m_circleTime; // Текущее время таймера
- QTimer *internalTimer; // Таймер, по которому будет изменяться время
- };
- #endif // CLOCKCIRCLE_H
clockcircle.cpp
Весь код, относящийся к сеттерам и геттерам свойств можно автоматически сгенерировать через контекстное меню, кликнув правой кнопкой мыши по написанным Q_PROPERTY. Обратите внимание только на setAngle(const qreal angle) , поскольку он модифицирован для обнуления угла поворота.
- #include "clockcircle.h"
- ClockCircle::ClockCircle(QQuickItem *parent) :
- QQuickPaintedItem(parent),
- m_backgroundColor(Qt::white),
- m_borderActiveColor(Qt::blue),
- m_borderNonActiveColor(Qt::gray),
- m_angle(0),
- m_circleTime(QTime(0,0,0,0))
- {
- internalTimer = new QTimer(this); // Инициализируем таймер
- /* А также подключаем сигнал от таймера к лямбда функции
- * Структура лямбда-функции [объект](аргументы){тело}
- * */
- connect(internalTimer, &QTimer::timeout, [=](){
- setAngle(angle() - 0.3); // поворот определяется в градусах.
- setCircleTime(circleTime().addMSecs(50)); // Добавляем к текущему времени 50 милисекунд
- update(); // Перерисовываем объект
- });
- }
- void ClockCircle::paint(QPainter *painter)
- {
- // Отрисовка объекта
- QBrush brush(m_backgroundColor); // выбираем цвет фона, ...
- QBrush brushActive(m_borderActiveColor); // активный цвет ободка, ...
- QBrush brushNonActive(m_borderNonActiveColor); // не активный цвет ободка
- painter->setPen(Qt::NoPen); // Убираем абрис
- painter->setRenderHints(QPainter::Antialiasing, true); // Включаем сглаживание
- painter->setBrush(brushNonActive); // Отрисовываем самый нижний фон в виде круга
- painter->drawEllipse(boundingRect().adjusted(1,1,-1,-1)); // с подгонкой под текущие размеры, которые
- // будут определяться в QML-слое.
- // Это будет не активный фон ободка
- // Прогресс бар будет формироваться с помощью отрисовки Pie графика
- painter->setBrush(brushActive); // Отрисовываем активный фон ободка в зависимости от угла поворота
- painter->drawPie(boundingRect().adjusted(1,1,-1,-1), // с подгонкой под размеры в QML слое
- 90*16, // Стартовая точка
- m_angle*16); // угол поворота, до которого нужно отрисовать объект
- painter->setBrush(brush); // основной фон таймера, перекрытием которого поверх остальных
- painter->drawEllipse(boundingRect().adjusted(10,10,-10,-10)); // будет сформирован ободок (он же прогресс бар)
- }
- void ClockCircle::clear()
- {
- setCircleTime(QTime(0,0,0,0)); // Очищаем время
- setAngle(0); // Выставляем поворот на ноль
- update(); // Обновляем объект
- emit cleared(); // ИСпускаем сигнал очистки
- }
- void ClockCircle::start()
- {
- internalTimer->start(50); // Запускаем таймер с шагом 50 мс
- }
- void ClockCircle::stop()
- {
- internalTimer->stop(); // Останавливаем таймер
- }
- QString ClockCircle::name() const
- {
- return m_name;
- }
- QColor ClockCircle::backgroundColor() const
- {
- return m_backgroundColor;
- }
- QColor ClockCircle::borderActiveColor() const
- {
- return m_borderActiveColor;
- }
- QColor ClockCircle::borderNonActiveColor() const
- {
- return m_borderNonActiveColor;
- }
- qreal ClockCircle::angle() const
- {
- return m_angle;
- }
- QTime ClockCircle::circleTime() const
- {
- return m_circleTime;
- }
- void ClockCircle::setName(const QString name)
- {
- if (m_name == name)
- return;
- m_name = name;
- emit nameChanged(name);
- }
- void ClockCircle::setBackgroundColor(const QColor backgroundColor)
- {
- if (m_backgroundColor == backgroundColor)
- return;
- m_backgroundColor = backgroundColor;
- emit backgroundColorChanged(backgroundColor);
- }
- void ClockCircle::setBorderActiveColor(const QColor borderActiveColor)
- {
- if (m_borderActiveColor == borderActiveColor)
- return;
- m_borderActiveColor = borderActiveColor;
- emit borderActiveColorChanged(borderActiveColor);
- }
- void ClockCircle::setBorderNonActiveColor(const QColor borderNonActiveColor)
- {
- if (m_borderNonActiveColor == borderNonActiveColor)
- return;
- m_borderNonActiveColor = borderNonActiveColor;
- emit borderNonActiveColorChanged(borderNonActiveColor);
- }
- void ClockCircle::setAngle(const qreal angle)
- {
- if (m_angle == angle)
- return;
- m_angle = angle;
- /* Данное добавление сделано для того,
- * чтобы обнулить поворот при достижении таймером
- * 60 секунд
- * */
- if(m_angle <= -360) m_angle = 0;
- emit angleChanged(m_angle);
- }
- void ClockCircle::setCircleTime(const QTime circleTime)
- {
- if (m_circleTime == circleTime)
- return;
- m_circleTime = circleTime;
- emit circleTimeChanged(circleTime);
- }
main.qml
Ну а теперь осталось только добавить новый объект в QML слой, настроить его и посмотреть результат. В данном случае будет три кнопки, которые будут управлять Q_INVOKABLE методами таймера, а также будет устанавливаться время в наш таймер во время его работы на основании работы сигналов и слотов .
- import QtQuick 2.6
- import QtQuick.Window 2.2
- import QtQuick.Controls 1.4
- import QtQml 2.2
- // После того, как объект зарегистрирован в C++ слое
- // его необходимо подключить в QML
- import ClockCircle 1.0
- Window {
- visible: true
- width: 400
- height: 400
- // А теперь добавляем объект
- ClockCircle {
- id: clockCircle
- // позиционируем его и задаём размеры
- anchors.top: parent.top
- anchors.topMargin: 50
- anchors.horizontalCenter: parent.horizontalCenter
- width: 200
- height: 200
- // Определяем его свойства, которые Q_PROPERTY
- name: "clock"
- backgroundColor: "whiteSmoke"
- borderActiveColor: "LightSlateGray"
- borderNonActiveColor: "LightSteelBlue"
- // Добавляем текст, на который будет выставляться время таймера
- Text {
- id: textTimer
- anchors.centerIn: parent
- font.bold: true
- font.pixelSize: 24
- }
- // При изменении времени, помещаем это время на таймер
- onCircleTimeChanged: {
- textTimer.text = Qt.formatTime(circleTime, "mm:ss.zzz")
- }
- }
- Button {
- id: start
- text: "Start"
- onClicked: clockCircle.start(); // Запуск таймера
- anchors {
- left: parent.left
- leftMargin: 20
- bottom: parent.bottom
- bottomMargin: 20
- }
- }
- Button {
- id: stop
- text: "Stop"
- onClicked: clockCircle.stop(); // остановка таймера
- anchors {
- horizontalCenter: parent.horizontalCenter
- bottom: parent.bottom
- bottomMargin: 20
- }
- }
- Button {
- id: clear
- text: "Clear"
- onClicked: clockCircle.clear(); // очистка таймера
- anchors {
- right: parent.right
- rightMargin: 20
- bottom: parent.bottom
- bottomMargin: 20
- }
- }
- }
Видеоурок
Скачать программный код урока - CustomQuickItem
Полгода назад искал как сделать такой объект, делал на QML - первое знакомство с ним было, по вашему "QML-004". А тут вот на блюдечке все готово) Попробуем потом переделать как в вашем 032 уроке, сравним кто шустрее.
Самый шустрый будет как раз в уроке 032. Там же OpenGL!!!
Когда на работе стояла такая задача, я сначала реализовал в этом уроке 024 по QML, но понял, что при большом количестве объектов ой как плохо CPU тянет, в итоге разобрался с OpenGL и всё стало работать с пол пинка. Реально быстрее отрисовывает. Всё-таки CPU не должен графикой заниматься ))