QSortFilterProxyModel - not updating

75 Views Asked by At

I am trying to use QTableView with a custom model and QSortFilterProxyModel.

If I set the base model: self.setModel(self._model) the table displays correctly.

However, if I set the proxy model: self.setModel(self._proxy_model) no rows are displayed.

Not sure why. The docs https://doc.qt.io/qtforpython-5/PySide2/QtCore/QSortFilterProxyModel.html does not give a clue... Nor in general is it possible to find reasonable PyQt resources or examples on the internet.

My code below (python3.9, PyQt5, Windows):

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:
        # The length of our headers.
        return len(self._headers)

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

    def insertRow(self, row: int, parent: QModelIndex = ...) -> bool:
        self._data.insert(row, [None] * self.columnCount())
        return True

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

    def setData(self, index: QModelIndex, value: typing.Any, role: int = Qt.DisplayRole) -> bool:
        """Set cell data."""
        if role == Qt.DisplayRole:
            self._data[index.row()][index.column()] = value
        return True

    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)

    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)
        self.repaint()


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

There are 0 best solutions below