Spec-Zone.ru › Qt

Фокусировка клавиатуры в 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: (event)=> {
            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. Если мы запустим следующий пример с помощью инструмента qml tool, мы увидим, что у элемента типа 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 сам имеет активный фокус, это приводит к тому, что сам делегат получает активный фокус. В этом примере корневой тип делегата также является областью фокуса, что, в свою очередь, предоставляет активный фокус типу Text, который фактически выполняет работу по обработке клавиши Return.

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

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

Spec-Zone.ru

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