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

PySide6同时使用两个TreeView代理致程序崩溃问题求助

Qt TreeView同时使用两个代理触发Segmentation Fault崩溃问题

问题现象

单独给TreeView的列添加复选框代理或进度条代理时,程序运行正常;但同时为两列分别添加这两个代理后,程序在加载并显示列表行时立即崩溃,抛出Segmentation Fault错误。尝试调用deleteLater()方法无法解决问题。

复现代码

delegates.py

from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QEvent, QPoint, QRect
from PySide6 import QtCore, QtGui


class CheckBoxDelegate(QtWidgets.QStyledItemDelegate):
    """
    为指定列提供可交互的复选框代理
    """
    def __init__(self, parent = None):
        QtWidgets.QStyledItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        """
        重写此方法避免点击时创建编辑器
        """
        return None

    def paint(self, painter, option, index):
        checked = bool(index.model().data(index, Qt.DisplayRole))
        check_box_style_option = QtWidgets.QStyleOptionButton()
        if (index.flags() & Qt.ItemIsEditable):
            check_box_style_option.state |= QtWidgets.QStyle.State_Enabled
        else:
            check_box_style_option.state |= QtWidgets.QStyle.State_ReadOnly
        if checked:
            check_box_style_option.state |= QtWidgets.QStyle.State_On
        else:
            check_box_style_option.state |= QtWidgets.QStyle.State_Off
        check_box_style_option.rect = self.getCheckBoxRect(option)
        QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_CheckBox, check_box_style_option, painter)

    def editorEvent(self, event, model, option, index):
        if not (index.flags() & Qt.ItemIsEditable):
            return False
        # 过滤无效操作
        if event.type() == QEvent.MouseButtonRelease or event.type() == QEvent.MouseButtonDblClick:
            if event.button() != Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
                return False
            if event.type() == QEvent.MouseButtonDblClick:
                return True
        elif event.type() == QEvent.KeyPress:
            if event.key() != Qt.Key_Space and event.key() != Qt.Key_Select:
                return False
        else:
            return False
        # 切换复选框状态
        self.setModelData(None, model, index)
        return True

    def getCheckBoxRect(self, option):
        check_box_style_option = QtWidgets.QStyleOptionButton()
        check_box_rect = QtWidgets.QApplication.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
        check_box_point = QPoint (option.rect.x() +
                                option.rect.width() / 2 -
                                check_box_rect.width() / 2,
                                option.rect.y() +
                                option.rect.height() / 2 -
                                check_box_rect.height() / 2)
        return QRect(check_box_point, check_box_rect.size())

    def setModelData (self, editor, model, index):
        newValue = not bool(index.model().data(index, Qt.DisplayRole))
        model.setData(index, newValue, Qt.EditRole)


class ProgressBarDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
            super().__init__(parent)

    def paint(self, painter, option, index):
        # 获取进度数据,处理空值情况
        progress = index.data(QtCore.Qt.ItemDataRole.UserRole)
        if progress is None:
            progress = 0.0

        # 绘制默认单元格样式
        super().paint(painter, option, index)
        
        # 绘制进度条填充(留出边距避免覆盖边框)
        painter.save()
        rect = option.rect.adjusted(2, 2, -2, -2)
        rect.setWidth(int(rect.width() * progress))
        painter.fillRect(rect, QtGui.QColor("#00c0ff"))
        painter.restore()

mainwindow.py

from ui_form import Ui_MainWindow
from PySide6.QtCore import QThread, SIGNAL, Slot
from PySide6 import QtGui, QtCore
from PySide6.QtWidgets import QApplication, QMainWindow

from delegates import CheckBoxDelegate, ProgressBarDelegate


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.row_data = []
        self.model = QtGui.QStandardItemModel()
        self.ui.actionExit.triggered.connect(self.exit)
        # 将代理设为实例属性,避免被垃圾回收
        self.cb_delegate = CheckBoxDelegate(self)
        self.pb_delegate = ProgressBarDelegate(self)

    def reinit_model(self):
        self.model.clear()
        self.rootItem = self.model.invisibleRootItem()
        self.model.setHorizontalHeaderLabels(['Checkbox', 'Title', 'Progress'])
        self.ui.treeView.setModel(self.model)

    def get_row_data(self):
        row_data = []
        for i in range(3):
            title = f"Col 1 Row {i}"
            row_data.append([title])
        return row_data

    def reset_row_data(self):
        self.row_data.clear()
        self.row_data = self.get_row_data()

    class Show_list(QThread):
        def __init__(self, AppWindow):
            QThread.__init__(self)
            self.AppWindow = AppWindow

        def run(self):
            self.AppWindow.reset_row_data()

    def populate_window_list(self):
        self.reinit_model()
        for title_link in self.row_data:
            item = [QtGui.QStandardItem(),
                    QtGui.QStandardItem(title_link[0]),
                    QtGui.QStandardItem()]
            progress = 0.5
            item[2].setData(progress, QtCore.Qt.ItemDataRole.UserRole)
            # 给复选框列设置默认值和可编辑属性
            item[0].setData(False, Qt.DisplayRole)
            item[0].setFlags(item[0].flags() | Qt.ItemIsEditable)
            self.rootItem.appendRow(item)
        self.ui.treeView.show()
        # 使用实例属性的代理
        self.ui.treeView.setItemDelegateForColumn(0, self.cb_delegate)
        self.ui.treeView.setItemDelegateForColumn(2, self.pb_delegate)
        for i in range(3):
            self.ui.treeView.resizeColumnToContents(i)

    @Slot()
    def show_list(self):
        self.thread = self.Show_list(self)
        self.connect(self.thread, SIGNAL("finished()"),
                     self.populate_window_list)
        self.thread.start()

    def exit(self):
        QApplication.quit()

main.py

import sys
from mainwindow import MainWindow
from PySide6.QtWidgets import QApplication


if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = MainWindow()
    widget.reinit_model()
    getRowDataButton = widget.ui.getRowDataButton
    getRowDataButton.clicked.connect(widget.show_list)
    widget.show()
    sys.exit(app.exec())

ui_form.py

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'form.ui'
##
## Created by: Qt User Interface Compiler version 6.5.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
    QMetaObject, QObject, QPoint, QRect,
    QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
    QFont, QFontDatabase, QGradient, QIcon,
    QImage, QKeySequence, QLinearGradient, QPainter,
    QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QAction, QActionGroup, QApplication,
    QMainWindow, QMenu, QMenuBar, QSizePolicy,
    QStatusBar, QTreeView, QWidget, QPushButton)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.treeView = QTreeView(self.centralwidget)
        self.treeView.setObjectName(u"treeView")
        self.treeView.setGeometry(QRect(10, 10, 781, 521))
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.getRowDataButton = QPushButton(self.centralwidget)
        self.getRowDataButton.setObjectName(u"getRowDataButton")
        self.getRowDataButton.setGeometry(QRect(320, 540, 161, 24))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(MainWindow)
        self.menubar.setObjectName(u"menubar")
        self.menubar.setGeometry(QRect(0, 0, 800, 22))
        self.menuFile = QMenu(self.menubar)
        self.menuFile.setObjectName(u"menuFile")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.actionExit = QAction(MainWindow)
        self.actionExit.setObjectName(u"actionExit")

        self.menuFile.addAction(self.actionExit)

        self.menubar.addAction(self.menuFile.menuAction())

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.getRowDataButton.setText(QCoreApplication.translate("MainWindow", u"Get Row Data", None))
        self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
        self.actionExit.setText(QCoreApplication.translate("MainWindow", u"Exit", None))
    # retranslateUi

问题原因

崩溃的核心原因是代理对象的生命周期管理问题:
原代码中cb_delegate和pb_delegate是populate_window_list方法内的局部变量,方法执行完毕后会被Python垃圾回收机制销毁。但Qt的setItemDelegateForColumn方法不会接管代理对象的所有权,后续TreeView尝试访问已被回收的代理内存时,就会触发Segmentation Fault错误。

解决方案

将代理对象保存为MainWindow的实例属性,确保其生命周期与主窗口一致,避免被提前回收:

  1. 在MainWindow的__init__方法中初始化代理,并传入父对象self,借助Qt的父子对象机制辅助管理生命周期
  2. 在populate_window_list中直接使用实例属性的代理对象设置给TreeView

同时补充了两处优化:

  • 给复选框列的Item添加Qt.ItemIsEditable标志,确保复选框可交互
  • 在ProgressBarDelegate的paint方法中先调用父类paint绘制默认样式,并添加边距让进度条显示更美观

内容的提问来源于stack exchange,提问作者sequence

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 11:10:11