3、QML语法基础:属性、类型、对象声明、表达式、注释、基本数据类型与变量

QML 是一种声明式语言,用于描述用户界面的结构、外观与行为。与命令式语言(如 C++、JavaScript)不同,QML 的核心思想是“声明对象及其属性”,由 Qt 运行时负责解析并渲染。本章将系统讲解 QML 的语法基石:对象声明、属性绑定、基本数据类型、变量与表达式、注释规则,以及这些元素如何组合成可维护的界面代码。理解这些基础语法,是后续编写自定义组件、处理交互逻辑、优化界面性能的前提。

QML 语法核心结构图 QML 对象声明 属性 (Property) id / width / color / 自定义 属性绑定 (Binding) 值依赖其他属性/表达式 类型 (Type) 基础类型 / 自定义组件 int / string / color / var 枚举 / 注册 C++ 类型 表达式 (Expression) 与注释 JavaScript 表达式 / // 与 /* */ 变量与基本数据类型 property var / 动态类型 / 类型转换 对象嵌套与层级 子对象声明在 {} 内 信号与槽 onClicked: 处理事件 附加属性 Keys.onReturnPressed

上图展示了 QML 语法中几个核心概念之间的关系。对象声明是骨架,属性与类型是血肉,表达式与注释是逻辑与说明,变量与数据类型则是数据流动的载体。下面我们逐一深入剖析。

3.1 对象声明:QML 的骨架

QML 文件的基本单元是对象声明。一个对象声明由类型名和一对花括号组成,花括号内包含该对象的属性、子对象、信号处理器等。对象声明可以嵌套,形成父子层级关系,这与 Qt 对象树的概念一致。

// 一个简单的 Rectangle 对象声明
Rectangle {
    width: 200
    height: 100
    color: "lightblue"

    // 子对象:Text
    Text {
        text: "Hello QML"
        anchors.centerIn: parent
    }
}

在上述代码中,Rectangle 是根对象,Text 是其子对象。子对象通过缩进和花括号嵌套在父对象内部。对象声明时,可以同时设置初始属性值,也可以后续通过 JavaScript 动态修改。

核心要点:对象声明是 QML 的“名词”,描述界面元素“是什么”。每个对象都有类型,类型决定了它可用的属性、信号和方法。根对象通常是 Window、Rectangle 或自定义组件。

3.2 属性:对象的特征描述

属性是 QML 对象的核心数据成员。每个属性都有类型和值。属性分为三类:内置属性(如 width、height、color)、自定义属性(通过 property 关键字声明)、附加属性(如 Keys.onReturnPressed)。

3.2.1 属性绑定

属性绑定是 QML 最强大的特性之一。它允许一个属性的值依赖于另一个属性或表达式,当依赖项变化时,绑定属性会自动更新。绑定使用冒号 : 而非赋值号 =。

Rectangle {
    width: 200
    height: width * 0.5  // 高度始终为宽度的一半
    color: mouseArea.pressed ? "orange" : "lightblue"

    MouseArea {
        id: mouseArea
        anchors.fill: parent
    }
}

这里 height 绑定到 width * 0.5,color 绑定到条件表达式。当 width 变化或鼠标按下状态改变时,相关属性自动刷新。注意:绑定是单向的,从被依赖项到绑定项。如果后续对 height 进行赋值(height = 100),绑定会被破坏。

风险提示:在 JavaScript 中使用 = 给已绑定的属性赋值会移除绑定关系。若需要保留绑定,应使用 Qt.binding() 函数重新创建绑定,或避免直接赋值。

3.2.2 自定义属性

使用 property 关键字可以声明自定义属性,格式为 property type name : value。自定义属性可以指定类型,也可以使用 var 表示任意类型。

Item {
    property int count: 0
    property string userName: "guest"
    property var dataList: [1, 2, 3]
    property bool isVisible: true
}

自定义属性常用于组件内部状态管理或对外暴露接口。当属性值变化时,可以通过 onCountChanged 信号处理器监听变化。

3.3 基本数据类型与变量

QML 支持多种基本数据类型,这些类型在属性声明、函数参数和变量中广泛使用。下表列出了常用类型及其说明:

类型说明示例
int整数width: 100
real浮点数opacity: 0.5
string字符串text: "Hello"
bool布尔值visible: true
color颜色值color: "#FF8800"
var通用类型,可存任意 JS 值property var obj: {a: 1}
list<T>元素类型为 T 的列表property list<int> nums: [1,2,3]
url资源定位符source: "images/logo.png"
date日期时间property date today: new Date()

在 QML 中声明变量通常使用 property 或 var。在 JavaScript 函数内部,可以使用 var、let、const 声明局部变量。QML 是动态类型语言,变量可以随时改变类型。

// 在信号处理器中使用变量
MouseArea {
    onClicked: {
        var temp = width * 0.3
        parent.width = temp
        console.log("新宽度: " + temp)
    }
}
经验建议:对于界面属性,尽量使用具体类型(如 int、color)而非 var,这样 Qt 可以提前优化,且代码可读性更好。var 适合存储复杂对象或临时数据。

3.4 表达式:QML 的逻辑引擎

表达式是 QML 中实现动态行为的关键。表达式可以是简单的算术运算、函数调用、条件判断,也可以是 JavaScript 代码块。表达式通常出现在属性绑定、信号处理器、函数体等位置。

Rectangle {
    width: Math.max(parent.width * 0.5, 200)  // 取较大值
    height: width > 300 ? 200 : 100            // 条件表达式
    color: ["red", "green", "blue"][index % 3] // 数组索引

    property int index: 0
    NumberAnimation on index { from: 0; to: 10; duration: 2000; loops: Animation.Infinite }
}

表达式可以访问对象自身的属性、父对象属性、全局对象(如 Math、Qt)以及已定义的 id。QML 表达式遵循 JavaScript 语法,但有一些限制:不能使用 var 声明语句(在绑定中),不能包含分号分隔的多条语句(除非用 {} 包裹)。

3.5 注释:代码的说明书

QML 支持两种注释风格:单行注释 // 和多行注释 /* ... */。注释不参与执行,仅用于说明代码意图。

// 这是单行注释
Rectangle {
    /* 这是多行注释
       可以跨越多行 */
    width: 100 // 行尾注释
    height: 50
}
最佳实践:在自定义属性、复杂绑定、信号处理器上方添加注释,说明“为什么”而非“是什么”。避免无意义的注释,如 // 设置宽度。

3.6 实战案例:构建一个可交互的计数器组件

下面通过一个完整的计数器组件,综合运用本章语法。该组件包含一个显示数字的文本、两个按钮(增加/减少),并演示属性绑定、自定义属性、信号处理器和表达式。

// Counter.qml
import QtQuick 2.15

Rectangle {
    id: root
    width: 200
    height: 80
    color: "#f0f0f0"
    radius: 8

    // 自定义属性:计数值
    property int count: 0
    // 自定义信号:当计数变化时发出
    signal countChanged(int newCount)

    // 显示区域
    Text {
        id: countText
        anchors.centerIn: parent
        font.pixelSize: 28
        font.bold: true
        text: root.count  // 绑定到根对象的 count 属性
        color: root.count > 0 ? "#2e7d32" : (root.count < 0 ? "#c62828" : "#333333")
    }

    // 增加按钮
    Rectangle {
        id: incBtn
        width: 40; height: 40
        anchors.right: parent.right
        anchors.rightMargin: 10
        anchors.verticalCenter: parent.verticalCenter
        radius: 20
        color: mouseInc.pressed ? "#43a047" : "#66bb6a"

        Text { text: "+"; anchors.centerIn: parent; font.pixelSize: 24; color: "white" }
        MouseArea {
            id: mouseInc
            anchors.fill: parent
            onClicked: {
                root.count++          // 修改属性
                root.countChanged(root.count)  // 发出信号
            }
        }
    }

    // 减少按钮
    Rectangle {
        id: decBtn
        width: 40; height: 40
        anchors.left: parent.left
        anchors.leftMargin: 10
        anchors.verticalCenter: parent.verticalCenter
        radius: 20
        color: mouseDec.pressed ? "#e53935" : "#ef5350"

        Text { text: "-"; anchors.centerIn: parent; font.pixelSize: 24; color: "white" }
        MouseArea {
            id: mouseDec
            anchors.fill: parent
            onClicked: {
                root.count--
                root.countChanged(root.count)
            }
        }
    }

    // 监听 count 变化(演示 onPropertyChanged 语法)
    onCountChanged: {
        console.log("计数器新值: " + newCount)
    }
}

在另一个 QML 文件中使用该组件:

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 300; height: 200
    visible: true

    Counter {
        anchors.centerIn: parent
        onCountChanged: {
            // 外部响应信号
            title = "当前计数: " + newCount
        }
    }
}

该案例展示了:对象嵌套(Rectangle 内包含 Text、MouseArea)、属性绑定(text 绑定到 count)、自定义属性(count)、自定义信号(countChanged)、表达式(条件颜色)、注释(代码说明)。运行后,点击加减按钮,数字实时变化,窗口标题同步更新。

3.7 常见误区与总结

学习 QML 语法时,初学者容易陷入以下误区:

  • 混淆绑定与赋值:在 onClicked 中直接写 width = 100 会破坏原有绑定。应使用 width = Qt.binding(function() { return ... }) 或避免对已绑定属性赋值。
  • 忽略 id 作用域:id 在声明它的 QML 文档内全局可见,但外部文件无法直接访问。跨文件通信应通过属性或信号。
  • 类型不匹配:给 int 属性赋字符串值会静默失败或产生警告。使用 var 时注意类型检查。
  • 注释过度或不足:注释应解释“为什么”,而非逐行翻译代码。
  • 表达式过于复杂:在绑定中写大量逻辑会降低可读性和性能。建议将复杂逻辑提取到函数中。
性能提醒:属性绑定是响应式的,但过度使用绑定(如在一个循环中创建大量绑定)会增加内存和计算开销。对于静态值,直接赋值即可;对于频繁变化的值,考虑使用 Behavior 动画或减少绑定层级。

总结本章,QML 语法以对象声明为骨架,属性与绑定赋予界面动态性,类型系统保证数据正确性,表达式提供逻辑能力,注释提升可维护性。掌握这些基础后,下一阶段将学习布局管理、组件封装与动画系统,逐步构建复杂应用。