美文网首页码农的世界程序员Qt & QML 日记
在 C++ 中, 怎么和 QML 对象交互 ?

在 C++ 中, 怎么和 QML 对象交互 ?

作者: ENG八戒 | 来源:发表于2019-01-19 17:43 被阅读70次

    以下内容为本人的学习笔记,如需要转载,请声明原文链接 [englyf] https://www.jianshu.com/p/66649d0e9bb6

    请注意这里使用的环境是
    IDE:Qt5.12
    Lang:C++、QML
    Compiler:vs2017x64

    所有 QML 对象都是 QObject 的派生类型, 无论这个对象是引擎的内部实现或者是由第三方源定义而来。也就是说,QML 引擎可以利用 Qt 的元对象系统(Meta Object System
    )去动态实例化任何的 QML 对象类型,以及检查被创建的对象。
    所以说,在 C++ 代码中,无论是因为要显示一个可渲染的 QML 对象,或者需要集成非可视化的 QML 对象数据,创建 QML 对象都是非常容易实现的。当一个 QML 对象被创建之后,为了读写这个对象的属性,调用这个对象的方法和接收这个对象的信号通知,都能够在 C++ 中对此进行检查。

    怎么在 C++ 中加载 QML 对象呢?

    我们可以使用 Qt 提供的两个类来加载 QML 文档,分别是
    QQmlComponentQQuickView
    QQmlComponent 加载一个 QML 文档后,生成一个 C++ 对象,可以在 C++ 代码中对这个对象进行修改。
    QQuickView 同样可以做到这些,但 QQuickViewQWindow 的一个派生类型,加载之后的对象也会被渲染出来。 QQuickView 通常被用来将可视化的 QML 对象集成到应用的用户界面中。

    下面看个举个栗子,

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        width: 100; height: 100
    }
    

    既可以用 QQmlComponent 也可以用 QQuickView 将上面这个 QML 文件加载到 C++ 代码中。如果使用 QQmlComponent 则需要调用 QQmlComponent::create() 创建组件的实例并返回对象(实例)指针,而使用 QQuickView
    就会自动创建组件的实例,然后调用 QQuickView::rootObject()
    获取实例指针。

    // Using QQmlComponent
    QQmlEngine engine;
    QQmlComponent component(&engine,
            QUrl::fromLocalFile("textItem.qml"));
    QObject *object = component.create();
    ...
    delete object;
    
    // Using QQuickView
    QQuickView view;
    view.setSource(QUrl::fromLocalFile("textItem.qml"));
    view.show();
    QObject *object = view.rootObject();
    
    怎么在 C++ 中设置 QML 对象的属性呢?

    现在可以调用上面实例的 QObject::setProperty() 或者借用 QQmlProperty::write() 来修改 Item 的属性:

    object->setProperty("width", 500);
    QQmlProperty(object, "width").write(500);
    

    这两种方式是有区别的,后者 QQmlProperty::write() 除了设置属性之外,还会移除原来的绑定,所以这里要特别注意一下。
    比如,假设在 QML 文件中,已将 width 绑定到 height:

    width: height
    

    如果设置属性的方式是调用 object->setProperty("width", 500),那么 width 的值只是临时被设置为 500,一旦 height 改变了,width也是会跟随改变的,因为绑定关系没有被移除。但是,如果设置属性的方式是调用 QQmlProperty(object, "width").write(500) ,那么width 的值不会再跟随 height 的改变而改变,因为原来的绑定关系已被移除。
    此外呢,设置属性还有一种方法就是,先将对象强制转换为实际类型,然后使用编译时安全性调用方法。在上面的文件 textItem.qml 中,Item 由类 QQuickItem 定义:

    QQuickItem *item = qobject_cast<QQuickItem*>(object);
    item->setWidth(500);
    
    怎么在 C++ 中按照对象名访问已加载的 QML 对象呢?

    QML 组件实际上是一组具有子节点的对象树,子节点同样有兄弟对象和子对象。可以使用 QObject::findChild() 并传入属性值 QObject::objectName(也即是对象名) 来定位到 QML 组件的子对象。下面看看我这的栗子大不大:

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        width: 100; height: 100
    
        Rectangle {
            anchors.fill: parent
            objectName: "rect"
        }
    }
    

    可以看到根项是 Item,然后还有个子项 Rectangle。可以通过下面的方式定位子对象:

    QObject *rect = object->findChild<QObject*>("rect");
    if (rect)
        rect->setProperty("color", "red");
    

    这里要注意一下,一个对象可以有多个相同 objectName(属性) 的子对象。比如,ListView 创建其委托的多个实例,如果使用特定的 objectName 声明其委托,则 ListView 将具有多个相同 objectName 的子节点。这种情况下,可以使用 QObject::findChildren() 来查找符合 objectName 的所有子节点。
    特别注意:虽然可以在 C++ 中访问并且操作 QML 对象,但是除了测试和原型设计之外,这种方法是不推荐的!QML 和 C++ 集成的优势之一就是实现与 C++ 逻辑和数据集后端分离的 UI 界面,如果在 C++ 中直接操作 QML 将意味着放弃优势。这种方法也使得在不影响对应 C++ 部分的前提下去改动 QML UI 变得困难。

    怎么在 C++ 中访问 QML 对象类型的成员呢?
    访问属性

    任何 QML 对象中声明的属性都自动可以在 C++ 代码中访问。下面再来个栗子:

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        property int a: 100
    }
    

    在上面 QML 文件中声明的属性 a 的值可以使用 QQmlProperty 来读写,或者用 QObject::setProperty() 来写属性值和用 QObject::property() 来读属性值:

    QQmlEngine engine;
    QQmlComponent component(&engine, "textItem.qml");
    QObject *object = component.create();
    
    qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
    QQmlProperty::write(object, "someNumber", 5000);
    
    qDebug() << "Property value:" << object->property("someNumber").toInt();
    object->setProperty("someNumber", 100);
    

    为了确保 QML 引擎知道属性的改变,你应该始终采用 QObject::setProperty(), QQmlProperty 或者 QMetaProperty::write() 来设置 QML 对象的属性值。比如,假如你有个自定义类型 PushButton, 在内部有个属性 buttonText 并且和成员变量 m_buttonText 关联。 像下面这样子直接修改成员变量 m_buttonText 是不受推荐的:

    // un-recommended
    QQmlComponent component(engine, "textItem.qml");
    PushButton *button = qobject_cast<PushButton*>(component.create());
    button->m_buttonText = "clicked !";
    

    如果变量 m_buttonText 被直接修改,那么QML 引擎将不会知道属性改变了,因为这种操作完美地躲开了 Qt 的 meta-object system。后果就是,绑定的 buttonText 属性不会被更新,而且属性变更信号槽 onButtonTextChanged() 不会被调用。

    访问 QML 方法

    由于所有的 QML 方法都暴露给了元对象系统 Meta-object system, 所以在 C++ 代码中可以通过 QMetaObject::invokeMethod() 调用对应的 QML 方法,并且输入的参数和来自 QML 中的返回值在 C++ 中通常被转换成 QVariant 值。下面有个例子:

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        function qmlFunction(msg) {
            console.log("Got msg:", msg)
            return "return value"
        }
    }
    
    // main.cpp
    QQmlEngine engine;
    QQmlComponent component(&engine, "textItem.qml");
    QObject *object = component.create();
    
    QVariant returnedValue;
    QVariant msg = "hi from C++";
    QMetaObject::invokeMethod(object, "qmlFunction",
            Q_RETURN_ARG(QVariant, returnedValue),
            Q_ARG(QVariant, msg));
    
    qDebug() << "value returned from QML :" << returnedValue.toString();
    delete object;
    

    这里要注意一下,参数 Q_RETURN_ARG() and Q_ARG() 必须指定为 QVariant 类型, 因为 QVariant 是用于 QML 方法输入参数和返回值的通用数据类型。

    连接 QML 信号

    所有 QML 信号都自动适用于 C++ 代码,就像任何普通 Qt C++ 信号一样用 QObject::connect() 连接。反过来,任何 C++ 信号都可以被 QML 对象的信号处理程序接收到。
    这里来个栗子,有个 QML 组件定义了一个带 string 类型参数的信号 qmlSignal. 这个信号通过 QObject::connect() 连接到 C++ 对象的信号槽 cppSlot(),所以每当 QML 的信号 qmlSignal 被发送时都会调用 C++ 里的 cppSlot()。

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        id: item
        width: 200; height: 200
    
        signal qmlSignal(string message)
    
        MouseArea {
            anchors.fill: parent
            onClicked: item.qmlSignal("QML say Hi ")
        }
    }
    
    class MyClass : public QObject
    {
        Q_OBJECT
    public slots:
        void cppSlot(const QString &message) {
            qDebug() << "C++ got message:" << message;
        }
    };
    
    int main(int argc, char *argv[]) {
        QGuiApplication app(argc, argv);
    
        QQuickView view(QUrl::fromLocalFile("textItem.qml"));
        QObject *item = view.rootObject();
    
        MyClass myClass;
        QObject::connect(item, SIGNAL(qmlSignal(QString)),
                         &myClass, SLOT(cppSlot(QString)));
    
        view.show();
        return app.exec();
    }
    

    当 QML 对象定义的信号带参数并且参数类型为 QML 对象类型时,参数类型应该声明为 var 并且 C++ 的对应接收类型应该使用 QVariant 类型。临走带个栗子吧:

    // textItem.qml
    import QtQuick 2.0
    
    Item {
        id: item
        width: 200; height: 200
    
        signal qmlSignal(var anObject)
    
        MouseArea {
            anchors.fill: parent
            onClicked: item.qmlSignal(item)
        }
    }
    
    class MyClass : public QObject
    {
        Q_OBJECT
    public slots:
        void cppSlot(const QVariant &v) {
           qDebug() << "C++ slot got value:" << v;
    
           QQuickItem *item =
               qobject_cast<QQuickItem*>(v.value<QObject*>());
           qDebug() << "Item w & h:" << item->width()
                    << item->height();
        }
    };
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        QQuickView view(QUrl::fromLocalFile("textItem.qml"));
        QObject *item = view.rootObject();
    
        MyClass myClass;
        QObject::connect(item, SIGNAL(qmlSignal(QVariant)),
                         &myClass, SLOT(cppSlot(QVariant)));
    
        view.show();
        return app.exec();
    }
    

    //
    参考英文资料[Qt]https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html

    相关文章

      网友评论

        本文标题:在 C++ 中, 怎么和 QML 对象交互 ?

        本文链接:https://www.haomeiwen.com/subject/gwyzdqtx.html