I made an MCVE for OP's issue.
I believe I cannot reproduce the issue of OP. May be, OP had other expectations than me. Of course, the menu bar will not receive key events as long as it's not activated. (I ignore the subject of short-cuts for now.)
In my tests, I always first clicked into menu bar to activate menu and then received key events. I tested this with menu bar as well as with sub-menu.
I observed that as soon as a sub-menu is opened, even both (menu bar and menu) received the (same) key event. (This seems reasonable considering that ← and → effect on menu bar but ↑ and ↓ on active menu instead.)
This is my sample code testQMenuKeyEvent.cc
:
#include <QtWidgets>
class FileMenu: public QMenu {
private:
QAction qCmdNew, qCmdOpen, qCmdQuit;
bool alt;
public:
FileMenu(): QMenu(),
qCmdNew(QString::fromUtf8("New")),
qCmdOpen(QString::fromUtf8("Open")),
qCmdQuit(QString::fromUtf8("Quit")),
alt(false)
{
addAction(&qCmdNew);
addAction(&qCmdOpen);
addAction(&qCmdQuit);
// install signal handlers
connect(&qCmdNew, &QAction::triggered,
[&]() {
qDebug() << (alt ? "Reset" : "New") << "triggered";
});
connect(&qCmdOpen, &QAction::triggered,
[&]() {
qDebug() << (alt ? "Save" : "Open") << "triggered";
});
}
protected:
virtual void showEvent(QShowEvent *pQEvent) override
{
qDebug() << "FileMenu::showEvent";
update();
QMenu::showEvent(pQEvent);
}
virtual void keyPressEvent(QKeyEvent *pQEvent) override
{
qDebug() << "FileMenu::keyPressEvent";
update(pQEvent->modifiers());
QMenu::keyPressEvent(pQEvent);
}
virtual void keyReleaseEvent(QKeyEvent *pQEvent) override
{
qDebug() << "FileMenu::keyReleaseEvent";
update(pQEvent->modifiers());
QMenu::keyReleaseEvent(pQEvent);
}
private:
void update()
{
update(
(QApplication::keyboardModifiers()
& Qt::ControlModifier)
!= 0);
}
void update(bool alt)
{
qDebug() << "alt:" << alt;
if (!alt != !this->alt) {
qCmdNew.setText(QString::fromUtf8(alt ? "Reset" : "New"));
qCmdOpen.setText(QString::fromUtf8(alt ? "Save" : "Open"));
}
this->alt = alt;
}
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QMainWindow qWin;
QMenuBar qMenuMain;
QAction qCmdFile(QString::fromUtf8("File"));
FileMenu qMenuFile;
qCmdFile.setMenu(&qMenuFile);
qMenuMain.addAction(&qCmdFile);
QAction qCmdEdit(QString::fromUtf8("Edit"));
qMenuMain.addAction(&qCmdEdit);
QAction qCmdHelp(QString::fromUtf8("Help"));
qMenuMain.addAction(&qCmdHelp);
qWin.setMenuBar(&qMenuMain);
qWin.show();
return app.exec();
}
The Qt project testQMenuKeyEvent.pro
I used to build:
SOURCES = testQMenuKeyEvent.cc
QT = widgets
Compiled and tested in cygwin64 on Windows 10:
$ qmake-qt5 testQMenuKeyEvent.pro
$ make && ./testQMenuKeyEvent
Qt Version: 5.9.4
FileMenu::showEvent
alt: false
New triggered
FileMenu::showEvent
alt: false
FileMenu::keyPressEvent
alt: true
Reset triggered
Afterwards, I built again in VS2013 with Qt bound to win32 API:
Despite of the slightly different look, it behaved identical.
Notes:
When I tested the code initially, I noticed that key navigation was broken. Hence, I find it worth to be mentioned that override
-ing should call the overridden methods of base class to ensure the original behavior as well.
The Ctrl key which I used for switching the menu items may be pressed before the menu is activated. To consider this, I overloaded showEvent()
as well.
For triggered action, the Ctrl is checked again to the latest possible moment. This is done using lambdas as signal handlers for the QAction
s. Moving it to the handler function itself would ensure that this becomes effective for other occurrences of theses actions as well. (I mean, these actions could be "re-used" in a toolbar.)
When QApplication::keyboardModifiers()
is called inside keyPressEvent()
or keyReleaseEvent()
it returned wrong values but using the QKeyEvent::modifiers()
instead worked fine. This let me think, that update of global states is done after processing these events.
It becomes a bit more complicated if the shown behavior shall be achieved for the menu bar itself. In this case, keyPressEvent()
doesn't help much. (It's not called as long as menu bar is not active (focused)). In this case, an event filter can be used to catch any key press and update the menu bar actions in case.
OP mentioned that the above solution didn't work on his MacBook.
I looked into Qt. doc. of QMenu
. All I found was:
QMenu on macOS with Qt Build Against Cocoa
QMenu can be inserted only once in a menu/menubar. Subsequent insertions will have no effect or will result in a disabled menu item.
This doesn't seem to be related directly. Though, it gave me the feeling that things might be a bit different on Mac...
So, I followed the idea with the event filter and changed the sample code respectively:
#include <functional>
#include <vector>
#include <QtWidgets>
class CtrlNotifier: public QObject {
private:
bool ctrl;
public:
// to be notified
std::vector<std::function<void(bool)> > sigNotify;
public:
CtrlNotifier():
ctrl(
(QApplication::keyboardModifiers() & Qt::ControlModifier)
!= 0)
{ }
bool isCtrl() const { return ctrl; }
protected:
virtual bool eventFilter(QObject *pQObj, QEvent *pQEvent) override
{
if (pQEvent->type() == QEvent::KeyPress
|| pQEvent->type() == QEvent::KeyRelease) {
const bool ctrl
= (dynamic_cast<QKeyEvent*>(pQEvent)->modifiers()
& Qt::ControlModifier)
!= 0;
if (!this->ctrl != !ctrl) {
qDebug() << "CtrlNotifier::eventFilter: Ctrl:" << ctrl;
for (std::function<void(bool)> &func : sigNotify) {
if (func) func(ctrl);
}
this->ctrl = ctrl;
}
}
// standard event processing
return QObject::eventFilter(pQObj, pQEvent);
}
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QMainWindow qWin;
QMenuBar qMenuMain;
QAction qCmdFile(QString::fromUtf8("File"));
QMenu qMenuFile;
QAction qCmdNew(QString::fromUtf8("New"));
qMenuFile.addAction(&qCmdNew);
QAction qCmdOpen(QString::fromUtf8("Open"));
qMenuFile.addAction(&qCmdOpen);
QAction qCmdQuit(QString::fromUtf8("Quit"));
qMenuFile.addAction(&qCmdQuit);
qCmdFile.setMenu(&qMenuFile);
qMenuMain.addAction(&qCmdFile);
QAction qCmdEdit(QString::fromUtf8("Edit"));
qMenuMain.addAction(&qCmdEdit);
QAction qCmdHelp(QString::fromUtf8("Help"));
qMenuMain.addAction(&qCmdHelp);
qWin.setMenuBar(&qMenuMain);
qWin.show();
// install event filter
CtrlNotifier ctrlNotifier;
app.installEventFilter(&ctrlNotifier);
// install signal handlers
ctrlNotifier.sigNotify.push_back(
[&](bool ctrl) {
qCmdNew.setText(QString::fromUtf8(ctrl ? "Reset" : "New"));
qCmdOpen.setText(QString::fromUtf8(ctrl ? "Save" : "Open"));
});
// install signal handlers
QObject::connect(&qCmdNew, &QAction::triggered,
[&]() {
qDebug() << (ctrlNotifier.isCtrl() ? "Reset" : "New") << "triggered";
});
QObject::connect(&qCmdOpen, &QAction::triggered,
[&]() {
qDebug() << (ctrlNotifier.isCtrl() ? "Save" : "Open") << "triggered";
});
// runtime-loop
return app.exec();
}
I tested again on Windows 10 (Qt bound to win32) and cygwin (Qt bound to X11).
It worked in both cases.
Note:
When QApplication::keyboardModifiers()
is called inside CtrlNotifier::eventFilter()
it returned wrong values again but using the QKeyEvent::modifiers()
instead worked fine.
I tried accidentally to filter events of main window first. This worked as long as I activated the menu. For my luck, I realized the note in the doc. (last paragraph in Event Filters chapter):
It is also possible to filter all events for the entire application, by installing an event filter on the QApplication or QCoreApplication object. Such global event filters are called before the object-specific filters. This is very powerful, but it also slows down event delivery of every single event in the entire application; the other techniques discussed should generally be used instead.
Thus, installing it on the app
instead brought the intended behavior.
I apologize for the non-Qt-ish signal in CtrlNotifier
. I'm using distinct build scripts for VS2013 (CMake) and cygwin (qmake-qt5). This makes correct MOC handling a bit trickier as usual. Hence, I try to prevent its necessity when possible. (I don't say it's impossible to integrate MOC for both cases. – I once managed it successfully.)