Spec-Zone.ru › Qt 5.11

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

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

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

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

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

    Если элемент типа 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.

См. также свойство присоединённых клавиш и свойство присоединённой навигации по клавишам.

Получение элемента с активным фокусом

Можно проверить, имеет ли элемент 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 Examples — Взаимодействие с клавиатурой для демонстрации перемещения фокуса клавиатуры между несколькими областями с использованием типов 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/archives/qt-5.11/qtquick-input-focus.html

Spec-Zone.ru

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