Spec-Zone.ru › Qt 5.9

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

Spec-Zone.ru

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