信号和槽作为 Qt 的核心机制,在 Qt 编程中有着广泛的应用。同样,QML 也继承了这样的特性 - 信号和信号处理程序 ,只不过叫法上略有不同。
-
信号:来自 QML 对象的通知。
-
信号处理程序:由信号触发的表达式(或函数),也被称为 Qt C++ 中的 “槽”。
信号是事件,信号通过信号处理程序来响应。当一个信号被发射时,相应的信号处理程序就会被调用,在处理程序中放置逻辑(例如:脚本或其他操作)以允许组件响应事件。
一、自定义信号槽的声明与使用
可以通过 signal 关键字来添加自定义信号,语法如下:
signal <name>[([<type> <parameter name>[, ...]])]
在 QML 中自定义信号signal clickButton()
,默认有一个onClickButton
的信号处理程序(槽函数)自动与之相关联,信号处理程序必须在发出信号的对象定义中声明。
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
id: window
visible: true
width: 200
height: 200
title: qsTr("hello world")
// 自定义信号
signal clickButton()
Text {
id: testText
text: qsTr("text")
}
// 发送信号
MouseArea {
anchors.fill: parent
onClicked: clickButton()
}
// 信号处理函数(格式:on<Signal>)
onclickButton: testText.text = "OK";
}
点击窗口可以查看到 text 变成 OK 说明成功。
二、页面间使用信号槽传递消息
先看效果:
CountSetPage.qml
Rectangle {
id: countSetPage
// 1.自定义信号,传递信息
signal clickBtnOk(string strName, string strDate)
// 确定按钮
Button {
text: qsTr("确定")
font.pointSize: 20
anchors.right: parent.right
anchors.rightMargin: 40
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
onClicked: {
if(textAreaName.text == "" && textAreaDate.text == "")
console.log("编辑框必须有输入")
// 2.发送信号(名称 + 剩余天数)
countSetPage.clickBtnOk(textAreaName.text, textAreaDate.text)
// 关闭该页面
countSetPage.destroy()
}
}
}
CountDownPage.qml
Rectangle {
id: countDownPage
// 3.定义接收槽函数
function recvName2Date(strName, strDate){
console.log(strName + " " + strDate);
textName.text = String("距离%1还有").arg(strName)
textDate.text = strDate
}
// 设置按钮
Button {
width: 32
height: 32
text: qsTr("设置")
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
onClicked: {
var component = Qt.createComponent("CountSetPage.qml");
if (component.status === Component.Ready) {
var object = component.createObject(iotMainPage, {x:100, y:50, width:400, height:320})
// 4.使用connect连接信号槽
object.clickBtnOk.connect(recvName2Date);
}
}
}
三、系统自带的信号处理程序
(1)属性改变信号处理程序
当 QML 属性值发生改变时,将自动发出信号。这种类型的信号是属性改变信号,对应的处理程序为属性改变信号处理程序。
属性改变信号处理程序以on<Property>Changed
的形式写入,<Property>
是属性的名称,首字母大写。
例如,MouseArea 类型具有pressed
属性,要在该属性改变时接收通知,需要编写名onPressedChanged
的信号处理程序:
import QtQuick 2.2
Rectangle {
id: rect
width: 100
height: 100
MouseArea {
anchors.fill: parent
onPressedChanged: {
// 鼠标按下/释放
console.log("Mouse area is pressed?", pressed)
}
}
}
/*
点击两下后的输出:
qml: Mouse area is pressed? true
qml: Mouse area is pressed? false
qml: Mouse area is pressed? true
qml: Mouse area is pressed? false
*/
尽管 MouseArea 文档中没有记录名为onPressedChanged
的信号处理程序,但是因为存在 pressed 属性,所以它也被隐式地提供了。
(2)使用 Connections 类型
QtQuick 模块提供了 Connections 类型,用于连接到任意对象的信号。Connections 的优点是: 可以在发射信号的对象外部访问该信号。
例如,上述示例中的onClicked
处理程序可以由根 Rectangle 接收,只需要将其放置在一个 Connections 对象中,并指定 target 为 mouseArea:
import QtQuick 2.2
Rectangle {
id: rect
width: 100; height: 100
MouseArea {
id: mouseArea
anchors.fill: parent
}
Connections {
target: mouseArea
onClicked: {
rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
}
}
}
(3)附加信号处理器
附加信号处理程序所接收的信号来自附加类型,而非声明处理程序的对象。附加,也称为额外。可以简单理解为:对象本身或其基类没有的属性和信号,需要通过外部(附加类型)提供。
要引用附加属性和处理程序,可以使用以下语法形式:
<AttachingType>.<propertyName>
<AttachingType>.on<SignalName>
例如,下面的 Item 可以通过附加类型 Keys 来访问其附加属性和附加信号处理程序:
import QtQuick 2.2
Item {
width: 100; height: 100
focus: true
Keys.enabled: true
Keys.onReturnPressed: console.log("Return key was pressed") // 按下回车键,打印 log 信息
}
enabled 是 Keys 的一个属性,为其赋值为 true(默认值是 true,这里主要用于说明如何使用附加类型的属性),表明启用键盘处理。由于 Keys 提供了returnPressed
信号,所以可以通过onReturnPressed
来引用相应的附加信号处理程序。
类似的附加类型还有很多,例如:Component,它有一个onCompleted
附加信号处理程序,通常用于在创建完成后执行某些 JavaScript 代码:
import QtQuick 2.2
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 信号。相反,Component 对象由 QML 引擎自动附加到 Rectangle 对象,当对象完全创建时,引擎发出completed
信号,从而触发Component.onCompleted
信号处理程序。
四、信号到方法/信号的连接
大部分情况下,通过信号处理程序接收信号就足够了,然而,要将信号连接至多个方法/信号,这对于信号处理程序来说是不可能的(因为信号处理程序的命名必须唯一)。
在 Qt C++ 中,信号与槽的连接方式使用的是QObject::connect()
。相应地,在 QML 中,signal 对象也有一个 connect() 方法,用于将信号连接到一个方法或另一信号。当信号连接到方法时,无论信号何时发出,该方法都将被自动调用。有了这种机制,可以通过方法来接收信号,而无需使用信号处理器。
所以呢,相对于信号处理程序来说,connect() 更加灵活,可以将信号连接至多个方法/信号 。
1、信号到方法的连接
下面,使用 connect() 方法将 messageReceived 信号连接到两个方法:
import QtQuick 2.2
Rectangle {
id: relay
signal messageReceived(string message, string qq)
Component.onCompleted: {
relay.messageReceived.connect(sendToLiLei) // 连接信号和方法
relay.messageReceived.connect(sendToHanMeimei) // 连接信号和方法
relay.messageReceived("Welcome to join us(QML分享与交流)", "26188347") // 发射信号
}
function sendToLiLei(message, qq) {
console.log("Sending to LiLei: " + message + ", " + qq)
}
function sendToHanMeimei(message, qq) {
console.log("Sending to HanMeimei: " + message + ", " + qq)
}
}
广播一下,李雷和韩梅梅就可以很快的找到组织了!
有 connect() 方法,必然也会有相应的 disconnect() 方法,用于删除连接的信号:
Rectangle {
id: relay
//...
function removeLiLeiSignal() {
relay.messageReceived.disconnect(sendToLiLei)
}
}
用法很简单,和 connect() 相同。
2、信号到信号的连接
通过将信号连接到其他信号,connect() 方法可以形成不同的信号链。
import QtQuick 2.2
Rectangle {
id: forwarder
width: 100; height: 100
signal sendToLiLei() // 自定义信号
signal sendToHanMeimei() // 自定义信号
onSendToLiLei: console.log("Send to LiLei") // 信号处理程序
onSendToHanMeimei: console.log("Send to HanMeimei") // 信号处理程序
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: console.log("Clicked")
}
Component.onCompleted: {
// 连接信号至两个信号
mousearea.clicked.connect(sendToLiLei)
mousearea.clicked.connect(sendToHanMeimei)
}
}
每当 MouseArea 的 clicked 信号被发射,sendToLiLei、sendToHanMeimei 信号也将自动发射,从而执行对应的信号处理程序。 这时,输出如下:
Clicked
Send to LiLei
Send to HanMeimei
建议: 在 QML 中,信号和信号处理器程序是一个核心机制,一定要熟练掌握。