Экспонирование атрибутов типов 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 назывался <property>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, такие как уведомления о сигналах при изменении списка, через систему метаобъектов Qt.
QQmlListProperty — шаблонный класс, который удобно создается из значения QList.
Например, класс 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.
Exposing Signals
Любой публичный сигнал типа, производного от 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/archives/qt-5.11/qtqml-cppintegration-exposecppattributes.html