qml基本语法
qml是一种声明式语言,可以无缝嵌入JavaScript,将UI视为对象树。
组件基本结构
组件是qml的基本单位,以大写字母开头。
| Rectangle {
id: statusBubble // id 是唯一标识符
width: 120 // 宽度
height: 40 // 高度
color: "#333333" // 颜色
}
|
Anchors布局
除了写死坐标,也使用Anchors(锚点)对布局进行定位。
| Text {
anchors.centerIn: parent // 让这段文字在它的父组件里绝对居中
text: "📖 学习中..."
}
Rectangle {
anchors.top: bgRect.bottom // 让这个组件的顶部,贴着 bgRect 这个组件的底部
anchors.topMargin: 10 // 并且往下留出 10 像素的间距
}
|
parent是qml内置的关键字,代表当前组件的父级。
事件响应
在 QML 中,如果发生了某个事件,它会发出一个“信号”(Signal),通过在组件的on\<SignalName>来监听和接收事件,响应的逻辑按js语法写。
| Button {
text: "学"
// onClicked 就是点击事件的响应槽
onClicked: {
BehaviorAct.startLearning() // 调用 C++ 调度器的方法
console.log("用户点击了学习按钮") // 里面可以直接写纯 JS 逻辑
}
}
|
qml也允许c++类使用single关键字自定义信号。
| class MyStateMachine : public QObject {
Q_OBJECT
// 这里的 NOTIFY 指向了下面的信号
Q_PROPERTY(int timerValue READ timerValue NOTIFY timerValueChanged)
public:
// ... 其他代码 ...
signals:
// --- 信号定义在这里 ---
// 信号没有函数体,也不需要 public/private 修饰(Qt 默认处理)
void timerValueChanged(int newValue);
};
|
自定义信号被认为是public的,在任意程序段都可以监听该信号,但仅允许信号所属的类及其子类发射它。
| // 假设这是定时器到期后的处理函数
void MyStateMachine::onTimeout() {
m_timerValue++;
// 手动触发信号,告诉 QML:值变了,快去调用 READ 函数刷新界面!
emit timerValueChanged(int newValue);
}
|
虽然 QML 绑定不需要信号带参数(它会自动重新调用 READ 函数),但规范的做法通常是让信号带上新值,例如 void timerValueChanged(int newValue);。
属性绑定
QML允许将组件的属性绑定到宿主程序类的变量上。绑定需要按以下两个部分分别处理:
-
C++类的声明
| class BehaviorScheduler : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(BehaviorState currentState READ currentState NOTIFY stateChanged)
Q_PROPERTY(int taskProgress READ taskProgress NOTIFY progressChanged)
//自定义枚举和导出
enum BehaviorState {
None, // 啥也没干
Learning, // 学习中
Searching // 检索中
};
Q_ENUM(BehaviorState)
//...其它逻辑....
}
|
被绑定的类需要继承QObject。
Q_OBJECT:声明这是一个需要使用Qt特性的类。
-
QML组件的调用
直接将属性赋值给组件的属性。
| Text {
text: "当前状态: " + BehaviorScheduler.currentState
}
ProgressBar {
value: BehaviorScheduler.taskProgress
}
|
单例类的定时数据更新
单例允许且仅允许在整个应用程序中共享同一个对象实例,常用于管理全局配置、用户信息、主题样式或跨页面的逻辑处理。
数据更新主要依赖于Q_Timer辅助实现。
实现
在类定义的开始使用QML_SINGLETON宏声明为单例,或通过手写instance()方法实现。
更推荐后者,便于管理生命周期。使用宏创建的单例所属于QML,在c++代码中调用较为困难;同时生命周期将跟随QML。
一种更合理的实践是将二者结合,这时不需要使用QML_ELEMENT和QML_SINGLETON,否则会出现逻辑冲突。
本节的代码说明均按照二者结合的方案。
单例类的定义
| #include <QObject>
#include <QTimer>
#include <QDebug>
class StateMachine : public QObject
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
// 标准 C++ 单例获取方法,允许且仅允许通过instance获取该单例类的实例
static StateMachine* instance() {
static StateMachine _instance;
return &_instance;
}
//getter,const用于确保该方法不会改变类中的任何成员
int count() const { return m_count; }
// 暴露给 QML 的控制接口
Q_INVOKABLE void start() { m_timer->start(); }
Q_INVOKABLE void stop() { m_timer->stop(); }
signals:
//声明一个属于该类的自定义信号
void countChanged();
private:
// 构造函数
explicit StateMachine(QObject *parent = nullptr) : QObject(parent), m_count(0) {
//在类的内部创建一个QTimer对象,并设置更新时间
m_timer = new QTimer(this);
m_timer->setInterval(1000); // 1秒更新一次
//绑定定时器事件,四个参数分别是定时器本身、触发定时器的信号(此处为单次更新,即1000ms触发一次)、处理该事件的类、处理的逻辑(此处为匿名函数)
connect(m_timer, &QTimer::timeout, this, [this]() {
m_count++;
qDebug() << "C++ 逻辑更新中:" << m_count;
// 发射自定义信号
//由于属性count监听了这个信号,所以每次定时器触发,绑定了count的qml组件都会更新一次count的值
emit countChanged();
});
}
QTimer *m_timer;
int m_count;
// 禁用拷贝
StateMachine(const StateMachine&) = delete;
StateMachine& operator=(const StateMachine&) = delete;
};
|
注册到qml
由c++创建的单例类需要使用qmlRegisterSingletonInstance()手动注册到qml中。
| #include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "StateMachine.h" // 引入头文件
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// 参数说明:
// 1. URI: "com.mycompany.logic" (QML 导入时用的包名)
// 2. 主版本号: 1, 次版本号: 0
// 3. QML 类型名: "MySM" (QML 代码里使用的对象名,绑定数据的时候会使用对象名.属性名,必须x开头
// 4. 实例指针: StateMachine::instance()
qmlRegisterSingletonInstance("com.mycompany.logic", 1, 0, "MySM", StateMachine::instance());
// ------------------
//其他逻辑...
}
|
注册到cmake
| # CMakeLists.txt
qt_add_qml_module(appmyapp
URI com.mycompany.logic
VERSION 1.0
SOURCES
StateMachine.h
StateMachine.cpp # 将它放在这里,Qt 会自动处理信号槽的编译
)
|