繁体   English   中英

PySide2:打开一些 Windows 并关闭它们

[英]PySide2: Open some Windows and close them

我想要一个主 Window( MyMainWindow类),您可以从中启动未定义数量的其他 Windows( MyWindow类),您可以使用它来获取一些信息。 通过按下主 window 中的按钮btnWindow可以打开其他 Windows 中的每一个,并且可以在不再需要时关闭(使用其(x)按钮)。

所有 Windows 都继承自QMainWindow 所以我必须保留一个变量来指向它们; 否则它们将被垃圾收集关闭。 为此,我使用列表self.children 由于主要的 window 保持打开数小时,并且用户打开了许多 windows 并关闭了其中一些,因此我想跟踪正在使用的 windows。 为此,我创建了一个eventFilter ,它从我的列表中删除了要关闭的 windows。 但不幸的是,这会导致整个应用程序因SIGKILLSIGSEGV而崩溃。

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

所以我的问题是:

  • 为什么我的应用程序崩溃?
  • 我怎样才能避免崩溃?
  • 或者有没有更聪明的方法来跟踪正在使用的 Windows 并删除未使用的?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM