You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

解决思路及修复代码

错误原因

  1. 非法持有根项引用:MinimalModel中把self.invisibleRootItem()赋值给self.root_item并长期持有,而invisibleRootItem()返回的是Qt内部对象的临时引用,后续访问时可能因Qt内部管理导致该C++对象已被销毁。
  2. 多余的手动销毁:TreeWidgetSingleSelectorWindow的closeEvent中手动调用self.tree.deleteLater(),子部件会随窗口自动销毁,手动调用可能引发提前销毁导致的引用失效。

修复步骤

  1. 移除MinimalModel中的self.root_item成员变量,所有需要访问根项的地方直接调用self.invisibleRootItem()。
  2. 删除TreeWidgetSingleSelectorWindow中closeEvent的self.tree.deleteLater()调用。
  3. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 20:24:51