[英]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.