Qt Quick中的键盘焦点¶
处理键盘焦点
当按下或释放一个键时,会生成一个键事件并将其传递给获得焦点的Qt Quick Item。为了促进可重用组件的构建并解决流体用户界面中一些独特的情况,Qt Quick项目为Qt的传统键盘焦点模型添加了基于范围的扩展。
密钥处理概述¶
当用户按下或释放一个键时,会发生以下情况:
Qt 接收按键动作并生成一个按键事件。
如果
QQuickWindow
是应用程序的焦点窗口,则键盘事件将传递给它。关键事件由场景传递给具有活动焦点的Item。如果没有项目具有活动焦点,则忽略关键事件。
如果具有活动焦点的
QQuickItem
接受了按键事件,传播将停止。否则,事件将被发送到项目的父级,直到事件被接受,或者到达根项目。如果以下示例中的
Rectangle
类型具有活动焦点并且按下了A
键,事件将不会进一步传播。按下B
键时,事件将传播到根项目,因此将被忽略。Rectangle { width: 100; height: 100 focus: true Keys.onPressed: (event)=> { if (event.key == Qt.Key_A) { console.log('Key A was pressed'); event.accepted = true; } } }如果到达根Item,则忽略键事件并继续常规的Qt键处理。
另请参阅Keys附加属性和KeyNavigation附加属性。
查询活动焦点项目¶
是否一个Item具有活动焦点可以通过Item::activeFocus
属性查询。例如,这里我们有一个Text类型,其文本由它是否具有活动焦点决定。
Text { text: activeFocus ? "I have active focus!" : "I do not have active focus" }
获取焦点和焦点范围¶
一个Item通过将focus
属性设置为true
来请求焦点。
对于非常简单的情况,有时只需设置focus
属性就足够了。如果我们使用qml工具运行以下示例,我们会看到keyHandler
类型具有活动焦点,并且按下A
、B
或C
键会适当地修改文本。
Rectangle { color: "lightsteelblue"; width: 240; height: 25 Text { id: myText } Item { id: keyHandler focus: true Keys.onPressed: (event)=> { 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' } } }![]()
然而,如果上述示例被用作可重用或导入的组件,这种简单的focus
属性的使用就不再足够了。
为了演示,我们创建了两个之前定义的组件的实例,并将第一个设置为具有焦点。目的是当按下A
、B
或C
键时,两个组件中的第一个接收事件并相应地响应。
导入并创建两个 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" } } }
MyWidget 代码:
Rectangle { id: widget color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing: true Text { id: label; anchors.centerIn: parent} focus: true Keys.onPressed: (event)=> { 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
属性设置为true
。然而,通过运行代码,我们可以确认第二个小部件获得了焦点。
查看MyWidget
和window
代码,问题很明显 - 有三种类型将focus
属性设置为true
。两个MyWidget
将focus
设置为true
,window
组件也设置了焦点。最终,只有一个类型可以获得键盘焦点,系统必须决定哪个类型接收焦点。当第二个MyWidget
被创建时,它接收焦点,因为它是最后一个将其focus
属性设置为true
的类型。
这个问题是由于可见性引起的。MyWidget
组件希望获得焦点,但在导入或重用时无法控制焦点。同样,window
组件无法知道其导入的组件是否在请求焦点。
为了解决这个问题,QML引入了一个称为焦点范围的概念。对于现有的Qt用户来说,焦点范围就像一个自动的焦点代理。焦点范围是通过声明FocusScope类型来创建的。
在下一个示例中,向组件添加了一个FocusScope类型,并显示了视觉结果。
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: (event)=> { 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' } } }![]()
从概念上讲,焦点范围非常简单。
在每个焦点范围内,一个对象可能将
Item::focus
设置为true
。如果有多个Item设置了focus
属性,最后一个设置focus
的类型将获得焦点,其他类型的焦点将被取消,类似于没有焦点范围的情况。当焦点范围接收到活动焦点时,包含的带有
focus
设置的类型(如果有)也会获得活动焦点。如果此类型也是FocusScope,则代理行为继续。焦点范围和子焦点项都将设置activeFocus
属性。
请注意,由于FocusScope类型不是视觉类型,其子元素的属性需要暴露给FocusScope的父项。布局和定位类型将使用这些视觉和样式属性来创建布局。在我们的示例中,Column
类型无法正确显示两个小部件,因为FocusScope缺少自身的视觉属性。MyWidget组件直接绑定到rectangle
属性,以允许Column
类型创建包含FocusScope子元素的布局。
到目前为止,示例中第二个组件是静态选择的。现在很容易扩展这个组件,使其可点击,并将其添加到原始应用程序中。我们仍然默认将其中一个小部件设置为焦点。现在,点击任何一个MyClickableWidget都会使其获得焦点,而另一个小部件则会失去焦点。
导入并创建两个 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" } } }
MyClickableWidget 代码:
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: (event)=> { 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
属性设置为false
,同时它具有活动焦点时),系统不会自动选择另一种类型来接收焦点。也就是说,有可能当前没有活动的焦点。
请参阅 Qt Quick 示例 - 键交互 以了解如何使用 FocusScope 类型在多个区域之间移动键盘焦点的演示。
焦点范围的高级用法¶
焦点范围允许焦点分配轻松分区。几个QML项目利用它来实现这一效果。
ListView,例如,本身就是一个焦点范围。通常这并不明显,因为ListView通常没有手动添加的视觉子元素。通过成为一个焦点范围,ListView可以聚焦当前列表项,而不必担心这将如何影响应用程序的其余部分。这允许当前项目委托对按键做出反应。
这个人为设计的例子展示了这是如何工作的。按下Return
键将打印当前列表项的名称。
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) } } } }![]()
虽然这个例子很简单,但背后有很多事情在发生。每当当前项目发生变化时,ListView会设置委托的Item::focus
属性。由于ListView是一个焦点范围,这不会影响应用程序的其余部分。然而,如果ListView本身具有活动焦点,这将导致委托本身接收活动焦点。在这个例子中,委托的根类型也是一个焦点范围,这反过来又给TextInput类型提供了活动焦点,该类型实际上执行处理Return
键的工作。