Захватить keyPressEvent на QMenuBar / QMenu? - PullRequest
0 голосов
/ 10 октября 2018

Я хочу изменить QAction, если пользователь удерживает нажатой определенную клавишу.Подобное поведение вы можете увидеть на Mac, например, если вы откроете меню Apple и нажмете / отпустите клавишу параметров - «About This Mac» изменится на «System Information ...», среди прочего.Это поведение, которое я хочу эмулировать в своем приложении.

С этой целью я попытался переопределить keyPressEvent как на QMenuBar, так и на QMenu, содержащем QAction.Однако моя отладка показала, что ни одна из этих функций keyPressEvent не вызывалась при нажатии любой клавиши с соответствующим отображаемым QMenu.

Мне интересно, возможно, это связано с тем, что событие обрабатывается на более низком уровне, чтобы обеспечить функциональность типа «впереди» в меню?Я заметил, что при вводе различных клавиш выбираются разные пункты меню, что, на мой взгляд, является нормальным поведением.

Как мне ответить на событие типа keyPress в QMenuBar или QMenu?

EDIT: это то, что я пытался:

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());}
}

Затем я инстанцировал строку меню как обычно, заполнил объект FileMenu несколькими QActions и добавил его в строку меню.Все это работает, но строки qDebug никогда не печатаются, и отладка приложения вручную показывает, что событие никогда не вызывается.

РЕДАКТИРОВАТЬ 2: Для получения дополнительной информации я использую Qt 5.9.6 в MacOS X 10.14.0.

РЕДАКТИРОВАТЬ 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.

То, что я видел при таком подходе, заключается в том, что DID-событие KeyPressEater вызывается для keyPresses - но ТОЛЬКО если меню не было активировано.Как только я активирую меню (любое меню, включая яблочное меню), функция eventFilter перестает вызываться.

У меня сложилось впечатление, что система берет на себя обработку меню, тем самым предотвращая любых событий от попадания в приложение.Конечно, это просто на основе наблюдаемого поведения.

РЕДАКТИРОВАТЬ 4: Еще некоторые раскопки обнаружили это: (doc.qt.io/qt-5/osx-issues.html#limitations):

"Объекты QMenu, используемые в собственной строке меню, не могут обрабатывать события Qt через обычные обработчики событий. Установите делегата в самом меню, чтобы получать уведомления об этих изменениях

ИтакЯ предполагаю, что «ответ» заключается в том, что мне нужно «установить делегата в самом меню», который «уведомляет меня об этих изменениях». Может кто-нибудь помочь с этим?

1 Ответ

0 голосов
/ 11 октября 2018

Я сделал 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

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 проверяется снова до самого последнего возможного момента.Это делается с использованием лямбд в качестве обработчиков сигналов для QAction s.Перемещение его в саму функцию-обработчик гарантирует, что оно станет эффективным и для других случаев действия этих действий.(Я имею в виду, что эти действия могут быть «повторно использованы» на панели инструментов.)

  4. Когда QApplication::keyboardModifiers() вызывается внутри keyPressEvent() или keyReleaseEvent(), он возвращает неправильные значения, но используяQKeyEvent::modifiers() вместо этого работал нормально.Это позволяет мне думать, что обновление глобальных состояний выполняется после обработки этих событий.

  5. Это становится немного сложнее, если показанное поведение должно быть достигнуто для самой строки меню.В этом случае 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).Это сработало в обоих случаях.

Примечание:

  1. Когда QApplication::keyboardModifiers() вызывается внутри CtrlNotifier::eventFilter(), он снова возвращал неправильные значения, но с использованиемQKeyEvent::modifiers() вместо этого работал нормально.

  2. Сначала я попытался случайно отфильтровать события в главном окне.Это работало, пока я активировал меню.К счастью, я понял примечание в документе.(последний абзац в Фильтры событий глава):

    Также возможно отфильтровать все события для всего приложения, установив фильтр событий на объект QApplication или QCoreApplication.Такие глобальные фильтры событий вызываются перед объектно-ориентированными фильтрами.Это очень мощный инструмент, но он также замедляет доставку событий для каждого отдельного события во всем приложении;вместо этого обычно следует использовать другие обсуждаемые методы.

    Таким образом, установка его на app вместо этого принесла ожидаемое поведение.

  3. Я прошу прощения засигнал не Qt-ish в CtrlNotifier.Я использую различные сценарии сборки для VS2013 (CMake) и Cygwin (qmake-qt5).Это делает правильную обработку MOC немного сложнее, чем обычно.Поэтому я стараюсь по возможности предотвратить его необходимость.(Я не говорю, что невозможно интегрировать MOC для обоих случаев. Однажды мне это удалось.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...