QML 訊號和訊號處理器程式
簡述
訊號和槽作為 Qt 的核心機制,在 Qt 程式設計中有著廣泛的應用。同樣,QML 也繼承了這樣的特性 - 訊號和訊號處理程式 ,只不過叫法上略有不同。
- 訊號:來自 QML 物件的通知。
- 訊號處理程式:由訊號觸發的表示式(或函式),也被稱為 Qt C++ 中的“槽”。
訊號是事件,訊號通過訊號處理程式來響應。當一個訊號被髮射時,相應的訊號處理程式就會被呼叫,在處理程式中放置邏輯(例如:指令碼或其他操作)以允許元件響應事件。
|
使用訊號處理程式接收訊號
訊號是來自物件的通知,表示發生了某些事件(例如:滑鼠已點選、屬性已更改、動畫已啟動/停止)。每當特定訊號被髮射時,若要接收通知:
- 物件定義應宣告一個名
on<Signal>
的訊號處理程式,其中<Signal>
是訊號的名稱,首字母大寫。 - 訊號處理程式必須在發出訊號的物件定義中宣告,並且處理程式應包含呼叫時要執行的 JavaScript 程式碼塊。
例如,MouseArea 型別有一個 clicked
訊號,無論何時在該區域內單擊滑鼠都會發出該訊號。由於訊號名稱是 clicked
,所以接收該訊號的訊號處理程式應命名為 onClicked
。
下面的示例中,每當滑鼠區域被點選時,onClicked
處理程式就會被呼叫,為 Rectangle 分配一個隨機顏色:
import QtQuick 2.3
Rectangle {
id: rect
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: { // 滑鼠單擊
rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
}
}
}
檢視 MouseArea 文件,可以看到 clicked
訊號發出時帶有一個名為 mouse 的引數,這是一個 MouseEvent 物件,包含了有關滑鼠單擊事件的很多詳細資訊,可以在 onClicked
例如,MouseEvent 型別有 x 和 y 座標,這使我們可以打印出滑鼠點選的確切位置:
import QtQuick 2.3
Rectangle {
id: rect
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: { // 滑鼠單擊
rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
// 訪問 mouse 引數
console.log("Clicked mouse at", mouse.x, mouse.y)
}
}
}
屬性改變訊號處理程式
當 QML 屬性值發生改變時,將自動發出訊號。這種型別的訊號是屬性改變訊號,對應的處理程式為屬性改變訊號處理程式。
- 屬性改變訊號處理程式以
on<Property>Changed
的形式寫入,<Property>
是屬性的名稱,首字母大寫。
例如,MouseArea 型別具有 pressed
屬性,要在該屬性改變時接收通知,需要編寫名為 onPressedChanged
的訊號處理程式:
import QtQuick 2.3
Rectangle {
id: rect
width: 100; height: 100
MouseArea {
anchors.fill: parent
onPressedChanged: { // 滑鼠按下/釋放
console.log("Mouse area is pressed?", pressed)
}
}
}
儘管 MouseArea 文件中沒有記錄名為 onPressedChanged
的訊號處理程式,但是因為存在 pressed 屬性,所以它也被隱式地提供了。
使用 Connections 型別
QtQuick 模組提供了 Connections 型別,用於連線到任意物件的訊號。Connections 的優點是:
- 可以在發射訊號的物件外部訪問該訊號
例如,上述示例中的 onClicked
處理程式可以由根 Rectangle 接收,只需要將其放置在一個 Connections 物件中,並指定 target 為 mouseArea:
import QtQuick 2.3
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);
}
}
}
附加訊號處理程式
附加訊號處理程式所接收的訊號來自附加型別,而非宣告處理程式的物件。
附加,也稱為額外。可以簡單理解為:物件本身或其基類沒有的屬性和訊號,需要通過外部(附加型別)提供。
要引用附加屬性和處理程式,可以使用以下語法形式:
<AttachingType>.<propertyName>
<AttachingType>.on<SignalName>
例如,下面的 Item 可以通過附加型別 Keys 來訪問其附加屬性和附加訊號處理程式:
import QtQuick 2.3
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.3
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
訊號處理程式。
附加訊號處理程式允許向物件通知有意義的特定訊號。如果沒有為某個特定物件註冊訊號,那麼就不能接收相應的通知,附加訊號處理機制使得物件能夠接收特定的訊號而無需這些額外的處理。
自定義訊號
當自定義型別不可避免,當現有訊號無法滿足,這時,最好的方法就是自定義訊號。
可以通過 signal 關鍵字來新增自定義訊號,語法如下:
signal <name>[([<type> <parameter name>[, ...]])]
例如,下面聲明瞭三個自定義訊號:
import QtQuick 2.3
Item {
signal clicked
signal hovered()
signal actionPerformed(string action, var actionResult)
}
如果訊號沒有引數,括號“()” 是可選的;倘若有引數,那麼必須宣告引數的型別(正如上述 actionPerformed 訊號中的 string 和 var 一樣)。
要發射訊號,可以將其作為方法來呼叫。任何相關的訊號處理程式將在發出訊號時被呼叫,處理程式可以使用定義的訊號引數名稱來訪問相應的引數。
例如,假設下面的程式碼被定義在一個名為 SquareButton.qml 的檔案中,根 Rectangle 物件有一個 activated 訊號。當子 MouseArea 被點選時,它會以滑鼠點選的座標發出 parent 的 activated 訊號:
// SquareButton.qml
Rectangle {
id: root
signal activated(real xPosition, real yPosition)
property int side: 100
width: side; height: side
MouseArea {
anchors.fill: parent
onPressed: root.activated(mouse.x, mouse.y)
}
}
然後,SquareButton 的任何物件都可以使用 onActivated 訊號處理程式連線到 activated 訊號:
// myapplication.qml
SquareButton {
onActivated: console.log("Activated at " + xPosition + "," + yPosition)
}
訊號到方法/訊號的連線
大部分情況下,通過訊號處理程式接收訊號就足夠了,然而,要將訊號連線至多個方法/訊號,這對於訊號處理程式來說是不可能的(因為訊號處理程式的命名必須唯一)。
在 Qt C++ 中,訊號與槽的連線方式使用的是 QObject::connect()。相應地,在 QML 中,signal 物件也有一個 connect() 方法,用於將訊號連線到一個方法或另一訊號。當訊號連線到方法時,無論訊號何時發出,該方法都將被自動呼叫。有了這種機制,可以通過方法來接收訊號,而無需使用訊號處理器。
所以呢,相對於訊號處理程式來說,connect() 更加靈活:
- 可以將訊號連線至多個方法/訊號
此外,當將訊號連線到動態建立的物件時,connect() 方法也很有用。
訊號到方法的連線
下面,使用 connect() 方法將 messageReceived 訊號連線到兩個方法:
import QtQuick 2.3
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)
}
}
廣播一下,李雷和韓梅梅就可以很快的找到組織了~O(∩_∩)O~!
有 connect() 方法,必然也會有相應的 disconnect() 方法,用於刪除連線的訊號:
Rectangle {
id: relay
//...
function removeLiLeiSignal() {
relay.messageReceived.disconnect(sendToLiLei)
}
}
用法很簡單,和 connect() 相同。
訊號到訊號的連線
通過將訊號連線到其他訊號,connect() 方法可以形成不同的訊號鏈。
import QtQuick 2.3
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 中,訊號和訊號處理器程式是一個核心機制,一定要熟練掌握。。。Good Luck!