捕获QMenuBar / QMenu上的keyPressEvent吗?

时间:2018-10-10 19:18:39

标签: c++ qt

如果用户按住特定键,我想修改QAction。您可以在Mac上看到类似的行为,例如,如果您下拉Apple菜单并按下/释放选项键-“关于此Mac”更改为“系统信息...”等等。这是我要在应用程序中模仿的行为。

为此,我尝试覆盖QMenuBar和包含QAction的QMenu上的keyPressEvent。但是,我的调试表明,在显示相关QMenu的情况下按任意键时都不会调用这些keyPressEvent函数。

我有点想知道这是否是由于为了在菜单中提供“预先输入”类型功能而在较低级别处理了事件?我确实注意到在键入各种键时选择了不同的菜单项,我认为这是正常现象。

如何响应QMenuBar或QMenu中的keyPress类型事件?

编辑:这是我尝试的:

class MainMenu: public QMenuBar
{
    Q_OBJECT
protected:
    void keyPressEvent(QKeyEvent *event) override
    {qDebug("Got Key Press Event in Menu Bar: %i",event->key());}
}

class FileMenu: public QMenu
{
    Q_OBJECT
protected:
    void keyPressEvent(QKeyEvent *event) override
    {qDebug("Got Key Press Event in Menu: %i",event->key());}
}

然后,我像往常一样实例化菜单栏,用多个QAction填充FileMenu对象,并将其添加到菜单栏。所有这些都有效,但是从不打印qDebug行,并且手动调试该应用程序表明该事件从未被调用。

编辑2:有关更多信息,我在MacOS X 10.14.0上使用Qt 5.9.6。

编辑3:作为进一步的测试,我尝试安装eventFilter,使用来自http://doc.qt.io/qt-5/qobject.html#installEventFilter的代码在两个对象上安装KeyPressEater对象作为事件过滤器:一个是我的菜单栏对象,两个是QApplication对象,根据http://doc.qt.io/qt-5/eventsandfilters.html#event-filters上的文档:

  

还可以通过在QApplication或QCoreApplication对象上安装事件过滤器来过滤整个应用程序的所有事件。

我用这种方法看到的是keyPressE调用了KeyPressEater eventFilter DID-但只有在菜单未激活的情况下才可以。一旦激活菜单(包括苹果菜单在内的任何菜单),eventFilter函数就会停止调用。

我在这里得到的印象是系统接管了菜单处理,从而防止了 any 事件进入应用程序。当然,这只是基于观察到的行为。

编辑4:进行了更多挖掘:(doc.qt.io/qt-5/osx-issues.html#limitations):

  

“本机菜单栏中使用的QMenu对象无法通过常规事件处理程序处理Qt事件。在菜单本身上安装一个委托以将这些更改通知给我们

所以我想“答案”是我需要“在菜单本身上安装一个委托”,“将这些更改通知我”。有人可以帮忙吗?

1 个答案:

答案 0 :(得分:1)

我为OP的问题制作了MCVE

我相信我无法复制OP的问题。可能是,OP除了我以外还有其他期望。当然,只要未激活菜单栏,它就不会收到按键事件。 (我暂时忽略了快捷方式。)

在测试中,我总是先单击菜单栏以激活菜单,然后接收按键事件。我通过菜单栏和子菜单对此进行了测试。

我发现打开子菜单后,即使两个菜单(菜单栏)都收到了(相同)按键事件。 (考虑到在菜单栏上起作用,而在活动菜单上起作用,这似乎是合理的。 )

这是我的示例代码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

已在Windows 10的cygwin64中进行了编译和测试:

$ 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

Snapshot of testQMenuKeyEvent in cygwin/X11 Snapshot of testQMenuKeyEvent in cygwin/X11 (Ctrl key pressed)

然后,我再次在VS2013中构建了Qt绑定到win32 API:

Snapshot of testQMenuKeyEvent in Windows 10 Snapshot of testQMenuKeyEvent in Windows 10 (Ctrl key pressed)

尽管外观略有不同,但其表现却完全相同。

注释:

  1. 最初测试代码时,我发现按键导航已损坏。因此,值得一提的是,override-ing应该调用基类的重写方法以确保原始行为。

  2. 在切换菜单项之前,可以按下用于切换菜单项的 Ctrl 键。考虑到这一点,我也使showEvent()重载了。

  3. 对于触发操作,再次检查 Ctrl 到最新的可能时刻。这是通过使用lambda作为QAction的信号处理程序来完成的。将其移到处理程序函数本身将确保这对于这些动作的其他出现也有效。 (我的意思是,这些操作可以在工具栏中“重用”。)

  4. QApplication::keyboardModifiers()keyPressEvent()内部调用keyReleaseEvent()时,它返回错误的值,但使用QKeyEvent::modifiers()可以正常工作。我想这是在处理这些事件之后完成的全局状态更新。

  5. 如果要针对菜单栏本身实现所示的行为,则会变得更加复杂。在这种情况下,keyPressEvent()并没有太大帮助。 (只要菜单栏未激活(聚焦),就不会调用它)。在这种情况下,event filter可用于捕获任何按键并在情况下更新菜单栏操作。


OP提到上述解决方案在他的MacBook上不起作用。

我调查了Qt。 doc。 QMenu中的。我发现的是:

  

在MacOS上使用针对可可的Qt构建的QMenu

     

QMenu只能在菜单/菜单栏中插入一次。后续插入无效或导致菜单项被禁用。

这似乎没有直接关系。不过,它给我的感觉是Mac上的情况可能有所不同...

因此,我遵循了event filter的想法,并分别更改了示例代码:

#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)上进行了测试。 在两种情况下都有效。

注意:

  1. QApplication::keyboardModifiers()中调用CtrlNotifier::eventFilter()时,它再次返回错误的值,但是使用QKeyEvent::modifiers()可以正常工作。

  2. 我不小心尝试首先过滤主窗口的事件。只要我激活菜单,它就可以工作。幸运的是,我在文档中意识到了这个注释。 (Event Filters章的最后一段):

      

    通过在QApplication或QCoreApplication对象上安装事件过滤器,还可以过滤整个应用程序的所有事件。此类全局事件过滤器在特定于对象的过滤器之前被调用。这非常强大,但是也会减慢整个应用程序中每个事件的事件传递速度。通常应使用所讨论的其他技术。

    因此,将其安装在app上会带来预期的行为。

  3. 我为CtrlNotifier中的非Qtish信号表示歉意。我正在为VS2013(CMake)和cygwin(qmake-qt5)使用不同的构建脚本。像往常一样,这会使正确的MOC处理变得有些棘手。因此,我尝试在可能的情况下避免其必要性。 (我并不是说不可能在两种情况下都集成MOC。–我曾经成功地对其进行过管理。)