QToolButton всплывает меню только при нажатии и удерживании

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

self.ui_open_btn.pressed.connect(self._onOpenBtnPressed)
self.ui_open_btn.triggered.connect(self._onOpenBtnTriggered)

def _onOpenBtnPressed(self):
    self.ui_open_btn.showMenu()

def _onOpenBtnTriggered(self, action):
    application_name = action.text()
    # code to launch the application

Прямо сейчас, когда пользователь нажимает кнопку, всплывает меню. Однако меню все еще существует, когда пользователь отпускает кнопку, и действие в меню запускается при нажатии на него. Я пробовал как в режиме DelayedPopup, так и в режиме InstantPopup. Пока в меню установлено значение self.ui_open_btn, я больше не могу поймать сигнал released. Как я могу скрыть меню, когда пользователь отпускает мышь? Как можно запустить действие в меню, отпустив мышь?

--добавлен---

Я обнаружил еще одну проблему с использованием QToolButton: меню всегда всплывает при нажатии кнопки. Вместо этого я хотел бы поймать сигнал pressed, сделать некоторую проверку, чтобы определить, должно ли всплывать меню или нет. Поэтому я изменил свой подход к написанию своей индивидуальной кнопки инструментов, создав подклассы QPushButton и QMenu. Пожалуйста, смотрите мой код, представленный в ответе ниже.

Спасибо.


person user110    schedule 03.10.2013    source источник
comment
Вы пытались скрыть меню с помощью сигнала released?   -  person mguijarr    schedule 03.10.2013
comment
@mguijarr: Да, я пробовал self.ui_open_btn.menu().close() и self.ui_open_btn.menu().hide(). Ни один из них не работал. Я думаю, что проблема в том, что пока в меню установлено значение self.ui_open_btn, я больше не могу поймать сигнал released.   -  person user110    schedule 03.10.2013


Ответы (2)


Создайте пользовательскую кнопку QToolButton, которая будет фильтровать события из меню и реагировать на событие отпускания мыши, полученное меню:

class MyToolButton(QtGui.QToolButton):
    def __init__(self, *args):
        QtGui.QToolButton.__init__(self, *args)
    def eventFilter(self, menu, event):
        if event.type() == QtCore.QEvent.MouseButtonRelease:
            if self.underMouse():
                menu.close()
                # and now do default action
                print "doing default action"
                return True
        return False

Установите фильтр событий после настройки меню:

self.ui_open_btn.menu().installEventFilter(self.ui_open_btn)
person mguijarr    schedule 03.10.2013
comment
Я только что попробовал. Это не работает. eventFilter() вообще не вызывается. - person user110; 03.10.2013
comment
Вы создали MyToolButton, верно? Не QToolButton. Вы также должны установить фильтр событий. Пожалуйста, покажите какой-нибудь код. - person mguijarr; 03.10.2013
comment
да. Я сделал: self.open_btn = MyToolButton(self)' and 'self.open_btn.installEventFilter(self.openBtn.menu()) и MyToolButton точно такие же, как вы разместили. - person user110; 03.10.2013
comment
Мне также интересно, почему мы используем eventFilter() вместо mouseReleaseEvent()? - person user110; 03.10.2013
comment
Просто чтобы убедиться: вы вызываете installEventFilter после того, как связали меню? Я имею в виду, что self.open_btn.menu() что-то возвращает, верно? Я могу сказать вам, что мне удалось заставить его работать нормально с кодом ответа, так что вы тоже можете :) - person mguijarr; 03.10.2013
comment
Мы используем eventFilter, потому что мы фильтруем события виджета меню в кнопке инструмента, нас не интересуют события, происходящие в кнопке инструмента; мы хотим изменить поведение меню - person mguijarr; 03.10.2013
comment
Вы действительно заставили это работать? Согласно документу, eventFilter(self, watchedObj, event) фильтрует события этого объекта, если он был установлен как eventFilter для watchedObj. Если мы хотим фильтровать события меню, не должны ли мы реализовать фильтр событий меню вместо кнопок инструментов? - person user110; 03.10.2013
comment
не сработает, так как в вашем коде вы фильтруете события кнопок вместо меню. Но на самом деле, как только после нажатия кнопки появляется всплывающее меню, mouseReleaseEvent будет принадлежать меню, а не кнопке. Следовательно, мы должны создать подкласс QMenu. Кроме того, поскольку нам не нужно следить за кнопкой, мы можем заменить mouseReleaseEvent на QMenu вместо eventFilter. Я разместил свое решение. Вы можете проверить это. Однако большое спасибо. Ваши посты очень помогли мне понять. - person user110; 04.10.2013
comment
Хорошо, как хотите... Мое решение работает для меня, но это не моя проблема ;) лол - Рад, что смог помочь. Кстати, почему вам не нравится фильтр событий? - person mguijarr; 04.10.2013
comment
потому что mouseReleaseEvent уже фильтрует для нас события отпускания мыши, и я мог бы вызвать исходный метод mouseReleaseEvent родительского класса QMenu в моем переопределенном, так что мне не нужно писать код для вычисления того, какое действие в меню запускается. - person user110; 04.10.2013

Мне удалось добиться того, чего я хочу, создав подклассы QPushButton и QMennu:

class MyMenu(QtGui.QMenu):
    """ Custom menu which will close when mouse is released. ""'"
    def mouseReleaseEvent(self, event):
        action = self.actionAt(event.pos())
        self.triggered.emit(action)
        self.close()

class MyButton(QtGui.QPushButton):
    triggered = QtCore.pyqtSignal("QAction")

    def __init__(self, menu=None, parent=None):
        super(MyButton, self).__init__(parent)
        self.setMenu(menu)

    def menu(self):
        return self._menu

    def setMenu(self, menu):
        self._menu = menu if menu else MyMenu(self)
        self._menu.triggered.connect(self.triggered.emit)

и в QDialog, содержащем эту кнопку, я делаю следующее:

menu = MyMenu(self)
# insert here code to add actions to menu
self.open_btn = MyButton(parent=self, menu=menu)
self.open_btn.pressed.connect(self._onOpenBtnPressed)
self.open_btn.triggered.connect(self._onOpenBtnTriggered)

def _onOpenBtnPressed(self):
    # insert here code to check whether we should pop up the menu
    pos = self.mapToGlobal(self.open_btn.pos())
    pos.setY(pos.y() + self.open_btn.height())
    self.open_btn.menu().move(pos)
    self.open_btn.menu().show()

def _onOpenBtnTriggered(self, action):
    if action:
        application_name = str(action.text())
        # insert here code to launch this application
    else:
        # insert here code to launch the default application
    self.close() # close this dialog
person user110    schedule 03.10.2013