本页内容

键盘焦点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 XML键处理流程。

另请参阅Keys attached property 和KeyNavigation 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 类型具有活动焦点,按下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'
        }
    }
}

显示按下了C键的蓝色圆形控件

然而,如果将上述示例用作可重用或导入的组件,仅简单使用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 。然而,运行代码后,可能会出现第二个控件获得焦点的情况。

两个堆叠的小部件,第二个绿色的小部件显示按下了键 A

观察MyWidget 和window 的代码,问题显而易见——有三种类型将focus 属性设置为true 。两个MyWidget将focus 设置为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'
        }
    }
}

两个堆叠的小部件,最上面的蓝色小部件显示按下了 B 键

从概念上讲,焦点范围非常简单。

  • 在每个焦点作用域内,一个对象的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)
                }
        }
    }
}

包含三个可编辑姓名的列表:Bob、John、Michael

虽然示例很简单,但幕后却发生了许多操作。每当当前项目发生变化时,ListView 都会设置委托的Item::focus 属性。由于ListView 是一个焦点作用域,因此这不会影响应用程序的其他部分。但是,如果ListView 本身具有活动焦点,则会导致委托本身获得活动焦点。 在此示例中,委托的根类型也是一个焦点作用域,这进而将活动焦点赋予了实际负责处理Return 键的TextInput 类型。

所有 QML 视图类(例如PathView 和GridView )的行为都类似,以允许在其各自的委托中处理键事件。

© 2026 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.