Spec-Zone.ru › Qt 5.14

Фокусировка клавиатуры в 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.

См. также свойство "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.14/qtquick-input-focus.html

Spec-Zone.ru

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