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()
我想知道分割这段代码的最佳方法。到目前为止,我已尝试将
CustomEventFilter
、MeatballEventFilter
、SubBoxEventFilter
、WindowEventFilter
类移动到自己的文件中,称为 EvFilter.py
。然后将 SubBox
、TestSongMeatball
导入到 EvFilter.py
中,以允许 QWidget.findChild()
找到有问题的小部件。
这给我留下了两个文件,
EvFilter.py
和mre.py
,前者包含CustomEventFilter
的所有子类,并将SubBox
、TestSongMeatball
导入其中;后者包含 TestSong
、TestSongButton
、TestSongMeatball
、SubBox
、TestWindow
和 main()
,然后导入 CustomEventFilter
的所有子类。
我可以清楚地看到这将如何创建循环导入,并尝试通过我在网上找到的修复程序来解决该问题。
当尝试导入整个文件然后访问各个类时,例如:
import mre
sub = obj.findChild(mre.SubBox)
findChild()
函数将返回 None。
显然我需要重组程序以避免这个缺陷,但不知道这样做的最佳方法。那,或者我需要在不使用
QWidget
的情况下找到 findChild()
的孩子。
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)
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()
如果您自己的代码中有“循环导入”,这意味着您可以创建第三个或第四个文件来保存您的内容。
找到“圆圈”。
这很简单,因为你知道你的圈子在哪里。
import mre
和
import EvFilter
找到在哪里使用这些东西。在 mre.py 中:
Class SubBox(QWidget):
...
和
Class TestWindow(QMainWindow):
...
并且在 EvFilter.py 中:
Class WindowEventFilter(CustomEventFilter):
...
和
Class SubBoxEventFilter(CustomEventFilter):
...
将这些类放入另一个文件中。一般来说,如果一个文件导入一个包含“main”的文件,我们就知道应该更正该文件,或者将 main 取出。
一个原则:“不要在其他文件中导入main。”
所以我们把其他文件中需要导入的东西拿出来,放到另一个文件中。
tsong.py(存储TestSong等)
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 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)
子盒.py
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
from tsong import TestSongMeatball, TestSong
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
我们只需要在 EvFilter.py 和 main.py 中导入这些东西:
EvFilter.py
from PySide6.QtCore import (QEvent, QObject,
Signal, SignalInstance,
)
from PySide6.QtWidgets import (QWidget, )
# from mre import (TestSongMeatball, SubBox, )
from tsong import TestSongMeatball
from subbox import SubBox
... and we don't change any below.
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
)
import EvFilter
from subbox import SubBox
from tsong import TestSong
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()