Evgenii Legotckoi
Evgenii Legotckoi16 апреля 2016 г. 22:23

QML - Урок 024. Custom QQuickItem - добавляем в QML объект из C++

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

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Docent
  • 23 октября 2018 г. 19:57

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

Evgenii Legotckoi
  • 24 октября 2018 г. 1:36

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

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

C++ - Тест 001. Первая программа и типы данных

  • Результат:46баллов,
  • Очки рейтинга-6
FL

C++ - Тест 006. Перечисления

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 19:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 8:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 15:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 22:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 19:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 15:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 17:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях