PySide6二次打开QTreeView触发QStandardItem已删除崩溃问题求助
问题:PySide6 QTreeView勾选项后崩溃,提示“RuntimeError: Internal C++ object (PySide6.QtGui.QStandardItem) already deleted.”
使用PySide6构建带QTreeView的UI,支持从层级结构中单选项,实现了带可勾选功能的QStandardItemModel,但首次勾选后程序崩溃,抛出上述错误。简化后的复现代码如下:
from PySide6.QtWidgets import QDialog, QPushButton, QWidget, QLineEdit from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QSizePolicy, QTreeView from PySide6.QtGui import QIcon, QStandardItemModel, QStandardItem from PySide6.QtCore import Qt class MinimalModel(QStandardItemModel): def __init__(self, parent=None): super(MinimalModel, self).__init__(parent) self.root_item = self.invisibleRootItem() for i in range(2): item = QStandardItem("parent " + str(i)) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) self.root_item.appendRow(item) parentItem = item for j in range(4): item = QStandardItem("item "+ str(j)) item.setFlags(item.flags() | Qt.ItemIsUserCheckable | Qt.ItemNeverHasChildren) item.setCheckState(Qt.Unchecked) parentItem.appendRow(item) class TreeWidgetSingleSelector(QTreeView): executing = False def __init__(self, model): super().__init__() self.setModel(model) self.setHeaderHidden(True) self.model().dataChanged.connect(self.on_check_state_changed) def on_check_state_changed(self, index, column): if self.executing: return self.executing = True # rest of the method code item = self.model().itemFromIndex(index) root = self.model().root_item # invisibleRootItem() for i in range(root.rowCount()): root.child(i).setCheckState(Qt.Unchecked) if root.child(i).hasChildren(): for j in range(root.child(i).rowCount()): root.child(i).child(j).setCheckState(Qt.Unchecked) item.setCheckState(Qt.Checked) self.executing = False class TreeWidgetSingleSelectorWindow(QDialog): def __init__(self, model, line_widget): super().__init__() self.model = model self.tree = TreeWidgetSingleSelector(self.model) self.line_widget = line_widget self.setWindowTitle("Select Single Item") layout = QVBoxLayout() layout.addWidget(self.tree) save_button = QPushButton("Select Item") save_button.clicked.connect(self.save_item) h_lout = QHBoxLayout() h_lout.addStretch(1) h_lout.addWidget(save_button) layout.addLayout(h_lout) self.setLayout(layout) self.finished.connect(self.deleteLater) def save_item(self): if self.tree.model().rowCount() > 0: items = [self.tree.model().root_item.child(i) for i in range(self.tree.model().root_item.rowCount())] for item in items: for i in range(item.rowCount()): if item.child(i).checkState() == Qt.Checked: self.line_widget.clear() self.line_widget.setText(item.text() + "." + item.child(i).text()) self.accept() def closeEvent(self, event): # Clean up the tree views when the main window is closed self.tree.deleteLater() event.accept() class LineEditTreeSelector(QWidget): def __init__(self, model): super().__init__() self.model = model self.line_edit = QLineEdit() button = QPushButton() button.setFixedWidth(20) button.setFixedHeight(20) button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button.setIcon(QIcon("art/add_24.png")) layout = QHBoxLayout(self) layout.addWidget(button) layout.addWidget(self.line_edit) layout.setSpacing(0) button.clicked.connect(self.on_button_clicked) def on_button_clicked(self): tree_selector = TreeWidgetSingleSelectorWindow(self.model, self.line_edit) tree_selector.exec() def get_text(self): return self.line_edit.text() class scatterplot_widget(QWidget): def __init__(self, model): QWidget.__init__(self) vertical_box = QVBoxLayout(self) self.tree_selector_x = LineEditTreeSelector(model) self.tree_selector_y = LineEditTreeSelector(model) vertical_box.addWidget(self.tree_selector_x) vertical_box.addWidget(self.tree_selector_y) if __name__ == '__main__': from PySide6.QtWidgets import QApplication import sys # creating application app = QApplication(sys.argv) model = MinimalModel() main = scatterplot_widget(model) # showing the window main.show() # loop sys.exit(app.exec_())
解决思路及修复代码
错误原因
- 非法持有根项引用:
MinimalModel中把self.invisibleRootItem()赋值给self.root_item并长期持有,而invisibleRootItem()返回的是Qt内部对象的临时引用,后续访问时可能因Qt内部管理导致该C++对象已被销毁。 - 多余的手动销毁:
TreeWidgetSingleSelectorWindow的closeEvent中手动调用self.tree.deleteLater(),子部件会随窗口自动销毁,手动调用可能引发提前销毁导致的引用失效。
修复步骤
- 移除
MinimalModel中的self.root_item成员变量,所有需要访问根项的地方直接调用self.invisibleRootItem()。 - 删除
TreeWidgetSingleSelectorWindow中closeEvent的self.tree.deleteLater()调用。 - 优化
on_check_state_changed方法,避免重复触发信号时的冗余操作。
修复后的完整代码
from PySide6.QtWidgets import QDialog, QPushButton, QWidget, QLineEdit from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QSizePolicy, QTreeView from PySide6.QtGui import QIcon, QStandardItemModel, QStandardItem from PySide6.QtCore import Qt class MinimalModel(QStandardItemModel): def __init__(self, parent=None): super(MinimalModel, self).__init__(parent) # 不保存根项引用,直接调用invisibleRootItem() root = self.invisibleRootItem() for i in range(2): item = QStandardItem("parent " + str(i)) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) root.appendRow(item) parentItem = item for j in range(4): item = QStandardItem("item "+ str(j)) item.setFlags(item.flags() | Qt.ItemIsUserCheckable | Qt.ItemNeverHasChildren) item.setCheckState(Qt.Unchecked) parentItem.appendRow(item) class TreeWidgetSingleSelector(QTreeView): executing = False def __init__(self, model): super().__init__() self.setModel(model) self.setHeaderHidden(True) self.model().dataChanged.connect(self.on_check_state_changed) def on_check_state_changed(self, index, column): if self.executing: return self.executing = True item = self.model().itemFromIndex(index) # 直接获取根项,不使用保存的引用 root = self.model().invisibleRootItem() # 遍历所有项取消勾选 for i in range(root.rowCount()): parent_item = root.child(i) parent_item.setCheckState(Qt.Unchecked) if parent_item.hasChildren(): for j in range(parent_item.rowCount()): child_item = parent_item.child(j) child_item.setCheckState(Qt.Unchecked) # 设置当前项为勾选状态 item.setCheckState(Qt.Checked) self.executing = False class TreeWidgetSingleSelectorWindow(QDialog): def __init__(self, model, line_widget): super().__init__() self.model = model self.tree = TreeWidgetSingleSelector(self.model) self.line_widget = line_widget self.setWindowTitle("Select Single Item") layout = QVBoxLayout() layout.addWidget(self.tree) save_button = QPushButton("Select Item") save_button.clicked.connect(self.save_item) h_lout = QHBoxLayout() h_lout.addStretch(1) h_lout.addWidget(save_button) layout.addLayout(h_lout) self.setLayout(layout) self.finished.connect(self.deleteLater) def save_item(self): if self.tree.model().rowCount() > 0: # 直接获取根项 root = self.tree.model().invisibleRootItem() items = [root.child(i) for i in range(root.rowCount())] for item in items: for i in range(item.rowCount()): child_item = item.child(i) if child_item.checkState() == Qt.Checked: self.line_widget.clear() self.line_widget.setText(item.text() + "." + child_item.text()) self.accept() def closeEvent(self, event): # 移除多余的deleteLater调用,子部件随窗口自动销毁 event.accept() class LineEditTreeSelector(QWidget): def __init__(self, model): super().__init__() self.model = model self.line_edit = QLineEdit() button = QPushButton() button.setFixedWidth(20) button.setFixedHeight(20) button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button.setIcon(QIcon("art/add_24.png")) layout = QHBoxLayout(self) layout.addWidget(button) layout.addWidget(self.line_edit) layout.setSpacing(0) button.clicked.connect(self.on_button_clicked) def on_button_clicked(self): tree_selector = TreeWidgetSingleSelectorWindow(self.model, self.line_edit) tree_selector.exec() def get_text(self): return self.line_edit.text() class scatterplot_widget(QWidget): def __init__(self, model): QWidget.__init__(self) vertical_box = QVBoxLayout(self) self.tree_selector_x = LineEditTreeSelector(model) self.tree_selector_y = LineEditTreeSelector(model) vertical_box.addWidget(self.tree_selector_x) vertical_box.addWidget(self.tree_selector_y) if __name__ == '__main__': from PySide6.QtWidgets import QApplication import sys app = QApplication(sys.argv) model = MinimalModel() main = scatterplot_widget(model) main.show() sys.exit(app.exec_())
内容的提问来源于stack exchange,提问作者RNunes
相关产品推荐
相关产品推荐

