Запретить закрытие QMenu при срабатывании одного из QAction - PullRequest
13 голосов
/ 12 января 2010

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

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

Есть идеи? Спасибо.

Ответы [ 5 ]

17 голосов
/ 13 января 2010

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

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

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

7 голосов
/ 20 февраля 2013

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

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

Это пример переопределенной 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 () самостоятельно.

1 голос
/ 12 сентября 2012

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

    // 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);
1 голос
/ 13 января 2010

Вот пара идей, которые у меня были ... Даже не уверен, что они сработают;)

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

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

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

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

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

0 голосов
/ 20 июля 2012

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

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 и щелкает меню, он щелкает переключателем, поэтому меню снова открывается, когда оно пытается закрыть. Позиция кэшируется, поэтому она открывается в той же позиции. Есть быстрое мерцание, но он чувствует себя нормально, так как пользователь знает, что он удерживает клавишу, чтобы сделать эту работу.

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

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