1. window
import QtQuick import QtQuick.Controls //控件库(button等) Window { //root控件 其父窗口是主界面 width: 640 //width等叫做属性 height: 480 visible: true title: qsTr("Hello World") x:200 //刚开始显示时的坐标 y:200 property int myValue: 0 //自定义属性,也会自动产生信号MyValueChanged和槽函数onMyValueChanged signal mysig() //自定义了一个信号 onMysig: { //无论是自定义信号还是默认信号,槽函数名称都是固定的,名字叫"on+信号名"(首位大写) } onWidthChanged: { //信号widthchanged的槽函数(宽度改变时触发) console.log("width:",width) } Button { id: but1 width:50 height:50 objectName: "but1" background: Rectangle { //background:按键的背景,rectangle:以矩形作为按钮的背景 border.color: but1.focus?"blue":"black" //判断该按键是否是焦点,从而绘制边框颜色 } onClicked:{ console.log("but1 is clicked") } //Keys.onLeftPressed是一个键盘事件处理器,只会在当前拥有焦点的元素上触发。 Keys.onRightPressed: { //键盘左键则把焦点移到button1 but2.focus=true //把焦点给but2 } } Button { id: but2 width:50 height:50 objectName: "but2" x:100 y:0 background: Rectangle { border.color: but2.focus?"blue":"black" //判断该按键是否是焦点 } onClicked:{ console.log("but2 is clicked") } Keys.onLeftPressed:{ but1.focus=true } } onActiveFocusItemChanged: { //activeFocusItem保存了焦点控件,可以确定焦点位置 console.log("active focus changed ",activeFocusItem, activeFocusItem.objectName) } }2. 自定义控件
使用另一个qml文件的自定义控件时:命名开头必须大写!然后要在pro中添加。
然后可以在main.qml文件中直接使用文件名来调用里面的控件。
import QtQuick 2.15 import QtQuick.Controls 2.15 Window { id: win1 width: 640 height: 480 visible: true title: qsTr("Hello World") Rectangle { id:rect1 x:50 y:50 width:100 height:50 color:"black" focus:true //获得焦点 property int num:0 MouseArea { //这里是声明了一个子元素,声明子元素不需要: anchors.fill:parent //填充满父元素 onClicked:{ rect1.num++ //使用父元素的属性,要标明 console.log("lets go ", rect1.num) } } Keys.onReturnPressed: { //当为焦点时,回车键才会对这个rectangle执行 num++ console.log("lets go ",num) } } Rectangle{ id: rect2 width: 100 height: 50 color:"blue" //anchor锚点: anchors.left: rect1.right //rect2的左边和rect1的右边对齐 anchors.top: rect1.top //rect2的上边和rect1对齐 anchors.leftMargin: 20 //左边空20 anchors.topMargin: 50 } Rectangle{ id: rect3 width: 100 height: 50 color:"red" //anchors.centerIn: parent //居中于父控件 anchors.horizontalCenter: parent.horizontalCenter //x横向居中 verticalCenter是纵向 } Apy { //Apy.qml是一个文件,直接使用文件名,调用里面的最顶层的控件 x:200 y:300 } }3.QML-states
qml中states语句有点像if语句。qml中有if else语句
语法:
4.component和loader
可以实现一个自定义组件的复用
import QtQuick import QtQuick.Controls Window { width: 640 height: 480 visible: true title: qsTr("Hello World") color: "white" Component.onCompleted:{ //window组件创建时会自动调用该槽函数 console.log("oncompleted ",width,height,x,y) } Component.onDestruction: { console.log("ondestruction") } //自定义组件。不会自动显示,需要用loader加载。 //作用是封装一个控件,然后可以被多个loader调用 Component{ id:com Rectangle{ width:100 height:50 x:300 color:"red" } } //loader可以加载component自定义控件,可以多个loader加载一个component,做到复用 Loader{ //loader元素 id:myloader asynchronous: true //异步。如果加载的控件很大很慢,用异步的话程序不会卡住,这时候状态status为2,可以做一些等待动画之类 //source:"./Apy.qml" //source可以加载其他文件的控件 sourceComponent: com //sourceComponent加载一个本文件的控件 onStatusChanged: { //加载控件状态改变槽函数 console.log("status: ".status) } width:500 //可以直接操作加载的控件 } Button{ y:300 width:100 height:100 onClicked: { //myloader.sourceComponent= null //关闭component myloader.item.width= 200 //在loader外部,要通过loader的item属性来操作加载的属性 myloader.item.color="blue" } } }5.信号与槽
第一种方法:首先是直接使用默认产生的槽汉数,如onClicked等(缺点:只能用于当前对象或其子对象中定义的信号)
第二种方法:使用Connections连接自定义的信号和槽(需要连接非当前对象或其子对象的信号时)
第三种方法:connect(不常用)
6.自定义组件与信号槽的使用
main.qml:
import QtQuick import QtQuick.Controls Window { id: win width: 640 height: 480 visible: true title: qsTr("Hello World") Component{ //实现另一个文件的组建的具体内容 id:com Button{ signal btnSig(int value) //定义一个信号 onClicked: { btnSig(10) } } } Test{ com1:com //设置另一个文件的属性 com2:com } }Test.qml:
import QtQuick Rectangle { width:300 height:200 property Component com1 //先自定义两个组件属性com1 com2,然后在main.qml里赋值 property Component com2 border.color: "black" Loader{ id:loader1 sourceComponent: com1 //加载组件,没有具体的组件(button等) //确定位置: anchors.bottom: parent.bottom anchors.bottomMargin: 20 anchors.left: parent.left anchors.leftMargin: 20 Connections{ target: loader1.item //这里必须用loader1.item来确定loader的组件,不能直接用com1 ignoreUnknownSignals: true //如果没有相应的信号,那么忽略对应的槽函数 function onBtnSig(value){ //btnSig的槽函数 console.log("left",value) } } } Loader{ id:loader2 sourceComponent: com2 anchors.bottom: parent.bottom anchors.bottomMargin: 20 anchors.right: parent.right anchors.rightMargin: 20 Connections{ target: loader2.item //和上面那个一样 ignoreUnknownSignals: true function onBtnSig(value){ //btnSig的槽函数 console.log("right",value) } } } }7.qml与C++交互
MVC设计结构
view model controller
view显示UI(QML)
model(data)获取QML需要的数据,设置QML的属性,获取QML端的数据
controller暴露外部接口,控制model的数据
QML 和 C++ 交互是 Qt 开发的核心,它允许你将强大的后端逻辑(C++)与灵活漂亮的前端界面(QML)结合起来。主要有三种方式:
(1)将 C++ 对象暴露为 QML 上下文属性:适用于注入单个实例或全局对象。
(2)将 C++ 类型注册为可实例化的 QML 类型:允许 QML 像使用内置类型一样new出 C++ 对象。
(3)在 C++ 中直接加载和操作 QML 对象:适用于从 C++ 端查找和修改 QML 元素。
下面我们分别详细说明这三种方法。
方法一:将 C++ 对象暴露为 QML 上下文属性
这是最简单直接的方法,特别适合将一些单例式的、工具类的或管理器式的 C++ 对象暴露给 QML。
第一步:创建 C++ 类并添加 QML 交互特性
你的 C++ 类需要继承自QObject,并使用Q_PROPERTY暴露属性,使用Q_INVOKABLE暴露方法,使用signals暴露信号。
myclass.h:
#include <QObject> #include <QString> // 必须继承自 QObject class MyClass : public QObject { Q_OBJECT // 定义一个可读可写的属性 'message',并带有通知信号 messageChanged Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) public: explicit MyClass(QObject *parent = nullptr); // Q_INVOKABLE 宏使得这个方法可以被 QML 调用 Q_INVOKABLE void showMessage(const QString &msg); // 属性的 READ 函数 QString message() const; // 属性的 WRITE 函数 void setMessage(const QString &message); signals: // 属性的通知信号,要用signals声明 void messageChanged(); // 一个普通信号,可供 QML 连接 void somethingHappened(); private: QString m_message; // 私有成员变量,存储属性值 };myclass.cpp:
#include "myclass.h" #include <QDebug> MyClass::MyClass(QObject *parent) : QObject(parent) { } void MyClass::showMessage(const QString &msg) { qDebug() << "C++ received message from QML:" << msg; emit somethingHappened(); // 触发信号 } QString MyClass::message() const { return m_message; } void MyClass::setMessage(const QString &message) { if (m_message != message) { m_message = message; emit messageChanged(); // 属性改变时,必须发射通知信号 } }第二步:在 main.cpp 中暴露对象给 QML
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> // 需要包含这个头文件 #include "myclass.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 1. 创建 C++ 对象 MyClass myObj; // 2. 将对象设置为 QML 根上下文的属性 // 参数:字符串名(在QML中使用),实际对象 engine.rootContext()->setContextProperty("myExposedObject", &myObj); // 3. 加载 QML 文件 engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }第三步:在 QML 中使用暴露的对象
main.qml:
import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow { width: 400 height: 300 visible: true // 访问暴露的 C++ 对象的属性(双向绑定) Text { text: myExposedObject.message // 读取 C++ 属性 anchors.centerIn: parent } Button { text: "Call C++ Method" anchors.bottom: parent.bottom onClicked: { // 调用 C++ 的 Q_INVOKABLE 方法 myExposedObject.showMessage("Hello from QML!"); // 写入 C++ 属性,这会触发 messageChanged 信号,更新上面的 Text myExposedObject.message = "New message set by QML"; } } Connections { target: myExposedObject // 连接 C++ 对象发出的信号 onSomethingHappened: { console.log("The C++ object emitted 'somethingHappened'!"); } // 连接属性改变的通知信号 onMessageChanged: { console.log("The message property changed to:", myExposedObject.message); } } }优点:简单快捷,适合全局对象。
缺点:对象名称在 QML 中是硬编码的字符串,不利于重构;不适合需要创建多个实例的场景。
方法二:将 C++ 类型注册为可实例化的 QML 类型
这种方法将你的 C++ 类变成一个 QML 元素,就像Button或Rectangle一样,可以在 QML 中声明和创建多个实例。
第一步:创建 C++ 类(同上)
同样使用Q_PROPERTY,Q_INVOKABLE,signals。类定义和上面一样。
第二步:使用 qmlRegisterType 注册类型
通常在main.cpp中注册。
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "myclass.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); // 在加载 QML 引擎之前注册类型 // 参数:uri(QML 中 import 后的内容,相当于头文件名),主版本号,次版本号,QML中的类型名 qmlRegisterType<MyClass>("MyCompany.Library", 1, 0, "MyClass"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }第三步:在 QML 中导入并实例化
main.qml:
import QtQuick 2.15 import QtQuick.Controls 2.15 import MyCompany.Library 1.0 // 导入我们注册的模块 ApplicationWindow { width: 400 height: 300 visible: true // 声明一个 MyClass 类型的实例,id 为其句柄 MyClass { id: myClassInstance // 可以像普通属性一样设置初始值 message: "Initial Message" // 可以连接信号 onSomethingHappened: { console.log("Something happened in the registered type!"); } } Text { text: myClassInstance.message anchors.centerIn: parent } Button { text: "Create and Use" onClicked: { // 甚至可以动态创建 var dynamicInstance = Qt.createQmlObject(` import MyCompany.Library 1.0; MyClass { message: "Dynamic" } `, parent, "DynamicObject"); dynamicInstance.showMessage(); } } }优点:更符合 QML 的声明式风格,可创建多个实例,类型安全(在 QML 中有代码补全)。
缺点:比上下文属性稍微复杂一点。
方法三:在 C++ 中直接加载和操作 QML 对象
这种方法是从 C++ 端主动出击,查找 QML 加载后形成的对象树中的特定对象,并直接调用其方法或设置其属性。
第一步:确保 QML 对象有 objectName
// main.qml Text { objectName: "myTextObject" // 为C++查找提供标识 text: "I'm from QML" }第二步:在 C++ 中查找该对象并与之交互
// main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QDebug> #include <QQmlComponent> #include <QObject> // 用于 dynamicCall 和 setProperty int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); // 引擎加载完成后,获取根对象(通常是ApplicationWindow) QObject *rootObject = engine.rootObjects().first(); // 通过 objectName 查找子对象 QObject *textObject = rootObject->findChild<QObject*>("myTextObject"); if (textObject) { // 方法1:使用 setProperty 设置属性 textObject->setProperty("text", "Text changed from C++!"); textObject->setProperty("color", "red"); // 方法2:使用 QMetaObject 调用方法(如果知道方法名和参数) // QMetaObject::invokeMethod: 在运行时动态地调用 textObject 对象的名为 "append" 的方法,并传递一个 QString 类型的参数 QMetaObject::invokeMethod(textObject, "append", Q_ARG(QString, " Appended text.")); } else { qWarning() << "Object not found!"; } return app.exec(); }优点:C++ 端拥有完全控制权。
缺点:破坏了 QML 的声明式特性,C++ 和 QML 耦合较紧,QML 结构变化容易导致 C++ 代码失效。
8. 元属性Q_PROPERTY详解(c++中定义, qml中调用)
在Qt框架中,Q_PROPERTY是一个用于声明元属性(Meta-Property)的宏,它允许将类的成员变量或逻辑属性暴露给Qt的元对象系统(Meta-Object System),从而支持信号槽机制、QML绑定、动态属性查询等高级特性。
主要是用于给qml界面提供动态的数据。
比如:
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)宏的结构:
Q_PROPERTY:Qt的元属性声明宏,必须放在继承自QObject的类中使用(且类需用Q_OBJECT宏标记)。
QString:属性的数据类型,此处为Qt的字符串类型。
message:属性的名称(标识符)。
READ:指定读取属性值的成员函数(必须为const函数)。
message:对应函数名为message(),返回QString类型值。
WRITE:指定写入属性值的成员函数。
setMessage:对应函数名为setMessage(const QString &value),用于设置属性值。
NOTIFY:指定属性值变更时触发的信号(用于通知监听者)。
messageChanged:对应信号名为messageChanged(),通常在WRITE函数中触发。
完整示例代码:
class MyClass : public QObject { Q_OBJECT Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) public: explicit MyClass(QObject *parent = nullptr); QString message() const { return m_message; } //READ void setMessage(const QString &value); //WRITE signals: void messageChanged(); //NOTIFY信号,注意要用signals声明一下 private: QString m_message; //m_message起一个中转站的作用,READ和WRITE读写它,并且可以在c++中修改 }; // 实现文件(WRITE时发NOTIFY信号) void MyClass::setMessage(const QString &value) { if (m_message != value) { m_message = value; emit messageChanged(); //要用emit手动发信号 } }9.qml中的属性,Qt.binding()函数
(1)qml中的属性:
如何判断qml中的数据,定义为属性还是定义为普通变量?:
(2)Qt.binding()函数
作用:定义或改变一个属性的绑定规则
举个例子:
Item { property real applePrice: 5.0 //苹果单价 property int appleCount: 2 //苹果数量 property real bananaPrice: 3.0 //香蕉单价 property int bananaCount: 3 //香蕉数量 // 这个显示值最初绑定的是“总花费” property real displayValue: (applePrice * appleCount) + (bananaPrice * bananaCount) Component.onCompleted: { // 过了一会儿,你动态地改变了它的绑定规则 // 从计算“总花费”变为计算“总数量” displayValue = Qt.binding(function() { return appleCount + bananaCount; }); } }10.环形进度条(使用Canvas画遮罩)
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtGraphicalEffects 1.0 Item { id: control //公共属性 property real value:0 //进度值 //尺寸 width: 355 height: 355 //背景圆环(底条) Image{ id: backgroundRing anchors.fill: parent fillMode: Image.PreserveAspectFit source: "../images/ProgressBar_off.png" } //进度圆环(前景) Image{ id: progressRing anchors.fill: parent fillMode: Image.PreserveAspectFit source:"../images/ProgressBar_on.png" layer.enabled: true //启用图层化渲染 layer.effect: OpacityMask{ //OpacityMask:遮罩 maskSource: Item{ //Item是画布 width: progressRing.width height: progressRing.height Canvas{ //Canvas是画笔 id: progressMask property real currentValue: control.value onCurrentValueChanged: { requestPaint() } anchors.fill: parent onPaint:{ var ctx = getContext("2d") ctx.clearRect(0, 0, width, height); var centerX= width/2 var centerY= height/2 var radius= Math.min(centerX,centerY) var startAngle = -Math.PI/2 var endAngle = startAngle + currentValue*2*Math.PI ctx.beginPath() //开始路径 ctx.moveTo(centerX,centerY) //将画笔移动到指定坐标,不画线 ctx.arc(centerX,centerY,radius,startAngle,endAngle,false) //画一个弧,最后一个参数是表示顺时针 ctx.closePath() //闭合路径 // OpacityMask规则: 黑色/白色 = 显示,透明 = 隐藏 ctx.fillStyle = "white" ctx.fill() } } } } } }11. ListModel组件
(1)append() 方法
参数是一个 JavaScript 对象,可以向ListModel对象中插入自定义条目的属性和值
// 简单添加 stateList.append({name: "temperature", value: "25"}) // 添加多个属性 stateList.append({ name: "device", value: "active", color: "green", timestamp: "2024-01-15" }) // 动态构建对象 var newData = {} newData["name"] = "pressure" newData["value"] = "100" newData["unit"] = "kPa" stateList.append(newData)(2)setProperty() 方法
listModel.setProperty(int index, string propertyName, variant value)(3)get()方法
//获取指定条目 var item = stateList.get(2)(4)set()方法
// 完全替换指定索引的条目 stateList.set(2, {name: "replaced", value: "new", extra: "data"})(5)remove()- 删除条目
stateList.remove(3) // 删除第4个条目(6)insert() 插入条目
// 在指定位置插入条目 stateList.insert(2, {name: "new", value: "data"}) // 插入到第3个位置 // insert比append+move更高效 stateList.insert(0, {name: "first", value: "1"}) // 插入到开头12.模型 - 视图 - 委托
(1)视图
ListView (一行行显示)
ListView { width: 200 height: 300 model: ListModel { ListElement { name: "Alice"; age: 25 } ListElement { name: "Bob"; age: 30 } ListElement { name: "Charlie"; age: 35 } } delegate: Rectangle { width: 200 height: 50 color: index % 2 ? "lightgray" : "white" Text { text: model.name + " (" + model.age + "岁)" anchors.centerIn: parent } } }GridView(显示网格状的数据)
格式与ListView类似
Repeater(简单重复,需要在布局容器中使用)
Column { //需要在布局容器中使用 Repeater { model: ["red", "green", "blue"] delegate: Rectangle { width: 100 height: 40 color: modelData // modelData包含数组元素 Text { text: modelData anchors.centerIn: parent } } } }TableView(表格数据)
TableView { anchors.fill: parent columnSpacing: 1 rowSpacing: 1 model: TableModel { //每个TableModelColumn可以指定role和title,role是数据字段名,title是显示标题 TableModelColumn { role: "name"; title: "姓名" } TableModelColumn { role: "age"; title: "年龄" } TableModelColumn { role: "city"; title: "城市" } rows: [ { name: "张三", age: "25", city: "北京" }, { name: "李四", age: "30", city: "上海" } ] } delegate: Rectangle { border.color: "gray" Text { text: model.display anchors.centerIn: parent } } }(我用的时候显示unknown component,不知道为啥)
SwipeView(页面切换,不需要model)
SwipeView { id: swipeView anchors.fill: parent currentIndex: tabBar.currentIndex Page { title: "首页" } Page { title: "设置" } Page { title: "关于" } } TabBar { id: tabBar anchors.top: parent.top width: parent.width TabButton { text: "首页" } TabButton { text: "设置" } TabButton { text: "关于" } }(2)模型
13.在qml中使用ffmpeg播放视频
开源项目https://github.com/yuan631111976/qtavplayer.git
在.frag文件开始位置添加 #version 120
14.qml类型相互转换方法
QML 里没有真正的 int 类型,只有 number。int 只是 QML 属性系统提供的一个“整数视图”
15 时间显示
获取当前时间:
var now = new Date()显示HH:mm:ss :
function currentTime() { var now = new Date() return now.toLocaleTimeString() }时间戳(显示ms):
var timestamp = Date.now() // 或 var timestamp = new Date().getTime()16.状态管理:state属性
每个Item及其派生类都有state属性,用于控制该组件的当前状态。
代码示例:
Rectangle{ id:rect1 Rectangle{ id:rect2 color:"red" } Rectangle{ id:rect3 color:"green" } Component.onCompleted:{ rect1.state="isHD" //给state属性赋值 } //rect1的state改变时,执行state对应的PropertyChanges动作。这里用state来控制组件的显示状态 states:[ State{ name: "isHD" //when: param == true //也可以用when来控制什么时候切换到该状态 PropertyChanges { target: rect2 visible:false } PropertyChanges { target: rect3 visible:true } }, State{ name:"notHD" PropertyChanges { target: rect2 visible:true } PropertyChanges { target: rect3 visible:false } } ] }