Spec-Zone.ru › Qt 5.12

Экспонирование атрибутов типов 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().

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 для нескольких свойств. Это следует делать с осторожностью, чтобы не пострадала производительность.

Наличие сигнала 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 var, которое ссылается на объект.

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: 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-5.12/qtqml-cppintegration-exposecppattributes.html

Spec-Zone.ru

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