Предоставление атрибутов типов 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, такие как уведомления о сигналах при изменении списка.
Например, класс 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++ с одинаковым именем, но различными аргументами, правильная функция будет вызвана в соответствии с количеством и типами предоставленных аргументов.
END_OF_DOCUMENT_MARKERЗначения, возвращаемые методами 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-6.0/qtqml-cppintegration-exposecppattributes.html