Фокусировка клавиатуры в Qt Quick
При нажатии или отпускании клавиши генерируется событие клавиатуры и передаётся фокусированному элементу Qt Quick Item. Для удобства создания повторно используемых компонентов и решения некоторых задач, специфичных для интуитивно понятных пользовательских интерфейсов, элементы Qt Quick добавляют расширение на основе области к традиционной модели фокуса клавиатуры Qt.
Обзор обработки нажатий клавиш
При нажатии или отпускании клавиши пользователем происходит следующее:
- Qt получает действие клавиши и генерирует событие клавиатуры.
- Если активным окном является QQuickWindow, событие клавиатуры передаётся ему.
- Событие клавиатуры передаётся сценой элементу Item с активным фокусом. Если ни один элемент не имеет активного фокуса, событие клавиатуры игнорируется.
- Если элемент 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; } } } - Если достигнут корневой элемент 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: (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.1/qtquick-input-focus.html