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

QSortFilterProxyModel不更新:使用代理模型后QTableView无行显示

问题原因与修复方案

你的问题核心在于自定义KeyedTableModel没有遵循Qt模型视图框架的信号通知规范,导致QSortFilterProxyModel无法同步到源模型的行插入操作。

具体问题点

  • insertRow方法直接修改内部数据列表_data,但未发出rowsInserted信号,代理模型无法感知新行的添加。
  • 插入新行时没有使用Qt模型要求的beginInsertRows和endInsertRows方法包裹操作,这两个方法会自动触发必要的信号,通知视图和代理模型更新。
  • setData方法修改数据后未单独发出dataChanged信号(虽然批量操作时最后发了,但单个数据修改时也需要保证信号触发)。

修复后的代码

import sys
import typing
from PyQt5.QtCore import QModelIndex, Qt, QSortFilterProxyModel, QAbstractTableModel
from PyQt5.QtWidgets import QTableView, QWidget, QApplication
from typing import Any, List, Dict, Optional, Iterable, Tuple, Union

KeyType = Tuple[Any, ...]


class KeyedTableModel(QAbstractTableModel):
    """Keyed table model."""

    def __init__(self, headers: Iterable[str],  key_columns: Union[int, Iterable[int]]) -> None:
        """Initialize the class."""
        super().__init__()
        self._headers = list(headers)
        self._key_columns = [key_columns] if isinstance(key_columns, int) else list(key_columns)

        self._key_to_row_idx: Dict[KeyType, int] = {}
        self._data: List[List[Any]] = []

    def _get_row_key(self, row: List[str]) -> KeyType:
        return tuple(row[c] for c in self._key_columns)

    def columnCount(self, parent: QModelIndex = ...) -> int:
        return len(self._headers)

    def rowCount(self, parent: QModelIndex = ...) -> int:
        return len(self._data)

    def insertRow(self, row: int, parent: QModelIndex = QModelIndex()) -> bool:
        # 使用begin/endInsertRows包裹插入操作,自动触发rowsInserted信号
        self.beginInsertRows(parent, row, row)
        self._data.insert(row, [None] * self.columnCount())
        self.endInsertRows()
        return True

    def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
        """Get cell data."""
        if not index.isValid():
            return None
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]
        return None

    def setData(self, index: QModelIndex, value: typing.Any, role: int = Qt.DisplayRole) -> bool:
        """Set cell data."""
        if not index.isValid():
            return False
        if role == Qt.DisplayRole:
            self._data[index.row()][index.column()] = value
            # 发出dataChanged信号通知视图更新
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any:
        """Get header data."""
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self._headers[section])
            if orientation == Qt.Vertical:
                return str(section)
        return None

    def add_or_update(self, rows: Union[Iterable[List[Any]], List[Any]]) -> None:
        """Add or update row in the model."""
        if isinstance(rows, List) and rows and not isinstance(rows[0], List):
            rows = [rows]
        min_row_idx, max_row_idx = sys.maxsize, -sys.maxsize
        for row in rows:
            key = self._get_row_key(row)
            row_idx = self._key_to_row_idx.get(key)
            if row_idx is None:
                row_idx = self.rowCount()
                self.insertRow(row_idx)
                self._key_to_row_idx[key] = row_idx
            for c, val in enumerate(row):
                self.setData(self.index(row_idx, c), val)
            min_row_idx = min(min_row_idx, row_idx)
            max_row_idx = max(max_row_idx, row_idx)
        # 批量数据更新的信号可以保留,确保代理模型同步范围数据
        top_left = self.index(min_row_idx, 0)
        bot_right = self.index(max_row_idx, self.columnCount() - 1)
        self.dataChanged.emit(top_left, bot_right)


class TableWidgetPlus2(QTableView):

    def __init__(self, headers: Iterable[str],
                 key_columns: Union[int, Iterable[int]],
                 parent: Optional[QWidget] = None) -> None:
        super().__init__(parent)
        self._model = KeyedTableModel(headers, key_columns)
        self._proxy_model = QSortFilterProxyModel(self)
        self._proxy_model.setSourceModel(self._model)
        self.setModel(self._proxy_model)

    def add_or_update(self, rows: Union[Iterable[List[Any]], List[Any]]) -> None:
        """Add or update row."""
        self._model.add_or_update(rows)


def table_widget_plus_2_demo_main() -> int:
    """Simulation main function."""
    app = QApplication(sys.argv)
    win = TableWidgetPlus2(['ID', 'Action', 'Repeat'], [0])
    win.add_or_update(['A1', 'walk', 100])
    win.add_or_update(['A1', 'stop', 200])
    win.resize(1000, 500)
    win.showMaximized()
    res = app.exec_()
    return res


if __name__ == '__main__':
    table_widget_plus_2_demo_main()

关键修改说明

  1. insertRow方法:用beginInsertRows和endInsertRows包裹数据插入操作,这两个方法会自动发出rowsInserted信号,让代理模型感知到新行的添加。
  2. setData方法:修改数据后发出dataChanged信号,确保单个单元格修改时视图和代理模型能同步更新,同时完善了索引有效性检查。
  3. data和headerData方法:添加了默认返回None的逻辑,符合Qt模型的规范(未处理的角色需要返回空值)。
  4. 移除repaint调用:模型信号会自动触发视图更新,不需要手动调用repaint。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:25:58