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

QSortFilterProxyModel移除过滤器后持久化编辑器丢失求助

问题描述

在QTableView中,为了实现居中对齐的复选框列,通过自定义委托替代了默认的CheckStateRole生成的复选框,并将该列设置为持久化编辑器状态。使用QSortFilterProxyModel实现排序和通配符过滤功能后,发现清除过滤器时,之前被过滤隐藏的行的复选框不再显示,转而显示0或1的文本。

相关代码

import sys
import pandas as pd
from PySide6.QtCore import Qt, Property, Signal, QAbstractTableModel
from PySide6.QtCore import QSortFilterProxyModel, QSignalBlocker
from PySide6.QtWidgets import (QCheckBox, QHBoxLayout, QStyledItemDelegate,
                               QTableView, QWidget, QApplication, QMainWindow,
                               QVBoxLayout, QLineEdit, QFrame)


class CenterCheckBox(QWidget):
    toggled = Signal(bool)

    def __init__(self, parent):
        super().__init__(parent)
        layout = QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self.check = QCheckBox()
        layout.addWidget(self.check, alignment=Qt.AlignmentFlag.AlignCenter)
        self.check.setFocusProxy(self)
        self.check.toggled.connect(self.toggled)
        # set a 0 spacing to avoid an empty margin due to the missing text
        self.check.setStyleSheet('color: red; spacing: 0px;')
        self.setAutoFillBackground(True)

    @Property(bool, user=True)   # note the user property parameter
    def checkState(self):
        return self.check.isChecked()

    @checkState.setter
    def checkState(self, state):
        with QSignalBlocker(self.check) as blocker:
            self.check.setChecked(state)


class MyCheckboxDelegate(QStyledItemDelegate):
    def __init__(self, parent):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        check = CenterCheckBox(parent)
        check.toggled.connect(lambda: self.commitData.emit(check))
        return check

    def setModelData(self, editor, model, index):
        model.setData(index, editor.checkState)


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        print(data)
        self._data = data

    def rowCount(self, index=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                if self._data.columns[index.column()]=='Delete':
                    return ''
                value = self._data.iloc[index.row(), index.column()]
                return str(value)


class MyWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.frame = QFrame(self)
        self.verticalLayout = QVBoxLayout(self.frame)
        self.lineEdit = QLineEdit(self.frame)
        self.verticalLayout.addWidget(self.lineEdit)
        self.table_view = QTableView(self.frame)
        self.verticalLayout.addWidget(self.table_view)

        table_model = TableModel(pd.DataFrame([['1', '1'],
                                               ['0', '0'],
                                               ['2', '0']]))
        proxy_model = QSortFilterProxyModel()
        proxy_model.setSourceModel(table_model)
        self.table_view.setModel(proxy_model)
        self.table_view.setSortingEnabled(True)
        self.lineEdit.textChanged.connect(proxy_model.setFilterWildcard)

        self.table_view.setItemDelegateForColumn(1, MyCheckboxDelegate(self))
        for row in range(0, proxy_model.rowCount()):
            self.table_view.openPersistentEditor(proxy_model.index(row, 1))

        self.setCentralWidget(self.frame)


app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec())

求助内容

  1. 如何让清除过滤器后复选框重新显示?是否需要再次调用openPersistentEditor,若需要该何时调用?
  2. 是否有关于委托、视图、模型之间交互的详细说明文档?目前仅找到类文档和模型视图架构的基础描述。

解决方案

问题根源

初始化时仅为代理模型当前存在的行打开了持久化编辑器,当过滤器生效时,部分行被代理模型隐藏;清除过滤器后,这些行重新被插入到代理模型中,但此时没有自动为它们打开持久化编辑器,导致视图直接显示模型DisplayRole返回的0/1文本。

具体修复步骤

  1. 监听代理模型的行插入事件:当代理模型因过滤器清除而重新插入行时,自动为这些行的复选框列打开持久化编辑器。
  2. 修正模型的data方法:确保复选框列的DisplayRole返回空字符串,避免在编辑器未打开时显示0/1文本。

修改后的代码关键部分如下:

1. 为MyWindow添加行插入处理方法并连接信号

class MyWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        # ... 原有代码 ...

        proxy_model = QSortFilterProxyModel()
        proxy_model.setSourceModel(table_model)
        # 新增:连接行插入信号到处理方法
        proxy_model.rowsInserted.connect(self.handleRowsInserted)
        self.table_view.setModel(proxy_model)

        # ... 原有代码 ...

    def handleRowsInserted(self, parent, start, end):
        # 为新插入的行的第1列打开持久化编辑器
        for row in range(start, end + 1):
            index = self.table_view.model().index(row, 1)
            self.table_view.openPersistentEditor(index)

2. 修正TableModel的data方法

class TableModel(QAbstractTableModel):
    # ... 原有代码 ...
    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                # 修正:判断复选框列(索引为1),返回空字符串
                if index.column() == 1:
                    return ''
                value = self._data.iloc[index.row(), index.column()]
                return str(value)

文档推荐

关于委托、视图、模型的交互细节,可以重点查看Qt官方文档中的以下内容:

  • Model View Programming:详细讲解模型视图架构的核心流程,包括数据角色、代理模型的工作机制、委托的生命周期。
  • QStyledItemDelegate类文档:重点关注createEditor、setEditorData、setModelData等方法的调用时机,以及持久化编辑器的管理逻辑。
  • QTableView类文档:查看视图如何响应模型的行/列变化,以及openPersistentEditor的使用场景。

这些文档会覆盖从数据交互到编辑器渲染的完整流程,比基础架构描述更深入。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 01:50:58