目录
QML可以很容易地通过c++代码中定义的功能进行扩展。由于QML引擎与Qt元对象系统的紧密集成,任何由qobject派生类适当公开的功能都可以从QML代码访问。这使得可以直接从QML访问c++数据和函数,通常很少或不需要修改。QML引擎能够通过元对象系统对QObject实例进行内省。这意味着任何QML代码都可以访问qobject派生类实例的以下成员:
- Properties (属性)
- Methods (providing they are public slots or flagged with Q_INVOKABLE) (方法)
- Signals (信号)
(此外,如果使用Q_ENUMS声明了枚举,那么枚举也是可用的。详见QML和c++之间的数据类型转换。)
通常,无论qobject派生类是否已注册到QML类型系统,都可以从QML访问这些类。但是,如果以一种需要引擎访问附加类型信息的方式使用类——例如,如果将类本身用作方法参数或属性,或者以这种方式使用其枚举类型之一则可能需要注册该类。
还要注意,本文档中涉及的许多重要概念在使用c++编写QML扩展教程中得到了演示。
1.数据类型处理和所有权
从c++传输到QML的任何数据,无论是作为属性值、方法参数或返回值,还是作为信号参数值,都必须是QML引擎支持的类型。
默认情况下,该引擎支持许多Qt c++类型,并可以在从QML中使用时自动将它们转换为适当的类型。此外,向QML类型系统注册的c++类可以用作数据类型,如果适当地注册,它们的枚举也可以用作数据类型。有关更多信息,请参阅QML和c++之间的数据类型转换。
此外,数据所有权规则也有缺点
2.暴露属性
可以使用Q_property()宏为任何从qobject派生的类指定属性。属性是具有关联的读函数和可选的写函数的类数据成员。qobject派生类的所有属性都可以从QML访问。
例如,下面是一个具有作者属性的Message类。正如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的文件时将该类的一个实例设置为上下文属性。从c++) qml:
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();
}
然后,可以从MyItem.qml中读取author属性:
// 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信号是authorChanged,这在Q_property()宏调用中指定。这意味着无论何时信号被发射
注意:建议将NOTIFY信号命名为<property>更改,其中<property>是属性的名称。由QML引擎生成的相关属性更改信号处理程序将始终采用< property >Changed的形式,而不管相关的c++信号的名称是什么,因此建议信号名称遵循此约定,以避免任何混淆。
为了防止循环或过度计算,开发人员应该确保只有在属性值实际发生更改时才发出属性更改信号。此外,如果一个属性或一组属性不经常使用,则允许对多个属性使用相同的NOTIFY信号。这样做时应该小心,以确保性能不会受到影响。
NOTIFY信号的存在确实会导致较小的开销。在某些情况下,属性的值是在对象构造时设置的,随后不会更改。最常见的情况是当类型使用Grouped时
2.1 带有对象类型的属性
只要对象类型已适当地注册到QML类型系统,就可以从QML访问对象类型属性。
例如,Message类型可能有一个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分配给Message的body属性是可能的,所有这些都可以在QML代码中实现:
Message {
body: MessageBody {
text: "Hello, world!"
}
}
2.2 具有对象列表类型的属性
包含qobject派生类型列表的属性也可以公开给QML。然而,出于这个目的,应该使用QQmlListProperty而不是QList<T>作为属性类型。这是因为QList不是一个qobject派生的类型,所以不能通过Qt元对象系统提供必要的QML属性特征,比如当列表被修改时的信号通知。
例如,下面的MessageBoard类有一个类型为QQmlListProperty的消息属性,它存储了一个消息实例列表:
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()函数简单地从它的QList<T> m_messages成员创建并返回一个QQmlListProperty,并根据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类型系统注册。
2.3 分组属性
任何只读对象类型属性都可以作为分组属性从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代码自由创建和销毁。
3. 暴露方法(包括Qt插槽)
qobject派生类型的任何方法都可以从QML代码中访问,如果它是:
例如,下面的MessageBoard类有一个postMessage()方法,它已经被Q_INVOKABLE宏标记,以及一个刷新()方法,它是一个公共槽位:
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*类型的参数,参数值可以使用引用该对象的对象id或JavaScript var值从QML传递。
QML支持重载c++函数的调用。如果存在多个名称相同但参数不同的c++函数,则将根据提供的参数的数量和类型调用正确的函数。
当从QML中的JavaScript表达式访问c++方法返回的值时,将转换为JavaScript值。
4.暴露的信号
qobject派生类型的任何公共信号都可以从QML代码中访问。
QML引擎自动为从QML使用的任何qobject派生类型的信号创建一个信号处理程序。信号处理程序总是以<Signal>命名,其中<Signal>是信号的名称,首字母大写。信号传递的所有参数都可以通过参数名称在信号处理程序中使用。
例如,假设MessageBoard类有一个带有单个参数subject的newmessagepost()信号:
class MessageBoard : public QObject
{
Q_OBJECT
public:
// ...
signals:
void newMessagePosted(const QString &subject);
};
如果 MessageBoard 类型已注册到 QML 类型系统,则在 QML 中声明的 MessageBoard 对象可以使用名为 onNewMessagePosted 的信号处理程序接收 newMessagePosted() 信号,并检查主题参数值:
MessageBoard {
onNewMessagePosted: console.log("New message received:", subject)
}
与属性值和方法参数一样,信号参数必须具有QML引擎支持的类型;参见QML和c++之间的数据类型转换。(使用未注册的类型不会产生错误,但该参数值将无法从处理程序访问。)
类可以有多个名称相同的信号,但只有最后一个信号可以作为QML信号访问。注意,名称相同但参数不同的信号无法区分。