• QML与C++的交互操作


      QML旨在通过C ++代码轻松扩展。Qt QML模块中的类使QML对象能够从C ++加载和操作,QML引擎与Qt元对象系统集成的本质使得C ++功能可以直接从QML调用。这允许开发混合应用程序,这些应用程序是通过混合使用QML,JavaScript和C ++代码实现的。除了从QML访问C ++功能的能力之外,Qt QML模块还提供了从C ++代码执行反向和操作QML对象的方法。下面会通过示例来讲解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文件。

    1. #ifndef CPPOBJECT_H
    2. #define CPPOBJECT_H
    3. #include
    4. //派生自QObject
    5. //使用qmlRegisterType注册到QML中
    6. class CppObject : public QObject
    7. {
    8. Q_OBJECT
    9. //注册属性,使之可以在QML中访问--具体语法百度Q_PROPERTY
    10. Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    11. Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged)
    12. public:
    13. explicit CppObject(QObject *parent = nullptr);
    14. //通过Q_INVOKABLE宏标记的public函数可以在QML中访问
    15. Q_INVOKABLE void sendSignal();//功能为发送信号
    16. //给类属性添加访问方法--myName
    17. void setName(const QString &name);
    18. QString getName() const;
    19. //给类属性添加访问方法--myYear
    20. void setYear(int year);
    21. int getYear() const;
    22. signals:
    23. //信号可以在QML中访问
    24. void cppSignalA();//一个无参信号
    25. void cppSignalB(const QString &str,int value);//一个带参数信号
    26. void nameChanged(const QString name);
    27. void yearChanged(int year);
    28. public slots:
    29. //public槽函数可以在QML中访问
    30. void cppSlotA();//一个无参槽函数
    31. void cppSlotB(const QString &str,int value);//一个带参数槽函数
    32. private:
    33. //类的属性
    34. QString myName;
    35. int myYear;
    36. };
    37. #endif // CPPOBJECT_H

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

    1. #include "CppObject.h"
    2. #include <QDebug>
    3. CppObject::CppObject(QObject *parent)
    4. : QObject(parent),
    5. myName("none"),
    6. myYear(0)
    7. {
    8. }
    9. void CppObject::sendSignal()
    10. {
    11. //测试用,调用该函数后发送信号
    12. qDebug()<<"CppObject::sendSignal";
    13. emit cppSignalA();
    14. emit cppSignalB(myName,myYear);
    15. }
    16. void CppObject::setName(const QString &name)
    17. {
    18. qDebug()<<"CppObject::setName"<<name;
    19. if(myName!=name){
    20. qDebug()<<"emit nameChanged";
    21. myName=name;
    22. emit nameChanged(name);
    23. }
    24. }
    25. QString CppObject::getName() const
    26. {
    27. qDebug()<<"CppObject::getName";
    28. return myName;
    29. }
    30. void CppObject::setYear(int year)
    31. {
    32. qDebug()<<"CppObject::setYear"<<year;
    33. if(year!=myYear){
    34. qDebug()<<"emit yearChanged";
    35. myYear=year;
    36. emit yearChanged(myYear);
    37. }
    38. }
    39. int CppObject::getYear() const
    40. {
    41. qDebug()<<"CppObject::getYear";
    42. return myYear;
    43. }
    44. void CppObject::cppSlotA()
    45. {
    46. qDebug()<<"CppObject::cppSlotA";
    47. }
    48. void CppObject::cppSlotB(const QString &str, int value)
    49. {
    50. qDebug()<<"CppObject::cppSlotB"<<str<<value;
    51. }

    为了测试方便,给每个函数都加了一个打印语句,当调用sendSignal函数时将会emit两个信号,稍后会在QML中调用该函数。

    1. #include <QGuiApplication>
    2. #include <QQmlApplicationEngine>
    3. #include <QQmlContext>
    4. #include "CppObject.h"
    5. int main(int argc, char *argv[])
    6. {
    7. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    8. QGuiApplication app(argc, argv);
    9. //qmlRegisterType注册C++类型至QML
    10. //arg1:import时模块名
    11. //arg2:主版本号
    12. //arg3:次版本号
    13. //arg4:QML类型名
    14. qmlRegisterType<CppObject>("MyCppObject",1,0,"CppObject");
    15. QQmlApplicationEngine engine;
    16. //也可以注册为qml全局对象
    17. //engine.rootContext()->setContextProperty("cppObj",new CppObject(qApp));
    18. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    19. if (engine.rootObjects().isEmpty())
    20. return -1;
    21. return app.exec();
    22. }

    通过使用qmlRegisterType,将刚才定义的QObject派生类注册到QML中。

    1. import QtQuick 2.9
    2. import QtQuick.Window 2.9
    3. //引入我们注册的模块
    4. import MyCppObject 1.0
    5. Window {
    6. id: root
    7. visible: true
    8. width: 500
    9. height: 300
    10. title: qsTr("QML调用Cpp对象:by 龚建波1992")
    11. color:"green"
    12. signal qmlSignalA
    13. signal qmlSignalB(string str,int value)
    14. //鼠标点击区域
    15. MouseArea{
    16. anchors.fill: parent
    17. acceptedButtons: Qt.LeftButton | Qt.RightButton
    18. //测试时点击左键或右键
    19. onClicked: {
    20. if(mouse.button===Qt.LeftButton){
    21. console.log('----qml 点击左键:Cpp发射信号')
    22. cpp_obj.name="gongjianbo" //修改属性会触发set函数,获取值会触发get函数
    23. cpp_obj.year=1992
    24. cpp_obj.sendSignal() //调用Q_INVOKABLE宏标记的函数
    25. }else{
    26. console.log('----qml 点击右键:QML发射信号')
    27. root.qmlSignalA()
    28. root.qmlSignalB('gongjianbo',1992)
    29. }
    30. }
    31. }
    32. //作为一个QML对象
    33. CppObject{
    34. id:cpp_obj
    35. //也可以像原生QML对象一样操作,增加属性之类的
    36. property int counts: 0
    37. onYearChanged: {
    38. counts++
    39. console.log('qml onYearChanged',counts)
    40. }
    41. onCountsChanged: {
    42. console.log('qml onCountsChanged',counts)
    43. }
    44. }
    45. //组件加载完成执行
    46. Component.onCompleted: {
    47. //关联信号与信号处理函数的方式同QML中的类型
    48. //Cpp对象的信号关联到Qml
    49. //cpp_obj.onCppSignalA.connect(function(){console.log('qml signalA process')})
    50. cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) //js的lambda
    51. cpp_obj.onCppSignalB.connect(processB)
    52. //Qml对象的信号关联到Cpp
    53. root.onQmlSignalA.connect(cpp_obj.cppSlotA)
    54. root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    55. }
    56. //定义的函数可以作为槽函数
    57. function processB(str,value){
    58. console.log('qml function processB',str,value)
    59. }
    60. }

    注册之后就能直接在QML中使用刚才定义的C++类型了,并且可以像QML定义的类型一样进行操作,如信号槽关联、属性绑定等。

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

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

    第二个例子:C++中加载QML对象

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

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

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

    1. import QtQuick 2.9
    2. Item{
    3. id: root
    4. width: 250
    5. height: 250
    6. //自定义属性 --cpp可以访问
    7. property string msg: "GongJianBo1992"
    8. //自定义信号 --可以触发cpp槽函数
    9. signal qmlSendMsg(string arg1,string arg2)
    10. Rectangle {
    11. anchors.fill: parent
    12. color: "green"
    13. objectName: "rect" //用于cpp查找对象
    14. }
    15. MouseArea {
    16. anchors.fill: parent
    17. onClicked: {
    18. console.log("qml 点击鼠标, 发送信号 qmlSendMsg")
    19. root.qmlSendMsg(root.msg,"myarg2")
    20. }
    21. }
    22. onHeightChanged: console.log("qml height changed")
    23. onWidthChanged: console.log("qml width changed")
    24. //QML中的方法可以被cpp调用,也可以作为槽函数
    25. function qml_method(val_arg){
    26. console.log("qml method runing",val_arg,"return ok")
    27. return "ok"
    28. }
    29. //注意槽函数参数为var类型
    30. function qmlRecvMsg(arg1,arg2){
    31. console.log("qml slot runing",arg1,arg2)
    32. }
    33. }

    在QML中定义了一些属性和方法等,用于测试。

     

    1. #ifndef CPPOBJECT_H
    2. #define CPPOBJECT_H
    3. #include <QObject>
    4. #include <QDebug>
    5. class CppObject : public QObject
    6. {
    7. Q_OBJECT
    8. public:
    9. explicit CppObject(QObject *parent = Q_NULLPTR)
    10. :QObject(parent){}
    11. signals:
    12. //信号 --用来触发qml的函数
    13. //注意参数为var类型,对应qml中js函数的参数类型
    14. void cppSendMsg(const QVariant &arg1,const QVariant &arg2);
    15. public slots:
    16. //槽函数 --用来接收qml的信号
    17. void cppRecvMsg(const QString &arg1,const QString &arg2){
    18. qDebug()<<"CppObject::cppRecvMsg"<<arg1<<arg2;
    19. qDebug()<<"emit cppSendMsg";
    20. emit cppSendMsg(arg1,arg2);
    21. }
    22. };
    23. #endif // CPPOBJECT_H

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

    1. #include <QGuiApplication>
    2. #include <QQmlProperty>
    3. #include <QQuickView>
    4. #include <QQuickItem>
    5. #include <QMetaObject>
    6. #include <QDebug>
    7. #include "CppObject.h"
    8. int main(int argc, char *argv[])
    9. {
    10. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    11. QGuiApplication app(argc, argv);
    12. //可以用QQmlComponent\QQuickView\QQuickWidget的C++代码加载QML文档
    13. //QQuickView不能用Window做根元素
    14. QQuickView view(QUrl("qrc:/main.qml"));
    15. view.show();
    16. //获取到qml根对象的指针
    17. QObject *qmlObj=view.rootObject();
    18. /*文档如是说:
    19. 应该始终使用QObject::setProperty()、QQmlProperty
    20. 或QMetaProperty::write()来改变QML的属性值,以确保QML引擎感知属性的变化。*/
    21. //1
    22. //通过QObject设置属性值
    23. qDebug()<<"Cpp set qml property height";
    24. //qmlObj->setProperty("height",300);
    25. QQmlProperty(qmlObj,"height").write(300);
    26. //通过QObject获取属性值
    27. qDebug()<<"Cpp get qml property height"<<qmlObj->property("height");
    28. //任何属性都可以通过C++访问
    29. qDebug()<<"Cpp get qml property msg"<<qmlObj->property("msg");
    30. //2
    31. QQuickItem *item=qobject_cast<QQuickItem*>(qmlObj);
    32. //通过QQuickItem设置属性值
    33. qDebug()<<"Cpp set qml property width";
    34. item->setWidth(300);
    35. //通过QQuickItem获取属性值
    36. qDebug()<<"Cpp get qml property width"<<item->width();
    37. //3
    38. //通过objectName访问加载的QML对象
    39. //QObject::findChildren()可用于查找具有匹配objectName属性的子项
    40. QObject *qmlRect=qmlObj->findChild<QObject*>("rect");
    41. if(qmlRect){
    42. qDebug()<<"Cpp get rect color"<<qmlRect->property("color");
    43. }
    44. //4
    45. //调用QML方法
    46. QVariant val_return; //返回值
    47. QVariant val_arg="GongJianBo"; //参数值
    48. //Q_RETURN_ARG()和Q_Arg()参数必须制定为QVariant类型
    49. QMetaObject::invokeMethod(qmlObj,
    50. "qml_method",
    51. Q_RETURN_ARG(QVariant,val_return),
    52. Q_ARG(QVariant,val_arg));
    53. qDebug()<<"QMetaObject::invokeMethod result"<<val_return; //qml函数中返回“ok”
    54. //5
    55. //关联信号槽
    56. CppObject cppObj;
    57. //关联qml信号与cpp槽
    58. //如果信号参数为QML对象类型,信号用var参数类型,槽用QVariant类型接收
    59. QObject::connect(qmlObj,SIGNAL(qmlSendMsg(QString,QString)),
    60. &cppObj,SLOT(cppRecvMsg(QString,QString)));
    61. //关联cpp信号与qml槽
    62. //qml中js函数参数为var类型,信号也用QVariant类型
    63. QObject::connect(&cppObj,SIGNAL(cppSendMsg(QVariant,QVariant)),
    64. qmlObj,SLOT(qmlRecvMsg(QVariant,QVariant)));
    65. //此外,cpp信号也可以关联qml信号
    66. return app.exec();
    67. }

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

  • 相关阅读:
    华为hcie报考条件
    Django Swagger文档库drf-spectacular
    AI 生成的唯美头像也太好看了吧!附好说 AI 一秒出图技巧
    2023 年高教社杯全国大学生数学建模竞赛获奖名单(初稿)公示
    集群加载支持 kerberos 认证_kerberos 版本要求及 集群参数配置
    【微信自动化】使用c#实现微信自动化
    Vue前端项目运行
    Spring注解 servlet3.0
    shell脚本的国际化——gettext与pot、po、mo
    uniapp父组件使用prop将异步的数据传给子组件
  • 原文地址:https://blog.csdn.net/hulinhulin/article/details/132642622