In Qt 5, assume the following custom widget:
class QMeow final :
public QWidget
{
public:
explicit QMeow()
{
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
this->setFocusPolicy(Qt::StrongFocus);
}
protected:
void keyPressEvent(QKeyEvent * const event) override
{
_s = !_s;
this->update();
QWidget::keyPressEvent(event);
}
void paintEvent(QPaintEvent *) override
{
QPainter painter {this};
painter.fillRect(this->rect(), QColor {_s ? "red" : "blue"});
}
private:
bool _s = false;
};
An instance of this widget changes its background color between red and blue when any key is pressed.
Now, let's make a window and add a QLineEdit
as well as a QMeow
, also setting an action with the shortcut key A:
QApplication app {argc, argv};
QWidget window;
QVBoxLayout layout {&window};
layout.addWidget(new QLineEdit);
layout.addWidget(new QMeow);
QAction action {"hello"};
action.setShortcut(Qt::Key_A);
window.addAction(&action);
window.show();
app.exec();
If you try this, you'll notice that:
QLineEdit
widget has the focus, it handles all the keys, including lowercase A, adding it to its rendered text.QMeow
widget has the focus, it handles all the keys except lowercase A, suggesting that the action handles it instead.How can QMeow::keyPressEvent()
get called for any key, including action shortcuts, like QLineEdit
?
I tried QWidget::grabKeyboard()
, different focus policies, and looking at qlineedit.cpp
without success.
Text input widgets in Qt normally use an internal "text control" which is used as a "proxy" for any text-related aspect, including keyboard input.
A focused widget always receives a special ShortcutOverride
event (a QKeyEvent) whenever it may trigger a shortcut valid for the current context.
See the relevant documentation about QEvent::ShortcutOverride
:
Key press in child, for overriding shortcut key handling (QKeyEvent). When a shortcut is about to trigger,
ShortcutOverride
is sent to the active window. This allows clients (e.g. widgets) to signal that they will handle the shortcut themselves, by accepting the event. If the shortcut override is accepted, the event is delivered as a normal key press to the focus widget. Otherwise, it triggers the shortcut action, if one exists.
Similarly, the related section of the QKeyEvent docs:
For QEvent::ShortcutOverride the receiver needs to explicitly accept the event to trigger the override. Calling ignore() on a key event will propagate it to the parent widget. The event is propagated up the parent widget chain until a widget accepts it or an event filter consumes it.
This is exactly what text input widgets do: they override event()
(QLineEdit::event()
) and send ShortcutOverride
events to the internal text control object (QWidgetLineControl::processShortcutOverrideEvent()
), which contains the following:
...
} else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
|| ke->modifiers() == Qt::KeypadModifier) {
if (ke->key() < Qt::Key_Escape) {
if (!isReadOnly())
ke->accept();
...
Since standard alphanumeric keys are below Qt::Key_Escape
(0x01000000
, and the Qt::Key
enum mostly uses the standard ASCII numeration for them, with upper case for letters) this means that shortcuts using such keys are always accepted if they don't have modifiers, use Shift or are pressed within the numeric pad. The result is that they accept the shortcut, preventing propagation to parents, which will then be "converted" to a standard KeyPress
and eventually sent to the keyPressEvent()
handler.
The solution is then to override event()
, check for ShortcutOverride
events, and accept it (assuming you do want to accept such key press).