Qt Quick:附加属性、函数与信号详解

Qt Quick:附加属性、函数与信号详解

  • [Qt Quick 学习笔记:附加属性、函数与信号详解](#Qt Quick 学习笔记:附加属性、函数与信号详解)
  • [二、附加属性(Attached Properties)](#二、附加属性(Attached Properties))
    • [2.1 什么是附加属性](#2.1 什么是附加属性)
    • [2.2 学习附加属性时要注意什么](#2.2 学习附加属性时要注意什么)
    • [2.3 附加属性的基本语法](#2.3 附加属性的基本语法)
    • [2.4 附加信号处理器的写法](#2.4 附加信号处理器的写法)
    • [2.5 附加属性示例:ListView 当前项高亮](#2.5 附加属性示例:ListView 当前项高亮)
    • [2.6 附加信号处理器示例:Component.onCompleted](#2.6 附加信号处理器示例:Component.onCompleted)
    • [2.7 附加属性的使用注意事项](#2.7 附加属性的使用注意事项)
  • 三、函数(Function)
    • [3.1 什么是函数](#3.1 什么是函数)
    • [3.2 为什么要使用函数](#3.2 为什么要使用函数)
    • [3.3 函数的基本语法](#3.3 函数的基本语法)
    • [3.4 函数的返回值](#3.4 函数的返回值)
    • [3.5 函数示例:按钮控制文本加减](#3.5 函数示例:按钮控制文本加减)
    • [3.6 多参数函数示例](#3.6 多参数函数示例)
    • [3.7 函数中可以定义变量](#3.7 函数中可以定义变量)
    • [3.8 函数可以嵌套调用](#3.8 函数可以嵌套调用)
    • [3.9 使用函数的建议](#3.9 使用函数的建议)
  • 四、信号(Signal)
    • [4.1 什么是信号](#4.1 什么是信号)
    • [4.2 信号处理器是什么](#4.2 信号处理器是什么)
    • [4.3 信号处理器的命名规则](#4.3 信号处理器的命名规则)
    • [4.4 使用内置信号的示例](#4.4 使用内置信号的示例)
  • 五、自定义信号
    • [5.1 为什么要自定义信号](#5.1 为什么要自定义信号)
    • [5.2 自定义信号的基本语法](#5.2 自定义信号的基本语法)
    • [5.3 自定义信号示例](#5.3 自定义信号示例)
    • [5.4 信号命名规则](#5.4 信号命名规则)
  • 六、属性值改变信号
    • [6.1 属性变化时会自动发出信号](#6.1 属性变化时会自动发出信号)
    • [6.2 示例:监听文本变化](#6.2 示例:监听文本变化)
    • [6.3 常见属性变化处理器](#6.3 常见属性变化处理器)
  • [七、Connections 的使用](#七、Connections 的使用)
    • [7.1 什么是 Connections](#7.1 什么是 Connections)
    • [7.2 Connections 的基本写法](#7.2 Connections 的基本写法)
    • [7.3 Connections 示例](#7.3 Connections 示例)
    • [7.4 新旧写法说明](#7.4 新旧写法说明)
      • [Qt6 推荐写法](#Qt6 推荐写法)
      • 旧写法
  • [八、connect() 函数](#八、connect() 函数)
    • [8.1 connect() 是什么](#8.1 connect() 是什么)
    • [8.2 connect() 的基本示例](#8.2 connect() 的基本示例)
    • [8.3 一个信号可以连接多个函数](#8.3 一个信号可以连接多个函数)
  • [九、综合示例:函数、信号与 Connections 一起使用](#九、综合示例:函数、信号与 Connections 一起使用)
  • 十、常见错误整理
    • [10.1 把附加属性当普通属性乱用](#10.1 把附加属性当普通属性乱用)
    • [10.2 信号处理器名字写错](#10.2 信号处理器名字写错)
    • [10.3 定义了函数却没调用](#10.3 定义了函数却没调用)
    • [10.4 自定义信号定义了但没有发射](#10.4 自定义信号定义了但没有发射)
    • [10.5 混淆 `Connections` 和对象内部的 `onXxx`](#10.5 混淆 Connections 和对象内部的 onXxx)
  • 十一、核心知识对比表
  • 十二、快速复习口诀
  • 十三、复习问题
  • 十四、本节总结

Qt Quick 学习笔记:附加属性、函数与信号详解

本文整理 Qt Quick 开发教程中关于 附加属性(Attached Properties)函数(Function)信号(Signal) 的核心知识点。

这几个内容在 QML 中非常常见,尤其是在界面交互、组件通信和事件处理时,会频繁使用。


一、本节内容概览

本文主要包括以下内容:

  • 什么是附加属性
  • 附加属性的语法与使用方式
  • 附加信号处理器
  • QML 中的函数定义与调用
  • 函数参数、返回值与使用建议
  • 什么是信号
  • 信号处理器的写法
  • 自定义信号
  • 属性值改变信号
  • Connections 的使用
  • connect() 函数的作用

二、附加属性(Attached Properties)

2.1 什么是附加属性

附加属性本质上也是一种属性,只不过它不是普通对象自身直接声明出来的属性,而是:

某些特定类型,为对象额外提供的一组属性或信号处理能力。

可以简单理解成:

text 复制代码
对象除了自己的普通属性之外,还能"额外拿到"的属性或信号处理器。

例如在 ListView 的委托中,经常会用到:

qml 复制代码
ListView.isCurrentItem

这里的 isCurrentItem 就是 ListView 提供的附加属性。


2.2 学习附加属性时要注意什么

学习附加属性时,不需要过度纠结"它和普通属性到底有什么本质区别"。

更重要的是记住两点:

  1. 它的写法是什么;
  2. 它通常在什么场景下使用。

一句话理解:

会用,比死扣概念更重要。


2.3 附加属性的基本语法

附加属性的写法通常是:

qml 复制代码
附加类型.属性名

也就是:

qml 复制代码
AttachingType.propertyName

例如:

qml 复制代码
ListView.isCurrentItem

其中:

  • ListView:附加类型
  • isCurrentItem:附加属性

2.4 附加信号处理器的写法

附加类型不仅可以提供附加属性,有时还会提供附加信号处理器。

其写法一般为:

qml 复制代码
附加类型.on信号名

例如:

qml 复制代码
Component.onCompleted

这里:

  • Component 是附加类型
  • onCompleted 是附加信号处理器

2.5 附加属性示例:ListView 当前项高亮

ListView 中,委托项可以通过 ListView.isCurrentItem 判断自己是否是当前选中项。

示例:

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    ListView {
        anchors.fill: parent
        model: ["Qt", "QML", "C++", "JavaScript"]
        currentIndex: 0

        delegate: Rectangle {
            width: parent.width
            height: 50
            color: ListView.isCurrentItem ? "lightgreen" : "lightgray"

            Text {
                anchors.centerIn: parent
                text: modelData
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    ListView.view.currentIndex = index
                }
            }
        }
    }
}

代码说明

这里最核心的一句是:

qml 复制代码
color: ListView.isCurrentItem ? "lightgreen" : "lightgray"

表示:

  • 如果当前委托项是选中项,则显示绿色;
  • 否则显示灰色。

2.6 附加信号处理器示例:Component.onCompleted

Component.onCompleted 会在组件创建完成后执行。

示例:

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    ListModel {
        id: nameModel

        Component.onCompleted: {
            append({ name: "Tom" })
            append({ name: "Jack" })
            append({ name: "Lucy" })
        }
    }

    ListView {
        anchors.fill: parent
        model: nameModel

        delegate: Text {
            text: name
            font.pixelSize: 20
        }
    }
}

代码说明

qml 复制代码
Component.onCompleted

表示:

ListModel 构建完成后,自动执行里面的代码。

因此这里会在模型创建完成后自动追加数据。


2.7 附加属性的使用注意事项

1)通常依赖特定场景

附加属性不是所有地方都能随便使用,它一般依赖特定对象或特定上下文。

例如:

  • ListView.isCurrentItem 一般用于 ListView 的委托内部;
  • Component.onCompleted 用于对象创建完成后的处理。

2)委托中经常见到附加属性

ListViewGridViewPathView 等视图组件中,附加属性在代理对象里非常常见。

3)不用把它想得太复杂

开发时你可以把它看成"特殊场景下可直接使用的一类额外属性"。


三、函数(Function)

3.1 什么是函数

QML 中的函数,本质上就是:

text 复制代码
函数 = 方法

它和 Java、C、C++、JavaScript 中的函数本质类似,都是用来:

  • 封装逻辑
  • 处理运算
  • 响应事件
  • 提高代码可读性
  • 避免把大量逻辑写在一行里

3.2 为什么要使用函数

如果所有逻辑都直接写进信号处理器里,代码会越来越乱。

例如这种写法虽然能运行,但可读性一般:

qml 复制代码
onClicked: {
    label.text = Number(label.text) + 1
}

如果逻辑复杂一点,建议单独封装成函数:

qml 复制代码
onClicked: {
    increaseValue()
}

这样结构会更清晰。


3.3 函数的基本语法

QML 中定义函数的写法如下:

qml 复制代码
function 函数名(参数列表) {
    函数体
}

例如:

qml 复制代码
function sayHello() {
    console.log("Hello QML")
}

带参数的例子:

qml 复制代码
function add(a, b) {
    return a + b
}

3.4 函数的返回值

函数可以有返回值,也可以没有返回值。

例如:

qml 复制代码
function add(a, b) {
    return a + b
}

调用:

qml 复制代码
var result = add(3, 5)
console.log(result)

输出:

text 复制代码
8

如果函数没有 return,则一般不返回有效结果。


3.5 函数示例:按钮控制文本加减

下面是一个常见示例,通过函数控制文本值加减。

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    Column {
        anchors.centerIn: parent
        spacing: 12

        Label {
            id: valueLabel
            text: "0"
            font.pixelSize: 28
        }

        Row {
            spacing: 10

            Button {
                text: "加"
                onClicked: {
                    increaseValue()
                }
            }

            Button {
                text: "减"
                onClicked: {
                    decreaseValue()
                }
            }
        }
    }

    function increaseValue() {
        valueLabel.text = Number(valueLabel.text) + 1
    }

    function decreaseValue() {
        valueLabel.text = Number(valueLabel.text) - 1
    }
}

代码说明

这里定义了两个函数:

qml 复制代码
function increaseValue() { ... }
function decreaseValue() { ... }

按钮点击后分别调用不同函数,实现数值变化。


3.6 多参数函数示例

函数也可以有多个参数。

qml 复制代码
function addNumbers(a, b, c) {
    return a + b + c
}

调用:

qml 复制代码
var result = addNumbers(10, 20, 30)
console.log("结果:", result)

输出:

text 复制代码
结果: 60

3.7 函数中可以定义变量

函数内部可以定义局部变量:

qml 复制代码
function calculateArea(width, height) {
    var area = width * height
    return area
}

调用:

qml 复制代码
console.log(calculateArea(10, 20))

输出:

text 复制代码
200

3.8 函数可以嵌套调用

例如:

qml 复制代码
function add(a, b) {
    return a + b
}

function calculate() {
    var result = add(10, 20)
    console.log(result)
}

这里 calculate() 调用了 add()


3.9 使用函数的建议

1)界面逻辑可以放在函数里

例如:

  • 数值加减
  • UI 状态切换
  • 控件显示隐藏
  • 文本更新
  • 简单数据处理

2)复杂逻辑不要都写在 QML 里

QML 更适合做:

  • 界面描述
  • 轻量交互
  • UI 层逻辑

对于复杂业务逻辑,通常更适合放到 C++ 层处理。

一句话:

QML 负责界面,复杂业务逻辑尽量交给 C++。


四、信号(Signal)

4.1 什么是信号

信号可以理解为:

对象在某个事件发生时发出的通知。

也可以把它看成:

  • event(事件)
  • 消息通知
  • 某种"触发器"

例如按钮被点击时,就会发出点击相关的信号。


4.2 信号处理器是什么

信号处理器就是:

当信号发出后,用来接收并处理这个信号的代码块。

最常见的写法就是:

qml 复制代码
onClicked: {
    console.log("按钮被点击了")
}

其中:

  • clicked 是信号
  • onClicked 是信号处理器

4.3 信号处理器的命名规则

信号处理器的命名规则非常固定:

text 复制代码
on + 信号名首字母大写

例如:

信号 处理器
clicked onClicked
pressed onPressed
released onReleased
textChanged onTextChanged

4.4 使用内置信号的示例

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    Label {
        id: infoLabel
        anchors.centerIn: parent
        text: "点击区域后更新文本"
    }

    MouseArea {
        anchors.fill: parent

        onClicked: {
            infoLabel.text = "你点击了界面"
            console.log("MouseArea 被点击")
        }
    }
}

五、自定义信号

5.1 为什么要自定义信号

除了系统自带信号之外,我们还可以自己定义信号,用于:

  • 自定义组件通信
  • 主动通知外部对象
  • 传递参数
  • 解耦逻辑

5.2 自定义信号的基本语法

定义信号的语法:

qml 复制代码
signal 信号名

带参数的写法:

qml 复制代码
signal 信号名(参数列表)

例如:

qml 复制代码
signal positionChanged(int x, int y)

5.3 自定义信号示例

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    signal positionChanged(int x, int y)

    MouseArea {
        anchors.fill: parent

        onPressed: function(mouse) {
            positionChanged(mouse.x, mouse.y)
        }
    }

    onPositionChanged: function(x, y) {
        console.log("当前点击坐标:", x, y)
    }
}

代码说明

这里定义了一个自定义信号:

qml 复制代码
signal positionChanged(int x, int y)

当按下鼠标时触发:

qml 复制代码
positionChanged(mouse.x, mouse.y)

然后通过:

qml 复制代码
onPositionChanged: function(x, y) { ... }

接收这个信号。


5.4 信号命名规则

信号命名一般建议:

  • 使用小驼峰命名
  • 同一作用域内不能重名
  • 名字尽量表达实际含义

例如:

qml 复制代码
signal loginSuccess()
signal valueChanged(int value)
signal positionChanged(int x, int y)

六、属性值改变信号

6.1 属性变化时会自动发出信号

QML 中很多属性在值发生变化时,都会自动产生对应的变化信号。

例如:

qml 复制代码
text

对应的变化信号处理器就是:

qml 复制代码
onTextChanged

6.2 示例:监听文本变化

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    TextField {
        id: inputField
        anchors.centerIn: parent
        placeholderText: "请输入内容"

        onTextChanged: {
            console.log("当前文本:", text)
        }
    }
}

只要输入框内容变化,就会自动执行:

qml 复制代码
onTextChanged

6.3 常见属性变化处理器

属性 处理器
text onTextChanged
width onWidthChanged
height onHeightChanged
x onXChanged
y onYChanged
visible onVisibleChanged

例如:

qml 复制代码
Rectangle {
    width: 100

    onWidthChanged: {
        console.log("宽度变化:", width)
    }
}

七、Connections 的使用

7.1 什么是 Connections

Connections 用于:

连接外部对象的信号,并进行统一处理。

它特别适合以下情况:

  • 想在当前对象中监听另一个对象的信号;
  • 不方便直接在对象内部写 onXxx
  • 一个对象需要集中处理外部多个信号;
  • QML 和 C++ 对象之间通信。

7.2 Connections 的基本写法

qml 复制代码
Connections {
    target: 目标对象

    function on信号名() {
        // 处理逻辑
    }
}

例如:

qml 复制代码
Connections {
    target: myButton

    function onClicked() {
        console.log("按钮被点击了")
    }
}

7.3 Connections 示例

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    Button {
        id: myButton
        text: "点击我"
        anchors.centerIn: parent
    }

    Connections {
        target: myButton

        function onClicked() {
            console.log("通过 Connections 接收到点击信号")
        }
    }
}

7.4 新旧写法说明

Qt6 推荐写法

qml 复制代码
Connections {
    target: myButton

    function onClicked() {
        console.log("clicked")
    }
}

旧写法

qml 复制代码
Connections {
    target: myButton

    onClicked: {
        console.log("clicked")
    }
}

在新版本 Qt 中,通常更推荐使用 function 写法。


八、connect() 函数

8.1 connect() 是什么

connect() 用于:

在运行时,把某个信号和某个函数动态连接起来。

它的特点是更加灵活,尤其适合:

  • 动态创建对象
  • 动态绑定信号
  • 一个信号连接多个函数
  • 更灵活地控制信号与槽关系

8.2 connect() 的基本示例

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400
    height: 300
    visible: true

    signal testSignal()

    Component.onCompleted: {
        testSignal.connect(handleSignal)
        testSignal.connect(printAnotherMessage)

        testSignal()
    }

    function handleSignal() {
        console.log("handleSignal 被调用")
    }

    function printAnotherMessage() {
        console.log("另一个处理函数也被调用")
    }
}

代码说明

这里做了三件事:

  1. 定义信号:
qml 复制代码
signal testSignal()
  1. 动态连接函数:
qml 复制代码
testSignal.connect(handleSignal)
testSignal.connect(printAnotherMessage)
  1. 发射信号:
qml 复制代码
testSignal()

结果是两个函数都会被调用。


8.3 一个信号可以连接多个函数

这是 connect() 很灵活的地方。

例如:

qml 复制代码
mySignal.connect(funcA)
mySignal.connect(funcB)
mySignal.connect(funcC)

mySignal() 发出时:

  • funcA
  • funcB
  • funcC

都会依次执行。


九、综合示例:函数、信号与 Connections 一起使用

下面给出一个综合示例,把本节内容串起来。

qml 复制代码
import QtQuick
import QtQuick.Controls

ApplicationWindow {
    id: root
    width: 500
    height: 360
    visible: true
    title: "函数、信号与 Connections 示例"

    signal countChangedSignal(int value)

    property int count: 0

    Column {
        anchors.centerIn: parent
        spacing: 16

        Label {
            id: countLabel
            text: "当前值:" + count
            font.pixelSize: 24
        }

        Row {
            spacing: 10

            Button {
                id: addButton
                text: "加一"

                onClicked: {
                    increaseCount()
                }
            }

            Button {
                id: subButton
                text: "减一"

                onClicked: {
                    decreaseCount()
                }
            }
        }
    }

    function increaseCount() {
        count += 1
        countChangedSignal(count)
    }

    function decreaseCount() {
        count -= 1
        countChangedSignal(count)
    }

    onCountChangedSignal: function(value) {
        console.log("自定义信号收到的值:", value)
    }

    Connections {
        target: addButton

        function onClicked() {
            console.log("Connections:点击了加一按钮")
        }
    }

    onCountChanged: {
        countLabel.text = "当前值:" + count
        console.log("属性 count 变化为:", count)
    }

    Component.onCompleted: {
        console.log("窗口创建完成")
    }
}

十、常见错误整理

10.1 把附加属性当普通属性乱用

错误理解:

qml 复制代码
Rectangle {
    color: ListView.isCurrentItem ? "red" : "blue"
}

如果当前对象不是 ListView 的委托,这样写可能没有意义。


10.2 信号处理器名字写错

错误:

qml 复制代码
onclicked: {
}

正确:

qml 复制代码
onClicked: {
}

注意:

  • on 开头
  • 信号名首字母大写

10.3 定义了函数却没调用

例如:

qml 复制代码
function increaseValue() {
    console.log("增加")
}

如果没有地方调用它,它就不会执行。


10.4 自定义信号定义了但没有发射

例如:

qml 复制代码
signal loginSuccess()

如果你从来没有调用:

qml 复制代码
loginSuccess()

那么信号处理器也不会执行。


10.5 混淆 Connections 和对象内部的 onXxx

如果只是处理对象自己的信号,通常直接写:

qml 复制代码
Button {
    onClicked: {
        console.log("clicked")
    }
}

如果要在外部监听它的信号,再用:

qml 复制代码
Connections {
    target: someButton
}

十一、核心知识对比表

知识点 语法示例 作用
附加属性 ListView.isCurrentItem 获取附加到对象上的额外属性
附加信号处理器 Component.onCompleted 在特定时机执行代码
函数 function add(a,b){} 封装逻辑、提高可读性
内置信号处理器 onClicked 处理对象发出的内置信号
自定义信号 signal positionChanged(int x, int y) 自定义事件通知
属性变化处理器 onTextChanged 监听属性值变化
Connections Connections { target: xxx } 监听外部对象的信号
connect() mySignal.connect(func) 动态绑定信号与函数

十二、快速复习口诀

text 复制代码
附加属性:特殊场景下可直接使用的额外属性。

函数:用来封装逻辑,减少代码混乱。

信号:对象发生事件时发出的通知。

信号处理器:用 onXxx 来接收和处理信号。

Connections:在外部监听对象信号。

connect():动态把信号和函数关联起来。

十三、复习问题

学完本节后,可以尝试回答下面这些问题:

  1. 什么是附加属性?
  2. ListView.isCurrentItem 通常用在什么场景?
  3. Component.onCompleted 的作用是什么?
  4. QML 中函数的基本写法是什么?
  5. 为什么复杂逻辑不建议全部写在 QML 里?
  6. 什么是信号?
  7. onClicked 属于什么?
  8. 如何自定义一个带参数的信号?
  9. onTextChanged 是哪类信号处理器?
  10. Connections 适合在什么情况下使用?
  11. Qt6 中 Connections 推荐使用哪种写法?
  12. connect() 和普通 onXxx 写法有什么区别?

十四、本节总结

这一节可以归纳为三句话:

附加属性

text 复制代码
附加属性是在特定场景下,类型额外提供给对象使用的属性或信号处理能力。

函数

text 复制代码
函数用于封装逻辑,让界面代码更清晰、更好维护。

信号

text 复制代码
信号用于通知事件发生,信号处理器负责接收并处理这些通知。

一句话总结:

在 QML 中,附加属性解决特殊场景下的属性访问问题,函数负责封装逻辑,信号负责驱动交互和通信。