[英]PySide2: Open some Windows and close them
我想要一个主 Window( MyMainWindow
类),您可以从中启动未定义数量的其他 Windows( MyWindow
类),您可以使用它来获取一些信息。 通过按下主 window 中的按钮btnWindow
可以打开其他 Windows 中的每一个,并且可以在不再需要时关闭(使用其(x)按钮)。
所有 Windows 都继承自QMainWindow
。 所以我必须保留一个变量来指向它们; 否则它们将被垃圾收集关闭。 为此,我使用列表self.children
。 由于主要的 window 保持打开数小时,并且用户打开了许多 windows 并关闭了其中一些,因此我想跟踪正在使用的 windows。 为此,我创建了一个eventFilter
,它从我的列表中删除了要关闭的 windows。 但不幸的是,这会导致整个应用程序因SIGKILL
或SIGSEGV
而崩溃。
class MyMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__() # from QMainWindow
self.setupUi(self) # from Ui_MainWindow
self.children = []
self.btnWindow.clicked.connect(self.onWindow)
self.show()
def onWindow(self):
win = MyWindow()
win.installEventFilter(self)
self.children.append(win)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Close and obj in self.children:
self.children.remove(obj)
return False
所以我的问题是:
QCloseEvent
被“发送到用户想要关闭的小部件”。 因此,不仅在 window仍处于打开状态时触发该事件,而且它也是一个可以忽略的事件。
在您的情况下发生的情况是,通过删除对该 window 的唯一引用,您正在破坏它。 But, at that point, a reference still exists: eventFilter
has not returned yet, you're just removing the object from the list but since obj
is within the scope of eventFilter
, it's still a valid Qt object.
之后,您将返回False
:当事件过滤器返回False
时,这意味着将处理该事件:这通常由 object 本身的event()
完成。 Qt 实际上会将Close
事件发送到小部件,然后执行其他操作。 问题是,此时eventFilter()
已经返回,python 丢失了对小部件的最后引用,然后它将销毁它。
因此,Qt 将做那些“其他”事情,而与此同时 object 将不复存在; 这会导致程序冻结/崩溃,因为 Qt 正在尝试访问已从 memory 中删除的 object。
简单的解决方案是在过滤器中返回True
:
def eventFilter(self, obj, event):
if event.type() == QEvent.Close and obj in self.children:
self.children.remove(obj)
return True
return False
这应确保不再处理该事件,因此您不会冒任何 memory 访问问题的风险。
另请注意,某些 Qt 类已经有一个被覆盖的eventFilter()
,因此在未处理或不应完全丢弃事件时return super().eventFilter(obj, event)
始终是一种好习惯(这并不意味着它会或不会被忽略)。
不过,这是一个重要的警告。 可以将多个事件过滤器添加到 object 中,这可能会产生问题:如果已经设置了另一个过滤器,则 go 回到第一方,因为 object 已被破坏。
更好的解决方案是不依赖垃圾收集器的删除,而是让 Qt 来做。 在您的情况下,可以通过在 window 上设置Qt.WA_DeleteOnClose
属性来完成,并连接到被destroyed
的信号以实际删除 python 参考:
def onWindow(self):
def removeWin():
if win in self.children:
self.children.remove(win)
win = MyWindow()
win.setAttribute(Qt.WA_DeleteOnClose)
self.children.append(win)
win.destroyed.connect(removeWin)
win.show()
如果您不需要对那些 windows 的引用,另一种解决方案是设置它们的父级。
对于 QMainWindow,您只需在构造函数中添加父参数即可:
QMainWindow 设置 Qt::Window 标志本身,因此将始终创建为顶级小部件。
因此,以下内容就足够了:
class MyWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
# ...
class MyMainWindow(QMainWindow):
# ...
def onWindow(self):
win = MyWindow(self)
win.setAttribute(Qt.WA_DeleteOnClose)
win.show()
或者只是在构造函数中这样做:
class MyWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
# ...
class MyMainWindow(QMainWindow):
# ...
def onWindow(self):
win = MyWindow(self)
win.show()
对于任何其他 QWidget,您要么自己设置标志,要么使用QObject.setParent()
function 调用手动设置父级,因为QWidget.setParent()
覆盖会自动重置所有 window 标志(使小部件也成为“物理“孩子,又名:显示在父母内部)。
假设MyWindow
只是一个 QWidget 子类:
def onWindow(self):
win = MyWindow(self)
win.setAttribute(Qt.WA_DeleteOnClose)
win.setWindowFlag(Qt.Window)
win.show()
# or, alternatively:
def onWindow(self):
win = MyWindow() # <- no parent in the constructor
QObject.setParent(win, self)
win.setAttribute(Qt.WA_DeleteOnClose)
win.show()
显然,如上,这也可以在MyWindow class的__init__
中完成:
def onWindow(self):
win = MyWindow(self)
win.show()
class MyWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlag(Qt.Window)
# otherwise
class MyWindow(QWidget):
def __init__(self, parent=None):
super().__init__()
QObject.setParent(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose)
注意:通常最好不要在小部件的__init__
中调用self.show()
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.