4. QML语言基础:QML语法入门、属性与信号绑定、JavaScript在QML中的使用、QML与C++交互
好,咱们进入正题。这一章是QML的根基,说白了就是你要学会怎么用QML“说话”。很多新手一上来就急着写动画、画界面,结果连属性绑定都搞反了,调试起来一头雾水。我个人习惯是,先把语法骨架搭稳,再往上添肉。
4.1 QML语法入门:从“Hello World”开始
QML的语法,其实很像搭积木。每个Rectangle、Text、Button都是一块积木,你通过属性来描述它长什么样,通过嵌套来组合它们。
先看一个最简单的例子:
import QtQuick 2.15
Rectangle {
width: 400
height: 300
color: "lightblue"
Text {
anchors.centerIn: parent
text: "Hello, HMI!"
font.pixelSize: 24
color: "navy"
}
}
这里有几个关键点:
- 导入语句:
import QtQuick 2.15是必须的,它告诉QML引擎我要用哪个版本的Qt Quick模块。版本号要和你用的Qt版本匹配,我遇到过有人把2.15写成2.5,结果一堆属性不识别。 - 对象声明:
Rectangle { ... }就是一个对象。大括号里是它的属性和子对象。 - 属性赋值:
width: 400,冒号左边是属性名,右边是值。注意,这里不是等号,是冒号。 - 锚点布局:
anchors.centerIn: parent意思是“把我放在父对象的正中间”。这是QML里最常用的布局方式之一。
QML的类型系统也很简单。基本类型包括int、real、string、bool、color、font等。你不需要像C++那样声明类型,QML会自动推断。但要注意,color类型可以用名字("red")、十六进制("#FF0000")或HSL方式指定。
4.2 属性与信号绑定:QML的灵魂
属性绑定是QML最强大的特性之一。说白了,就是让一个属性的值“自动跟随”另一个属性的变化。不用写任何更新代码,QML引擎帮你搞定。
举个例子:
Rectangle {
width: 400; height: 300
color: "lightgray"
Rectangle {
id: slider
width: 50; height: 200
color: "steelblue"
x: 20; y: 50
MouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAxis
drag.minimumX: 20
drag.maximumX: 330
}
}
Text {
text: "滑块位置: " + slider.x.toFixed(0)
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 18
}
}
看到text: "滑块位置: " + slider.x.toFixed(0)这行了吗?这就是属性绑定。当slider.x变化时,text会自动更新。你不需要写任何onXChanged信号处理函数。
slider.x = 100。这会破坏绑定!一旦你用JavaScript直接赋值,原来的绑定关系就断了,属性不再自动更新。正确的做法是使用 slider.x = Qt.binding(function() { return ... }) 来重新建立绑定。
信号绑定是另一个核心概念。QML里几乎所有用户交互都会产生信号,比如点击、拖拽、键盘输入。你可以用onSignalName的语法来响应信号。
Button {
text: "点击我"
onClicked: {
console.log("按钮被点击了")
// 这里可以写更多逻辑
}
}
信号也可以带参数。比如MouseArea的clicked信号会传递鼠标坐标:
MouseArea {
onClicked: (mouse) => {
console.log("点击位置: " + mouse.x + ", " + mouse.y)
}
}
4.3 JavaScript在QML中的使用
QML本身不是一门完整的编程语言,它主要做界面描述。真正的逻辑处理,需要靠JavaScript。你可以在QML文件里直接写JS代码,也可以把JS函数放到独立的.js文件中。
在QML里写JS,主要有三种方式:
- 属性值里写表达式:比如
text: "总数: " + (count + 1) - 信号处理器里写代码块:比如
onClicked: { ... } - 自定义函数:用
function关键字定义
看个综合例子:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400; height: 300
title: "JS示例"
property int counter: 0
function formatTime(seconds) {
var min = Math.floor(seconds / 60)
var sec = seconds % 60
return min + "分" + sec + "秒"
}
Column {
anchors.centerIn: parent
spacing: 20
Text {
id: display
text: "计时: " + formatTime(counter)
font.pixelSize: 28
}
Button {
text: "加1秒"
onClicked: {
counter++
if (counter > 3600) {
console.warn("超过1小时了,注意!")
}
}
}
Button {
text: "重置"
onClicked: {
counter = 0
display.text = "计时: 0分0秒" // 注意:这里直接赋值会破坏绑定
}
}
}
}
onClicked 里直接写了 display.text = "..."。这会破坏 display.text 原本的绑定关系。如果后面 counter 再变化,display 不会更新。正确的做法是只修改 counter = 0,让绑定自动更新。嗯,这里要特别小心。
对于复杂的JS逻辑,我建议放到独立的.js文件中:
// utils.js
function calculateSpeed(distance, time) {
if (time === 0) return 0
return (distance / time) * 3.6 // 转换为km/h
}
function formatSpeed(speed) {
return speed.toFixed(1) + " km/h"
}
然后在QML里导入:
import "utils.js" as Utils
Text {
text: Utils.formatSpeed(currentSpeed)
}
这样做的好处是逻辑复用,而且代码更清晰。我在做车载仪表盘项目时,所有单位换算、数据格式化都放在独立的JS文件里,界面文件只负责布局和绑定。
4.4 QML与C++交互:打通任督二脉
QML做界面确实快,但复杂的业务逻辑、硬件通信、高性能计算,还是得靠C++。两者怎么配合?主要有两种方式:
| 方式 | 适用场景 | 实现方法 |
|---|---|---|
| 注册C++类到QML | 需要创建多个C++对象实例 | qmlRegisterType<MyClass>() |
| 暴露C++对象给QML | 单例对象,如数据模型、后端服务 | engine.rootContext()->setContextProperty() |
先说第一种,注册C++类。假设你有一个SpeedModel类:
// speedmodel.h
class SpeedModel : public QObject
{
Q_OBJECT
Q_PROPERTY(int speed READ speed WRITE setSpeed NOTIFY speedChanged)
public:
explicit SpeedModel(QObject *parent = nullptr);
int speed() const;
void setSpeed(int newSpeed);
signals:
void speedChanged();
private:
int m_speed = 0;
};
在main.cpp里注册:
qmlRegisterType<SpeedModel>("com.hmi.models", 1, 0, "SpeedModel");
然后在QML里直接用:
import com.hmi.models 1.0
SpeedModel {
id: carSpeed
speed: 80
}
Text {
text: "当前车速: " + carSpeed.speed + " km/h"
}
第二种方式,暴露对象。适合那种全局唯一的对象,比如车辆状态管理器:
// main.cpp
VehicleManager manager;
engine.rootContext()->setContextProperty("vehicle", &manager);
QML里直接使用vehicle这个变量名:
Text {
text: "电池电量: " + vehicle.batteryLevel + "%"
}
setContextProperty 暴露给QML。这样QML只负责显示和动画,C++负责和CAN总线、传感器打交道。分工明确,调试起来也方便。有一次CAN总线数据频率太高,QML界面卡顿,我就在C++层做了数据降采样,问题立刻解决。
C++和QML之间还可以通过信号互相通信。C++发射信号,QML用onSignalName接收:
// C++
emit speedChanged(newSpeed);
// QML
onSpeedChanged: {
console.log("速度变化了: " + speed)
}
反过来,QML也可以调用C++的Q_INVOKABLE方法:
// C++
Q_INVOKABLE void setTargetTemperature(int temp);
// QML
Button {
onClicked: vehicle.setTargetTemperature(22)
}
嗯,到这里,QML语言基础的核心内容就差不多了。记住一句话:QML管界面,C++管逻辑,JS管粘合。下一章我们会深入QML的布局系统,到时候你会看到这些基础语法怎么组合成真正的车载界面。