Spec-Zone.ru › Qt 6.1

Размещение атрибутов типов C++ в QML

QML легко расширяется функциональностью, определённой в коде C++. Благодаря тесной интеграции движка QML с системой метаобъектов Qt, любая функциональность, должным образом экспонированная классом, производным от QObject, доступна в коде QML. Это позволяет использовать данные и функции C++ непосредственно в QML, часто с минимальными или нулевыми изменениями.

Движок QML может интроспектировать экземпляры QObject через систему метаобъектов. Это означает, что любой код QML может получить доступ к следующим членам экземпляра класса, производного от QObject:

  • Свойства
  • Методы (если они являются публичными слотами или помечены Q_INVOKABLE)
  • Сигналы

(Кроме того, перечисления доступны, если они были объявлены с помощью Q_ENUMS. Смотрите Преобразование типов данных между QML и C++ для получения дополнительной информации.)

В общем случае, они доступны из QML независимо от того, был ли класс, производный от QObject, зарегистрирован в системе типов QML или нет. Однако, если класс должен использоваться способом, который требует от движка доступа к дополнительной информации о типе — например, если сам класс используется в качестве параметра метода или свойства, или если один из его типов перечисления используется таким образом — то класс может потребоваться зарегистрировать.

Также обратите внимание, что ряд важных концепций, описанных в данном документе, демонстрируется в учебнике по написанию расширений QML с помощью C++.

Обработка типов данных и владение

Любые данные, передаваемые из C++ в QML, будь то значение свойства, параметр или возвращаемое значение метода, или значение параметра сигнала, должны быть типа, поддерживаемого движком QML.

По умолчанию движок поддерживает ряд типов Qt C++ и может автоматически преобразовывать их при использовании из QML. Кроме того, классы C++, зарегистрированные в системе типов QML, могут использоваться как типы данных, а также их перечисления, если они должным образом зарегистрированы. Смотрите Преобразование типов данных между QML и C++ для получения дополнительной информации.

Кроме того, правила владения данными учитываются при передаче данных из C++ в QML. Смотрите Владение данными для получения дополнительной информации.

Экспонирование свойств

Свойство может быть указано для любого класса, производного от QObject, с помощью макроса Q_PROPERTY(). Свойство — это член данных класса с связанной функцией чтения и необязательной функцией записи.

Все свойства класса, производного от QObject, доступны из QML.

Например, ниже приведён класс Message со свойством author. Как указано вызовом макроса Q_PROPERTY, это свойство читается через метод author(), и записывается через метод setAuthor().

Примечание: Не используйте typedef или using для типов Q_PROPERTY, так как это может привести к путанице в moc. Это может привести к сбою определённых сравнений типов.

Вместо этого:

using FooEnum = Foo::Enum;

class Bar : public QObject {
    Q_OBJECT
    Q_PROPERTY(FooEnum enum READ enum WRITE setEnum NOTIFY enumChanged)
};

Обращайтесь к типу непосредственно:

class Bar : public QObject {
    Q_OBJECT
    Q_PROPERTY(Foo::Enum enum READ enum WRITE setEnum NOTIFY enumChanged)
};
class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
    void setAuthor(const QString &a) {
        if (a != m_author) {
            m_author = a;
            emit authorChanged();
        }
    }
    QString author() const {
        return m_author;
    }
signals:
    void authorChanged();
private:
    QString m_author;
};

Если экземпляр этого класса был установлен как свойство контекста при загрузке файла MyItem.qml из C++:

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQuickView view;
    Message msg;
    view.engine()->rootContext()->setContextProperty("msg", &msg);
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}

Тогда свойство author можно считать из MyItem.qml:

// MyItem.qml
import QtQuick 2.0

Text {
    width: 100; height: 100
    text: msg.author    // invokes Message::author() to get this value

    Component.onCompleted: {
        msg.author = "Jonah"  // invokes Message::setAuthor()
    }
}

Для максимальной совместимости с QML, любое записываемое свойство должно иметь связанный сигнал NOTIFY, который генерируется всякий раз, когда значение свойства изменяется. Это позволяет использовать свойство с связыванием свойств, что является важной функцией QML, которая устанавливает отношения между свойствами, автоматически обновляя свойство всякий раз, когда любое из его зависимостей меняет значение.

В приведенном выше примере связанный сигнал NOTIFY для свойства author является authorChanged, как указано в вызове макроса Q_PROPERTY(). Это означает, что каждый раз, когда сигнал генерируется — как и при изменении автора в Message::setAuthor() — это уведомляет движок QML о необходимости обновления любых связей, связанных со свойством author, и в свою очередь, движок обновит свойство text вызвав Message::author() снова.

Если свойство author было записываемым, но не имело связанного сигнала NOTIFY, значение text было бы инициализировано начальным значением, возвращённым Message::author(), но не обновлялось бы при последующих изменениях этого свойства. Кроме того, любые попытки привязаться к свойству из QML приведут к предупреждению во время выполнения от движка.

Примечание: Рекомендуется, чтобы сигнал NOTIFY назывался <свойство>Changed, где <property> — это имя свойства. Обработчик связанных сигналов изменения свойств, генерируемый движком QML, всегда будет иметь вид on<Property>Changed, независимо от имени соответствующего сигнала C++, поэтому рекомендуется, чтобы имя сигнала следовало этой конвенции, чтобы избежать путаницы.

Примечания об использовании сигналов уведомления

Чтобы предотвратить циклы или избыточную оценку, разработчики должны убедиться, что сигнал изменения свойства генерируется только тогда, когда значение свойства фактически изменилось. Также, если свойство или группа свойств используется редко, допускается использовать один и тот же сигнал NOTIFY для нескольких свойств. Это должно делаться с осторожностью, чтобы не пострадала производительность.

Наличие сигнала NOTIFY влечёт за собой небольшую нагрузку. Существуют случаи, когда значение свойства устанавливается во время создания объекта и не изменяется впоследствии. Наиболее распространённый случай — когда тип использует группированные свойства, и объект группированного свойства выделяется один раз и освобождается только при удалении объекта. В таких случаях атрибут CONSTANT может быть добавлен в объявление свойства вместо сигнала NOTIFY.

Атрибут CONSTANT следует использовать только для свойств, чьё значение устанавливается и завершается только в конструкторе класса. Все остальные свойства, которые необходимо использовать в связях, должны иметь сигнал NOTIFY вместо этого.

Свойства с типами объектов

Свойства типа объекта доступны из QML, при условии, что тип объекта должным образом зарегистрирован в системе типов QML.

Например, тип Message может иметь свойство body типа MessageBody*:

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged)
public:
    MessageBody* body() const;
    void setBody(MessageBody* body);
};

class MessageBody : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged)
// ...
}

Предположим, что тип Message был зарегистрирован в системе типов QML, что позволяет использовать его как тип объекта из кода QML:

Message {
    // ...
}

Если тип MessageBody также был зарегистрирован в системе типов, то можно было бы присвоить MessageBody свойству body объекта Message, всё из кода QML:

Message {
    body: MessageBody {
        text: "Hello, world!"
    }
}

Свойства со списками объектов

Свойства, содержащие списки типов, производных от QObject, также могут быть экспонированы в QML. Однако для этой цели следует использовать QQmlListProperty, а не QList<T> в качестве типа свойства. Это связано с тем, что QList не является типом, производным от QObject, и поэтому не может предоставить необходимые характеристики свойства QML, такие как уведомления о сигналах при изменении списка.

Например, класс MessageBoard ниже имеет свойство messages типа QQmlListProperty, которое хранит список экземпляров Message:

class MessageBoard : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
    QQmlListProperty<Message> messages();

private:
    static void append_message(QQmlListProperty<Message> *list, Message *msg);

    QList<Message *> m_messages;
};

Функция MessageBoard::messages() просто создаёт и возвращает QQmlListProperty из своего члена QList<T> m_messages, передавая необходимые функции изменения списка, как требуется конструктором QQmlListProperty:

QQmlListProperty<Message> MessageBoard::messages()
{
    return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
}

void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg)
{
    MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object);
    if (msg)
        msgBoard->m_messages.append(msg);
}

Обратите внимание, что тип шаблона класса для QQmlListProperty — в данном случае, Message — должен быть зарегистрирован в системе типов QML.

Группированные свойства

Любое чисто-чтение свойство типа объекта доступно в коде QML как группированное свойство. Это можно использовать для экспонирования группы связанных свойств, описывающих набор атрибутов для типа.

Например, предположим, что свойство Message::author было типа MessageAuthor вместо просто строки, с подсвойствами name и email:

class MessageAuthor : public QObject
{
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(QString email READ email WRITE setEmail)
public:
    ...
};

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageAuthor* author READ author)
public:
    Message(QObject *parent)
        : QObject(parent), m_author(new MessageAuthor(this))
    {
    }
    MessageAuthor *author() const {
        return m_author;
    }
private:
    MessageAuthor *m_author;
};

Свойство author можно было бы записать, используя синтаксис группированных свойств в QML, так:

Message {
    author.name: "Alexandra"
    author.email: "alexandra@mail.com"
}

Тип, экспонированный как группированное свойство, отличается от свойства типа объекта тем, что группированное свойство является только для чтения и инициализируется с помощью действительного значения родительским объектом во время построения. Подсвойства группированного свойства могут быть изменены из QML, но сам объект группированного свойства никогда не изменится, тогда как свойство типа объекта может получить новое значение объекта из QML в любое время. Таким образом, жизненный цикл объекта группированного свойства строго контролируется реализацией родительского объекта C++, в то время как свойство типа объекта может быть свободно создано и уничтожено через код QML.

Экспонирование методов (включая Qt-слоты)

Любой метод типа, производного от QObject, доступен в коде QML, если он:

  • Является публичным методом, помеченным макросом Q_INVOKABLE()
  • Является публичным Qt слотом

Например, класс MessageBoard ниже имеет метод postMessage(), помеченный макросом Q_INVOKABLE, а также метод refresh(), который является публичным слотом:

class MessageBoard : public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE bool postMessage(const QString &msg) {
        qDebug() << "Called the C++ method with" << msg;
        return true;
    }

public slots:
    void refresh() {
        qDebug() << "Called the C++ slot";
    }
};

Если экземпляр MessageBoard был установлен в качестве данных контекста для файла MyItem.qml, тогда MyItem.qml мог вызвать два метода, как показано в примерах ниже:

C++
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    MessageBoard msgBoard;
    QQuickView view;
    view.engine()->rootContext()->setContextProperty("msgBoard", &msgBoard);
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}
QML
// MyItem.qml
import QtQuick 2.0

Item {
    width: 100; height: 100

    MouseArea {
        anchors.fill: parent
        onClicked: {
            var result = msgBoard.postMessage("Hello from QML")
            console.log("Result of postMessage():", result)
            msgBoard.refresh();
        }
    }
}

Если у метода C++ есть параметр типа QObject*, значение параметра можно передать из QML, используя объект id или значение JavaScript переменной, ссылающееся на этот объект.

QML поддерживает вызов перегруженных функций C++. Если существует несколько функций C++ с одинаковым именем, но разными аргументами, правильная функция будет вызвана в соответствии с количеством и типами предоставленных аргументов.

Значения, возвращаемые методами C++, преобразуются в значения JavaScript при доступе к ним из выражений JavaScript в QML.

Возложение сигналов

Любой публичный сигнал типа, производного от QObject, доступен из кода QML.

Движок QML автоматически создает обработчик сигнала для любого сигнала типа, производного от QObject, который используется из QML. Обработчики сигналов всегда имеют имя on<Signal>, где <Signal> — это имя сигнала с заглавной первой буквой. Все параметры, передаваемые сигналом, доступны в обработчике сигнала через имена параметров.

Например, предположим, что класс MessageBoard имеет newMessagePosted() сигнал с одним параметром, subject.

class MessageBoard : public QObject
{
    Q_OBJECT
public:
   // ...
signals:
   void newMessagePosted(const QString &subject);
};

Если тип MessageBoard был зарегистрирован в системе типов QML, то объект MessageBoard, объявленный в QML, мог бы получать сигнал newMessagePosted() с помощью обработчика сигнала под названием onNewMessagePosted, и изучить значение параметра subject:

MessageBoard {
    onNewMessagePosted: (subject)=> console.log("New message received:", subject)
}

Как и значения свойств и параметры методов, параметр сигнала должен иметь тип, поддерживаемый движком QML; см. Преобразование типов данных между QML и C++. (Использование незарегистрированного типа не вызовет ошибки, но значение параметра не будет доступно из обработчика.)

Классы могут иметь несколько сигналов с одинаковым именем, но только последний сигнал доступен как сигнал QML. Обратите внимание, что сигналы с одинаковым именем, но разными параметрами, не могут быть различимы друг от друга.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/qtqml-cppintegration-exposecppattributes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API