Я сделал MCVE для выпуска ОП.
Я считаю, что не могу воспроизвести проблему ОП.Может быть, у ОП были иные ожидания, чем у меня.Конечно, строка меню не будет получать ключевые события, пока она не активирована.(Я пока игнорирую тему ярлыков.)
В своих тестах я всегда сначала нажимал на строку меню, чтобы активировать меню, а затем получал ключевые события.Я протестировал это как с помощью строки меню, так и с помощью подменю.
Я заметил, что как только открывается подменю, даже оба (строка меню и меню) получают (тоже самое) ключевое событие.(Это кажется разумным, учитывая, что ← и → влияют на строку меню, но вместо этого ↑ и ↓ в активном меню.)
Это мой пример кода 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();
}
Проект Qt testQMenuKeyEvent.pro
Я использовал для сборки:
SOURCES = testQMenuKeyEvent.cc
QT = widgets
Скомпилировано и протестировано в cygwin64 в 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
Впоследствии я снова собрал VS2013 с привязкой Qt кWin32 API:
Несмотря на немного другой внешний вид, он вел себя идентично.
Примечания:
При первоначальном тестировании кода я заметил, что навигация по клавишам была нарушена.Следовательно, я считаю, что стоит упомянуть, что override
-ing должен вызывать переопределенные методы базового класса, чтобы также обеспечить исходное поведение.
* Ctrl Клавиша, которую я использовал для переключения пунктов меню, может быть нажата до активации меню.Чтобы учесть это, я также перегрузил showEvent()
.
Для инициируемого действия Ctrl проверяется снова до самого последнего возможного момента.Это делается с использованием лямбд в качестве обработчиков сигналов для QAction
s.Перемещение его в саму функцию-обработчик гарантирует, что оно станет эффективным и для других случаев действия этих действий.(Я имею в виду, что эти действия могут быть «повторно использованы» на панели инструментов.)
Когда QApplication::keyboardModifiers()
вызывается внутри keyPressEvent()
или keyReleaseEvent()
, он возвращает неправильные значения, но используяQKeyEvent::modifiers()
вместо этого работал нормально.Это позволяет мне думать, что обновление глобальных состояний выполняется после обработки этих событий.
Это становится немного сложнее, если показанное поведение должно быть достигнуто для самой строки меню.В этом случае keyPressEvent()
мало помогает.(Он не вызывается, пока строка меню не активна (не сфокусирована)).В этом случае фильтр событий может использоваться для перехвата любого нажатия клавиши и обновления действий строки меню в случае, если
OP упомянул, что вышеРешение не работало на его MacBook.
Я посмотрел на Qt.док.QMenu
.Все, что я нашел, было:
QMenu в macOS с Qt Build Against Cocoa
QMenu можно вставить только один раз в меню / меню.Последующие вставки не будут иметь эффекта или приведут к отключению пункта меню.
Кажется, это не связано напрямую.Тем не менее, у меня возникло ощущение, что на Mac все может быть немного по-другому ...
Итак, я последовал этой идее с помощью фильтра событий и изменил пример кода соответственно:
#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();
}
Я снова протестировал на Windows 10 (Qt связан с win32) и cygwin (Qt связан с X11).Это сработало в обоих случаях.
Примечание:
Когда QApplication::keyboardModifiers()
вызывается внутри CtrlNotifier::eventFilter()
, он снова возвращал неправильные значения, но с использованиемQKeyEvent::modifiers()
вместо этого работал нормально.
Сначала я попытался случайно отфильтровать события в главном окне.Это работало, пока я активировал меню.К счастью, я понял примечание в документе.(последний абзац в Фильтры событий глава):
Также возможно отфильтровать все события для всего приложения, установив фильтр событий на объект QApplication или QCoreApplication.Такие глобальные фильтры событий вызываются перед объектно-ориентированными фильтрами.Это очень мощный инструмент, но он также замедляет доставку событий для каждого отдельного события во всем приложении;вместо этого обычно следует использовать другие обсуждаемые методы.
Таким образом, установка его на app
вместо этого принесла ожидаемое поведение.
Я прошу прощения засигнал не Qt-ish в CtrlNotifier
.Я использую различные сценарии сборки для VS2013 (CMake) и Cygwin (qmake-qt5).Это делает правильную обработку MOC немного сложнее, чем обычно.Поэтому я стараюсь по возможности предотвратить его необходимость.(Я не говорю, что невозможно интегрировать MOC для обоих случаев. Однажды мне это удалось.)