Skip to content

qml基本语法

qml是一种声明式语言,可以无缝嵌入JavaScript,将UI视为对象树。

组件基本结构

组件是qml的基本单位,以大写字母开头。

1
2
3
4
5
6
Rectangle {
    id: statusBubble  // id 是唯一标识符
    width: 120        // 宽度
    height: 40        // 高度
    color: "#333333"  // 颜色
}

Anchors布局

除了写死坐标,也使用Anchors(锚点)对布局进行定位。

1
2
3
4
5
6
7
8
9
Text {
    anchors.centerIn: parent  // 让这段文字在它的父组件里绝对居中
    text: "📖 学习中..."
}

Rectangle {
    anchors.top: bgRect.bottom // 让这个组件的顶部,贴着 bgRect 这个组件的底部
    anchors.topMargin: 10      // 并且往下留出 10 像素的间距
}

parent是qml内置的关键字,代表当前组件的父级。

事件响应

在 QML 中,如果发生了某个事件,它会发出一个“信号”(Signal),通过在组件的on\<SignalName>来监听和接收事件,响应的逻辑按js语法写。

1
2
3
4
5
6
7
8
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的,在任意程序段都可以监听该信号,但仅允许信号所属的类及其子类发射它。

1
2
3
4
5
6
7
// 假设这是定时器到期后的处理函数
void MyStateMachine::onTimeout() {
    m_timerValue++; 

    // 手动触发信号,告诉 QML:值变了,快去调用 READ 函数刷新界面!
    emit timerValueChanged(int newValue); 
}

虽然 QML 绑定不需要信号带参数(它会自动重新调用 READ 函数),但规范的做法通常是让信号带上新值,例如 void timerValueChanged(int newValue);

属性绑定

QML允许将组件的属性绑定到宿主程序类的变量上。绑定需要按以下两个部分分别处理:

  1. 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_ELEMENT:使qml识别该类。
    • QML_SINGLETON:声明该类为单例。
    • Q_PROPERTY:将方法、变量包装为一个属性,暴露给qml组件,其中的每个参数使用空格分隔。

      格式:Q_PROPERTY(属性类型 属性名字 READ 读取属性的接口名 NOTIFY 通知信号)

      • 属性类型:通常是int、QString或自定义枚举,如果是自定义枚举需要用Q_ENUM导出。
      • 读取属性的接口:供 QML 自动调用来获取当前值。
      • 通知信号:qml会监听这个信号(事件),当监听到该信号时,读该C++类中对应属性的数据并更新到qml组件。

        因此,对于需要随c++逻辑更新的数据,每次更新后要发射(emit)该通知信号。

  2. QML组件的调用

    直接将属性赋值给组件的属性。

    1
    2
    3
    4
    5
    6
    Text {
        text: "当前状态: " + BehaviorScheduler.currentState 
    }
    ProgressBar {
        value: BehaviorScheduler.taskProgress  
    }
    

单例类的定时数据更新

单例允许且仅允许在整个应用程序中共享同一个对象实例,常用于管理全局配置、用户信息、主题样式跨页面的逻辑处理

数据更新主要依赖于Q_Timer辅助实现。

实现

在类定义的开始使用QML_SINGLETON宏声明为单例,或通过手写instance()方法实现。
更推荐后者,便于管理生命周期。使用宏创建的单例所属于QML,在c++代码中调用较为困难;同时生命周期将跟随QML。

一种更合理的实践是将二者结合,这时不需要使用QML_ELEMENTQML_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

1
2
3
4
5
6
7
8
# CMakeLists.txt
qt_add_qml_module(appmyapp
    URI com.mycompany.logic
    VERSION 1.0
    SOURCES
        StateMachine.h
        StateMachine.cpp  # 将它放在这里Qt 会自动处理信号槽的编译
)