Search code examples
pythonpyqtpyqt6

PyQt6 Isn't calling any events for hovering over a frame


My goal is to detect when a user hovers or stops hovering over a frame, but whenever I try to detect that with an eventFilter, there are just no events that get run that show that. The event IDs for hoverEnter, hoverLeave, and hoverMouseMove are 127, 128, and 129, but if you run the code, you'll see that they just don't come up. Here is the code that fails:

import sys
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *


class MainApp(QWidget):
    def __init__(self):
        super().__init__()
              
        self.setWindowTitle("Test Window")
        self.resize(300, 200)
        
        self.outerLayout = QHBoxLayout()
        self.outerLayout.setContentsMargins(50, 50, 50, 50)
        
        self.frame = QFrame()
        self.frame.setStyleSheet("background-color: lightblue;")
        self.innerLayout = QHBoxLayout(self.frame)
        self.label = QLabel(self.frame)
        self.label.setText("Example Frame")

        self.innerLayout.addWidget(self.label)
        self.outerLayout.addWidget(self.frame)
        
        self.setLayout(self.outerLayout)
    
    def eventFilter(self, obj, event):
        if event.type() == 127:
            print("hovered")
        elif event.type() == 128:
            print("no longer hovered")
        elif event.type() == 129:
            print("hover move event")
        print(event.type())
        return True

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainApp()
    window.installEventFilter(window)
    window.show()
    sys.exit(app.exec())

My end goal here is to be able to detect when a QFrame is clicked. I was thinking I would try to do that by checking for mouse clicks, and if the mouse is hovering over the frame, trigger the function.


Solution

  • First of all it should be noted that clicked is not an event but a signal. The button clicked signal is emitted when the button receives the MouseButtonRelease event.

    In this answer I will show at least the following methods to implement the clicked signal in the QFrame.

    • Override mouseReleaseEvent

      import sys
      
      from PyQt6.QtCore import pyqtSignal, pyqtSlot
      from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
      
      
      class Frame(QFrame):
          clicked = pyqtSignal()
      
          def mouseReleaseEvent(self, event):
              super().mouseReleaseEvent(event)
              self.clicked.emit()
      
      
      class MainApp(QWidget):
          def __init__(self):
              super().__init__()
      
              self.setWindowTitle("Test Window")
              self.resize(300, 200)
      
              self.outerLayout = QHBoxLayout(self)
              self.outerLayout.setContentsMargins(50, 50, 50, 50)
      
              self.frame = Frame()
              self.frame.setStyleSheet("background-color: lightblue;")
      
              self.label = QLabel(text="Example Frame")
      
              self.innerLayout = QHBoxLayout(self.frame)
              self.innerLayout.addWidget(self.label)
      
              self.outerLayout.addWidget(self.frame)
      
              self.frame.clicked.connect(self.handle_clicked)
      
          @pyqtSlot()
          def handle_clicked(self):
              print("frame clicked")
      
      
      if __name__ == "__main__":
          app = QApplication(sys.argv)
          window = MainApp()
          window.show()
          sys.exit(app.exec())
      
    • Use a eventFilter:

      import sys
      
      from PyQt6.QtCore import QEvent
      from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
      
      
      class MainApp(QWidget):
          def __init__(self):
              super().__init__()
      
              self.setWindowTitle("Test Window")
              self.resize(300, 200)
      
              self.outerLayout = QHBoxLayout(self)
              self.outerLayout.setContentsMargins(50, 50, 50, 50)
      
              self.frame = QFrame()
              self.frame.setStyleSheet("background-color: lightblue;")
      
              self.label = QLabel(text="Example Frame")
      
              self.innerLayout = QHBoxLayout(self.frame)
              self.innerLayout.addWidget(self.label)
      
              self.outerLayout.addWidget(self.frame)
      
              self.frame.installEventFilter(self)
              # for move mouse
              # self.frame.setMouseTracking(True)
      
          def eventFilter(self, obj, event):
              if obj is self.frame:
                  if event.type() == QEvent.Type.MouseButtonPress:
                      print("press")
                  # for move mouse
                  # elif event.type() == QEvent.Type.MouseMove:
                  #    print("move")
                  elif event.type() == QEvent.Type.MouseButtonRelease:
                      print("released")
              return super().eventFilter(obj, event)
      
      
      if __name__ == "__main__":
          app = QApplication(sys.argv)
          window = MainApp()
          window.show()
          sys.exit(app.exec())
      

    Plus

    A big part of the error of the O attempt is that by doing window.installEventFilter(window) it is only listening for events from the window itself and not from the QFrame. The solution is to send the QFrame events to the class window.frame.installEventFilter(window).

    On the other hand, do not use numerical codes but the enumerations since they are more readable.

    On the other hand, for the mouse event, the Qt::WA_Hover attribute must be enabled(Read the docs for more information)

    import sys
    
    from PyQt6.QtCore import QEvent, Qt
    from PyQt6.QtWidgets import QApplication, QFrame, QHBoxLayout, QLabel, QWidget
    
    
    class MainApp(QWidget):
        def __init__(self):
            super().__init__()
    
            self.setWindowTitle("Test Window")
            self.resize(300, 200)
    
            self.outerLayout = QHBoxLayout(self)
            self.outerLayout.setContentsMargins(50, 50, 50, 50)
    
            self.frame = QFrame()
            self.frame.setStyleSheet("background-color: lightblue;")
    
            self.label = QLabel(text="Example Frame")
    
            self.innerLayout = QHBoxLayout(self.frame)
            self.innerLayout.addWidget(self.label)
    
            self.outerLayout.addWidget(self.frame)
    
            self.frame.setAttribute(Qt.WidgetAttribute.WA_Hover)
            self.frame.installEventFilter(self)
    
        def eventFilter(self, obj, event):
            if obj is self.frame:
                if event.type() == QEvent.Type.HoverEnter:
                    print("enter")
                elif event.type() == QEvent.Type.HoverMove:
                    print("move")
                elif event.type() == QEvent.Type.HoverLeave:
                    print("leave")
            return super().eventFilter(obj, event)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = MainApp()
        window.show()
        sys.exit(app.exec())