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