Original file, mre.py import sys from PySide6.QtCore import (Qt, QSize, QPoint, QRect, QEvent, QObject, Signal, SignalInstance, Slot, ) from PySide6.QtGui import (QResizeEvent, QMoveEvent, QFocusEvent, ) from PySide6.QtWidgets import (QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QMainWindow, QApplication, QSizePolicy, QLayout, QLayoutItem ) class CustomEventFilter(QObject): """Installs eventFilter obj on a widget. Args: widget: QWidget instance """ def __init__(self, widget: QWidget): # if you don't pass widget to be a parent of this evFilter, # then the event filter will most likely be destroyed. super().__init__(widget) # set protected variable self._widget = widget # Install this filterObj instance on the given widget self.widget.installEventFilter(self) # Encapsulation stuff @property def widget(self): return self._widget def eventFilter(self, obj: QWidget, event: QEvent): match (event.type()): case _: # For all events other than move and resize, handle according to QObject built-in eventFilter(). return super().eventFilter(obj, event) class MeatballEventFilter(CustomEventFilter): def __init__(self, widget, sig: Signal = None): super().__init__(widget) self._sig = sig @property def widget(self) -> QWidget: return self._widget @property def sig(self) -> SignalInstance: return self._sig def eventFilter(self, obj: QWidget, event: QEvent): match (event.type()): case QEvent.FocusOut: # print("Lost focus!\nReason: {}".format(event.reason())) # if event.reason() == Qt.FocusReason.ActiveWindowFocusReason: # return True # self.sig.emit(self.widget) return True case QEvent.FocusIn: # print("Gained focus!") return True case _: return super().eventFilter(obj, event) class WindowEventFilter(CustomEventFilter): def __init__(self, widget): super().__init__(widget) @property def widget(self): return super().widget def eventFilter(self, obj: QWidget, event:QEvent): match (event.type()): case QEvent.MouseButtonRelease: #TODO print("mouse window!") sub: SubBox = obj.findChild(SubBox) evFilter: SubBoxEventFilter = sub.findChild(SubBoxEventFilter) return evFilter.eventFilter(sub, event) # return sub.eventFilter(sub, event) # this doesn't work. case _: return super().eventFilter(obj, event) class SubBoxEventFilter(CustomEventFilter): """blah blah blah """ def __init__(self, widget: QWidget): super().__init__(widget) @property def widget(self): return super().widget def eventFilter(self, obj: QObject, event:QEvent): # print(type(obj)) match (event.type()): case QEvent.Resize: # TODO # Won't matter for final product(I think?) but for test purposes will be implemented here return True case QEvent.Move: # Check meatball print("Sub moving meatball!") if obj.findChild(TestSongMeatball): meatball: TestSongMeatball = obj.findChild(TestSongMeatball) meatball.findPos() return True # Return True to notify app that moving took place. return True case _: # For all events other than move and resize, handle according to QObject built-in eventFilter(). return super().eventFilter(obj, event) class TestSong(QWidget): # Create signals to relay info about meatball meatballCreated = Signal(QWidget, name="meatballCreated") meatballDestroyed = Signal(QWidget, name="meatballDestroyed") def __init__(self): super().__init__() self.setStyleSheet("background-color: blue") # Init layout on widget layout = QHBoxLayout(self) # Crate widgets label = QLabel("Label") meatball = TestSongButton("Meatball button") layout.addWidget(label) layout.addWidget(meatball) # connect clicked signal to relay into a meatballCreate signal, passing this widget # as a param meatball.clicked.connect( lambda checked: self.meatballCreated.emit(self)) class TestSongButton(QPushButton): def __init__(self, text): super().__init__(text) self.setStyleSheet("background-color: purple") class TestSongMeatball(QWidget): def __init__(self, songWidget: TestSong, parent=None): super().__init__(parent) # Set reference to song widget self._songWidget = songWidget self.setStyleSheet("background-color: red") # Create sizePolicy obj, init with horizontal and vertical preferences policy = QSizePolicy() policy.setHorizontalPolicy(QSizePolicy.Policy.Preferred) policy.setVerticalPolicy(QSizePolicy.Policy.Preferred) # Set policy, min and max size of the widget. self.setSizePolicy(policy) self.setMinimumSize(QSize(70, 50)) self.setMaximumSize(QSize(160, 80)) fPolicy = Qt.FocusPolicy(Qt.StrongFocus) self.setFocusPolicy(fPolicy) # Assign layout to widget layout = QVBoxLayout(self) # Add demo label for clarity text = QLabel("Meatball Menu") layout.addWidget(text) @property def songWidget(self): return self._songWidget def findPos(self): p = self.parent() # First check that there is a song meatball created as a child of monitorObj (_widget) if p.findChild(TestSongMeatball): # find song item and store its position item_pos = self.songWidget.pos() # Type hint for Python intellisense purposes # find button within song item and store its position button: TestSongButton = self.songWidget.findChild(TestSongButton) button_pos = button.pos() # since item sits inside SubBox, the meatball will have the same geometry starting point (I think?) # So to find the button we need to add them x,y = item_pos.x(), item_pos.y() nx,ny, = button_pos.x(), button_pos.y() # Add co-ordinates to get button's top left corner meatball_pos = QPoint(x+nx,y+ny) # Move to new position, then allow QT to find correct size, using SizePolicy. self.move(meatball_pos) self.adjustSize() return def sizeHint(self): return QSize(80,60) class SubBox(QWidget): def __init__(self): super().__init__() self.setStyleSheet("background-color: green") # Create and apply layout layout = QVBoxLayout(self) @Slot() def addWid(self, item: TestSong): if self.findChild(TestSongMeatball): print("Meatball already present.") return # Create meatball widget meatball = TestSongMeatball(item, self) evFilter = MeatballEventFilter(meatball, item.meatballDestroyed) meatball.findPos() meatball.show() # show meatball meatball.setFocus() return @Slot() def remWid(self, meatball: TestSongMeatball): meatball.deleteLater() meatball = None return class TestWindow(QMainWindow): def __init__(self): super().__init__() window_widget = QWidget() window_layout = QVBoxLayout(window_widget) winFilter = WindowEventFilter(window_widget) fPolicy = Qt.FocusPolicy(Qt.StrongFocus) self.setFocusPolicy(fPolicy) #TODO: put this into a function # Give total layout a label window_layout.addWidget(QLabel("Window")) # Create sub-box sub = SubBox() # Install event filter to SubBox subFilter = SubBoxEventFilter(sub) test_widget = TestSong() sub.layout().addWidget(test_widget) window_layout.addWidget(sub) test_widget.meatballCreated.connect( lambda item: sub.addWid(item)) test_widget.meatballDestroyed.connect( lambda meatball : sub.remWid(meatball)) # set window as central self.setCentralWidget(window_widget) def main(): # Create app app = QApplication(sys.argv) window = TestWindow() window.show() app.exec() if __name__ == '__main__': main() I am wondering the best way to split this code up. I have so far tried to move the CustomEventFilter, MeatballEventFilter, SubBoxEventFilter, WindowEventFilter classes into their own file, called EvFilter.py. Then importing SubBox, TestSongMeatball into EvFilter.py to allow QWidget.findChild() to find the widget in question. This left me with two files, EvFilter.py and mre.py, the former containing all subclasses of CustomEventFilter and importing SubBox, TestSongMeatball to it; and the latter containing TestSong, TestSongButton, TestSongMeatball, SubBox, TestWindow and main(), then importing all subclasses of CustomEventFilter. I can see how this would create a circular import clearly, and tried to remedy the issue with fixes I found online. When trying to import the whole file and then access the individual classes, something like: import mre sub = obj.findChild(mre.SubBox) The findChild() function would return None. Clearly I need to restructure the program to avoid this flaw, but don't know the best way of doing so. That, or I need to find the child of a QWidget without using findChild(). EvFilter.py from PySide6.QtCore import (QEvent, QObject, Signal, SignalInstance, ) from PySide6.QtWidgets import (QWidget, ) from mre import (TestSongMeatball, SubBox, ) class CustomEventFilter(QObject): """Installs eventFilter obj on a widget. Args: widget: QWidget instance """ def __init__(self, widget: QWidget): # if you don't pass widget to be a parent of this evFilter, # then the event filter will most likely be destroyed. super().__init__(widget) # set protected variable self._widget = widget # Install this filterObj instance on the given widget self.widget.installEventFilter(self) # Encapsulation stuff @property def widget(self): return self._widget def eventFilter(self, obj: QWidget, event: QEvent): match (event.type()): case _: # For all events other than move and resize, handle according to QObject built-in eventFilter(). return super().eventFilter(obj, event) class MeatballEventFilter(CustomEventFilter): def __init__(self, widget, sig: Signal = None): super().__init__(widget) self._sig = sig @property def widget(self) -> QWidget: return self._widget @property def sig(self) -> SignalInstance: return self._sig def eventFilter(self, obj: QWidget, event: QEvent): match (event.type()): case QEvent.FocusOut: # print("Lost focus!\nReason: {}".format(event.reason())) # if event.reason() == Qt.FocusReason.ActiveWindowFocusReason: # return True # self.sig.emit(self.widget) return True case QEvent.FocusIn: # print("Gained focus!") return True case _: return super().eventFilter(obj, event) class WindowEventFilter(CustomEventFilter): def __init__(self, widget): super().__init__(widget) @property def widget(self): return super().widget def eventFilter(self, obj: QWidget, event:QEvent): match (event.type()): case QEvent.MouseButtonRelease: #TODO print("mouse window!") sub: SubBox = obj.findChild(SubBox) evFilter: SubBoxEventFilter = sub.findChild(SubBoxEventFilter) return evFilter.eventFilter(sub, event) # return sub.eventFilter(sub, event) # this doesn't work. case _: return super().eventFilter(obj, event) class SubBoxEventFilter(CustomEventFilter): """blah blah blah """ def __init__(self, widget: QWidget): super().__init__(widget) @property def widget(self): return super().widget # Actual eventFilter function. Obj will be the widget this filterObj is installed on, # Event is a subclass of QEvent and can be accessed through its enum: # QEvent.Resize is similar to QResizeEvent def eventFilter(self, obj: QObject, event:QEvent): # print(type(obj)) match (event.type()): case QEvent.Resize: # TODO # Won't matter for final product(I think?) but for test purposes will be implemented here return True case QEvent.Move: # Check meatball print("Sub moving meatball!") if obj.findChild(TestSongMeatball): meatball: TestSongMeatball = obj.findChild(TestSongMeatball) meatball.findPos() return True # Return True to notify app that moving took place. return True case _: # For all events other than move and resize, handle according to QObject built-in eventFilter(). return super().eventFilter(obj, event) mre.py, without event filters import sys from PySide6.QtCore import (Qt, QSize, QPoint, QRect, QEvent, QObject, Signal, SignalInstance, Slot, ) from PySide6.QtGui import (QResizeEvent, QMoveEvent, QFocusEvent, ) from PySide6.QtWidgets import (QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QMainWindow, QApplication, QSizePolicy, QLayout, QLayoutItem ) import EvFilter class TestSong(QWidget): # Create signals to relay info about meatball meatballCreated = Signal(QWidget, name="meatballCreated") meatballDestroyed = Signal(QWidget, name="meatballDestroyed") def __init__(self): super().__init__() self.setStyleSheet("background-color: blue") # Init layout on widget layout = QHBoxLayout(self) # Crate widgets label = QLabel("Label") meatball = TestSongButton("Meatball button") layout.addWidget(label) layout.addWidget(meatball) # connect clicked signal to relay into a meatballCreate signal, passing this widget # as a param meatball.clicked.connect( lambda checked: self.meatballCreated.emit(self)) class TestSongButton(QPushButton): def __init__(self, text): super().__init__(text) self.setStyleSheet("background-color: purple") class TestSongMeatball(QWidget): def __init__(self, songWidget: TestSong, parent=None): super().__init__(parent) # Set reference to song widget self._songWidget = songWidget self.setStyleSheet("background-color: red") # Create sizePolicy obj, init with horizontal and vertical preferences policy = QSizePolicy() policy.setHorizontalPolicy(QSizePolicy.Policy.Preferred) policy.setVerticalPolicy(QSizePolicy.Policy.Preferred) # Set policy, min and max size of the widget. self.setSizePolicy(policy) self.setMinimumSize(QSize(70, 50)) self.setMaximumSize(QSize(160, 80)) fPolicy = Qt.FocusPolicy(Qt.StrongFocus) self.setFocusPolicy(fPolicy) # Assign layout to widget layout = QVBoxLayout(self) # Add demo label for clarity text = QLabel("Meatball Menu") layout.addWidget(text) @property def songWidget(self): return self._songWidget def findPos(self): p = self.parent() # First check that there is a song meatball created as a child of monitorObj (_widget) if p.findChild(TestSongMeatball): # find song item and store its position item_pos = self.songWidget.pos() # Type hint for Python intellisense purposes # find button within song item and store its position button: TestSongButton = self.songWidget.findChild(TestSongButton) button_pos = button.pos() # since item sits inside SubBox, the meatball will have the same geometry starting point (I think?) # So to find the button we need to add them x,y = item_pos.x(), item_pos.y() nx,ny, = button_pos.x(), button_pos.y() # Add co-ordinates to get button's top left corner meatball_pos = QPoint(x+nx,y+ny) # Move to new position, then allow QT to find correct size, using SizePolicy. self.move(meatball_pos) self.adjustSize() return def sizeHint(self): return QSize(80,60) class SubBox(QWidget): def __init__(self): super().__init__() self.setStyleSheet("background-color: green") # Create and apply layout layout = QVBoxLayout(self) @Slot() def addWid(self, item: TestSong): if self.findChild(TestSongMeatball): print("Meatball already present.") return # Create meatball widget meatball = TestSongMeatball(item, self) evFilter = EvFilter.MeatballEventFilter(meatball, item.meatballDestroyed) meatball.findPos() meatball.show() # show meatball meatball.setFocus() return @Slot() def remWid(self, meatball: TestSongMeatball): meatball.deleteLater() meatball = None return class TestWindow(QMainWindow): def __init__(self): super().__init__() window_widget = QWidget() window_layout = QVBoxLayout(window_widget) winFilter = EvFilter.WindowEventFilter(window_widget) fPolicy = Qt.FocusPolicy(Qt.StrongFocus) self.setFocusPolicy(fPolicy) #TODO: put this into a function # Give total layout a label window_layout.addWidget(QLabel("Window")) # Create sub-box sub = SubBox() # Install event filter to SubBox subFilter = EvFilter.SubBoxEventFilter(sub) test_widget = TestSong() sub.layout().addWidget(test_widget) window_layout.addWidget(sub) test_widget.meatballCreated.connect( lambda item: sub.addWid(item)) test_widget.meatballDestroyed.connect( lambda meatball : sub.remWid(meatball)) # set window as central self.setCentralWidget(window_widget) def main(): # Create app app = QApplication(sys.argv) window = TestWindow() window.show() app.exec() if __name__ == '__main__': main() Continue reading...