Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Parent and Child Handling of Events
Forum Updated to NodeBB v4.3 + New Features

Parent and Child Handling of Events

Scheduled Pinned Locked Moved Unsolved Qt for Python
pyside
9 Posts 2 Posters 2.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    D-Drum
    wrote on last edited by
    #1

    I am trying to understand how to handle events by parent and child QWidgets. For example I would like a child QWidget to handle events and prevent them from being passed up to its parent. Reading the docs I thought if I return True from my event handler and/or calling event.accept() it would prevent the parent receiving the event signal however it seems whatever I try the parent always receive the event. I also tried inspecting event.accepted() but this seems to be set True regardless. The following is my example code:

    import logging
    import sys
    from typing import Optional
    
    import PySide6.QtWidgets
    from PySide6.QtCore import QEvent
    from PySide6.QtGui import QTabletEvent, QColor, Qt
    from PySide6.QtWidgets import QApplication, QMainWindow, QWidget
    
    
    class TestApplication(QMainWindow):
    
        def __init__(self) -> None:
            super().__init__()
            menuBar = self.menuBar()
            menuBar.addMenu("&File")
            menuBar.addMenu("&Edit")
            self.logger = logging.getLogger("TestApplication")
    
        def event(self, event: QEvent) -> bool:
            #self.logger.info("isAccepted: {}".format(event.isAccepted()))
            if not event.isAccepted() and (event.type() == QEvent.Enter or event.type() == QEvent.Leave):
                self.logger.info("Event: {}".format(event.type()))
                event.accept()
                return True
            return super(TestApplication, self).event(event)
    
        def tabletEvent(self, event: QTabletEvent) -> None:
            super().tabletEvent(event)
    
    
    class Canvas(QWidget):
    
        def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget]) -> None:
            super().__init__(parent)
            self.logger = logging.getLogger("Canvas")
            self.setAutoFillBackground(True)
            p = self.palette()
            p.setColor(self.backgroundRole(), QColor(Qt.red))
            self.setPalette(p)
    
        def event(self, event: QEvent) -> bool:
            self.logger.info("isAccepted: {}".format(event.isAccepted()))
            if event.type() == QEvent.Enter or event.type() == QEvent.Leave:
                self.logger.info("Event: {}".format(event.type()))
                event.accept()
                return True
            return super(Canvas, self).event(event)
    
    
    if __name__ == "__main__":
        app = QApplication()
        win = TestApplication()
        win.setCentralWidget(Canvas(win))
    
        win.resize(500, 500)
        win.show()
        sys.exit(app.exec())
    
    eyllanescE 1 Reply Last reply
    0
    • D D-Drum

      I am trying to understand how to handle events by parent and child QWidgets. For example I would like a child QWidget to handle events and prevent them from being passed up to its parent. Reading the docs I thought if I return True from my event handler and/or calling event.accept() it would prevent the parent receiving the event signal however it seems whatever I try the parent always receive the event. I also tried inspecting event.accepted() but this seems to be set True regardless. The following is my example code:

      import logging
      import sys
      from typing import Optional
      
      import PySide6.QtWidgets
      from PySide6.QtCore import QEvent
      from PySide6.QtGui import QTabletEvent, QColor, Qt
      from PySide6.QtWidgets import QApplication, QMainWindow, QWidget
      
      
      class TestApplication(QMainWindow):
      
          def __init__(self) -> None:
              super().__init__()
              menuBar = self.menuBar()
              menuBar.addMenu("&File")
              menuBar.addMenu("&Edit")
              self.logger = logging.getLogger("TestApplication")
      
          def event(self, event: QEvent) -> bool:
              #self.logger.info("isAccepted: {}".format(event.isAccepted()))
              if not event.isAccepted() and (event.type() == QEvent.Enter or event.type() == QEvent.Leave):
                  self.logger.info("Event: {}".format(event.type()))
                  event.accept()
                  return True
              return super(TestApplication, self).event(event)
      
          def tabletEvent(self, event: QTabletEvent) -> None:
              super().tabletEvent(event)
      
      
      class Canvas(QWidget):
      
          def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget]) -> None:
              super().__init__(parent)
              self.logger = logging.getLogger("Canvas")
              self.setAutoFillBackground(True)
              p = self.palette()
              p.setColor(self.backgroundRole(), QColor(Qt.red))
              self.setPalette(p)
      
          def event(self, event: QEvent) -> bool:
              self.logger.info("isAccepted: {}".format(event.isAccepted()))
              if event.type() == QEvent.Enter or event.type() == QEvent.Leave:
                  self.logger.info("Event: {}".format(event.type()))
                  event.accept()
                  return True
              return super(Canvas, self).event(event)
      
      
      if __name__ == "__main__":
          app = QApplication()
          win = TestApplication()
          win.setCentralWidget(Canvas(win))
      
          win.resize(500, 500)
          win.show()
          sys.exit(app.exec())
      
      eyllanescE Offline
      eyllanescE Offline
      eyllanesc
      wrote on last edited by
      #2

      @D-Drum The documentation for that method says:

      This virtual function receives events to an object and should return true if the event e was recognized and processed.
      
      The function can be reimplemented to customize the behavior of an object.
      
      Make sure you call the parent event class implementation for all the events you did not handle.
      

      Where does it say: it would prevent the parent receiving the event signal?

      If you want me to help you develop some work then you can write to my email: e.yllanescucho@gmal.com.

      D 1 Reply Last reply
      0
      • eyllanescE eyllanesc

        @D-Drum The documentation for that method says:

        This virtual function receives events to an object and should return true if the event e was recognized and processed.
        
        The function can be reimplemented to customize the behavior of an object.
        
        Make sure you call the parent event class implementation for all the events you did not handle.
        

        Where does it say: it would prevent the parent receiving the event signal?

        D Offline
        D Offline
        D-Drum
        wrote on last edited by
        #3

        @eyllanesc said in Parent and Child Handling of Events:

        @D-Drum The documentation for that method says:

        This virtual function receives events to an object and should return true if the event e was recognized and processed.
        
        The function can be reimplemented to customize the behavior of an object.
        
        Make sure you call the parent event class implementation for all the events you did not handle.
        

        Where does it say: it would prevent the parent receiving the event signal?

        My point is, no matter what I tried the parent always receives the event which is fine but I cannot distinguish between whether the child has already process the event. Try experimenting with my example. I never said the documents 'would prevent the parent receiving the event' .. I said I thought it would - else how else would the parent know the event had been processed or not, especially if the event.accepted() always seemed to be true?

        Please read the question and if you like experiment with my example e.g. just return True from the child handler and see the result.

        eyllanescE 2 Replies Last reply
        0
        • D D-Drum

          @eyllanesc said in Parent and Child Handling of Events:

          @D-Drum The documentation for that method says:

          This virtual function receives events to an object and should return true if the event e was recognized and processed.
          
          The function can be reimplemented to customize the behavior of an object.
          
          Make sure you call the parent event class implementation for all the events you did not handle.
          

          Where does it say: it would prevent the parent receiving the event signal?

          My point is, no matter what I tried the parent always receives the event which is fine but I cannot distinguish between whether the child has already process the event. Try experimenting with my example. I never said the documents 'would prevent the parent receiving the event' .. I said I thought it would - else how else would the parent know the event had been processed or not, especially if the event.accepted() always seemed to be true?

          Please read the question and if you like experiment with my example e.g. just return True from the child handler and see the result.

          eyllanescE Offline
          eyllanescE Offline
          eyllanesc
          wrote on last edited by
          #4

          @D-Drum Each type of event has a special life cycle, and each "event" object is different from the one that was sent to another widget, for example the QEnterEvent event has a localPos method that has to be different for each widget since each one has its own coordinate system so if you accept the event in a widget it will not imply that isAccepted is true in another widget. That an event is consumed is not notified to another widget. For example, another case is the mousePress event where the event is sent from child to parent, and if a widget consumes it then the parent will no longer receive the event but will not be notified when the children consumed the event, isAccepted is handled internally by Qt and not by parent widgets.

          If you want me to help you develop some work then you can write to my email: e.yllanescucho@gmal.com.

          1 Reply Last reply
          0
          • D D-Drum

            @eyllanesc said in Parent and Child Handling of Events:

            @D-Drum The documentation for that method says:

            This virtual function receives events to an object and should return true if the event e was recognized and processed.
            
            The function can be reimplemented to customize the behavior of an object.
            
            Make sure you call the parent event class implementation for all the events you did not handle.
            

            Where does it say: it would prevent the parent receiving the event signal?

            My point is, no matter what I tried the parent always receives the event which is fine but I cannot distinguish between whether the child has already process the event. Try experimenting with my example. I never said the documents 'would prevent the parent receiving the event' .. I said I thought it would - else how else would the parent know the event had been processed or not, especially if the event.accepted() always seemed to be true?

            Please read the question and if you like experiment with my example e.g. just return True from the child handler and see the result.

            eyllanescE Offline
            eyllanescE Offline
            eyllanesc
            wrote on last edited by
            #5

            @D-Drum Your requirement does not make sense that a parent widget has to be notified when the "enter" event occurs in a child widget, for example let's say the parent widget is 100x100 and the child 50x50 is centered on the parent. Let's say that the mouse is in the 10x10 position of the parent so the "enter" event of the parent has already fired but not yet in the child, and now let's say that the mouse moves towards the center so that at a given moment it is will trigger the "enter" event on the child, why would the parent be notified? It should not be notified as for the parent there is no such "enter" event as it was always within the parent

            If you want me to help you develop some work then you can write to my email: e.yllanescucho@gmal.com.

            D 1 Reply Last reply
            0
            • eyllanescE eyllanesc

              @D-Drum Your requirement does not make sense that a parent widget has to be notified when the "enter" event occurs in a child widget, for example let's say the parent widget is 100x100 and the child 50x50 is centered on the parent. Let's say that the mouse is in the 10x10 position of the parent so the "enter" event of the parent has already fired but not yet in the child, and now let's say that the mouse moves towards the center so that at a given moment it is will trigger the "enter" event on the child, why would the parent be notified? It should not be notified as for the parent there is no such "enter" event as it was always within the parent

              D Offline
              D Offline
              D-Drum
              wrote on last edited by
              #6

              @eyllanesc

              Your example is valid however that is not the scenario I have and demonstrated in the example and besides, according to the docs for QEvent.accept() 'Unwanted events might be propagated to the parent widget'.
              In my example I have set my QMainWindow as the parent of the child window and I have found that the event is ALWAYS propagated to the parent. As far as I see it It doesn't matter if the child is smaller than it's designated parent. What is the point of returning True or False from the child parent handler or calling the accept() if it's going to be passed up to the parent and the parent cannot tell if it's child(ren) have handled the event or not? I'm assuming Pyside6 is using event bubbling - but I'm doing something wrong or I'm misunderstanding how it works in Pyside6.

              1 Reply Last reply
              0
              • D Offline
                D Offline
                D-Drum
                wrote on last edited by D-Drum
                #7

                Looking at the C++ docs regarding the Event system (assuming these are valid for Pyside6) it would appear my understanding of how the event system works is correct. event() appears to be a 'catch all' method for all events but there are specialist functions that are for general use. For my case there are enterEvent() and leaveEvent() functions and I tried overriding these and calling accept() but still the parent is receiving events.

                Particularly for the event() method it states: 'Note that QWidget::event() is still called for all of the cases not handled, and that the return value indicates whether an event was dealt with; a true value prevents the event from being sent on to other objects.'

                Either I am interpreting the docs incorrectly, I'm making an error or something just isn't working correctly.

                eyllanescE 1 Reply Last reply
                0
                • D D-Drum

                  Looking at the C++ docs regarding the Event system (assuming these are valid for Pyside6) it would appear my understanding of how the event system works is correct. event() appears to be a 'catch all' method for all events but there are specialist functions that are for general use. For my case there are enterEvent() and leaveEvent() functions and I tried overriding these and calling accept() but still the parent is receiving events.

                  Particularly for the event() method it states: 'Note that QWidget::event() is still called for all of the cases not handled, and that the return value indicates whether an event was dealt with; a true value prevents the event from being sent on to other objects.'

                  Either I am interpreting the docs incorrectly, I'm making an error or something just isn't working correctly.

                  eyllanescE Offline
                  eyllanescE Offline
                  eyllanesc
                  wrote on last edited by eyllanesc
                  #8

                  @D-Drum As I already pointed out: Each type of event has its own workflow.

                  Enter

                  In the case of QEvent::Enter it happens when the mouse cursor moves from the outer region to the inner region:

                  import logging
                  import random
                  import sys
                  
                  from PySide6.QtCore import QEvent
                  from PySide6.QtGui import QColor
                  from PySide6.QtWidgets import QApplication, QWidget
                  
                  logging.basicConfig(level=logging.DEBUG)
                  
                  
                  class Widget(QWidget):
                      def __init__(self, *, name, parent=None):
                          super().__init__(objectName=name, parent=parent)
                          self.setAutoFillBackground(True)
                          p = self.palette()
                          p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                          self.setPalette(p)
                  
                      def event(self, event):
                          if event.type() == QEvent.Enter:
                              logging.debug(f"objectName: {self.objectName()}")
                          return super().event(event)
                  
                  
                  def create_widgets():
                      size = 512
                      widgets = []
                      parent = None
                      for i in range(4):
                          child = Widget(name=f"Child-{i}", parent=parent)
                          child.setGeometry(size / 2, size / 2, size, size)
                          widgets.append(child)
                          parent = child
                          size /= 2
                      return widgets
                  
                  
                  if __name__ == "__main__":
                      app = QApplication()
                      widgets = create_widgets()
                      widgets[0].show()
                      sys.exit(app.exec())
                  

                  That event QEvent::Enter is fired on the child does not imply that it should be fired on the parent, they are independent. In your example, what happens is that the borders are very close, so 2 independent events are triggered since neither depends on the other.

                  MouseButtonPress

                  In contrast to other events that are dependent, for example the QEvent::MouseButtonPress event that happens when the mouse presses the interior region of a widget. On the other hand, the inner region of a child widget is always the inner region of a parent widget, so the event is fired from child to parent as shown in the following example.

                  import logging
                  import random
                  import sys
                  
                  from PySide6.QtCore import QEvent
                  from PySide6.QtGui import QColor
                  from PySide6.QtWidgets import QApplication, QWidget
                  
                  logging.basicConfig(level=logging.DEBUG)
                  
                  
                  class Widget(QWidget):
                      def __init__(self, *, name, parent=None):
                          super().__init__(objectName=name, parent=parent)
                          self.setAutoFillBackground(True)
                          p = self.palette()
                          p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                          self.setPalette(p)
                  
                      def event(self, event):
                          if event.type() == QEvent.MouseButtonPress:
                              logging.debug(f"objectName: {self.objectName()}")
                          return super().event(event)
                  
                  
                  def create_widgets():
                      size = 512
                      widgets = []
                      parent = None
                      for i in range(4):
                          child = Widget(name=f"Child-{i}", parent=parent)
                          child.setGeometry(size / 2, size / 2, size, size)
                          widgets.append(child)
                          parent = child
                          size /= 2
                      return widgets
                  
                  
                  if __name__ == "__main__":
                      app = QApplication()
                      widgets = create_widgets()
                      widgets[0].show()
                      sys.exit(app.exec())
                  
                  DEBUG:root:objectName: Child-3
                  DEBUG:root:objectName: Child-2
                  DEBUG:root:objectName: Child-1
                  DEBUG:root:objectName: Child-0
                  

                  Now if only one widget consumes the event and does not propagate to the parents then return True:

                      def event(self, event):
                          if event.type() == QEvent.MouseButtonPress:
                              logging.debug(f"objectName: {self.objectName()}")
                              return True
                          return super().event(event)
                  
                  DEBUG:root:objectName: Child-3
                  

                  Now the event is not propagated from child to parent.

                  Another way to do the same is by accepting the event in mousePressEvent:

                  class Widget(QWidget):
                      def __init__(self, *, name, parent=None):
                          super().__init__(objectName=name, parent=parent)
                          self.setAutoFillBackground(True)
                          p = self.palette()
                          p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                          self.setPalette(p)
                  
                      def event(self, event):
                          if event.type() == QEvent.MouseButtonPress:
                              logging.debug(f"objectName: {self.objectName()}")
                          return super().event(event)
                  
                      def mousePressEvent(self, event):
                          event.accept()
                  

                  KeyPress

                  For example, another case where events are dependent is that of QEvent::KeyPress, which happens when the focus is pressed or the window has focus. This is fired from the widget that has the focus to its parents.

                  import logging
                  import random
                  import sys
                  
                  from PySide6.QtCore import QEvent
                  from PySide6.QtGui import QColor
                  from PySide6.QtWidgets import QApplication, QWidget
                  
                  logging.basicConfig(level=logging.DEBUG)
                  
                  
                  class Widget(QWidget):
                      def __init__(self, *, name, parent=None):
                          super().__init__(objectName=name, parent=parent)
                          self.setAutoFillBackground(True)
                          p = self.palette()
                          p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                          self.setPalette(p)
                  
                      def event(self, event):
                          if event.type() == QEvent.KeyPress:
                              logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                          return super().event(event)
                  
                  def create_widgets():
                      size = 512
                      widgets = []
                      parent = None
                      for i in range(4):
                          child = Widget(name=f"Child-{i}", parent=parent)
                          child.setGeometry(size / 2, size / 2, size, size)
                          widgets.append(child)
                          parent = child
                          size /= 2
                      return widgets
                  
                  
                  if __name__ == "__main__":
                      app = QApplication()
                      widgets = create_widgets()
                      widgets[0].show()
                      sys.exit(app.exec())
                  
                  DEBUG:root:objectName: Child-0, hasFocus?: False
                  

                  As there was no widget with focus then the toplevel consumed it.

                  But if we set the focus on the widget that has no children then the event is sent to its parents:

                  if __name__ == "__main__":
                      app = QApplication()
                      widgets = create_widgets()
                      widgets[0].show()
                      widgets[-1].setFocus()
                      sys.exit(app.exec())
                  
                  DEBUG:root:objectName: Child-3, hasFocus?: True
                  DEBUG:root:objectName: Child-2, hasFocus?: False
                  DEBUG:root:objectName: Child-1, hasFocus?: False
                  DEBUG:root:objectName: Child-0, hasFocus?: False
                  

                  Now if we want to avoid propagation then it is enough to return True in the event method:

                      def event(self, event):
                          if event.type() == QEvent.KeyPress:
                              logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                              return True
                          return super().event(event)
                  
                  DEBUG:root:objectName: Child-3, hasFocus?: True
                  

                  Or accept the event in keyPressEvent:

                  class Widget(QWidget):
                      def __init__(self, *, name, parent=None):
                          super().__init__(objectName=name, parent=parent)
                          self.setAutoFillBackground(True)
                          p = self.palette()
                          p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                          self.setPalette(p)
                  
                      def event(self, event):
                          if event.type() == QEvent.KeyPress:
                              logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                          return super().event(event)
                  
                      def keyPressEvent(self, event):
                          event.accept()
                  
                  DEBUG:root:objectName: Child-3, hasFocus?: True
                  

                  If you want me to help you develop some work then you can write to my email: e.yllanescucho@gmal.com.

                  D 1 Reply Last reply
                  1
                  • eyllanescE eyllanesc

                    @D-Drum As I already pointed out: Each type of event has its own workflow.

                    Enter

                    In the case of QEvent::Enter it happens when the mouse cursor moves from the outer region to the inner region:

                    import logging
                    import random
                    import sys
                    
                    from PySide6.QtCore import QEvent
                    from PySide6.QtGui import QColor
                    from PySide6.QtWidgets import QApplication, QWidget
                    
                    logging.basicConfig(level=logging.DEBUG)
                    
                    
                    class Widget(QWidget):
                        def __init__(self, *, name, parent=None):
                            super().__init__(objectName=name, parent=parent)
                            self.setAutoFillBackground(True)
                            p = self.palette()
                            p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                            self.setPalette(p)
                    
                        def event(self, event):
                            if event.type() == QEvent.Enter:
                                logging.debug(f"objectName: {self.objectName()}")
                            return super().event(event)
                    
                    
                    def create_widgets():
                        size = 512
                        widgets = []
                        parent = None
                        for i in range(4):
                            child = Widget(name=f"Child-{i}", parent=parent)
                            child.setGeometry(size / 2, size / 2, size, size)
                            widgets.append(child)
                            parent = child
                            size /= 2
                        return widgets
                    
                    
                    if __name__ == "__main__":
                        app = QApplication()
                        widgets = create_widgets()
                        widgets[0].show()
                        sys.exit(app.exec())
                    

                    That event QEvent::Enter is fired on the child does not imply that it should be fired on the parent, they are independent. In your example, what happens is that the borders are very close, so 2 independent events are triggered since neither depends on the other.

                    MouseButtonPress

                    In contrast to other events that are dependent, for example the QEvent::MouseButtonPress event that happens when the mouse presses the interior region of a widget. On the other hand, the inner region of a child widget is always the inner region of a parent widget, so the event is fired from child to parent as shown in the following example.

                    import logging
                    import random
                    import sys
                    
                    from PySide6.QtCore import QEvent
                    from PySide6.QtGui import QColor
                    from PySide6.QtWidgets import QApplication, QWidget
                    
                    logging.basicConfig(level=logging.DEBUG)
                    
                    
                    class Widget(QWidget):
                        def __init__(self, *, name, parent=None):
                            super().__init__(objectName=name, parent=parent)
                            self.setAutoFillBackground(True)
                            p = self.palette()
                            p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                            self.setPalette(p)
                    
                        def event(self, event):
                            if event.type() == QEvent.MouseButtonPress:
                                logging.debug(f"objectName: {self.objectName()}")
                            return super().event(event)
                    
                    
                    def create_widgets():
                        size = 512
                        widgets = []
                        parent = None
                        for i in range(4):
                            child = Widget(name=f"Child-{i}", parent=parent)
                            child.setGeometry(size / 2, size / 2, size, size)
                            widgets.append(child)
                            parent = child
                            size /= 2
                        return widgets
                    
                    
                    if __name__ == "__main__":
                        app = QApplication()
                        widgets = create_widgets()
                        widgets[0].show()
                        sys.exit(app.exec())
                    
                    DEBUG:root:objectName: Child-3
                    DEBUG:root:objectName: Child-2
                    DEBUG:root:objectName: Child-1
                    DEBUG:root:objectName: Child-0
                    

                    Now if only one widget consumes the event and does not propagate to the parents then return True:

                        def event(self, event):
                            if event.type() == QEvent.MouseButtonPress:
                                logging.debug(f"objectName: {self.objectName()}")
                                return True
                            return super().event(event)
                    
                    DEBUG:root:objectName: Child-3
                    

                    Now the event is not propagated from child to parent.

                    Another way to do the same is by accepting the event in mousePressEvent:

                    class Widget(QWidget):
                        def __init__(self, *, name, parent=None):
                            super().__init__(objectName=name, parent=parent)
                            self.setAutoFillBackground(True)
                            p = self.palette()
                            p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                            self.setPalette(p)
                    
                        def event(self, event):
                            if event.type() == QEvent.MouseButtonPress:
                                logging.debug(f"objectName: {self.objectName()}")
                            return super().event(event)
                    
                        def mousePressEvent(self, event):
                            event.accept()
                    

                    KeyPress

                    For example, another case where events are dependent is that of QEvent::KeyPress, which happens when the focus is pressed or the window has focus. This is fired from the widget that has the focus to its parents.

                    import logging
                    import random
                    import sys
                    
                    from PySide6.QtCore import QEvent
                    from PySide6.QtGui import QColor
                    from PySide6.QtWidgets import QApplication, QWidget
                    
                    logging.basicConfig(level=logging.DEBUG)
                    
                    
                    class Widget(QWidget):
                        def __init__(self, *, name, parent=None):
                            super().__init__(objectName=name, parent=parent)
                            self.setAutoFillBackground(True)
                            p = self.palette()
                            p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                            self.setPalette(p)
                    
                        def event(self, event):
                            if event.type() == QEvent.KeyPress:
                                logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                            return super().event(event)
                    
                    def create_widgets():
                        size = 512
                        widgets = []
                        parent = None
                        for i in range(4):
                            child = Widget(name=f"Child-{i}", parent=parent)
                            child.setGeometry(size / 2, size / 2, size, size)
                            widgets.append(child)
                            parent = child
                            size /= 2
                        return widgets
                    
                    
                    if __name__ == "__main__":
                        app = QApplication()
                        widgets = create_widgets()
                        widgets[0].show()
                        sys.exit(app.exec())
                    
                    DEBUG:root:objectName: Child-0, hasFocus?: False
                    

                    As there was no widget with focus then the toplevel consumed it.

                    But if we set the focus on the widget that has no children then the event is sent to its parents:

                    if __name__ == "__main__":
                        app = QApplication()
                        widgets = create_widgets()
                        widgets[0].show()
                        widgets[-1].setFocus()
                        sys.exit(app.exec())
                    
                    DEBUG:root:objectName: Child-3, hasFocus?: True
                    DEBUG:root:objectName: Child-2, hasFocus?: False
                    DEBUG:root:objectName: Child-1, hasFocus?: False
                    DEBUG:root:objectName: Child-0, hasFocus?: False
                    

                    Now if we want to avoid propagation then it is enough to return True in the event method:

                        def event(self, event):
                            if event.type() == QEvent.KeyPress:
                                logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                                return True
                            return super().event(event)
                    
                    DEBUG:root:objectName: Child-3, hasFocus?: True
                    

                    Or accept the event in keyPressEvent:

                    class Widget(QWidget):
                        def __init__(self, *, name, parent=None):
                            super().__init__(objectName=name, parent=parent)
                            self.setAutoFillBackground(True)
                            p = self.palette()
                            p.setColor(self.backgroundRole(), QColor(*random.sample(range(255), 3)))
                            self.setPalette(p)
                    
                        def event(self, event):
                            if event.type() == QEvent.KeyPress:
                                logging.debug(f"objectName: {self.objectName()}, hasFocus?: {self.hasFocus()}")
                            return super().event(event)
                    
                        def keyPressEvent(self, event):
                            event.accept()
                    
                    DEBUG:root:objectName: Child-3, hasFocus?: True
                    
                    D Offline
                    D Offline
                    D-Drum
                    wrote on last edited by
                    #9

                    @eyllanesc
                    Thanks for taking the time to put together your detailed response. I've spent the last week investigating this further and duplicating my Python code into C++. However I've come to the conclusion that MouseEnter and MouseLeave events are treated differently to at least MouseButtonPress and MouseButtonRelease events.

                    I can partly accept that the events for each mouse enter/leave are distinctly separate calls. This is not because as you suggested that the borders are very close; they are the same and the child is on top of the parent and therefore crossing one crosses both which results in two separate events. However where these events vary from mouse press/release is that mouse press/release events can be prevented from bubbled up to the parent by calling event.accept() and returning True. I have verified this with said mouse events. but no matter what I try, I could never get the child mouse enter/leave events to bubble up to the parent. For example jJust using simple logging I would expect to see 3 logging statements per mouse entry/exit event; One for the parent, a second for the child and then a third from the parent when the child does not process the event but pass it up the chain.

                    I've tried to consider why it would be like this and the only reason I can think of is to handle the very case I need to; that is where the child has its border directly above its parent. If the parent were to receive two events i.e. it's own direct mouse enter/leave and then the unprocessed child mouse enter/leave as far as I am aware there is no way to distinguish where the event originated and generally speaking any subsequent action would only be required once, not twice. But this could easily be resolved if the enter/leave events were handled in the same manner as the mouse press/release events. If the runtime is capable of distinguishing the child from the parent for mouse press/release events (presumably using z ordering) then why can't it do so in the case of mouse enter/leave events?

                    I've been looking at this issue for too long now so it's difficult to get out of my train of thought but if anyone has any other explanation I'd be happy to hear it.

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved