zoukankan      html  css  js  c++  java
  • 在 C++ 中使用 QML 对象

    看过了如何在 QML 中使用 C++ 类型或对象,现在来看如何在 C++ 中使用 QML 对象。

    我们可以使用 QML 对象的信号、槽,访问它们的属性,都没有问题,因为很多 QML 对象对应的类型,原本就是 C++ 类型,比如 Image 对应 QQuickImage , Text 对应 QQuickText……但是,这些与 QML 类型对应的 C++ 类型都是私有的,你写的 C++ 代码也不能直接访问。肿么办?

    Qt 最核心的一个基础特性,就是元对象系统,通过元对象系统,你可以查询 QObject 的某个派生类的类名、有哪些信号、槽、属性、可调用方法等等信息,然后也可以使用 QMetaObject::invokeMethod() 调用 QObject 的某个注册到元对象系统中的方法。而对于使用 Q_PROPERTY 定义的属性,可以使用 QObject 的 property() 方法访问属性,如果该属性定义了 WRITE 方法,还可以使用 setProperty() 修改属性。所以只要我们找到 QML 环境中的某个对象,就可以通过元对象系统来访问它的属性、信号、槽等。


    一、查找一个对象的孩子

    QObject 类的构造函数有一个 parent 参数,可以指定一个对象的父亲, QML 中的对象其实借助这个组成了以根 item 为父的一棵对象树。
    而 QObject 定义了一个属性 objectName ,这个对象名字属性,就可以用于查找对象。现在该说到查找对象的方法了:findChild()findChildren()。它们的函数原型如下:

    T QObject::findChild(const QString & name = QString(),
        Qt::FindChildOptions options = 
        Qt::FindChildrenRecursively) const;
    
    QList<T> QObject::findChildren(const QString & name = 
        QString(), Qt::FindChildOptions options = 
    Qt::FindChildrenRecursively) const;
    
    QList<T> QObject::findChildren(const QRegExp & regExp, 
        Qt::FindChildOptions options = 
        Qt::FindChildrenRecursively) const;
    
    QList<T> QObject::findChildren(const QRegularExpression & re,
         Qt::FindChildOptions options = 
         Qt::FindChildrenRecursively) const;
    

    都是模板方法,从命名上也可以看出,一个返回单个对象,一个返回对象列表。闲话少说,现在让我们看看如何查询一个或多个对象,我们先以 Qt Widgets 为例来说明用法哈。

    示例 1 :

    QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
    

    查找 parentWidget 的名为 "button1" 的类型为 QPushButton 的孩子。

    示例 2 :

    QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
    

    返回 parentWidget 所有名为 "widgetname" 的 QWidget 类型的孩子列表。


    二、使用元对象调用一个对象的方法

    QMetaObject 的 invokeMethod() 方法用来调用一个对象的信号、槽、可调用方法。它是个静态方法,其函数原型如下(省略了部分参数):

    bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, ...) [static]
    
    • 第一个参数是被调用对象的指针。
    • 第二个参数是方法名字。
    • 第三个参数是连接类型,看到这里你就知道, invokeMethod 为信号与槽而生,你可以指定连接类型,如果你要调用的对象和发起调用的线程是同一个线程,那么可以使用 Qt::DirectConnection 或 Qt::AutoConnection 或 Qt::QueuedConnection ,如果被调用对象在另一个线程,那么建议你使用 Qt::QueuedConnection 。
    • 第四个参数用来接收返回指。
    • 然后就是多达 10 个可以传递给被调用方法的参数(这里省略了)。嗯,看来信号与槽的参数个数是有限制的,不能超过 10 个。

    对于要传递给被调用方法的参数,使用 QGenericArgument 来表示,你可以使用 Q_ARG 宏来构造一个参数,它的定义是:

    QGenericArgument Q_ARG( Type, const Type & value)
    

    返回类型是类似的,使用 QGenericReturnArgument 表示,你可以使用 Q_RETURN_ARG 宏来构造一个接收返回指的参数,它的定义是:

    QGenericReturnArgument Q_RETURN_ARG( Type, Type & value)
    

    好啦,总算把这个天杀的函数介绍完了,下面我们看看怎么用。


    假设一个对象有这么一个槽 compute(QString, int, double) ,返回一个 QString 对象,那么你可以这么调用(同步方式):

    QString retVal;
    QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
                              Q_RETURN_ARG(QString, retVal),
                              Q_ARG(QString, "sqrt"),
                              Q_ARG(int, 42),
                              Q_ARG(double, 9.7));
    

    如果你要让一个线程对象退出,可以这么调用(队列连接方式):

    QMetaObject::invokeMethod(thread, "quit",
                              Qt::QueuedConnection);
    

    三、callQml 示例

    现在让我们创建一个新的项目,名字是 callQml ,添加 changeColor.h 、 changeColor.cpp 两个文件。 main.qml 内容如下:

    import QtQuick 2.2
    import QtQuick.Controls 1.2
    import QtQuick.Window 2.1
    
    Window {
        objectName: "rootObject";
         360;
        height: 360;
        visible: true;
        Text {
            objectName: "textLabel";
            text: "Hello World";
            anchors.centerIn: parent;
            font.pixelSize: 26;
        }
    
        Button {
            anchors.right: parent.right;
            anchors.rightMargin: 4;
            anchors.bottom: parent.bottom;
            anchors.bottomMargin: 4;
            text: "quit";
            objectName: "quitButton";
        }
    }
    

    我们给根元素起了个名字 "rootRect" ,给退出按钮起了个名字 "quitButton" ,给文本起了名字 "textLabel" ,我们会在 C++ 代码中通过这些个名字来查找对应的对象并改变它们。

    现在来看看 main.cpp :

    #include <QtGui/QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "changeColor.h"
    #include <QMetaObject>
    #include <QDebug>
    #include <QColor>
    #include <QVariant>
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
        engine.load(QUrl("qrc:///main.qml"));
    
        QObject * root = NULL;//= qobject_cast<QObject*>(viewer.rootObject());
        QList<QObject*> rootObjects = engine.rootObjects();
        int count = rootObjects.size();
        qDebug() << "rootObjects- " << count;
        for(int i = 0; i < count; i++)
        {
            if(rootObjects.at(i)->objectName() == "rootObject")
            {
                root = rootObjects.at(i);
                break;
            }
        }
        new ChangeQmlColor(root);
        QObject * quitButton = root->findChild<QObject*>("quitButton");
        if(quitButton)
        {
            QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
        }
    
        QObject *textLabel = root->findChild<QObject*>("textLabel");
        if(textLabel)
        {
            //1. failed call
            bool bRet = QMetaObject::invokeMethod(textLabel, "setText", Q_ARG(QString, "world hello"));
            qDebug() << "call setText return - " << bRet;
            textLabel->setProperty("color", QColor::fromRgb(255,0,0));
            bRet = QMetaObject::invokeMethod(textLabel, "doLayout");
            qDebug() << "call doLayout return - " << bRet;
        }
    
        return app.exec();
    }
    

    在一开始我通过 viewer.rootObject() ,获取到了作为根元素的 Rectangle ,然后把它交给一个 ChangeQmlColor 对象,该对象会内部通过一个定时器,一秒改变一次传入对象的颜色。

    紧接着,我使用 QObject 的 findChild() 找到了 quitButton 按钮,把它的 clicked() 信号连接到 QGuiApplication 的 quit() 槽上。所以你点击这个按钮,应用就退出了。

    后来,我又通过名字 "textLabel" 找到了 textLabel 对象。首先我企图使用 invodeMethod() 调用 setText() 方法来改变 textLabel 的文本,这个注定是会失败的,因为 QML 中的Text 对象对应 C++ QQuickText 类,而 QQuickText 没有名为 setText 的槽或者可调用方法。我查看了头文件 qquicktext_p.h ,发现它有一个使用 Q_INVOKABLE 宏修饰的 doLayout() 的方法,所以后来我又调用 doLayout() ,这次成功了。


    下图是运行效果:


    Hello World 这行字变成了红色,是因为我在 main() 函数中使用 setProperty 修改了 textLabel 的 color 属性。

    下面是 Qt Creator 应用程序输出窗口的信息,可以验证对 Text 方法的调用是否成功:

    Starting D:projects...
    eleasecallQml.exe...
    QMetaObject::invokeMethod: No such method QQuickText::setText(QString)
    call setText return -  false 
    call doLayout return -  true 
    

    好啦,最后看看界面背景为么变成了浅绿色。这正是下面这行代码的功劳:

    new ChangeQmlColor(rootItem);
    

    它以 rootItem 为参数创建了一个 ChangeQmlColor 对象,而 ChangeQmlColor 类会改变传给它的对象的颜色。

    ChangeQmlColor 类定义如下:

    #ifndef CHANGECOLOR_H
    #define CHANGECOLOR_H
    #include <QObject>
    #include <QTimer>
     
    class ChangeQmlColor : public QObject
    {
        Q_OBJECT
    public:
        ChangeQmlColor(QObject *target, QObject *parent = 0);
        ~ChangeQmlColor();
     
    protected slots:
        void onTimeout();
     
    private:
        QTimer m_timer;
        QObject *m_target;
    };
     
    #endif
    

    实现文件 changeColor.cpp :

    #include "changeColor.h"
    #include <QDateTime>
    #include <QColor>
    #include <QVariant>
     
    ChangeQmlColor::ChangeQmlColor(QObject *target, QObject *parent)
        : QObject(parent)
        , m_timer(this)
        , m_target(target)
    {
        qsrand(QDateTime::currentDateTime().toTime_t());
        connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
        m_timer.start(1000);
    }
     
    ChangeQmlColor::~ChangeQmlColor()
    {}
     
    void ChangeQmlColor::onTimeout()
    {
        QColor color = QColor::fromRgb(qrand()%256, qrand()%256, qrand()%256);
        m_target->setProperty("color", color);
    }
    

    参考:

    《Qt Quick核心编程》第11章

    Qt Quick 之 QML 与 C++ 混合编程详解


  • 相关阅读:
    React中条件渲染
    React 中this.setStat是批量执行的, 它发现做三次是多余的,所以只执行一次
    React 修改获取state中的值
    POJ3417 Network (树上差分)
    POJ3349 Snowflake Snow Snowflakes(Hash)
    Codeforces Round #630 (Div. 2) C. K-Complete Word(字符串)
    Codeforces Round #630 (Div. 2) B. Composite Coloring(数论)
    Codeforces Round #630 (Div. 2) A. Exercising Walk(水题)
    Codeforces Round #629 (Div. 3)/1328 E.Tree Queries(LCA)
    洛谷P5836 [USACO19DEC]Milk Visits S(LCA/并查集)
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/11962142.html
Copyright © 2011-2022 走看看