QML与 C++ 交互(二)

一、在 C++ 使用 QML 对象

官方文档说,所有 QML 对象类型都是源自 QObject 类型,无论它们是由引擎内部实现还是第三方定义。这意味着 QML 引擎可以使用 Qt 元对象系统动态实例化任何 QML 对象类型并检查创建的对象。

这对于从 C++ 代码创建 QML 对象非常有用,无论是显示可以直观呈现的 QML 对象,还是将非可视 QML 对象数据集成到 C++ 应用程序中。一旦创建了 QML 对象,就可以从 C++ 中检查它,以便读取和写入属性,调用方法和接收信号通知。

可以使用 QQmlComponent 或 QQuickView 来加载 QML 文档。QQmlComponent 将 QML 文档作为为一个 C++ 对象加载,然后可以从 C++ 代码进行修改。QQuickView 也可以这样做,但由于 QQuickView 是一个基于 QWindow 的派生类,加载的对象也将可视化显示,QQuickView 通常用于将一个可视化的 QML 对象集成到应用程序的用户界面中。

下面通过代码来演示。

Widget.h:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QObject>
#include <QQuickView>
#include <QQuickItem>
#include <QMetaObject>
#include <QQmlProperty>
#include <QDebug>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

signals:
    // 信号 --用来触发qml的函数
    // 注意参数为var类型,对应qml中js函数的参数类型
    void cppSendMsg(const QVariant &arg1,const QVariant &arg2);

public slots:
    // 槽函数 --用来接收qml的信号
    void cppRecvMsg(const QString &arg1,const QString &arg2);

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    QQuickView *view;
};

#endif // WIDGET_H

Widget.cpp:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    view = new QQuickView;
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    //【1】使用QQuickView的C++代码加载QML文档,显示QML界面
    // 另外可以用QQmlComponent、QQuickWidget加载QML文档 【QQuickView不能用Window做根元素】
    view->setSource(QUrl("qrc:/main.qml"));
    view->show();

    /* 文档如是说:
    应该始终使用QObject::setProperty()、QQmlProperty
    或QMetaProperty::write()来改变QML的属性值,以确保QML引擎感知属性的变化。*/

    //【2】通过QObject设置属性值、获取属性值
    QObject *qmlObj = view->rootObject(); // 获取到qml根对象的指针
    //qmlObj->setProperty("height",300);
    QQmlProperty(qmlObj, "height").write(300);
    qDebug() << "Cpp get qml property height" << qmlObj->property("height");
    // 任何属性都可以通过C++访问
    qDebug() << "Cpp get qml property msg" << qmlObj->property("msg");

    //【3】通过QQuickItem设置属性值、获取属性值
    QQuickItem *item = qobject_cast<QQuickItem*>(qmlObj);
    item->setWidth(300);
    qDebug() << "Cpp get qml property width" << item->width();

    //【4】通过objectName访问加载的QML子对象
    // QObject::findChildren()可用于查找具有匹配objectName属性的子项
    QObject *qmlRect = qmlObj->findChild<QObject*>("rect");
    if(qmlRect) {
        qmlRect->setProperty("color", "red");
        qDebug() << "Cpp get rect color" << qmlRect->property("color");
    }

    //【5】调用QML方法
    QVariant val_return;  // 返回值
    QVariant val_arg = "GongJianBo";  // 参数值
    // Q_RETURN_ARG()和Q_Arg()参数必须制定为QVariant类型
    QMetaObject::invokeMethod(qmlObj,
                              "qml_method",
                              Q_RETURN_ARG(QVariant,val_return),
                              Q_ARG(QVariant,val_arg));
    qDebug()<<"QMetaObject::invokeMethod result"<<val_return; // qml函数中返回“ok”

    //【6】关联信号槽
    // 1.关联qml信号与cpp槽,如果信号参数为QML对象类型,信号用var参数类型,槽用QVariant类型接收
    QObject::connect(qmlObj, SIGNAL(qmlSendMsg(QString,QString)),
                     this, SLOT(cppRecvMsg(QString,QString)));
    // 2.关联cpp信号与qml槽
    // qml中js函数参数为var类型,信号也用QVariant类型
    QObject::connect(this, SIGNAL(cppSendMsg(QVariant,QVariant)),
                     qmlObj, SLOT(qmlRecvMsg(QVariant,QVariant)));
    // 此外,cpp信号也可以关联qml信号
}

void Widget::cppRecvMsg(const QString &arg1,const QString &arg2)
{
    qDebug() << "CppObject::cppRecvMsg" << arg1 << arg2;
    qDebug() << "emit cppSendMsg";
    emit cppSendMsg(arg1,arg2);
}

main.qml:

import QtQuick 2.9

Item{
    id: root
    width: 250
    height: 250
    // 自定义属性  --cpp可以访问
    property string msg: "fengMisaka"
    // 自定义信号  --可以触发cpp槽函数
    signal qmlSendMsg(string arg1,string arg2)

    // QML中的方法可以被cpp调用,也可以作为槽函数
    function qml_method(val_arg) {
        console.log("qml method runing", val_arg, "return ok")
        return "ok"
    }
    // 注意槽函数参数为var类型
    function qmlRecvMsg(arg1,arg2) {
        console.log("qml slot runing",arg1,arg2)
    }

    Rectangle {
        anchors.fill: parent
        color: "green"
        objectName: "rect" // 用于cpp查找对象
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            console.log("qml 点击鼠标, 发送信号 qmlSendMsg")
            root.qmlSendMsg(root.msg, "myarg2")
        }
    }

    onHeightChanged: console.log("qml height changed")
    onWidthChanged: console.log("qml width changed")
}

效果如下图所示:

 二、在 QML 中使用 C++ 类和对象

 

Qt Quick 技术的引入,使得你能够快速构建 UI ,具有动画、各种绚丽效果的 UI 都不在话下。但它不是万能的,也有很多局限性,原来 Qt 的一些技术,比如低阶的网络编程如 QTcpSocket ,多线程等等,在 QML 中要么不可用,要么用起来不方便,所以呢,很多时候我们是会基于这样的原则来混合使用 QML 和 C++: QML 构建界面, C++ 实现非界面的业务逻辑和复杂运算。

我们知道, QML 其实是对 JavaScript 的扩展,融合了 Qt Object 系统,它是一种新的解释型的语言, QML 引擎虽然由 Qt C++ 实现,但 QML 对象的运行环境,说到底和 C++ 对象的上下文环境是不同的,是平行的两个世界。如果你想在 QML 中访问 C++ 对象,那么必然要找到一种途径来在两个运行环境之间建立沟通桥梁。

Qt 提供了两种在 QML 环境中使用 C++ 对象的方式:

(1)在 C++ 中实现一个类,注册到 QML 环境中, QML 环境中使用该类型创建对象。

(2)在 C++ 中构造一个对象,将这个对象设置为 QML 的上下文属性,在 QML 环境中直接使用该属性。

在 QML 中使用 C++ 类和对象,大概需要分四步:

(1)实现 C++ 类;

(2)注册 QML 类型;

(3)在 QML 中导入类型;

(4)在 QML 创建由 C++ 导出的类型的实例并使用。

下面就分别说明这 4 个步骤。

首先我们需要创建一个 Qt Quick App ,新建两个文件, CppObject.h 和 CppObject.cpp 。QmlCallCpp 只是一个示例项目,我在 C++ 中实现一个 CppObject 类,它可以被注册为一个 QML 类型供 QML 像内建类型一样使用,它的实例也可以导出为 QML 上下文属性在 QML 中访问。

1、实现 C++ 类

前提条件

要想将一个类或对象导出到 QML 中,下列前提条件必须满足:

  • 从 QObject 或 QObject 的派生类继承。
  • 使用 Q_OBJECT 宏。

看起来好像和使用信号与槽的前提条件一样……没错,的确是一样的。这两个条件是为了让一个类能够进入 Qt 强大的元对象系统(meta-object system)中,只有使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用,才具有了在 QML 中访问的基础条件。


a、 信号,槽

只要是信号或者槽,都可以直接在 QML 中访问,你可以把 C++ 对象的信号连接到 QML 中定义的方法上,也可以把 QML 对象的信号连接到 C++ 对象的槽上,还可以直接调用 C++ 对象的槽或信号。所以,这是最简单好用的一种途径。

下面是 CppObject 类的声明:

#ifndef CPPOBJECT_H
#define CPPOBJECT_H

#include <QObject>

// 需要派生自QObject
// 使用qmlRegisterType注册到QML中
class CppObject : public QObject
{
    Q_OBJECT

public:
    explicit CppObject(QObject *parent = nullptr);

signals:
    // 信号:可以直接在QML中访问信号
    void cppSignalA();//一个无参信号
    void cppSignalB(const QString &str,int value); // 一个带参数信号
    void nameChanged(const QString name);
    void yearChanged(int year);

public slots:
    // 槽函数:可以直接在QML中访问public槽函数
    void cppSlotA();//一个无参槽函数
    void cppSlotB(const QString &str,int value); // 一个带参数槽函数

private:
    // 类的属性
    QString myName;
    int myYear;
};

#endif // CPPOBJECT_H

mian.qml 中的部分相关代码如下:

import QtQuick 2.9
import QtQuick.Window 2.9
// 引入我们注册的模块
import MyCppObject 1.0

Window {
    id: root
    visible: true
    width: 500
    height: 300
    title: qsTr("QML调用Cpp对象")
    color: "green"    
    
    // 作为一个QML对象
    CppObject{
        id: cpp_obj
        //也可以像原生QML对象一样操作,增加属性之类的
        property int counts: 0

        onYearChanged: {
            counts++
            console.log('qml onYearChanged', counts)
        }
        onCountsChanged: {
            console.log('qml onCountsChanged', counts)
        }
    }

    // 关联信号与信号处理函数的方式同QML中的类型
    Component.onCompleted: {
        // 1. Cpp对象的信号关联到Qml的槽函数
        // cpp_obj.onCppSignalA.connect(function() {console.log('qml signalA process')})
        cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) // js的lambda
        cpp_obj.onCppSignalB.connect(processB)
        // 2. Qml对象的信号关联到Cpp的槽函数
        root.onQmlSignalA.connect(cpp_obj.cppSlotA)
        root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    }
    
}    

我们定义了一些信号和槽函数,都可以在 QML 中直接使用。 另外,Cpp 对象的信号关联到了 Qml 的槽函数,同时 Qml 对象的信号关联到了 Cpp 的槽函数。


b、 使用 Q_INVOKABLE 修饰函数

在定义一个类的成员函数时使用 Q_INVOKABLE 宏来修饰,就可以让该方法被元对象系统调用。这个宏必须放在返回类型前面。

我给 CppObject 添加了两个使用 Q_INVOKABLE 宏修饰的方法,现在 CppObject 类的声明变成了这个样子:

#ifndef CPPOBJECT_H
#define CPPOBJECT_H

#include <QObject>

// 需要派生自QObject
// 使用qmlRegisterType注册到QML中
class CppObject : public QObject
{
    Q_OBJECT

public:
    explicit CppObject(QObject *parent = nullptr);

    // 函数:通过Q_INVOKABLE宏标记的public函数可以在QML中访问
    Q_INVOKABLE void testFun(); // 功能为打印信息

// ...

private:
    // 类的属性
    QString myName;
    int myYear;
};

#endif // CPPOBJECT_H

一旦你使用 Q_INVOKABLE 将某个方法注册到元对象系统中,在 QML 中就可以用${Object}.${method} 来访问了:

// 调用Q_INVOKABLE宏标记的函数
cpp_obj.testFun()


c、Q_PROPERTY

Q_PROPERTY 宏用来定义可通过元对象系统访问的属性,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。

下面是 Q_PROPERTY 宏的原型:

Q_PROPERTY(type name
           (READ getFunction [WRITE setFunction] |
            MEMBER memberName [(READ getFunction | WRITE setFunction)])
           [RESET resetFunction]
           [NOTIFY notifySignal]
           [REVISION int]
           [DESIGNABLE bool]
           [SCRIPTABLE bool]
           [STORED bool]
           [USER bool]
           [CONSTANT]
           [FINAL])

是不是很复杂?你可以为一个属性命名,可以设定的选项数超过10个……我是觉得有点儿头疼。不过,不是所有的选项都必须设定,看一个最简短的属性声明:

Q_PROPERTY(int x READ getX)

上面的声明定义了一个类型为 int 名为 x 的属性,通过方法 getX() 来访问。

选项说明

其实我们在实际使用中,很少能够用全 Q_PROPERTY 的所有选项,这里介绍些常用的选项:

  • type 是属性的类型,可以是 int、QString、QObject、QColor 等等,name 就是属性的名字。

  • READ 标记,如果你没有为属性指定 MEMBER 标记,则 READ 标记必不可少。声明一个读取属性的函数,该函数一般没有参数,返回定义的属性。

  • WRITE 标记,可选配置。声明一个设定属性的函数。它指定的函数,只能有一个与属性类型匹配的参数,必须返回 void 。

  • NOTIFY 标记,可选配置。给属性关联一个信号(该信号必须是已经在类中声明过的),当属性的值发生变化时就会触发该信号。信号的参数,一般就是你定义的属性。

代码

#ifndef CPPOBJECT_H
#define CPPOBJECT_H

#include <QObject>

// 需要派生自QObject
// 使用qmlRegisterType注册到QML中
class CppObject : public QObject
{
    Q_OBJECT

    // 属性:使用Q_PROPERTY注册属性,使之可以在QML中访问
    Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged)

public:
    explicit CppObject(QObject *parent = nullptr);

    // 给类属性添加访问方法--myName
    void setName(const QString &name);
    QString getName() const;
    // 给类属性添加访问方法--myYear
    void setYear(int year);
    int getYear() const;

// ...

private:
    // 类的属性
    QString myName;
    int myYear;
};

#endif // CPPOBJECT_H


d、C++ 类的完整代码

在头文件中,定义了信号和 public 槽函数,以及 Q_INVOKABLE 宏标记的 public 函数,还通过 Q_PROPERTY 注册了两个属性,这些方法和属性之后都可以在 QML 中进行访问。CppObject.h 的内容如下:

#ifndef CPPOBJECT_H
#define CPPOBJECT_H

#include <QObject>

// 需要派生自QObject
// 使用qmlRegisterType注册到QML中
class CppObject : public QObject
{
    Q_OBJECT

    // 属性:使用Q_PROPERTY注册属性,使之可以在QML中访问
    Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged)

public:
    explicit CppObject(QObject *parent = nullptr);

    // 给类属性添加访问方法--myName
    void setName(const QString &name);
    QString getName() const;
    // 给类属性添加访问方法--myYear
    void setYear(int year);
    int getYear() const;

    // 函数:通过Q_INVOKABLE宏标记的public函数可以在QML中访问
    Q_INVOKABLE void testFun(); // 功能为打印信息

signals:
    // 信号:可以直接在QML中访问信号
    void cppSignalA();//一个无参信号
    void cppSignalB(const QString &str,int value); // 一个带参数信号
    void nameChanged(const QString name);
    void yearChanged(int year);

public slots:
    // 槽函数:可以直接在QML中访问public槽函数
    void cppSlotA();//一个无参槽函数
    void cppSlotB(const QString &str,int value); // 一个带参数槽函数

private:
    // 类的属性
    QString myName;
    int myYear;
};

#endif // CPPOBJECT_H

为了测试方便,我给每个函数都加了一个打印语句,稍后会在 QML 中调用这些函数。CppObject.cpp 的内容如下:

#include "CppObject.h"

#include <QDebug>

CppObject::CppObject(QObject *parent)
    : QObject(parent),
      myName("none"),
      myYear(0)
{

}

void CppObject::setName(const QString &name)
{
    qDebug() << "CppObject::setName"<<name;
    if(myName != name){
        qDebug() << "emit nameChanged";
        myName = name;
        emit nameChanged(name);
    }
}

QString CppObject::getName() const
{
    qDebug() << "CppObject::getName";
    return myName;
}

void CppObject::setYear(int year)
{
    qDebug() << "CppObject::setYear" << year;
    if(year != myYear){
        qDebug() << "emit yearChanged";
        myYear = year;
        emit yearChanged(myYear);
    }
}

int CppObject::getYear() const
{
    qDebug() << "CppObject::getYear";
    return myYear;
}

void CppObject::testFun()
{
    // 测试用,调用该函数后打印信息
    qDebug() << "CppObject::testFun";
}

void CppObject::cppSlotA()
{
    qDebug() << "CppObject::cppSlotA";
}

void CppObject::cppSlotB(const QString &str, int value)
{
    qDebug() << "CppObject::cppSlotB" << str << value;
}

2、注册一个 QML 中可用的类型

CppObject 已经就绪了,现在看看怎样将其注册为 QML 可以使用的类型。有以下两种方式:

1、C++定义方式(主要使用setContextProperty()函数)

  • a)、比如我们有一个功能单一的 Configure 类,我们需要把它暴露给 QML,在使用之前必须要先创建类对象 m_configuration,就是说类实例化一次,QML 中可以直接使用这个类,注意功能单一的类只适合该方式;
  • b)、比如我们的业务比较复杂,我们有很多类,若要供 QML 调用,我们就要写一个总的被调用类 Complex(包含所有的业务类),然后实例化一次这个 Complex,然后 QML 中直接使用实例化后的对象;

两种业务方式的使用方式如下:

// C++方式:也可以注册为qml全局对象
engine.rootContext()->setContextProperty("cppObj", new CppObject(qApp));

cppObj 便可直接在 qml 中使用,cppObj 自然也是一个全局变量。

2、QML定义方式(主要使用qmlRegisterType()函数)

该方式都是使用在业务复杂情况下,还是上面的例子,我们有一堆业务类,这个时候我们使用注册的方式,用在 QML 中定义的方式去定义各个实例,也就不用再需要一个总类:

// QML方式:qmlRegisterType注册C++类型至QML
// 参数:qmlRegisterType<C++类型名> (import时模块名 主版本号 次版本号 QML中的类型名)
qmlRegisterType<CppObject>("MyCppObject", 1, 0, "CppObject");

我们可以在 QML 中直接使用 CppObject 去定义实例:

// 引入我们注册的模块
import MyCppObject 1.0

// 作为一个QML对象
CppObject{
    id: cpp_obj
}

qmlRegisterSingletonType()用来注册一个单例类型,qmlRegisterType()注册一个非单例的类型,这里只说常规的类型注册,其它请参考 Qt 帮助文档。

3、二者比较

与 C++ 方式相比,QML 方式具有如下优势:

  • 变量名前面可以加 $(全局变量可用),从而方便区分全局变量和局部变量,这个在 C++ 定义属性的时候是不允许的;
  • 如果某个全局变量(一般是 QML 对象)构造很慢,可以通过 QML 中的 Loader 来很方便异步构造,从而加速程序启动。

下面是 CppObject 示例的 main.cpp 文件:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "CppObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    // QML方式:qmlRegisterType注册C++类型至QML
    // 参数:qmlRegisterType<C++类型名> (import时模块名 主版本号 次版本号 QML中的类型名)
    qmlRegisterType<CppObject>("MyCppObject", 1, 0, "CppObject");

    QQmlApplicationEngine engine;

    // C++方式:也可以注册为qml全局对象
    //engine.rootContext()->setContextProperty("cppObj", new CppObject(qApp));

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

上面的代码将 CppObject 类注册为 QML 类 cppObj,主版本为 1 ,次版本为 0 ,而我起的类名称则是 CppObject。注册动作一定要放在 QML 上下文创建之前,否则的话,会无效。

3、在 QML 中导入 C++ 注册的类型

一旦你在 C++ 中注册好了 QML 类型,就可以在 QML 文档中引入你注册的类,然后使用注册的类型。要引入类,使用 import 语句。比如要使用我们注册的 ColorMaker 类,可以在 QML 文档中加入下面的 import 语句:

import MyCppObject 1.0

4、在 QML 中创建 C++ 导入类型的实例

引入 C++ 类后,你就可以在 QML 中创建 C++ 导入类型的对象了,与 QML 内建类型的使用完全一样。这里看下完整的 main.qml 文档:

import QtQuick 2.9
import QtQuick.Window 2.9
// 引入我们注册的模块
import MyCppObject 1.0

Window {
    id: root
    visible: true
    width: 500
    height: 300
    title: qsTr("QML调用Cpp对象")
    color: "green"

    signal qmlSignalA
    signal qmlSignalB(string str, int value)

    //定义的函数可以作为槽函数
    function processB(str, value){
        console.log('qml function processB', str, value)
    }

    // 鼠标点击区域
    MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton

        onClicked: {
            if(mouse.button === Qt.LeftButton){
                console.log('----qml 点击左键:Cpp发射信号')
                // 1.修改属性会触发set函数,获取值会触发get函数
                cpp_obj.name = "gongjianbo"
                cpp_obj.year = 1992
                // 2.调用Q_INVOKABLE宏标记的函数
                cpp_obj.testFun()
                // 3.发射C++信号
                cpp_obj.cppSignalA()
                cpp_obj.cppSignalB("chenglong", 1995)
            }
            else{
                console.log('----qml 点击右键:QML发射信号')
                root.qmlSignalA()
                root.qmlSignalB('gongjianbo', 1992)
            }
        }
    }

    // 作为一个QML对象
    CppObject{
        id: cpp_obj
        //也可以像原生QML对象一样操作,增加属性之类的
        property int counts: 0

        onYearChanged: {
            counts++
            console.log('qml onYearChanged', counts)
        }
        onCountsChanged: {
            console.log('qml onCountsChanged', counts)
        }
    }

    // 关联信号与信号处理函数的方式同QML中的类型
    Component.onCompleted: {
        // 1. Cpp对象的信号关联到Qml的槽函数
        // cpp_obj.onCppSignalA.connect(function() {console.log('qml signalA process')})
        cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) // js的lambda
        cpp_obj.onCppSignalB.connect(processB)
        // 2. Qml对象的信号关联到Cpp的槽函数
        root.onQmlSignalA.connect(cpp_obj.cppSlotA)
        root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    }
}

这个示例很简单,点击鼠标左键调用 CppObject 的 testFun 函数来发送信号,QML 处理;点击鼠标右键 QML 发送信号,CppObject 处理,效果图如下:

三、在 C++ 中构造一个对象,将这个对象设置为 QML 的上下文属性,在 QML 环境中直接使用该属性。

1、注册属性

要将一个 C++ 类注册为属性很简单,CppObject 的 main.cpp 修改后如下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "CppObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    // QML方式:qmlRegisterType注册C++类型至QML
    // 参数:qmlRegisterType<C++类型名> (import时模块名 主版本号 次版本号 QML中的类型名)
    //qmlRegisterType<CppObject>("MyCppObject", 1, 0, "CppObject");

    QQmlApplicationEngine engine;

    // 【修改1】C++方式:也可以注册为qml全局对象
    engine.rootContext()->setContextProperty("cpp_obj", new CppObject(qApp));

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

注意我在定义 engine 变量后增加的新语句:

engine.rootContext()->setContextProperty("cpp_obj", new CppObject(qApp));

正是这行代码从堆上分配了一个 CppObject 对象,然后注册为 QML 上下文的属性,起了个名字就叫 cpp_obj,为了方便,跟上一篇 QML 文件中定义的对象名保持一致了。

engine.rootContext()返回的是 QQmlContext 对象。 QQmlContext 类代表一个 QML 上下文,它的setContextProperty()方法可以为该上下文设置一个全局可见的属性。要注意的是,你 new 出来的对象, QQmlContext 只是使用,不会帮你删除,你需要自己找一个合适的时机来删除它。

2、在 QML 中使用关联到 C++ 对象的属性

一旦调用 setContextProperty() 导出了属性,就可以在 QML 中使用了,不需要 import 语句哦。下面是 main.qml 修改后的代码:

import QtQuick 2.9
import QtQuick.Window 2.9
// 【修改2】引入我们注册的模块
//import MyCppObject 1.0

Window {
    id: root
    visible: true
    width: 500
    height: 300
    title: qsTr("QML调用Cpp对象")
    color: "green"

    signal qmlSignalA
    signal qmlSignalB(string str, int value)

    //定义的函数可以作为槽函数
    function processB(str, value){
        console.log('qml function processB', str, value)
    }

    // 鼠标点击区域
    MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton

        onClicked: {
            if(mouse.button === Qt.LeftButton){
                console.log('----qml 点击左键:Cpp发射信号')
                // 1.修改属性会触发set函数,获取值会触发get函数
                cpp_obj.name = "gongjianbo"
                cpp_obj.year = 1992
                // 2.调用Q_INVOKABLE宏标记的函数
                cpp_obj.testFun()
                // 3.发射C++信号
                cpp_obj.cppSignalA()
                cpp_obj.cppSignalB("chenglong", 1995)
            }
            else{
                console.log('----qml 点击右键:QML发射信号')
                root.qmlSignalA()
                root.qmlSignalB('gongjianbo', 1992)
            }
        }
    }

    // 【修改3】作为一个QML对象
//    CppObject{
//        id: cpp_obj
//        //也可以像原生QML对象一样操作,增加属性之类的
//        property int counts: 0

//        onYearChanged: {
//            counts++
//            console.log('qml onYearChanged', counts)
//        }
//        onCountsChanged: {
//            console.log('qml onCountsChanged', counts)
//        }
//    }

    // 关联信号与信号处理函数的方式同QML中的类型
    Component.onCompleted: {
        // 1. Cpp对象的信号关联到Qml的槽函数
        // cpp_obj.onCppSignalA.connect(function() {console.log('qml signalA process')})
        cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) // js的lambda
        cpp_obj.onCppSignalB.connect(processB)
        // 2. Qml对象的信号关联到Cpp的槽函数
        root.onQmlSignalA.connect(cpp_obj.cppSlotA)
        root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    }
}

main.qml 代码也就修改了两处,我已经使用方括号标注出来了。效果和功能与上篇一致,这里就不贴效果图和下载链接了,需要可以去上篇查看和下载。

可以看出,导出的属性可以直接使用,与属性关联的对象,它的信号、槽、可调用方法(使用 Q_INVOKABLE 宏修饰的方法)都可以直接使用,但不能通过类名来引用枚举值了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
QMLC++ 交互的过程一般分为以下几个步骤: 1. 定义 C++ 类并注册到 QML 中 你需要在 C++ 中定义一个类,该类封装了你想要实现的功能。同时,你需要使用 `qmlRegisterType` 函数将该类注册到 QML 中,以便在 QML 中使用该类。 例如,你可以定义一个名为 `MyClass` 的类,并将其注册到 QML 中: ```cpp class MyClass : public QObject { Q_OBJECT public: Q_INVOKABLE int myFunction(int arg) { // 实现你的功能 } }; qmlRegisterType<MyClass>("com.example", 1, 0, "MyClass"); ``` 在上面的代码中,`Q_INVOKABLE` 用于声明 `myFunction` 函数可从 QML 中调用,`qmlRegisterType` 函数用于将 `MyClass` 类注册到 QML 中。`"com.example"` 表示注册的命名空间,`1` 和 `0` 表示主版本号和次版本号,`"MyClass"` 是在 QML 中使用的类名。 2. 在 QML 中使用 C++ 类 在 QML 中使用 C++ 类时,你需要使用 `import` 语句导入该类所在的命名空间。然后,你可以通过该命名空间来访问该类。例如: ```qml import com.example 1.0 MyClass { id: myClass } Button { onClicked: { var result = myClass.myFunction(42) // 处理返回值 } } ``` 在上面的代码中,`import` 语句用于导入 `com.example` 命名空间,`MyClass` 用于创建一个 `MyClass` 实例,`id` 属性用于设置实例的标识符,`Button` 用于创建一个按钮,`onClicked` 事件处理程序中调用了 `myFunction` 函数,并处理了它的返回值。 3. 在 C++ 中访问 QML 中的对象 如果你需要从 C++ 中访问 QML 中的对象,你可以使用 `QQuickItem` 类提供的 `findChild` 函数。例如: ```cpp QQuickItem *item = qmlEngine.rootObjects().value(0)->findChild<QQuickItem*>("myItem"); if (item) { // 处理 item 对象 } ``` 在上面的代码中,`qmlEngine.rootObjects()` 函数返回 QML 引擎中所有的根对象,`value(0)` 返回第一个根对象,`findChild` 函数用于查找名为 `"myItem"` 的子对象。 以上就是 QMLC++ 交互的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高亚奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值