簡體   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