Search code examples
pythonpyqtmouseeventpyqt5mouse-buttons

PyQt5: Check if mouse is held down in enter-event


My actual application is much more complicated than this, but the example below sums up the majority of my problem. I have multiple QLabels that I've subclassed to make them clickable. The labels display 16x16 images which requires a process of loading the images via Pillow, converting them to ImageQt objects, and then setting the pixmap of the label. In the example, I have 3 clickable QLabels that run the print_something function each time I click on them. My goal is to be able to hold the mouse down, and for each label I hover over, the function gets called. Any pointers would be great.

from PyQt5 import QtCore, QtWidgets, QtGui
from PIL import Image
from PIL.ImageQt import ImageQt
import sys


class ClickableLabel(QtWidgets.QLabel):

    def __init__(self):
        super().__init__()

    clicked = QtCore.pyqtSignal()

    def mousePressEvent(self, ev):
        if app.mouseButtons() & QtCore.Qt.LeftButton:
            self.clicked.emit()


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()
        central_widget = QtWidgets.QWidget()

        self.setFixedSize(300, 300)
        image = Image.open("16x16image.png")
        image_imageqt = ImageQt(image)

        hbox = QtWidgets.QHBoxLayout()
        hbox.setSpacing(0)
        hbox.addStretch()

        label01 = ClickableLabel()
        label01.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
        label01.clicked.connect(self.print_something)
        hbox.addWidget(label01)

        label02 = ClickableLabel()
        label02.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
        label02.clicked.connect(self.print_something)
        hbox.addWidget(label02)

        label03 = ClickableLabel()
        label03.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
        label03.clicked.connect(self.print_something)
        hbox.addWidget(label03)

        hbox.addStretch()

        central_widget.setLayout(hbox)

        self.setCentralWidget(central_widget)

    def print_something(self):
        print("Printing something..")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Solution

  • The cause of the problem is stated in the docs for QMouseEvent:

    Qt automatically grabs the mouse when a mouse button is pressed inside a widget; the widget will continue to receive mouse events until the last mouse button is released.

    It does not look like there is a simple way around this, so something hackish will be required. One idea is to initiate a fake drag and then use dragEnterEvent instead of enterEvent. Something like this should probably work:

    class ClickableLabel(QtWidgets.QLabel):
        clicked = QtCore.pyqtSignal()
    
        def __init__(self):
            super().__init__()
            self.setAcceptDrops(True)
            self.dragstart = None
    
        def mousePressEvent(self, event):
            if event.buttons() & QtCore.Qt.LeftButton:
                self.dragstart = event.pos()
                self.clicked.emit()
    
        def mouseReleaseEvent(self, event):
            self.dragstart = None
    
        def mouseMoveEvent(self, event):
            if (self.dragstart is not None and
                event.buttons() & QtCore.Qt.LeftButton and
                (event.pos() - self.dragstart).manhattanLength() >
                 QtWidgets.qApp.startDragDistance()):
                self.dragstart = None
                drag = QtGui.QDrag(self)
                drag.setMimeData(QtCore.QMimeData())
                drag.exec_(QtCore.Qt.LinkAction)
    
        def dragEnterEvent(self, event):
            event.acceptProposedAction()
            if event.source() is not self:
                self.clicked.emit()