pythonpyqtpyqt5qmenuqtoolbutton

How to ignore the first mouse event in the QToolButton Menu to prevent unwanted selection of items


A colleague made a custom QMenu derivative so that one can select multiple entries before the menu closes.

It is triggered via a QToolButton.

The problem is if the menu is large enough it will overlap with the button. The item at the current cursor position then gets selected instantly when clicking the QToolButton.

How does one prevent this?

Code for my menu, I tried to ignore the first event with a Bool flag, but it doesn't work.

class StayOpenMenu(QMenu):
    """
    a class that overrides the QMenu mouseReleaseEvent to let the menu stay open when an element is selected
    """
    def __init__(self, parent=None):
        self.isfirstEvent = True
        super().__init__("Stay open Menu", parent=parent)

    def mouseReleaseEvent(self, a0: QMouseEvent):
        if self.isfirstEvent:
            a0.ignore()
            self.isfirstEvent = False
            return
        try:
            action = self.actionAt(a0.pos())
            action.trigger()
        except:
            pass
    
    def aboutToShow(self):
        self.isfirstEvent = True
        return super().aboutToShow()

    def aboutToHide(self):
        self.isfirstEvent = True
        return super().aboutToShow()

Image: Before clicking the button

Image: After clicking the QToolButton


Solution

  • aboutToShow() and aboutToHide() are signals, not methods, so they can't be "overridden".

    Create a slot for setting the variable to True and connect it to the aboutToShow signal only.
    Also note that you'll have to take care of the mousePressEvent too: if the menu is not activated from a mouse click (most likely by pressing the tool button via keyboard), it will prevent it from receiving a legitimate release event.

    class StayOpenMenu(QMenu):
        def __init__(self, parent=None):
            self.isfirstEvent = False
            super().__init__("Stay open Menu", parent=parent)
            self.aboutToShow.connect(self.isShowing)
    
        def isShowing(self):
            self.isfirstEvent = True
    
        def mousePressEvent(self, a0: QMouseEvent):
            self.isfirstEvent = False
            super().mousePressEvent(a0)
    
        def mouseReleaseEvent(self, a0: QMouseEvent):
            if self.isfirstEvent:
                a0.ignore()
                self.isfirstEvent = False
                return
            try:
                action = self.actionAt(a0.pos())
                action.trigger()
            except:
                pass