Qt中QML和C++混合编程

devtools/2024/11/29 6:14:41/

使用QML开发界面

加载QML的3种方式

支持文件路径资源(资源文件)路径网络路径

  • 使用QQmlApplicationEngine

这个类结合了QQmlEngine和QQmlComponent

QGuiApplication app(argc,argv);//1、使用QQmlApplicationEngine加载qml代码,他结合了QQmlEngine和QQmlComponent
//构造函数指定qml文件的路径
QQmlApplicationEngine appeng("qrc:/Main.qml");//获取根节点并设置参数
QList < QObject * > objs = appeng.rootObjects();//假设qml中的根节点是Window,那么其实例化对象的类型是QQuickWindow
//因此将其转为实际的类型QQuickWindow*
auto win = (QQuickWindow * ) objs[0];//修改其坐标和标题
win -> setX(30);
win -> setY(30);
win -> setTitle("你好世界");return app.exec();
  • 使用QQuickView 

要求:qml文件的根节点不能是Window及其派生元素,因为QQuickView 会自己创建根窗口

此外,还需要调用其show()方法,窗口才会显示出来

QGuiApplication app(argc,argv);//使用QQuickView
//这种方式的话qml代码的根类型不能是Window,他会自动创建根窗口
QQuickView view;
//设置qml文件路径
view.setSource(QUrl("qrc:/MyItem.qml"));
//需要手动调用show才会显示
view.show();return app.exec();
  • 使用QQmlComponentQQmlEngine
  1. QQmlComponent加载QQmlEngine引擎
  2. QQmlComponent设置qml文件路径
  3. 调用QQmlComponent的create()方法(返回根节点的实例化对象的指针),创建实例后才会显示窗口
  4. 以通过QQmlComponent的isError()方法判断qml文件是否出错
  5. 以及errorString()方法获取具体的错误信息
QGuiApplication app(argc,argv);//使用QQmlComponent和QQmlEngine
QQmlEngine eng;
//1、组件加载引擎
QQmlComponent com( & eng);
//2、加载qml文件
com.loadUrl(QUrl("qrc:/Main.qml"));//获取错误信息,并打印
if (com.isError()) {qDebug() << com.errorString();
}//4、然后创建这个组件才会实例化并显示
//创建后会返回根节点的指针(假设根节点是Window,实例化之后就是QQuickWindow)
//使用智能指针管理这个实例化的组件
std::shared_ptr < QQuickWindow > p((QQuickWindow * ) com.create());//设置根节点的一些属性
p -> setTitle("Main.qml");
p -> setColor(Qt::red);return app.exec();

查找子节点并读取和修改节点的属性

  1. 先获取到根节点的实例的指针
  2. qml中子节点需要设置objectName属性
  3. 调用findChild<>()方法根据objectName来查找对应的子节点,获取到子节点实例的指针
  4. 找到了则调用property()setProperty()方法来读取设置对应的属性
//加载引擎和qml文件
QQmlEngine eng;
QQmlComponent com( & eng);
com.loadUrl(QUrl("qrc:/Main.qml"));//假设qml中根节点是Window,实例化之后就是QQuickWindow
std::shared_ptr < QQuickWindow > p((QQuickWindow * ) com.create());//通过findChild方法(模板方法)访问qml里面的节点并修改属性
//需要先给qml中的节点设置objectName属性,然后根据这个objectName进行查找
//假设查找objectName为"mytxt"的子节点
auto mytxt = p -> findChild < QObject * > ("mytxt");//非空则找到了
if (mytxt != nullptr) {qDebug() << "找到了";//读取属性,返回的是QVariantauto width = mytxt -> property("width");qDebug() << "mytxt的宽度是:" << width.toInt();//修改属性mytxt -> setProperty("text", "你好世界");
}

递归遍历所有节点

可以调用自带的void QObject::dumpObjectTree() const,这个函数可以打印出所有的子节点

手写:

//参数1:某个节点的实例的指针
void printAllNode(QObject* obj,int level=0){if(obj==nullptr) return;QString head="";for(int i=0;i<level;++i){head+="-";}QString str=head;str+="className:";str+=obj->metaObject()->className();str+=" ";str+="objectName:";str+=obj->objectName();str+=" ";str+=obj->property("width").toString();str+=":";str+=obj->property("height").toString();qDebug()<<str;//获取子节点auto subs=obj->children();//递归遍历子节点for(auto itor:subs){printAllNode(itor,level+1);}
};

cpp和qml中的类型对应

  • 基础类型对应关系

如果qml中函数参数的类型是这些基础类型,那么cpp中可以传下面的类型

    QVariant                                                                   var 

  • 数组类型对应关系

如果qml中函数参数实际类型是数组,那么cpp中可以传下面的类型

  • 对象类型对应关系

如果qml中函数参数实际类型是js对象,那么cpp中可以传下面的类型

QVariantMap

cpp端直接调用qml端的函数

使用静态方法QMetaObject::invokeMethod调用qml中函数

  • 参数1:节点指针

一定要获取到qml函数所在节点的实例化对象的指针

只获取到父节点或者祖宗节点都不行,会找不到这个函数

  • 参数2:qml函数名

qml文件:

Window{id:rootwidth: 400height: 300visible: truetitle: "main.qml"//qml自定义信号signal sig1(msg:string)//无参数 无返回值function qmlFunc1(){print("call qmlFunc1")}//参数为int 或string或var   无返回值function qmlFunc2(index:int,str:string,param:var){print("call qmlFunc2;",index,"  ",str,"  ",param)}//有返回值,没有显示指定返回值类型,则返回值类型为varfunction qmlFunc3(){print("call qmlFunc3")return "a string"}//有返回值,显示指定类型为stringfunction qmlFunc4():string{print("call qmlFunc4")return "a string"}//有参数,有返回值function qmlFunc5(cnt:var):var{print("call qmlFunc5:",cnt)return "a string"}//参数是数组function qmlFunc6(arr:var){print("qmlFunc6")//遍历这个数组for(var i=0;i<arr.length;++i){print(arr[i]," ")}}//参数是js对象function qmlFunc7(obj:var){print("qmlFunc7")//遍历这个对象for(var key in obj){print("key:",key," value:",obj[key]);}}Text {id: txtobjectName: "mytxt"text: qsTr("text")font.pixelSize: 25Rectangle{anchors.fill: txtcolor: "red"z:-1}}Button{id:btnobjectName: "btn"anchors.centerIn: parentwidth: 100height: 30text: "test qml"onClicked: {root.sig1("signal1 from qml")}}}
调用qml中无参数,无返回值的函数 
QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//参数1:qml函数所在节点的实例化对象的指针
//参数2:qml函数名
QMetaObject::invokeMethod(win, "qmlFunc1");
调用qml中带参数,无返回值的函数

两端的参数类型要对应好

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//qml中的var类型对应qt里面的QVariant
//qml中的string类型对应qt里面的QString
//后面传对应的参数
QMetaObject::invokeMethod(win, "qmlFunc2",100, QString("你好"), QVariant(123));
调用qml中带返回值的函数,且没有显示指定返回值的类型

用QVariant接收返回值

然后用qReturnArg包裹

作为QMetaObject::invokeMethod的第三个参数传进去

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//调用qml函数,有返回值的函数(但是没有显示指定返回值类型),使用QVariant接收返回值
//用qReturnArg包裹下返回值
QVariant ret;
QMetaObject::invokeMethod(win, "qmlFunc3", qReturnArg(ret));
//转为实际的类型
qDebug() << ret.toString();
调用qml中带返回值的函数,且显示指定返回值的类型

如果qml中的函数显示指定了类型,那么就用Cpp端对应的类型接收

比如这里qml中的函数显示指定了返回值的函数时string类型

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//调用qml函数,有返回值的函数(显示指定了返回值类型),直接使用qt中对应的类型接收
QString ret2;
QMetaObject::invokeMethod(win, "qmlFunc4", qReturnArg(ret2));
qDebug() << ret2;
调用qml中带返回值,带参数的函数

显然就是综合前面的来调用

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//调用带参数,带返回值的函数
//参数3:用来接收返回值
//后面的参数就传qml函数所需的参数
QVariant ret3;
QMetaObject::invokeMethod(win, "qmlFunc5", qReturnArg(ret3), QVariant(4));
qDebug() << ret3.toString();
调用qml中参数实际类型是数组的函数

cpp端可以传QVariantList等等(见上面的类型对应)

但是传参的时候仍然要用QVariant包裹,

因为qml中的函数参数类型是var(只不过实际类型是数组)

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//qml中的函数参数实际类型是js数组
//cpp端则传QVariantList...等等
//调用时要用 QVariant包裹
QVariantList arr {1,2,3,4};
QMetaObject::invokeMethod(win, "qmlFunc6",QVariant(arr));//传vector<int>
std::vector < int > arr2 {1,2,3,4};
QMetaObject::invokeMethod(win, "qmlFunc6",QVariant::fromValue(arr2));
调用qml中参数实际类型是js对象的函数

cpp端只能传QVariantMap

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//qml中的函数参数实际类型是js对象
//cpp端则传QVariantMap
QVariantMap maps;
maps["name"] = "张三";
maps["age"] = 18;
QMetaObject::invokeMethod(win, "qmlFunc7",QVariant::fromValue(maps));

cpp端接收qml端的信号

  • 仍然是通过connect函数连接,且只支持qt4的写法,也不支持lambda表达式
  • 需要获取到信号所在节点的实例化对象的指针(即信号的发送者)
  • qml中信号的参数类型在连接时要转成cpp的类型

先定义cpp中接受信号的对象和槽函数

class MyClass : public QObject
{Q_OBJECT
public:explicit MyClass(QObject *parent = nullptr) : QObject{parent}{}
public slots:void cppSlot(QString msg){qDebug()<<"CppSlot:"<<msg;}void cppSlot2(){qDebug()<<"按钮点击了";}signals:
};

 连接槽函数

QQmlApplicationEngine appeng("qrc:/Main.qml");
QList < QObject * > objs = appeng.rootObjects();
auto win = (QQuickWindow * ) objs[0];//定义接收的对象
MyClass obj;//绑定接收对象的槽函数
//qml中信号所在节点的实例化对象的地址  具体的信号 接收对象的地址  接收对象的槽函数
//注意参数类型要对应,比如这里qml中的信号参数是string 这里变成QString
QObject::connect(win, SIGNAL(sig1(QString)), & obj, SLOT(cppSlot(QString)));//绑定qml自带的信号:Button元素的clicked信号
auto btn = win -> findChild < QObject * > ("btn");if (btn != nullptr) {QObject::connect(btn, SIGNAL(clicked()), & obj, SLOT(cppSlot2()));
}

qml端接收cpp端的信号

方式1:和上面一样,只不过发送者变成cpp里面的对象接收者变成qml里面的节点

方式2:cpp端自己接收信号,绑定cpp端的槽函数,然后在槽函数中通过invokeMethod来调用qml中的函数

cpp扩展qml类型

自定义一个类,继承自QObject或者QQuickItem或者QQuickPaintedItem,这样才能被qml使用

类中添加宏Q_OBJECT,使他支持信号槽

类中添加宏QML_ELEMENT,使他能够在qml文件中使用

使用Q_PROPERTY定义各种属性,给属性提供读取函数和属性改变信号,这些属性可以在qml中使用

成员方法前面加上Q_INVOKABLE,这样就可以将cpp的函数暴露给qml了,qml中可以调用

如果有定义的枚举,使用强类型枚举,且使用Q_ENUM注册

下面是一个cpp类扩展了qml的类型

#ifndef CPPTYPE_H
#define CPPTYPE_H#include <QObject>
#include <QQmlEngine>//1、继承QObject或他的派生类  只有这样才能给qml使用
class CppType : public QObject
{//2、添加Q_OBJEC支持信号槽Q_OBJECT//3、添加QML_ELEMENT使得他能够在qml中使用QML_ELEMENT//4、设置属性  提供读写函数  和  属性改变信号Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)//属性还可以绑定数据成员  此时不用提供读写函数  和  手动发射属性改变信号了(属性改变信号还是要提供)  他自己会处理Q_PROPERTY(int age MEMBER age_ NOTIFY ageChanged)
public://cpp定义的枚举enum class MyEnum{Value1,Value2};//注册到元对象中Q_ENUM(MyEnum)explicit CppType(QObject *parent = nullptr) : QObject{parent}{}~CppType()=default;//提供属性读取函数QString name(){return objectName();}//提供属性修改函数void setName(QString name){//前后属性一样就不用改了if(name==objectName()){return;}setObjectName(name);//发射属性改变信号emit nameChanged();}//5、将成员函数暴露给qml,qml中调用//函数前面加Q_INVOKABLE就可以暴漏出去Q_INVOKABLE void cppFunc1(){qDebug()<<"call cppFunc1";}//函数参数3种类型 基础  数组  js对象Q_INVOKABLE QString cppFunc2(int index,std::vector<int> arr,QVariantMap maps){qDebug()<<"call cppFunc2";qDebug()<<"index:"<<index;for(const auto& itor:arr){qDebug()<<itor;}for(const auto& key:maps.keys()){qDebug()<<"key:"<<key<<" value:"<<maps[key];}return "from cpp";}signals://提供属性改变信号void nameChanged();void ageChanged();//其他信号   他的信号处理器在qml中: onOtherSignal,遵循qml的规则on+信号名void otherSingal();
private:int age_=0;
};#endif // CPPTYPE_H

然后在加载qml文件之前

需要将这个类注入到qml中

注入完成之后,qml中就可以使用这个cpp扩展的类型了

使用qmlRegisterType这个模板函数进行注入

template <typename T> 
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
  • 模板参数:自定义的cpp类
  • 参数1:qml中使用import时的导入名
  • 参数2:主版本号
  • 参数3:子版本号
  • 参数4:qml使用这个类时的元素名 
//qml调用cpp  即使用cpp扩展qml类型
//一定要在加载qml文件之前将cpp扩展的类型注入到qml中
//模板参数:cpp扩展的类
//参数1:qml中使用import时的导入名
//参数2:主版本号
//参数3:子版本号
//参数4:qml使用这个类时的元素名
qmlRegisterType < CppType > ("CppType", 1, 0, "CppType");QQmlApplicationEngine appeng;
appeng.load("qrc:/Main2.qml");

然后就可以把他当成一个qml的类型在qml中使用了,遵循qml的各种语法和使用方式

import QtQuick 2.15
import QtQuick.Controls//需要先import,这里要和之前qmlRegisterType传入的参数对应
import CppType 1.0Window{id:rootwidth: 400height: 300visible: truetitle: "main2.qml"Button{width: 100height: 30text: "btn"onClicked: {cpp1.name="aaa"cpp1.age=20//调用cpp扩展类型的函数cpp1.cppFunc1()//可以直接这样传//cpp1.cppFunc2(3,[1,2,3],{name:"张三",age:18})//js数组var arr=[1,2,3]//js对象var obj={name:"张三",age:18}cpp1.cppFunc2(3,arr,obj)//访问cpp里面的枚举print(CppType.MyEnum.Value1)}}//使用cpp扩展的qml类型CppType{id:cpp1name:"cpptype"age:18onAgeChanged: {print("年龄:",age)}onNameChanged: {print("姓名:",name)}onOtherSingal: {}}
}

如果我们在qml中仅仅只是想使用cpp里面的一些函数和属性

不是直接使用cpp扩展的整个类型

那么我们可以不用将这个类型注入到qml中,

而是在加载qml文件之前实例化这个cpp类型的对象

然后设置上下文属性

//创建要使用的cpp类的实例
CppType cppType;QQmlApplicationEngine appeng;
//在加载qml文件之前获取上下文环境
//设置上下文属性
//参数1:在qml中使用实例名称
//参数2:对应的实例地址
appeng.rootContext() -> setContextProperty("cppType", & cppType);
appeng.load("qrc:/Main3.qml");

qml中直接通过对象名.函数名 或者 对象名.属性名直接调用

(这种方式无法访问cpp里面定义的枚举类型)

import QtQuick 2.15
import QtQuick.ControlsWindow{id:rootwidth: 400height: 300visible: truetitle: "window"Button{width: 100height: 30text: "btn"//还可以直接自定义属性来绑定cpp对象的属性property int age:cppType.ageonAgeChanged: {print("age变化:",age)}onClicked: {//直接使用cpp的函数,通过对象名.函数名调用(这里的函数名就是setContextProperty中设置的名称)cppType.cppFunc1()//直接访问里面的属性cppType.age=10}}
}

QWidget中局部使用qml

可以使用QQuickWidget

使用QQuickWidget来加载qml文件,就可以显示在QWidget中

qml中的根节点不能是Window,一般使用item或者其派生元素

QQuickWidget有两种resizeMode

  • QQuickWidget::SizeViewToRootObject

将QQuickWidge的大小调整为和qml根元素的大小一致,默认就是这种模式,

在这种情况下,QQuickWidge的大小可以不用设置(只设置位置)

qml根元素的大小需要显式地设置,不设置就是0,QQuickWidget的宽高也会跟着缩小到0,导致显示不出来

  • QQuickWidget::SizeRootObjectToView

将qml根元素的大小调整为和QQuickWidge的大小一致,

在这种情况下,QQuickWidget的大小要显式的设置(或者添加进布局之中)

qml根元素的大小可以不用设置(设置了也不生效),会调整为和QQuickWidge的大小一致

resizeMode的模式设置一定要在加载qml文件之前设置,否则会不生效!

这之后就可以在QQuickWidget显示qml的元素了,如何和里面的qml元素交互,就是前面说的那些方法了

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include<QQuickWidget>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr) : QWidget(parent){resize(800,600);QQuickWidget* w=new QQuickWidget(this);//设置QuickWidget的几何属性w->setGeometry(10,10,300,300);//resize模式的设置一定要在加载qml文件(setSource)之前设置,否则会无效//resizeMode设置:qml根元素的大小调整为和QQuickWidget一致w->setResizeMode(QQuickWidget::SizeRootObjectToView);w->setSource(QUrl("qrc:/Main.qml"));}~Widget()=default;
};
#endif // WIDGET_H

qml文件

import QtQuick 2.15
import QtQuick.ControlsRectangle{color: "red"Slider{width: parentheight: 50anchors.centerIn: parent}
}

 

Q_PROPERTY的一些说明

定义:

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

一般需要注意的字段就几个点

  • 属性是只读的,需要加CONSTANT
  • 属性绑定了成员变量,可以不用提供属性读写函数不用手动发射属性改变信号
  • 需要在属性写入函数中发射属性改变信号(如果前后值不一样,即真正修改后才发射)

 

快速给数据成员设置属性

  1. 右键数据成员 
  2. 点击重构 
  3. 点击生成Q_PROPERTY


http://www.ppmy.cn/devtools/137845.html

相关文章

深入理解React Hooks:使用useState和useEffect

引言 React Hooks是React 16.8引入的一项强大功能&#xff0c;它使函数组件能够使用状态和其他React特性。本文将深入探讨两个最常用的Hooks&#xff1a;useState和useEffect&#xff0c;并通过实际代码示例展示它们的使用方法。 1. 什么是React Hooks&#xff1f; React Ho…

第二章:编写第一个 Go 程序 2.Go 语言的基本结构 --Go 语言轻松入门

Go 语言是一种简洁、高效且易于学习的编程语言&#xff0c;它由Google开发。一个基本的Go程序通常包含以下几个部分&#xff1a; 包声明&#xff1a;在Go中&#xff0c;每个文件都必须属于一个包。最常用的包是main&#xff0c;它表示这个文件可以作为独立的应用程序运行。包声…

C++:使用CRTP代替虚函数实现静态多态的效果

这个代码实现了一个 Curiously Recurring Template Pattern (CRTP)&#xff0c;它是一种通过模板实现静态多态的方法。在这个模式中&#xff0c;基类使用其派生类作为模板参数&#xff0c;从而实现类似虚函数的行为&#xff0c;但没有动态多态的开销。 调用示例 下面是如何调用…

工业物联网网关在设备接入物联网中的核心作用

一、工业物联网网关的定义与功能 工业物联网网关是工业领域中的一种重要设备&#xff0c;它位于工业物联网系统的边缘位置&#xff0c;负责连接、管理和协调工业设备与云平台之间的通信。作为边缘计算的关键组件&#xff0c;工业物联网网关能够实现工业设备、传感器、PLC、DCS…

linux线程资源回收

在 POSIX 线程&#xff08;pthread&#xff09;中&#xff0c;线程终止后需要回收的资源主要包括以下几个方面&#xff1a; 1. 线程栈 每个线程都有自己的栈空间&#xff0c;用于存储局部变量、函数调用帧等。当线程终止时&#xff0c;如果没有及时回收栈空间&#xff0c;可能…

【大数据测试之:RabbitMQ消息列队测试-发送、接收、持久化、确认、重试、死信队列并处理消息的并发消费、负载均衡、监控等】详细教程---保姆级

RabbitMQ消息列队测试教程 一、环境准备1. 安装 RabbitMQ2. 安装 Python 依赖 二、基本消息队列中间件实现1. 消息发送模块2. 消息接收模块 三、扩展功能1. 消息持久化和队列持久化2. 消息优先级3. 死信队列&#xff08;DLQ&#xff09; 四、并发处理和负载均衡1. 使用 Python …

基于华为昇腾910B,实战InternLM个人小助手认知微调

本文将带领大家基于华为云 ModelArts&#xff0c;使用 XTuner 单卡微调一个 InternLM 个人小助手。 开源链接&#xff1a;&#xff08;欢迎 star&#xff09; https://github.com/InternLM/InternLM https://github.com/InternLM/xtuner XTuner 简介 XTuner 是一个高效、灵…

【数字图像处理+MATLAB】通过迭代全局阈值处理算法(Iterative Global Algorithm)实现图像分割

引言 图像分割是将数字图像划分为多个区域&#xff08;或像素的集合&#xff09;的过程&#xff0c;这些区域通常对应于真实世界的物体或图像中的特定部分。图像分割的目标是简化或改变图像的表示形式&#xff0c;使得图像更容易理解和分析。图像分割通常用于定位图像中的物体…