4

I have a QWidget containing another (child) widget for which I'd like to process hoverEnterEvent and hoverLeaveEvent. The documentation mentions that

Mouse events occur when a mouse cursor is moved into, out of, or within a widget, and if the widget has the Qt::WA_Hover attribute.

So I tried to receive the hover events by setting this attribute and implementing the corresponding event handlers:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout

class TestWidget(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        layout.addWidget(TestLabel('Test 1'))
        layout.addWidget(TestLabel('Test 2'))
        self.setLayout(layout)
        self.setAttribute(Qt.WA_Hover)

class TestLabel(QLabel):
    def __init__(self, text):
        super().__init__(text)
        self.setAttribute(Qt.WA_Hover)

    def hoverEnterEvent(self, event):  # this is never invoked
        print(f'{self.text()} hover enter')

    def hoverLeaveEvent(self, event):  # this is never invoked
        print(f'{self.text()} hover leave')

    def mousePressEvent(self, event):
        print(f'{self.text()} mouse press')

app = QApplication([])
window = TestWidget()
window.show()
sys.exit(app.exec_())

However it doesn't seem to work, no hover events are received. The mousePressEvent on the other hand does work.

In addition I tried also the following things:

  • Set self.setMouseTracking(True) for all widgets,
  • Wrap the TestWidget in a QMainWindow (though that's not what I want to do for the real application),
  • Implement event handlers on parent widgets and event.accept() (though as I understand it, events propagate from inside out, so this shouldn't be required).

How can I receive hover events on my custom QWidgets?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
a_guest
  • 34,165
  • 12
  • 64
  • 118

2 Answers2

4

The QWidget like the QLabel do not have the hoverEnterEvent and hoverLeaveEvent methods, those methods are from the QGraphicsItem so your code doesn't work.

If you want to listen to the hover events of the type you must override the event() method:

import sys
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout


class TestWidget(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout(self)
        layout.addWidget(TestLabel("Test 1"))
        layout.addWidget(TestLabel("Test 2"))


class TestLabel(QLabel):
    def __init__(self, text):
        super().__init__(text)
        self.setAttribute(Qt.WA_Hover)

    def event(self, event):
        if event.type() == QEvent.HoverEnter:
            print("enter")
        elif event.type() == QEvent.HoverLeave:
            print("leave")
        return super().event(event)


def main():
    app = QApplication(sys.argv)
    window = TestWidget()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks, do you know why `QWidget` doesn't implement these event handlers? It feels cumbersome to go through `event` and then manually check for the event type. – a_guest Jun 05 '20 at 23:11
  • 1
    @a_guest mmm, I suppose (because the answer is only known by the developers of Qt) because the vast majority of QWidgets do not use it and it would be wasting memory unnecessarily. Qt tries to optimize so that the binaries are not heavy. – eyllanesc Jun 05 '20 at 23:14
  • @eyllanesc looks like there are methods for handling hover in QWidget named as `enterEvent` and `leaveEvent`. See [Shilo's answer](https://stackoverflow.com/a/62229017/13123836). – Asocia Jun 06 '20 at 10:16
2

Did you know that you can do this with QWidget's enterEvent and leaveEvent? All you need to do is change the method names. You won't even need to set the Hover attribute on the label.

from PyQt5.QtWidgets import QApplication, QGridLayout, QLabel, QWidget


class Window(QWidget):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        layout = QGridLayout()
        self.label = MyLabel(self)
        layout.addWidget(self.label)
        self.setLayout(layout)
        text = "hover label"
        self.label.setText(text)


class MyLabel(QLabel):
    def __init__(self, parent=None):
        super(MyLabel, self).__init__(parent)
        self.setParent(parent)

    def enterEvent(self, event):
        self.prev_text = self.text()
        self.setText('hovering')

    def leaveEvent(self, event):
        self.setText(self.prev_text)


if __name__ == "__main__":
    app = QApplication([])
    w = Window()
    w.show()
    app.exit(app.exec_())
Shilo
  • 313
  • 3
  • 16
  • That seems like a decent solution. I'm just wondering why these separate `enterEvent` and `leaveEvent` were created? Is it just a naming convention and one is used for QWidget and the other for QGraphicsItem? Or is there a more fundamental difference in terms of how these events work (compared to `hoverEnterEvent` and `hoverLeaveEvent`)? Reading [the documentation](https://doc.qt.io/qt-5/qwidget.html#enterEvent) it seems that `enterEvent` and `leaveEvent` are exactly what I'm looking for. In addition, as far as I understand, `WA_Hover` causes the element to be repainted on hovering. – a_guest Jun 06 '20 at 09:41
  • @a_guest From what I can tell, the `hoverEnter` and `enterEvent` have almost identical behavior. The only difference I know of is that the `HoverEnterEvent` calls `update`. I presume this is to ensure a repaint is scheduled, but I'm not positive. Honestly, I'm not certain what `WA_Hover` actually does. The documentation says it forces a repaint on enter/leave events, but it doesn't say how it's achieved, or when it's necessary. The only time I've ever used `WA_Hover` is when I've overridden paintEvent, but had I called `update` in the enter/leave events, it may not have been necessary` – Shilo Jun 07 '20 at 04:02
  • I got the same understanding regarding repaint for hover events from the docs. In my application I want to highlight a widget when the user hovers over another, so I actually don't need this update/repaint of the hovered-over widget. So your solution is the one I eventually went with. However I'll leave the other answer as accepted since the question explicitly asks about how to receive hover events for a widget. Anyway I appreciate your answer and explanations regarding these dedicated event handlers. – a_guest Jun 07 '20 at 19:10