信号和处理器事件系统

应用程序和用户界面组件需要相互通信。例如,一个按钮需要知道用户是否点击了它。按钮可能会改变颜色来指示其状态或执行某些逻辑。同时,应用程序也需要知道用户是否在点击按钮。应用程序可能需要将此点击事件转发给其他应用程序。

QML 有一个信号和处理器机制,其中 信号 是事件,通过 信号处理器 来响应信号。当信号被发出时,相应的信号处理器会被调用。在处理器中放置逻辑,如脚本或其他操作,允许组件对事件做出响应。

使用信号处理器接收信号

要接收特定对象在发出特定信号时的通知,对象定义应声明一个名为 on<Signal> 的信号处理器,其中 <Signal> 是信号名称,首字母大写。信号处理器应包含在调用信号处理器时执行的 JavaScript 代码。

例如,来自 Qt Quick Controls 模块的 Button 类型有一个 clicked 信号,每当按钮被点击时都会发出。在这种情况下,接收此信号的信号处理器应该是 onClicked。在下面的示例中,每当按钮被点击时,都会调用 onClicked 处理器,应用随机颜色给父 Rectangle

import QtQuick
import QtQuick.Controls

Rectangle {
    id: rect
    width: 250; height: 250

    Button {
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        text: "Change color!"
        onClicked: {
            rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
        }
    }
}

注意: 虽然信号处理器看起来有点像 JavaScript 函数,但不应直接调用它们。如果需要在信号处理器和其他功能之间共享代码,则将其重构为单独的函数。否则,如果需要调用信号处理器,请始终发出信号。可以为同一信号有多个处理器,在不同的范围内。

属性更改信号处理器

当 QML 属性的值更改时,会自动发出信号。这种类型的信号是 属性更改信号,这些信号的信号处理器写作 on<Property>Changed 的形式,其中 <Property> 是属性名称,首字母大写。

例如,MouseArea 类型有一个 pressed 属性。要接收此属性更改时的通知,应编写一个名为 onPressedChanged 的信号处理器。

import QtQuick

Rectangle {
    id: rect
    width: 100; height: 100

    TapHandler {
        onPressedChanged: console.log("taphandler pressed?", pressed)
    }
}

尽管 TapHandler 文档中没有记录名为 onPressedChanged 的信号处理器,但该信号通过 pressed 属性的存在隐式提供。

信号参数

信号可能有参数。要访问这些参数,应将函数分配给处理器。箭头函数和匿名函数都适用。

以下示例中,考虑一个具有错误发生信号(参见将信号添加到自定义 QML 类型以获取有关如何将信号添加到 QML 组件的更多信息)的状态组件。

// Status.qml
import QtQuick

Item {
    id: myitem

    signal errorOccurred(message: string, line: int, column: int)
}
Status {
    onErrorOccurred: (mgs, line, col) => console.log(`${line}:${col}: ${msg}`)
}

注意:函数中形式参数的名称不必与信号中的一致。

如果您不需要处理所有参数,可以省略后面的参数。

Status {
    onErrorOccurred: message => console.log(message)
}

不能省略您感兴趣的起始参数,但是可以使用一些占位符名称来告诉读者这些参数不重要。

Status {
    onErrorOccurred: (_, _, col) => console.log(`Error happened at column ${col}`)
}

注意:除了使用函数外,还可以使用普通的代码块,但这不是鼓励的做法。在这种情况下,所有信号参数都会注入到代码块的作用域中。然而,这可能会使代码难以阅读,因为它不清楚参数的来源,并在 QML 引擎中导致查找速度变慢。以这种方式注入参数已被弃用,并且如果实际上使用了参数,将会在运行时产生警告。

使用 Connections 类型

在某些情况下,可能需要在发出信号的以外对象中访问一个信号。为此目的,QtQuick 模块提供了Connections 类型,用于连接到任意对象的信号。一个Connections 对象可以接收其指定的 target 的任何信号。

例如,前面的例子中的 onClicked 处理程序可以被放在将 Rectangle 设置为 Connections 对象的目标的 onClicked 处理程序中,而不是由根 Rectangle 来接收。

import QtQuick
import QtQuick.Controls

Rectangle {
    id: rect
    width: 250; height: 250

    Button {
        id: button
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        text: "Change color!"
    }

    Connections {
        target: button
        function onClicked() {
            rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
        }
    }
}

附加信号处理器

附加信号处理器 从一个 attaching 类型 接收信号,而不是从声明处理器的对象中。

例如,Component.onCompleted 是一个附加信号处理器。它通常用于在创建过程完成时执行一些 JavaScript 代码。以下是一个示例

import QtQuick

Rectangle {
    width: 200; height: 200
    color: Qt.rgba(Qt.random(), Qt.random(), Qt.random(), 1)

    Component.onCompleted: {
        console.log("The rectangle's color is", color)
    }
}

onCompleted 处理器并不是在响应来自 Rectangle 类型的 completed 信号。相反,由 QML 引擎自动将一个具有 completed 信号的 Component attaching 类型 对象 附加Rectangle 对象上。当 Rectangle 对象创建时,引擎会发出这个信号,从而触发 Component.onCompleted 信号处理器。

附加信号处理器允许对象通知与每个对象都十分重要的特定信号。如果没有 Component.onCompleted 附加信号处理器,例如,对象就不能在没有注册某些特殊对象的某些特殊信号的情况下接收此通知。《i>附加信号处理器 机制允许对象接收特定信号而无需额外的代码。

有关附加信号处理器的更多信息,请参阅附加属性和附加信号处理器

将信号添加到自定义 QML 类型

可以通过使用 signal 关键字将信号添加到自定义 QML 类型。

定义新信号的语法是

signal <name>[([<type> <parameter name>[, ...]])]

通过将信号作为方法调用来发射信号。

例如,下面的代码定义在一个名为SquareButton.qml的文件中。根Rectangle对象有一个activated信号,每当子TapHandlertapped时该信号就会被发出。在这个特定的例子中,激活信号会发布鼠标点击的x和y坐标。

// SquareButton.qml
import QtQuick

Rectangle {
    id: root

    signal activated(real xPosition, real yPosition)
    property point mouseXY
    property int side: 100
    width: side; height: side

    TapHandler {
        id: handler
        onTapped: root.activated(root.mouseXY.x, root.mouseXY.y)
        onPressedChanged: root.mouseXY = handler.point.position
    }
}

现在,任何SquareButton的对象都可以通过一个onActivated信号处理程序将信号连接到activated信号。

// myapplication.qml
SquareButton {
    onActivated: (xPosition, yPosition) => console.log(`Activated at {xPosition}, ${yPosition}`)
}

有关为自定义QML类型编写信号的详细信息,请参阅信号属性

将信号连接到方法和信号

信号对象有一个connect()方法来连接信号,无论是连接到方法还是另一个信号。当信号连接到方法时,每当该信号被发出时,该方法就会自动调用。这种机制使得信号可以被方法而不是信号处理程序接收。

下面,使用了connect()方法将messageReceived信号连接到三个方法。

import QtQuick

Rectangle {
    id: relay

    signal messageReceived(string person, string notice)

    Component.onCompleted: {
        relay.messageReceived.connect(sendToPost)
        relay.messageReceived.connect(sendToTelegraph)
        relay.messageReceived.connect(sendToEmail)
        relay.messageReceived("Tom", "Happy Birthday")
    }

    function sendToPost(person: string, notice: string) {
        console.log(`Sending to post: ${person}, ${notice}`)
    }
    function sendToTelegraph(person: string, notice: string) {
        console.log(`Sending to telegraph: ${person}, ${notice}`)
    }
    function sendToEmail(person: string, notice: string) {
        console.log(`Sending to email: ${person}, ${notice}`)
    }
}

在许多情况下,通过信号处理程序接收信号而不是使用connect函数足够了。但是,使用connect方法可以使用户将信号接收多个方法,正如前面所展示的,这是使用信号处理程序不可能实现的,因为它们必须具有唯一的名字。此外,当连接信号到动态创建的对象时,connect方法也非常有用。

有一个相应的disconnect()方法用于删除已连接的信号。

Rectangle {
    id: relay
    //...

    function removeTelegraphSignal() {
        relay.messageReceived.disconnect(sendToTelegraph)
    }
}

信号到信号连接

通过将信号连接到其他信号,connect()方法可以形成不同的信号链。

import QtQuick

Rectangle {
    id: forwarder
    width: 100; height: 100

    signal send()
    onSend: console.log("Send clicked")

    TapHandler {
        id: mousearea
        anchors.fill: parent
        onTapped: console.log("Mouse clicked")
    }

    Component.onCompleted: {
        mousearea.tapped.connect(send)
    }
}

每当TapHandlertapped信号被发出时,send信号也会自动被发出。

output:
    MouseArea clicked
    Send clicked

注意:连接到函数对象的连接将保持活跃,直到发出信号的发送者还活着。这种行为类似于C++中QObject::connect的3个参数版本。

Window {
    visible: true
    width: 400
    height: 400

    Item {
        id: item
        property color globalColor: "red"

        Button {
            text: "Change global color"
            onPressed: {
                item.globalColor = item.globalColor === Qt.color("red") ? "green" : "red"
            }
        }

        Button {
            x: 150
            text: "Clear rectangles"
            onPressed: repeater.model = 0
        }

        Repeater {
            id: repeater
            model: 5
            Rectangle {
                id: rect
                color: "red"
                width: 50
                height: 50
                x: (width + 2) * index + 2
                y: 100
                Component.onCompleted: {
                    if (index % 2 === 0) {
                        item.globalColorChanged.connect(() => {
                            color = item.globalColor
                        })
                    }
                }
            }
        }
    }
}

在上面的虚构示例中,目标是翻转每个偶数矩形的颜色,以跟随某种全局颜色。为了达到这个目的,对于每个偶数矩形,都将全局颜色已更改信号连接到一个设置矩形颜色的函数。当矩形存在时,这按预期工作。然而,一旦按下清除按钮,矩形就会消失,但处理信号的函数仍然会在信号每次发出时被调用。这可以通过函数尝试在后台更改全局颜色时抛出的错误消息来看到。

在当前的设置中,只有在包含全局颜色的项被销毁时,连接才会被销毁。为了防止连接持续存在,可以在矩形被销毁时显式断开连接。

© 2024 Qt公司有限公司。本中的文档贡献权归各自的所有者所有。本中提供的文档是根据自由软件基金会发布的GNU自由文档许可版本1.3许可的。Qt和相关商标是芬兰的Qt公司或其他全球国家的商标。所有其他商标均为各自所有者的财产。