Spec-Zone.ru › Qt 5.13

Обработка фокуса клавиатуры в Qt Quick

При нажатии или отпускании клавиши генерируется событие и передаётся в имеющий фокус элемент Qt Quick Item. Для создания повторно используемых компонентов и решения некоторых задач, характерных для интерактивных пользовательских интерфейсов, элементы Qt Quick добавляют расширение на основе области видимости к традиционной модели фокуса клавиатуры Qt.

Обзор обработки нажатий клавиш

При нажатии или отпускании клавиши происходит следующее:

  1. Qt получает действие клавиши и генерирует событие.
  2. Если активным окном является QQuickWindow, событие передаётся ему.
  3. Событие передаётся сценой элементу Item с активным фокусом. Если ни один элемент не имеет активного фокуса, событие игнорируется.
  4. Если элемент QQuickItem с активным фокусом принимает событие, распространение останавливается. В противном случае событие отправляется в родительский элемент элемента, пока событие не будет принято или не будет достигнут корневой элемент.

    Если у элемента типа Rectangle в следующем примере есть активный фокус, и нажата клавиша A, событие не будет распространяться дальше. При нажатии клавиши B, событие будет распространено до корневого элемента и, следовательно, будет проигнорировано.

    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. Если достигнут корневой элемент 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 может оказаться достаточной. Если мы запустим следующий пример с помощью qmlscene, мы увидим, что элемент типа keyHandler имеет активный фокус, и нажатие клавиш A, B, или C соответственно изменяет текст.

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'
        }
    }
}

Однако, если вышеуказанный пример используется как повторно используемый или импортированный компонент, этого простого использования свойства 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: {
        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: {
            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: {
            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 сам по себе есть активный фокус, это приводит к тому, что сам делегат получает активный фокус. В данном примере корневой тип делегата также является областью фокуса, что, в свою очередь, даёт активный фокус типу Text, который фактически выполняет работу по обработке клавиши Return.

Все классы QML-отображения, такие как PathView и GridView, ведут себя аналогичным образом, чтобы обеспечить обработку нажатий клавиш в соответствующих делегатах.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/qtquick-input-focus.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API