Введение
Многие пользователи — не только с ограниченными возможностями, но и те, кто предпочитает эффективную работу с клавиатуры, или временно не может использовать мышь (например, при использовании планшета без указателя) — должны иметь возможность выполнить любое действие в приложении исключительно через клавиатуру. В статье разберём три ключевых аспекта клавиатурной навигации в Qt: правильный порядок обхода фокуса (tab order), видимую индикацию фокуса, и горячие клавиши (shortcuts) для часто используемых действий.
Концепция
Tab order — последовательность, в которой фокус перемещается между виджетами при нажатии Tab/Shift+Tab — по умолчанию определяется порядком создания виджетов в коде, что не всегда совпадает с логичным, визуальным порядком расположения элементов на экране (особенно при сложных layout’ах). QWidget::setTabOrder() позволяет явно задать правильную последовательность. Шорткаты задаются через QShortcut или непосредственно через & в тексте действия меню (мнемоники) — критично, что они должны срабатывать независимо от того, какой конкретный виджет в данный момент находится в фокусе (в пределах разумной области действия — глобальные против локальных для конкретного виджета шорткатов).
Пример кода: Навигация с клавиатуры
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QFormLayout>
#include <QShortcut>
#include <QKeySequence>
class LoginForm : public QWidget
{
Q_OBJECT
public:
explicit LoginForm(QWidget *parent = nullptr) : QWidget(parent)
{
auto *usernameField = new QLineEdit(this);
auto *passwordField = new QLineEdit(this);
passwordField->setEchoMode(QLineEdit::Password);
auto *loginButton = new QPushButton("&Войти", this); // & задаёт мнемонику Alt+В
auto *cancelButton = new QPushButton("&Отмена", this);
auto *layout = new QFormLayout(this);
layout->addRow("Имя пользователя:", usernameField);
layout->addRow("Пароль:", passwordField);
layout->addRow(loginButton, cancelButton);
// Явный порядок обхода фокуса, гарантированно совпадающий
// с визуальным порядком полей формы сверху вниз
setTabOrder(usernameField, passwordField);
setTabOrder(passwordField, loginButton);
setTabOrder(loginButton, cancelButton);
// Enter в любом текстовом поле формы активирует вход — частое
// ожидание пользователей форм авторизации
connect(usernameField, &QLineEdit::returnPressed, loginButton, &QPushButton::click);
connect(passwordField, &QLineEdit::returnPressed, loginButton, &QPushButton::click);
// Глобальный шорткат Escape для отмены — работает независимо от
// того, какое конкретно поле находится в фокусе в момент нажатия
auto *escapeShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(escapeShortcut, &QShortcut::activated, cancelButton, &QPushButton::click);
usernameField->setFocus(); // фокус на первом логичном поле при открытии формы
}
};
Пояснения к коду
Символ & перед буквой в тексте кнопки ("&Войти") создаёт мнемонику — нажатие Alt+В (буква после &) активирует кнопку независимо от текущего фокуса, что особенно ценно в диалогах с несколькими кнопками, где нужен быстрый доступ без последовательного перебора Tab. setTabOrder() вызывается цепочкой, явно фиксируя порядок перехода фокуса между полями — без этого порядок определялся бы порядком создания объектов в коде, что в данном простом примере совпадает с желаемым, но в более сложных layout’ах (особенно при динамическом добавлении виджетов) может непредсказуемо отличаться.
Подключение returnPressed от обоих текстовых полей к loginButton->click() реализует стандартное пользовательское ожидание — нажатие Enter в любом поле формы логина должно отправить форму, а не просто перевести фокус на следующее поле (что было бы поведением Tab, а не Enter, в формах с одним полем на строку). QShortcut с Qt::Key_Escape, привязанный непосредственно к самой форме (this как родитель), работает в пределах области действия этой формы независимо от того, какой именно дочерний виджет внутри неё сейчас сфокусирован.
Подводные камни
- Tab order, не совпадающий с визуальным расположением элементов на экране. Пользователь, ориентирующийся на визуальный порядок (сверху вниз, слева направо), будет дезориентирован, если Tab перемещает фокус в порядке, не соответствующем этому ожиданию (например, из-за порядка создания виджетов в коде, отличного от их визуального расположения после применения сложного layout) — это особенно критично для пользователей screen reader, которые полностью полагаются на объявляемый порядок, не имея визуальной подсказки вообще.
- Конфликтующие мнемоники в пределах одного видимого окна (например, два элемента с одинаковой буквой после
&) приводят к тому, что соответствующий Alt+буква активирует не тот элемент, который ожидал пользователь, либо переключает между несколькими элементами с одинаковой мнемоникой по очереди при повторных нажатиях, что может сбивать с толку. - Шорткаты, перехватывающие стандартные системные комбинации клавиш (например, переопределение Ctrl+C для собственного действия там, где пользователь ожидает стандартного копирования текста) — нарушение устоявшихся платформенных конвенций клавиатурных сочетаний создаёт неожиданное и фрустрирующее поведение для опытных пользователей, привыкших к стандартному поведению этих комбинаций во всех приложениях.
- Видимая индикация фокуса, отключённая или малозаметная из-за кастомного стиля оформления. Если приложение использует собственный, нестандартный визуальный стиль (QSS stylesheet), переопределяющий отображение фокуса, и забывает явно сохранить заметную, контрастную индикацию текущего сфокусированного элемента, пользователь, навигирующий только с клавиатуры (но без screen reader, то есть зрячий, но не использующий мышь), теряет возможность визуально понять, какой элемент сейчас активен — это легко проверить, просматривая интерфейс, перемещаясь только Tab без мыши.