Evgenii Legotckoi
Evgenii Legotckoi16 квітня 2016 р. 12: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 р. 09:57

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

Evgenii Legotckoi
  • 23 жовтня 2018 р. 15:36

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

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах