Предотвращение закрытия QMenu при срабатывании одного из его QAction

Я использую QMenu в качестве контекстного меню. Это меню заполнено QActions. Один из этих QActions можно проверить, и я хотел бы проверить/снять флажок, не закрывая контекстное меню (и снова его снова открыть, чтобы выбрать тот вариант, который я хочу).

Я попытался отключить сигналы, испускаемые проверяемым QAction, без везения.

Любые идеи? Спасибо.

Ответ 1

Используйте QWidgetAction и QCheckBox для "проверяемого действия", которое не приводит к закрытию меню.

QCheckBox *checkBox = new QCheckBox(menu);
QWidgetAction *checkableAction = new QWidgetAction(menu);
checkableAction->setDefaultWidget(checkBox);
menu->addAction(checkableAction);

В некоторых стилях это не будет выглядеть точно так же, как проверяемое действие. Например, для стиля Plastique флажок нужно немного отступать.

Ответ 2

Кажется, нет никакого элегантного способа предотвратить закрытие меню. Тем не менее, меню будет закрываться только в том случае, если действие может фактически инициировать, то есть оно включено. Итак, самым изящным решением, которое я нашел, является обманом меню, коротко отключив действие в тот момент, когда он будет запущен.

  • Подкласс QMenu
  • Повторить соответствующие обработчики событий (например, mouseReleaseEvent())
  • В обработчике событий отключите действие, затем вызовите реализацию базового класса, затем снова включите действие и запустите его вручную.

Это пример переопределенного mouseReleaseEvent():

void mouseReleaseEvent(QMouseEvent *e)
{
    QAction *action = activeAction();
    if (action && action->isEnabled()) {
        action->setEnabled(false);
        QMenu::mouseReleaseEvent(e);
        action->setEnabled(true);
        action->trigger();
    }
    else
        QMenu::mouseReleaseEvent(e);
}

Чтобы сделать решение идеальным, подобное должно выполняться во всех обработчиках событий, которые могут инициировать действие, например keyPressEvent() и т.д.

Проблема заключается в том, что не всегда легко узнать, должно ли ваша повторная реализация фактически инициировать действие или даже какое действие должно быть инициировано. Наиболее сложным является, вероятно, действие, инициируемое мнемониками: вам нужно будет реализовать комплексный алгоритм в QMenu:: keyPressEvent() самостоятельно.

Ответ 3

Вот пара идей, которые у меня были... Не уверен, что они будут работать tho;)

1) Попытайтесь поймать Событие, используя метод QMenu aboutToHide(); Может быть, вы можете "Отменить" процесс скрытия?

2) Возможно, вы могли бы использовать EventFilter?

Попробуйте взглянуть на: http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter

3) В противном случае вы могли бы переопределить QMenu, чтобы добавить свое собственное поведение, но мне кажется, что это много работает...

Надеюсь, это поможет немного!

Ответ 4

Это мое решение:

    // this menu don't hide, if action in actions_with_showed_menu is chosen.
    class showed_menu : public QMenu
    {
      Q_OBJECT
    public:
      showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; }
      showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; }
      void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); }

      virtual void setVisible (bool visible)
      {
        if (is_ignore_hide)
          {
            is_ignore_hide = false;
            return;
          }
        QMenu::setVisible (visible);
      }

      virtual void mouseReleaseEvent (QMouseEvent *e)
      {
        const QAction *action = actionAt (e->pos ());
        if (action)
          if (actions_with_showed_menu.contains (action))
            is_ignore_hide = true;
        QMenu::mouseReleaseEvent (e);
      }
    private:
      // clicking on this actions don't close menu 
      QSet <const QAction *> actions_with_showed_menu;
      bool is_ignore_hide;
    };

    showed_menu *menu = new showed_menu ();
    QAction *action = menu->addAction (new QAction (menu));
    menu->add_action_with_showed_menu (action);

Ответ 5

(Я начал с ответа Энди, так что спасибо Энди!)

1) aboutToHide() работает, повторно открывая меню в кэшированной позиции, НО также может вводить бесконечный цикл. Тестирование, если мышь нажата за пределами меню, чтобы игнорировать повторное открытие, должна сделать трюк.

2) Я попробовал фильтр событий, но он блокирует фактический щелчок по пункту меню.

3) Используйте оба параметра.

Вот грязный шаблон, чтобы доказать, что он работает. Это удерживает меню открытым, когда пользователь удерживает CTRL при нажатии:

    # in __init__ ...
    self.options_button.installEventFilter(self)
    self.options_menu.installEventFilter(self)
    self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu)

    self.__options_menu_pos_cache = None
    self.__options_menu_open = False

def onAboutToHideOptionsMenu(self):
    if self.__options_menu_open:          # Option + avoid an infinite loop
        self.__options_menu_open = False  # Turn it off to "reset"
        self.options_menu.popup(self.__options_menu_pos_cache)

def eventFilter(self, obj, event):
    if event.type() == QtCore.QEvent.MouseButtonRelease:
        if obj is self.options_menu:
            if event.modifiers() == QtCore.Qt.ControlModifier:
                self.__options_menu_open = True

            return False

        self.__options_menu_pos_cache = event.globalPos()
        self.options_menu.popup(event.globalPos())
        return True

    return False

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

Возможно, bools можно заменить проверкой, чтобы увидеть, находится ли мышь над меню, а если нет, не открывайте ее. Тем не менее, ключ CTRL по-прежнему должен учитываться для моего варианта использования, поэтому он, вероятно, не за горами - это хорошее решение, как оно есть.

Когда пользователь удерживает CTRL и нажимает на меню, он переворачивает переключатель, поэтому меню открывается, когда оно пытается закрыть. Позиция кэшируется так, что она открывается в том же положении. Быстрое мерцание, но он чувствует себя хорошо, так как пользователь знает, что он держит ключ, чтобы сделать эту работу.

В конце дня (буквально) у меня уже было все меню, правильное. Я просто хотел добавить эту функциональность, и я определенно не хотел меняться на использование виджета именно для этого. По этой причине я сохраняю даже этот грязный патч на данный момент.

Ответ 6

Начиная с решения Baysmith, флажок не работал так, как я ожидал, потому что я подключался к триггеру(), а не к переключателю (bool). Я использую код для открытия меню с несколькими флажками при нажатии кнопки:

           QMenu menu;

            QCheckBox *checkBox = new QCheckBox("Show Grass", &menu);
            checkBox->setChecked(m_showGrass);
            QWidgetAction *action = new QWidgetAction(&menu);
            action->setDefaultWidget(checkBox);
            menu.addAction(action);
            //connect(action, SIGNAL(triggered()), this, SLOT(ToggleShowHardscape_Grass()));
            connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(ToggleShowHardscape_Grass()));

            menu.exec(QCursor::pos() + QPoint(-300, 20));

В моем случае это работает как шарм