键盘焦点Qt Quick

当按键被按下或释放时,会生成一个按键事件,并将其传递给聚焦的Qt Quick Item 。为了便于构建可重复使用的组件,并解决一些流体用户界面特有的情况,Qt Quick 项为 Qt 传统的键盘焦点模型添加了一个基于作用域的扩展。

按键处理概述

当用户按下或释放一个按键时,会发生以下情况:

  1. Qt 接收按键操作并生成按键事件。
  2. 如果QQuickWindow 是应用程序的focus window ,则按键事件会传递给它。
  3. 按键事件由场景传递给具有活动焦点Item 。如果没有项目处于活动焦点,按键事件将被忽略。
  4. 如果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;
            }
        }
    }
  5. 如果到达根Item ,按键事件将被ignored ,并继续常规的 Qt 按键处理。

另请参阅Keys attached propertyKeyNavigation attached property

查询活动焦点项

可以通过Item::activeFocus 属性查询Item 是否有活动焦点。例如,这里我们有一个Text 类型,其文本由是否具有活动焦点决定。

    Text {
        text: activeFocus ? "I have active focus!" : "I do not have active focus"
    }

获取焦点和焦点范围

Item 通过将focus 属性设置为true 来请求焦点。

对于非常简单的情况,有时只需设置focus 属性即可。如果我们使用qml 工具运行下面的示例,就会发现keyHandler 类型具有活动焦点,按ABC 键可适当修改文本。

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 属性就不够了。

为了演示,我们为先前定义的组件创建了两个实例,并将第一个实例设置为有焦点。这样做的目的是,当按下ABC 键时,两个组件中的第一个会接收到事件并做出相应响应。

导入并创建两个 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 。但是,通过运行代码,可能会出现第二个部件接收焦点的情况。

查看MyWidgetwindow 代码,问题显而易见--有三种类型将focus 属性设置为true 。两个MyWidgetfocus 设置为true ,而window 组件也设置了焦点。最终,只能有一种类型拥有键盘焦点,系统必须决定哪种类型获得焦点。由于 QML 不能保证哪个元素的属性先被初始化,可能最后一个MyWidget 才获得初始焦点。

这个问题是由于可见性造成的。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 } }
}

当一个 QMLItem 明确放弃焦点时(当它有活动焦点时,通过设置它的focus 属性为false ),系统不会自动选择另一种类型来接收焦点。也就是说,当前可能没有活动焦点。

有关使用FocusScope 类型在多个区域之间移动键盘焦点的演示,请参阅Qt Quick 示例 - 按键交互

焦点范围的高级用法

焦点范围允许轻松分割焦点分配。有几个 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 键的工作。

所有 QML 视图类,如PathViewGridView ,都以类似方式允许在各自的委托中处理按键。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.