第23章 QML与Qt Quick:从入门到实战
QML和Qt Quick,说白了就是Qt家族里专门做界面的一把快刀。我最早接触Qt时用的是QWidget,后来转到QML,说实话,刚开始有点不适应——写界面居然不用C++?但用顺手之后,真香。
这一章我们聊聊QML的语法基础、常用控件、怎么和C++交互、动画状态,还有Canvas绘图。嗯,内容不少,但都是干货。
23.1 QML语法基础——声明式UI的骨架
QML的语法,其实特别像JSON和JavaScript的混血儿。一个QML文件就是一个对象树,每个对象都有属性和信号。我刚开始学的时候,觉得这东西不就是写配置嘛,后来才发现,它的属性绑定机制才是精髓。
核心要点:QML是声明式语言,你描述界面长什么样,而不是一步步怎么画。这跟QWidget那种命令式写法完全不同。
来看一个最简单的例子:
// main.qml
import QtQuick 2.15
Rectangle {
width: 400
height: 300
color: "lightblue"
Text {
anchors.centerIn: parent
text: "Hello, QML!"
font.pixelSize: 24
color: "navy"
}
}
这里有个坑,我刚开始踩过——anchors.centerIn: parent 这个写法,parent必须是一个Item或者有几何属性的对象。如果你把Text直接放在Window下面,Window本身不是Item,会报错。嗯,记住就好。
属性绑定是QML最强大的特性之一。你可以把一个属性绑定到另一个属性上,当源属性变化时,目标属性自动更新:
Rectangle {
width: parent.width * 0.8 // 自动跟随父容器宽度变化
height: width * 0.6 // 保持宽高比
}
我个人习惯把复杂的绑定逻辑写在JavaScript函数里,这样代码更清晰。比如:
function calculateWidth(parentWidth) {
return parentWidth > 600 ? parentWidth * 0.7 : parentWidth * 0.9;
}
23.2 Qt Quick控件——开箱即用的UI组件
Qt Quick Controls 2 提供了一套现代化的控件集。我做过一个项目,用纯QML写了一个配置面板,Button、ComboBox、Slider这些控件直接拿来用,开发效率比QWidget高了一倍不止。
| 控件 | 用途 | 常用属性 |
|---|---|---|
| Button | 按钮触发操作 | text, onClicked, enabled |
| TextField | 单行文本输入 | text, placeholderText, validator |
| Slider | 数值范围选择 | from, to, value, onValueChanged |
| ComboBox | 下拉选择 | model, currentIndex, currentText |
| ListView | 列表展示 | model, delegate, spacing |
举个例子,一个带验证的输入框:
TextField {
id: ipInput
placeholderText: "请输入IP地址"
validator: RegExpValidator { regExp: /^(\d{1,3}\.){3}\d{1,3}$/ }
onAccepted: {
console.log("用户输入了:", text)
}
}
小技巧:ListView的delegate可以用Loader动态加载不同的组件,实现列表项多样化。我在做聊天软件时就用过这招,不同类型的消息显示不同的样式。
23.3 QML与C++交互——打通任督二脉
QML做界面快,但复杂业务逻辑还是C++拿手。怎么让它们配合?说白了就三种方式:注册C++类型、暴露属性、连接信号。
先看C++端怎么暴露一个对象给QML:
// MyDataModel.h
class MyDataModel : public QObject {
Q_OBJECT
Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged)
public:
QString userName() const { return m_userName; }
void setUserName(const QString &name) {
if (m_userName != name) {
m_userName = name;
emit userNameChanged();
}
}
signals:
void userNameChanged();
private:
QString m_userName;
};
然后在main.cpp里注册:
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
MyDataModel model;
model.setUserName("张三");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("dataModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
QML端直接使用:
Text {
text: dataModel.userName // 自动绑定,C++那边变了这里跟着变
}
Button {
text: "修改名字"
onClicked: {
dataModel.userName = "李四" // 直接调用C++的setter
}
}
注意:我曾经犯过一个错误——在C++的setter里忘记发射信号,结果QML界面死活不更新。记住,Q_PROPERTY的NOTIFY信号必须发射,否则绑定不会生效。
还有一种方式是把C++类注册为QML类型:
qmlRegisterType<MyDataModel>("com.myapp.models", 1, 0, "MyDataModel");
这样在QML里可以直接 import com.myapp.models 1.0 然后 MyDataModel { id: model } 来创建实例。
23.4 动画与状态——让界面活起来
QML的动画系统,说实话,比QWidget的动画框架好用太多了。你想想看,在QWidget里做个渐变动画得写多少代码?在QML里几行就搞定。
最简单的属性动画:
Rectangle {
id: box
width: 100; height: 100
color: "red"
PropertyAnimation on x {
from: 0
to: 300
duration: 1000
loops: Animation.Infinite
}
}
状态机是QML另一个杀手锏。我做过一个登录界面,用状态管理不同UI状态:
Item {
state: "idle"
states: [
State {
name: "idle"
PropertyChanges { target: loginBtn; enabled: true }
PropertyChanges { target: spinner; visible: false }
},
State {
name: "loading"
PropertyChanges { target: loginBtn; enabled: false; text: "登录中..." }
PropertyChanges { target: spinner; visible: true }
},
State {
name: "success"
PropertyChanges { target: loginBtn; text: "登录成功" }
PropertyChanges { target: spinner; visible: false }
}
]
transitions: [
Transition {
from: "*"; to: "*"
PropertyAnimation { properties: "opacity"; duration: 300 }
}
]
}
为什么用状态机?因为当界面有多个控件需要同时变化时,状态机比手动设置每个控件的属性要清晰得多。你想想看,如果不用状态机,你得写一堆if-else来切换控件的可见性、文本、颜色……维护起来简直是噩梦。
23.5 Canvas绘图——自由发挥的画布
QML的Canvas元素提供了2D绘图能力,API跟HTML5 Canvas几乎一样。如果你做过Web前端,上手零成本。
画一个简单的饼图:
Canvas {
id: canvas
width: 300; height: 300
onPaint: {
var ctx = getContext("2d");
ctx.reset();
var data = [30, 20, 50]; // 三个扇区
var colors = ["#FF6384", "#36A2EB", "#FFCE56"];
var total = 0;
data.forEach(function(v) { total += v; });
var startAngle = 0;
for (var i = 0; i < data.length; i++) {
var sliceAngle = (data[i] / total) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(150, 150);
ctx.arc(150, 150, 120, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = colors[i];
ctx.fill();
startAngle += sliceAngle;
}
}
}
经验之谈:Canvas的onPaint在初始化时只会调用一次。如果你需要重绘,记得调用 canvas.requestPaint()。我在做实时波形图时,就是靠定时器不断调用requestPaint来刷新画面的。
Canvas还支持渐变、阴影、图像绘制等高级功能。不过要注意,Canvas是像素级操作,如果界面需要频繁重绘,性能可能会成为瓶颈。这时候可以考虑用ShaderEffect或者直接上OpenGL。
小结
这一章我们聊了QML的语法基础、常用控件、C++交互、动画状态和Canvas绘图。QML这套东西,说白了就是让你用更少的代码写出更漂亮的界面。我个人觉得,QML+Qt Quick的组合,在嵌入式设备和桌面应用的快速原型开发上,优势非常明显。
如果你之前一直用QWidget,不妨试试QML。刚开始可能有点不习惯,但用上几个项目之后,你会爱上这种声明式写界面的感觉。
公众号:蓝海资料掘金营,微信deep3321