将 C++ 类型的属性暴露给 QML

可以使用 C++ 代码中定义的功能轻松扩展 QML。由于 QML 引擎与 Qt 元对象系统的紧密集成,QObject 派生类公开的任何功能都可以从 QML 代码访问。这使得 C++ 数据和函数可以直接从 QML 访问,通常几乎不需要修改。

QML 引擎能够通过元对象系统反射 QObject 实例。这意味着任何 QML 代码都可以访问 QObject 派生类的实例的以下成员:

  • 属性
  • 方法(前提是它们是 public slots 或用 Q_INVOKABLE 标记)
  • 信号

(此外,如果枚举是用 Q_ENUMS 声明的,则也可以访问)

一、数据类型处理和所有权

任何从 C++ 传输到 QML 的数据,无论是作为属性值、方法参数或返回值,还是信号参数值,都必须是 QML 引擎支持的类型。

默认情况下,引擎支持许多 Qt C++ 类型,并且可以在从 QML 使用时自动适当地转换它们。 

1.1、暴露属性

可以使用 Q_PROPERTY() 宏为任何 QObject 派生类指定属性。

例如,下面是一个具有作者属性的 Message 类。 正如 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;
};

如果在从 C++ 加载名为 MyItem.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    // 调用 Message::author() 来获取这个值

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

为了实现C++与 QML 的最大互操作性,任何可写的属性都应该有一个关联的 NOTIFY 信号,该信号在属性值更改时发出。这允许属性与属性绑定一起使用,这是 QML 的一个基本特性,它通过在任何依赖项的值发生变化时自动更新属性来强制执行属性之间的关系。即发出信号会通知 QML 引擎更新任何涉及属性的绑定。

上面示例中如果 author 属性可写但没有关联的 NOTIFY 信号,则文本值将使用 Message::author() 返回的初始值进行初始化,但不会随此属性的任何后续更改而更新。此外,任何从 QML 绑定到属性的尝试都会从引擎产生运行时警告。

建议将 NOTIFY 信号命名为 <property>Changed,其中 <property> 是属性的名称。QML 引擎生成的关联属性更改信号处理程序将始终采用 on<Property>Changed 形式,无论相关 C++ 信号的名称如何,因此建议信号名称遵循此约定以避免任何混淆。

1.2、使用通知信号的注意事项

开发人员应确保仅在属性值实际更改时才发出属性更改信号。此外,如果一个属性或一组属性不经常使用,则允许对多个属性使用相同的 NOTIFY 信号。这应该小心完成以确保性能不会受到影响。

NOTIFY 信号的存在确实会产生很小的开销。在某些情况下,属性的值是在对象构造时设置的,随后不会更改。在这些情况下,可以将 CONSTANT 属性而不是 NOTIFY 信号添加到属性声明中。

CONSTANT 属性应仅用于其值仅在类构造函数中设置和最终确定的属性。

Qt属性系统

1.3、具有对象类型的属性

对象类型属性可从 QML 访问,前提是对象类型已正确注册到 QML 类型系统。

例如,Message 类型可能具有 MessageBody* 类型的 body 属性:

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 属性:

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

1.4、具有对象列表类型的属性

包含 QObject 派生类型列表的属性也可以暴露给 QML。然而,应该使用 QQmlListProperty 而不是 QList<T> 作为属性类型。 这是因为 QList 不是 QObject 派生的类型,因此无法通过 Qt 元对象系统提供必要的 QML 属性特征,例如修改列表时的信号通知。

例如,下面的 MessageBoard 类有一个 QQmlListProperty 类型的消息属性,用于存储 Message 实例列表:

class MessageBoard : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
    QQmlListProperty<Message> messages()
    {
        return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
    }

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

QQmlListProperty 的模板类类型(在本例中为 Message)必须在 QML 类型系统中注册。

1.5、分组属性

任何只读的对象类型属性都可以作为分组属性从 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;
};

可以使用 QML 中的分组属性语法编写 author 属性:

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

分组属性是只读的,并且在构造时由父对象初始化为有效值。

分组属性的子属性可以从 QML 修改,但分组属性对象本身永远不会改变,

二、暴露方法

QObject 派生类型的任何方法都可以从 QML 代码访问,只要它满足其中一项:

  • Q_INVOKABLE() 宏标记的公共方法
  • 作为 public slot 的方法

例如,下面的 MessageBoard 类有一个使用 Q_INVOKABLE 宏标记的 postMessage() 方法,以及一个公共槽的 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 可以调用两个方法,如下例所示:

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();
}
// 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 变量值从 QML 传递参数值。

QML 支持调用重载的 C++ 函数。 如果存在多个同名但参数不同的 C++ 函数,则会根据提供的参数数量和类型调用正确的函数。

当从 QML 中的 JavaScript 表达式访问时,从 C++ 方法返回的值将转换为 JavaScript 值。

三、暴露信号

QObject 派生类型的任何公共信号都可以从 QML 代码访问。

QML 引擎自动为从 QML 使用的 QObject 派生类型的任何信号创建信号处理程序。信号处理程序始终命名为 on<Signal>,其中 <Signal> 是信号的名称,首字母大写。 信号传递的所有参数都可以通过参数名称在信号处理程序中使用。

例如,假设 MessageBoard 类有一个带有单个参数主题的 newMessagePosted() 信号:

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

 如果 MessageBoard 类型已注册到 QML 类型系统,则在 QML 中声明的 MessageBoard 对象可以使用名为 onNewMessagePosted 的信号处理程序接收 newMessagePosted() 信号,并检查主题参数值:

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

与属性值和方法参数一样,信号参数必须具有 QML 引擎支持的类型。(使用未注册的类型不会产生错误,但处理程序将无法访问参数值。)

请注意,QML引擎无法区分名称相同但参数不同的信号,类可能有多个同名的信号,但只有最后一个信号可以作为 QML 信号访问。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值