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()
关键修改说明
insertRow方法:用beginInsertRows和endInsertRows包裹数据插入操作,这两个方法会自动发出rowsInserted信号,让代理模型感知到新行的添加。setData方法:修改数据后发出dataChanged信号,确保单个单元格修改时视图和代理模型能同步更新,同时完善了索引有效性检查。data和headerData方法:添加了默认返回None的逻辑,符合Qt模型的规范(未处理的角色需要返回空值)。- 移除
repaint调用:模型信号会自动触发视图更新,不需要手动调用repaint。
内容的提问来源于stack exchange,提问作者Elad Weiss
相关产品推荐
相关产品推荐

