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 не должен графикой заниматься ))