qt-model-view

Qt Model/View Architecture

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "qt-model-view" with this command: npx skills add l3digital-net/claude-code-plugins/l3digital-net-claude-code-plugins-qt-model-view

Qt Model/View Architecture

Architecture Overview

Data Source ──→ Model ──→ [Proxy Model] ──→ View ──→ Delegate (renders cells) ↕ ↕ QAbstractItemModel QAbstractItemView

Separate data (model) from presentation (view). The delegate handles painting and editing per-cell. Proxy models layer transformations (sort, filter) without modifying the source model.

Choosing a Model Base Class

Base class When to use

QStringListModel

Simple list of strings

QStandardItemModel

Quick prototype or small dataset

QAbstractListModel

Custom list with single column

QAbstractTableModel

Custom table with rows × columns

QAbstractItemModel

Tree structures with parent/child

For anything non-trivial, subclass QAbstractTableModel or QAbstractListModel — QStandardItemModel has poor performance with large datasets and poor testability.

Custom Table Model

from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt from PySide6.QtGui import QColor

class PersonTableModel(QAbstractTableModel): HEADERS = ["Name", "Age", "Email"]

def __init__(self, data: list[dict], parent=None) -> None:
    super().__init__(parent)
    self._data = data

# --- Required overrides ---

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

def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
    return 0 if parent.isValid() else len(self.HEADERS)

def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> object:
    if not index.isValid():
        return None
    row, col = index.row(), index.column()
    item = self._data[row]

    match role:
        case Qt.ItemDataRole.DisplayRole:
            return str(item[self.HEADERS[col].lower()])
        case Qt.ItemDataRole.BackgroundRole if item.get("active") is False:
            return QColor("#f5f5f5")
        case Qt.ItemDataRole.ToolTipRole:
            return f"Row {row}: {item}"
        case _:
            return None

def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole) -> object:
    if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
        return self.HEADERS[section]
    return None

# --- Mutation support ---

def setData(self, index: QModelIndex, value: object, role: int = Qt.ItemDataRole.EditRole) -> bool:
    if not index.isValid() or role != Qt.ItemDataRole.EditRole:
        return False
    self._data[index.row()][self.HEADERS[index.column()].lower()] = value
    self.dataChanged.emit(index, index, [role])
    return True

def flags(self, index: QModelIndex) -> Qt.ItemFlag:
    base = super().flags(index)
    return base | Qt.ItemFlag.ItemIsEditable

# --- Batch updates (correct reset pattern) ---

def replace_all(self, new_data: list[dict]) -> None:
    self.beginResetModel()
    self._data = new_data
    self.endResetModel()

def append_row(self, item: dict) -> None:
    pos = len(self._data)
    self.beginInsertRows(QModelIndex(), pos, pos)
    self._data.append(item)
    self.endInsertRows()

Always bracket mutations with begin*/end* methods (beginInsertRows , beginRemoveRows , beginResetModel ). Skipping them causes views to lose sync with the model.

Connecting Model to View

from PySide6.QtWidgets import QTableView

model = PersonTableModel(people_data) view = QTableView() view.setModel(model)

Tuning

view.horizontalHeader().setStretchLastSection(True) view.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) view.setSortingEnabled(True) # requires QSortFilterProxyModel for custom models view.resizeColumnsToContents()

Sort and Filter with QSortFilterProxyModel

from PySide6.QtCore import QSortFilterProxyModel, Qt

source_model = PersonTableModel(data) proxy = QSortFilterProxyModel() proxy.setSourceModel(source_model) proxy.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) proxy.setFilterKeyColumn(0) # filter on "Name" column

view.setModel(proxy) view.setSortingEnabled(True)

Filter dynamically from a search box

setFilterRegularExpression is preferred for new code (uses QRegularExpression internally)

search_box.textChanged.connect(proxy.setFilterRegularExpression)

For modifying multiple filter parameters efficiently, use beginFilterChange/endFilterChange

rather than calling invalidateFilter() after each change

For custom filter logic, subclass QSortFilterProxyModel and override filterAcceptsRow .

Custom Item Delegate

Use delegates to render non-text data (progress bars, icons, custom widgets):

from PySide6.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QApplication from PySide6.QtGui import QPainter from PySide6.QtCore import QRect, Qt

class ProgressDelegate(QStyledItemDelegate): def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex) -> None: value = index.data(Qt.ItemDataRole.DisplayRole) if not isinstance(value, int): super().paint(painter, option, index) return # Draw progress bar using the style opt = QStyleOptionProgressBar() opt.rect = option.rect.adjusted(2, 4, -2, -4) opt.minimum = 0 opt.maximum = 100 opt.progress = value opt.text = f"{value}%" opt.textVisible = True QApplication.style().drawControl(QStyle.ControlElement.CE_ProgressBar, opt, painter)

view.setItemDelegateForColumn(2, ProgressDelegate(view))

Key Rules

  • Never access self._data directly from outside the model — always go through the model API

  • rowCount() and columnCount() must return 0 when parent.isValid() (Qt tree contract, even for tables)

  • dataChanged must be emitted with the exact changed index range — emitting the full model unnecessarily forces full view repaint

  • For large datasets (>10k rows), consider lazy loading via canFetchMore() / fetchMore()

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

qt-qml

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

qt-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

qt-packaging

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

qt-debugging

No summary provided by upstream source.

Repository SourceNeeds Review