Search code examples
pythonpyqtqpushbutton

PyQt QPushButton leaveEvent while press QPushButton


I want a button. I'm trying to make a state machine, so it will be used in QState class.

If Mouse cursor is hovering, the button changes it's color.

If left mouse button is released on the button, it should work.

If mouse left button is pressed on the button but released on outside of the button, (drag cursor to outside while pressing) the button shouldn't work. (so users can cancel their miss click by dragging out.)

Here is my code but it doesn't work well. When I drag cursor outside and release mouse button, the button works.

I tested several things and now I understand something.

  1. While mouse button is pressed, QPushButton.enterEvent and QPushButton.leaveEvent doesn't work.

  2. When cursor leaves QPushButton while the button is pressed, it seems that QPushButton.released emit signal.

How can I make this button?

class MyButton(QtWidgets.QPushButton):
    selected = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)
        self.is_hover = False

    def enterEvent(self, event):
        self.setStyleSheet\
            ("color: black; border-style: solid;\
                border-width: 2px; border-color: white;\
                    background-color: rgba(250,0,0,1);")            
        self.is_hover = True
        print('is_hover = True')

    def leaveEvent(self, event):
        self.setStyleSheet\
            ("color: white; border-style: solid;\
                border-width: 2px; border-color: white;\
                    background-color: rgba(250,50,150,0.5);")
        self.is_hover = False
        print('is_hover = False')

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            if self.is_hover:
                self.selected.emit(True)
            else:
                return
        else:            
            return

Solution

  • Subclassing and method overriding should only be done when the class doesn't provide what you need, and that's not your case.

    The clicked signal does exactly what you describe: the signal is emitted when the user presses the left mouse button and releases it while the cursor is inside the button (and when click() or animateClick() are called programmatically, but that's not the point).

    If you want to also change the appearance while the button is hovered, you must use the :hover pseudo state. Note that to correctly use stylesheets selectors, you cannot use simple stylesheet syntax like you did, but the class must be specified exactly as it happens with standard CSS.

        self.button = QPushButton()
        self.button.setStyleSheet('''
            QPushButton {
                color: white;
                border: 2px solid white;
                background-color: rgba(250, 50, 150, 128);
            }
    
            QPushButton:hover {
                background-color: rgba(250, 0, 0, 255);
            }
        ''')
        self.button.clicked.connect(self.someFunction)
    

    Some considerations:

    • the rgb() and rgba() syntax accepts integers between 0 and 255, or percentages, so the alpha values you used (0.5 and 1) won't work as expected, since they will be considered practically transparent (0 and 1, respectively) since non percentage values are always in the 0-255 range;
    • pseudo states can be chained, so you can also set a stylesheet specification for both :hover and :pressed
    • since the look of a button should always reflect its state (most importantly, whenever it's pressed or not), you should at least add the class selector for the :pressed state; also, using the inset and outset border style (instead of solid) is also considered good practice; note that it only works well with darker colors, so if you want a "whitish" border, use the following:
        self.button.setStyleSheet('''
            QPushButton {
                color: white;
                border: 2px outset lightGray;
                background-color: rgba(250, 50, 150, 128);
            }
            QPushButton:pressed {
                border-style: inset;
            }
            QPushButton:hover {
                background-color: rgba(250, 0, 0, 255);
            }
            QPushButton:hover:pressed {
                /* since no background was set for pressed, hover state takes
                precedence, so we need to specify the "default" color again here */
                background-color: rgba(250, 50, 150, 128);
            }
        ''')
    
    • setMouseTracking() is useful only when you need to track mouse movements when no mouse button is pressed, and are intercepted by mouseMoveEvent() (which you didn't implement) which is normally always called for mouse movements after a mouse button has been pressed;
    • enter and leave events are only received when no mouse button is pressed: as soon as a widget (any widget) receives and accepts a mousePressEvent(), that widget will become the "mouse grabber" (see mouseGrabber() and grabMouse()) and all mouse movements will be received by that widget only: no enter/leave/hover events will be received by any other widget until the mouse button is released;
    • while I can understand your statement ("I search functions that helps to achieve my purpose"), you have to consider that Qt is a huge framework that has been developed in a 25 years span: all its classes already provide most of the common used interfaces and features, so instead of looking up for what you need for each individual case, it's better to go to the documentation about the class you're using, and study it; consider that you should also always do the same for all the inherited classes of the class you're using: in your case, don't just look for QPushButton, but read the docs about QAbstractButton and QWidget too.