Qt Quick 的keyboard focus (本文源於 http://doc.qt.io/qt-5/qtquick-input-focus.html)
按下或釋放按鍵時,會生成一個按鍵事件並將其傳送到focus的Qt Quick item。 為了便於構建可重複使用的組件(reusable components)並解決Fluid UI特有的一些案例,Qt Quick item為Qt的傳統keyboard focus model添加了以scope為基礎的擴充。
Key Handling Overview - 按鍵處理概觀
當使用者按下或釋放按鍵時,會發生以下情況:
1. Qt收到key action並產生key event。
2. 如果QQuickWindow是active window,則會將key event傳遞給它。
3. Key event由場景(scene)傳遞給具有active focus的Item。 如果沒有item具有active focus,則忽略key event。
4. 如果具有active focus的QQuickItem接受key event,則停止傳播(propagation)。 否則,event將被發送到item的父項(parent),直到接受event或到達root item。
Rectangle type具有active focus並且按下了A鍵,則不會進一步傳播該event。 按下B鍵後,event將傳播到root item,從而被忽略。
Rectangle {
width: 100; height: 100
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A) {
console.log('Key A was pressed');
event.accepted = true;
}
}
}
5. 如果到root item,則忽略key event並繼續常規Qt鍵處理。
另請參閱Keys附加屬性和KeyNavigation附加屬性。
Querying the Active Focus Item - 查詢Active Focus Item
可以通過Item :: activeFocus屬性查詢Item是否具有active focus。 例如,這裡我們有一個Text類型,其text取決於它是否具有活動焦點。
Text {
text: activeFocus ? "I have active focus!" : "I do not have active focus"
}
Acquiring Focus and Focus Scopes - 獲得Focus與Focus Scopes
Item通過將focus屬性設置為true來請求焦點。
對於非常簡單的情況,僅設置focus屬性有時就足夠了。 如果我們使用qmlscene運行以下示例,我們會看到keyHandler類型具有active focus,按A,B或C鍵可以適當地修改text。
Rectangle {
color: "lightsteelblue"; width: 240; height: 25
Text { id: myText }
Item {
id: keyHandler
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A)
myText.text = 'Key A was pressed'
else if (event.key == Qt.Key_B)
myText.text = 'Key B was pressed'
else if (event.key == Qt.Key_C)
myText.text = 'Key C was pressed'
}
}
}
如果將上述範例用作可重複使用或import的component,則只需使用focus屬性即可。
為了示範,我們創建了先前定義的component的兩個實例,並將第一個組件設置為具有焦點。 目的是當按下A,B或C鍵時,兩個component中的第一個component去接收事件並相應地作出響應。
導入和創建兩個MyWidget實例的代碼:
//Window code that imports MyWidget
Rectangle {
id: window
color: "white"; width: 240; height: 150
Column {
anchors.centerIn: parent; spacing: 15
MyWidget {
focus: true //set this MyWidget to receive the focus
color: "lightblue"
}
MyWidget {
color: "palegreen"
}
}
}
The MyWidget code:
Rectangle {
id: widget
color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true
Text { id: label; anchors.centerIn: parent}
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A)
label.text = 'Key A was pressed'
else if (event.key == Qt.Key_B)
label.text = 'Key B was pressed'
else if (event.key == Qt.Key_C)
label.text = 'Key C was pressed'
}
}
我們希望第一個MyWidget對象具有focus,因此我們將其focus屬性設置為true。 但是,通過運行代碼,我們可以確認第二個小部件是否獲得焦點。
查看MyWidget和window代碼,問題很明顯 - 有三種type將focus屬性設置為true。
兩個MyWidgets將焦點設置為true,window component也設置焦點。最終,只有一種類型可以具有鍵盤焦點,系統必須決定哪種類型獲得焦點。 創建第二個MyWidget時,它會收到焦點,因為它是將focus屬性設置為true的最後一種類型。
這個問題是由於可見性(visibility)。 MyWidget component希望具有焦點,但在導入或重用時無法控制焦點。 同樣,window component無法知道其導入的組件是否正在請求焦點。
為了解決這個問題,QML引入了一個被稱為焦點範圍(focus scope)的概念。 對於現有的Qt使用者,一個focus scope就像自動焦點代理(automatic focus proxy)。 通過聲明FocusScope類型創focus scope。
在下一個範例中,將FocusScope類型添加到component中,並顯示視覺結果。
FocusScope {
//FocusScope needs to bind to visual properties of the Rectangle
property alias color: rectangle.color
x: rectangle.x; y: rectangle.y
width: rectangle.width; height: rectangle.height
Rectangle {
id: rectangle
anchors.centerIn: parent
color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true
Text { id: label; anchors.centerIn: parent }
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A)
label.text = 'Key A was pressed'
else if (event.key == Qt.Key_B)
label.text = 'Key B was pressed'
else if (event.key == Qt.Key_C)
label.text = 'Key C was pressed'
}
}
}
從概念上講,focus scope非常簡單。
1. 在每個focus scope內,就一個object可以將Item :: focus設置為true。 如果設置了focus屬性的多個item,則設置focus的最後一種type將具有focus而其他則沒有設置,類似於沒有focus scope時。
2. 當focus scope接收到active focus時,在其scope內中有設focus的type(如果有)也會獲得active focus。 如果此type也是FocusScope,則代理行為將繼續下去。 focus scope和子焦點項(sub-focused item)都都將設置activeFocus屬性。
請注意,由於FocusScope類型不是視覺類型(visual type) (只是綁為群組的概念),因此需要將其子項的屬性公開給FocusScope的父項。 Layouts和positioning類型將使用這些視覺(visual)和樣式(styling)屬性來創建layout。 在我們的範例中,Column類型無法正確顯示兩個widgets,因為FocusScope缺少自己的可視屬性。 MyWidget component直接綁定到矩形屬性(rectangle properties),以允許Column類型創建包含FocusScope子項的layout。
到目前為止,該範例已靜態選擇了第二個component。 現在擴展此組件以使其可點擊(clickable),並將其添加到原始應用程序。 我們仍默認將其中一個widget設置為focus。 現在,點擊MyClickableWidget會使其focus,而另一個widget會失去focus。
導入並創建兩個MyClickableWidget實例的代碼:
Rectangle {
id: window
color: "white"; width: 240; height: 150
Column {
anchors.centerIn: parent; spacing: 15
MyClickableWidget {
focus: true //set this MyWidget to receive the focus
color: "lightblue"
}
MyClickableWidget {
color: "palegreen"
}
}
}
The MyClickableWidget code:
FocusScope {
id: scope
//FocusScope needs to bind to visual properties of the children
property alias color: rectangle.color
x: rectangle.x; y: rectangle.y
width: rectangle.width; height: rectangle.height
Rectangle {
id: rectangle
anchors.centerIn: parent
color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true
Text { id: label; anchors.centerIn: parent }
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A)
label.text = 'Key A was pressed'
else if (event.key == Qt.Key_B)
label.text = 'Key B was pressed'
else if (event.key == Qt.Key_C)
label.text = 'Key C was pressed'
}
}
MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } }
}
當QML Item明確地放棄focus時(經由設定: 當處於active focus時將其focus屬性設置為false),系統不會自動選擇另一種類型來獲得focus。 也就是說,可能沒有目前的active focus。
有關使用FocusScope類型在多個區域之間移動鍵盤focus的範例,請參閱Qt Quick Examples - Key Interaction (Qt快速示例 – 鍵盤交互)。
Advanced Uses of Focus Scopes – Focus Scopes的進階使用
Focus scope允許集中分配(allocation)以便輕鬆分區(partitioned)。 有一些QML項目就是使用它來達到這個效果。
例如,ListView本身就是一個focus scope。 這通常不容易被注意到,因為ListView通常不會手動添加可視子項。 通過作為focus scope,ListView可以focus當下的list item而不必擔心它將如何影響應用程序的其餘部分。這是由於允許當下item delegate對按鍵做出反應。
以下這個人工的例子說明了這是如何運作的。 按Return鍵將印出當下list item的name。
Rectangle {
color: "lightsteelblue"; width: 100; height: 50
ListView {
anchors.fill: parent
focus: true
model: ListModel {
ListElement { name: "Bob" }
ListElement { name: "John" }
ListElement { name: "Michael" }
}
delegate: FocusScope {
width: childrenRect.width; height: childrenRect.height
x:childrenRect.x; y: childrenRect.y
TextInput {
focus: true
text: name
Keys.onReturnPressed: console.log(name)
}
}
}
}
雖然這個例子很簡單,但幕後有很多事情要做。 每當當下的item改變時,ListView都會設置delegate的Item :: focus屬性。 由於ListView是一個focus scope,因此不會影響應用程序的其餘部分。 但是,如果ListView本身俱有active focus,則會造成delegate本身去接收active focus。 在此範例中,delegate的root類型也是一個focus scope,會將active focus給予實際執行處理Return鍵工作的Text type。
所有QML view classes(如PathView和GridView)的行為方式都相似於此 - 允許了在各自delegate中的鍵盤處理方式。