zoukankan      html  css  js  c++  java
  • QML与C++交互

    第一个例子:QML中创建C++对象
    文档如是说,使用C ++代码中定义的功能可以轻松扩展QML。由于QML引擎与Qt元对象系统的紧密集成,可以从QML代码访问由QObject派生的类适当公开的任何功能。这使得C ++类的属性和方法可以直接从QML访问,通常很少或无需修改。

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

    属性(使用Q_PROPERTY注册的属性)
    方法(需注册为public slots或是标记为Q_INVOKABLE)
    信号
    (此外,如果已使用Q_ENUMS声明枚举,则可以使用枚举。)

    通常,无论是否已向QML类型系统注册了QObject派生类,都可以从QML访问它们。但是,如果QML引擎要访问其他类型信息(例如,如果要将类本身用作方法参数或属性,或者要将其中一个枚举类型用于以这种方式使用),那么该类可能需要注册。

    代码示例有四个文件,QtQuick Empty工程的两个加自定义的Cpp类h和cpp文件,因为我把几种常用的方法都写出来了,所以看起来有点乱(完整代码链接见文末)。

    #ifndef CPPOBJECT_H
    #define CPPOBJECT_H

    #include <QObject>

    //派生自QObject
    //使用qmlRegisterType注册到QML中
    class CppObject : public QObject
    {
    Q_OBJECT
    //注册属性,使之可以在QML中访问--具体语法百度Q_PROPERTY
    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);
    //通过Q_INVOKABLE宏标记的public函数可以在QML中访问
    Q_INVOKABLE void sendSignal();//功能为发送信号

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

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

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

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

    #endif // CPPOBJECT_H
    在头文件中,我定义了信号和public槽函数,以及Q_INVOKABLE宏标记的public函数,还通过Q_PROPERTY注册了两个属性,这些方法和属性之后都可以在QML中进行访问。

    #include "CppObject.h"

    #include <QDebug>

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

    }

    void CppObject::sendSignal()
    {
    //测试用,调用该函数后发送信号
    qDebug()<<"CppObject::sendSignal";
    emit cppSignalA();
    emit cppSignalB(myName,myYear);
    }

    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::cppSlotA()
    {
    qDebug()<<"CppObject::cppSlotA";
    }

    void CppObject::cppSlotB(const QString &str, int value)
    {
    qDebug()<<"CppObject::cppSlotB"<<str<<value;
    }
    为了测试方便,我给每个函数都加了一个打印语句,当调用sendSignal函数时将会emit两个信号,稍后会在QML中调用该函数。

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

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

    QGuiApplication app(argc, argv);

    //qmlRegisterType注册C++类型至QML
    //arg1:import时模块名
    //arg2:主版本号
    //arg3:次版本号
    //arg4:QML类型名
    qmlRegisterType<CppObject>("MyCppObject",1,0,"CppObject");

    QQmlApplicationEngine engine;

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

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

    return app.exec();
    }
    通过使用qmlRegisterType,将刚才定义的QObject派生类注册到QML中(Qt5.15增加了新的注册方式)。

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

    Window {
    id: root
    visible: true
    500
    height: 300
    title: qsTr("QML调用Cpp对象:by 龚建波1992")
    color:"green"

    signal qmlSignalA
    signal qmlSignalB(string str,int value)

    //鼠标点击区域
    MouseArea{
    anchors.fill: parent
    acceptedButtons: Qt.LeftButton | Qt.RightButton
    //测试时点击左键或右键
    onClicked: {
    if(mouse.button===Qt.LeftButton){
    console.log('----qml 点击左键:Cpp发射信号')
    cpp_obj.name="gongjianbo" //修改属性会触发set函数,获取值会触发get函数
    cpp_obj.year=1992
    cpp_obj.sendSignal() //调用Q_INVOKABLE宏标记的函数
    }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)
    }
    }

    //组件加载完成执行
    Component.onCompleted: {
    //关联信号与信号处理函数的方式同QML中的类型
    //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)
    //Qml对象的信号关联到Cpp
    root.onQmlSignalA.connect(cpp_obj.cppSlotA)
    root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    }

    //定义的函数可以作为槽函数
    function processB(str,value){
    console.log('qml function processB',str,value)
    }
    }
    注册之后就能直接在QML中使用刚才定义的C++类型了,并且可以像QML定义的类型一样进行操作,如信号槽关联、属性绑定等。

    这个示例很简单,点击鼠标左键调用CppObj的sendSignal函数来发送信号,QML处理;点击鼠标右键QML发送信号,CppObj处理,下面是操作结果:

    可以看到QML成功的访问了CppObj的属性和方法,并能进行信号槽的关联。

    第二个例子: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对象集成到应用程序的用户界面中。参见文档Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html

    下面通过代码来演示(完整代码链接见文末)。

    import QtQuick 2.9

    Item{
    id: root
    250
    height: 250
    //自定义属性 --cpp可以访问
    property string msg: "GongJianBo1992"
    //自定义信号 --可以触发cpp槽函数
    signal qmlSendMsg(string arg1,string 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中的方法可以被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)
    }
    }
    在QML中我定义了一些属性和方法等,用于测试。

    #ifndef CPPOBJECT_H
    #define CPPOBJECT_H

    #include <QObject>
    #include <QDebug>

    class CppObject : public QObject
    {
    Q_OBJECT
    public:
    explicit CppObject(QObject *parent = Q_NULLPTR)
    :QObject(parent){}

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

    public slots:
    //槽函数 --用来接收qml的信号
    void cppRecvMsg(const QString &arg1,const QString &arg2){
    qDebug()<<"CppObject::cppRecvMsg"<<arg1<<arg2;
    qDebug()<<"emit cppSendMsg";
    emit cppSendMsg(arg1,arg2);
    }
    };

    #endif // CPPOBJECT_H
    Cpp中定义了一个槽函数,用来接收QML对象的信号。 

    #include <QGuiApplication>
    #include <QQmlProperty>
    #include <QQuickView>
    #include <QQuickItem>
    #include <QMetaObject>
    #include <QDebug>

    #include "CppObject.h"

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

    QGuiApplication app(argc, argv);

    //可以用QQmlComponentQQuickViewQQuickWidget的C++代码加载QML文档
    //QQuickView不能用Window做根元素
    QQuickView view(QUrl("qrc:/main.qml"));
    view.show();

    //获取到qml根对象的指针
    QObject *qmlObj=view.rootObject();

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

    //【1】
    //通过QObject设置属性值
    qDebug()<<"Cpp set qml property height";
    //qmlObj->setProperty("height",300);
    QQmlProperty(qmlObj,"height").write(300);
    //通过QObject获取属性值
    qDebug()<<"Cpp get qml property height"<<qmlObj->property("height");
    //任何属性都可以通过C++访问
    qDebug()<<"Cpp get qml property msg"<<qmlObj->property("msg");

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

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

    //【4】
    //调用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”

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

    return app.exec();
    }
    然后就把文档中的东西测试了下,操作起来很简单。不过相对于QML中使用C++对象来说,感觉作用没那么大,因为一般把QML嵌入到Widgets中才会做这些操作,但是混合两个框架很多坑。下面是我的测试输出结果:

     以上两种方式应该就是最简单的QML与C++交互应用了,对照文档或是博客敲一遍代码可以很容易地理解。

    (完结)

    代码完整链接(GitHub)如下:

    Qml中创建Cpp对象(Cpp注册给Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/QmlCallCpp2020

    Cpp中加载Qml对象(Cpp操作Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/CppCallQml2020

    代码的CSDN下载链接:https://download.csdn.net/download/gongjianbo1992/12552274

    参考
    文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-overview.html

    文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html

    文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-topic.html

    (注:文档中有很多相关链接,此处忽略)

    博客:https://blog.csdn.net/u011012932/column/info/14318

    博客:https://blog.csdn.net/baidu_33850454/article/details/81907857

    博客:https://blog.csdn.net/baidu_33850454/article/details/81914821
    ————————————————
    版权声明:本文为CSDN博主「龚建波」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/gongjianbo1992/article/details/87965925

  • 相关阅读:
    使用JNI技术实现JAVA程序调用dll
    vim 技巧集
    Powershell: 计算机硬盘扩容
    jira:3、API 调用 (jira insight)
    Jira: 1、服务器搭建(测试环境)
    Powershell :AD 域操作之OU 导出、创建、 用户导出、创建、移动OU
    jira :数据库配置要求
    powershell:定期删除指定目录的文件
    Jira:2、创建API 访问Token
    证书:常见操作记录
  • 原文地址:https://www.cnblogs.com/deadwood-2016/p/14858722.html
Copyright © 2011-2022 走看看