简体   繁体   English

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

[英]PySide2: Open some Windows and close them

I want to have a main Window (class MyMainWindow ), from which you can launch an undefined number of other Windows (class MyWindow ), which you can use to get some information.我想要一个主 Window( MyMainWindow类),您可以从中启动未定义数量的其他 Windows( MyWindow类),您可以使用它来获取一些信息。 Each of these other Windows is opened by pressing a button btnWindow in the main window and can be closed (with its (x)-button) when it is not needed any more.通过按下主 window 中的按钮btnWindow可以打开其他 Windows 中的每一个,并且可以在不再需要时关闭(使用其(x)按钮)。

All Windows are inherited from QMainWindow .所有 Windows 都继承自QMainWindow So I have to keep a variable to point to them;所以我必须保留一个变量来指向它们; otherwise they would be closed by the garbage collection.否则它们将被垃圾收集关闭。 For this I use the list self.children .为此,我使用列表self.children Since the main window stays open for hours and the user opens many windows and closes some of them, I want to keep track of the windows in use.由于主要的 window 保持打开数小时,并且用户打开了许多 windows 并关闭了其中一些,因此我想跟踪正在使用的 windows。 For that I created an eventFilter which removes the windows to be closed from my list.为此,我创建了一个eventFilter ,它从我的列表中删除了要关闭的 windows。 But unfortunately this causes the entire application to crash with a SIGKILL or a SIGSEGV .但不幸的是,这会导致整个应用程序因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

So my questions are:所以我的问题是:

  • Why does my application crash?为什么我的应用程序崩溃?
  • How can I avoid the crash?我怎样才能避免崩溃?
  • Or is there a smarter way to keep track of the Windows in use and remove unused ones?或者有没有更聪明的方法来跟踪正在使用的 Windows 并删除未使用的?

The QCloseEvent is "sent to widgets that the user wants to close". QCloseEvent被“发送到用户想要关闭的小部件”。 So, not only the event is triggered when the window is still open, but it is also an event that can be ignored .因此,不仅在 window仍处于打开状态时触发该事件,而且它也是一个可以忽略的事件。

What happens in your case is that, by removing the only reference to that window, you're destroying it.在您的情况下发生的情况是,通过删除对该 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. 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.

After that, you're returning False : when the event filter returns False , it means that the event will be handled: this is normally done by the event() of the object itself.之后,您将返回False :当事件过滤器返回False时,这意味着处理该事件:这通常由 object 本身的event()完成。 Qt will actually send the Close event to the widget, and do other things after that. Qt 实际上会将Close事件发送到小部件,然后执行其他操作。 The problem is that at that point, eventFilter() has already returned, python has lost the last reference to the widget, and then it will destroy it.问题是,此时eventFilter()已经返回,python 丢失了对小部件的最后引用,然后它将销毁它。

So, Qt will be doing those "other" things, while in the meantime the object ceases to exist;因此,Qt 将做那些“其他”事情,而与此同时 object 将不复存在; this causes the program to freeze/crash because Qt is trying to access an object that has been removed from memory.这会导致程序冻结/崩溃,因为 Qt 正在尝试访问已从 memory 中删除的 object。

The simple solution would be to return True in the filter:简单的解决方案是在过滤器中返回True

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Close and obj in self.children:
            self.children.remove(obj)
            return True
        return False

This should ensure that the event won't be processed any more, so you don't risk any memory access issue.确保不再处理该事件,因此您不会冒任何 memory 访问问题的风险。

Also note that some Qt classes already have an overridden eventFilter() , so it's always good practice to return super().eventFilter(obj, event) when the event is not handled or shouldn't be completely discarded (which doesn't mean it will or won't be ignored).另请注意,某些 Qt 类已经有一个被覆盖的eventFilter() ,因此在未处理或不应完全丢弃事件时return super().eventFilter(obj, event)始终是一种好习惯(这并不意味着它会或不会被忽略)。

An important warning, though.不过,这是一个重要的警告。 Multiple event filters can be added to an object, and that could create a problem: if another filter was already set, you go back to square one, since the object has been destroyed.可以将多个事件过滤器添加到 object 中,这可能会产生问题:如果已经设置了另一个过滤器,则 go 回到第一方,因为 object 已被破坏。

A better solution is to not rely on the deletion by the garbage collector, but allow Qt to do it.更好的解决方案是不依赖垃圾收集器的删除,而是让 Qt 来做。 In your case, it can be done by setting the Qt.WA_DeleteOnClose attribute on the window, and connect to the destroyed signal for the actual removal of the python reference:在您的情况下,可以通过在 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()

An alternative solution, if you don't need a reference to those windows, is to set their parent.如果您不需要对那些 windows 的引用,另一种解决方案是设置它们的父级。

For QMainWindow, you can do it just by adding the parent argument in the constructor :对于 QMainWindow,您只需在构造函数中添加父参数即可:

QMainWindow sets the Qt::Window flag itself, and will hence always be created as a top-level widget. QMainWindow 设置 Qt::Window 标志本身,因此将始终创建为顶级小部件。

So, the following is quite sufficient:因此,以下内容就足够了:

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()

Or just do that in the constructor already:或者只是在构造函数中这样做:

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()

For any other QWidget, you either set the flag by yourself, or you manually set the parent using the QObject.setParent() function call, since the QWidget.setParent() override automatically resets all window flags (making the widget also a "physical" child, aka: shown inside the parent).对于任何其他 QWidget,您要么自己设置标志,要么使用QObject.setParent() function 调用手动设置父级,因为QWidget.setParent()覆盖会自动重置所有 window 标志(使小部件也成为“物理“孩子,又名:显示父母内部)。

Supposing that MyWindow is just a QWidget subclass:假设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()

Obviously, as above, this can also be done in the __init__ of the MyWindow class:显然,如上,这也可以在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)

Note: it's normally good practice to not call self.show() in the __init__ of a widget.注意:通常最好不要在小部件的__init__中调用self.show()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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