I am using a simple QTableView
/QAbstractTabmeModel
/QSortFilterProxyModel
setup (edited for brevity):
rom collections import namedtuple
from typing import Optional
from PyQt6 import uic
from PyQt6.QtCore import QAbstractTableModel, pyqtSlot, Qt, QSortFilterProxyModel
from PyQt6.QtWidgets import QWidget, QTableView, QHeaderView, QButtonGroup, QPushButton
import Fandom
class IModel(QAbstractTableModel):
_column = namedtuple('_column', "name func hint align")
def __init__(self, columns: [_column]):
self._columns = columns
super().__init__()
self._rows = []
self.select()
def data(self, index, role=...):
match role:
case Qt.ItemDataRole.DisplayRole:
row = self._rows[index.row()]
return self._columns[index.column()].func(row)
case Qt.ItemDataRole.TextAlignmentRole:
return self._columns[index.column()].align
return None
def headerData(self, section, orientation, role=...):
if orientation == Qt.Orientation.Horizontal:
match role:
case Qt.ItemDataRole.DisplayRole:
return self._columns[section].name
return None
def rowCount(self, parent=...):
return len(self._rows)
def columnCount(self, parent=...):
return len(self._columns)
def select(self, what=None):
self.beginResetModel()
self._rows = []
self.endResetModel()
def set_hints(self, view: QTableView):
header = view.horizontalHeader()
for i, x in enumerate(self._columns):
header.setSectionResizeMode(i, x.hint)
class ItemModel(IModel):
def __init__(self):
super().__init__([
IModel._column('ID', lambda x: x['ID'],
QHeaderView.ResizeMode.ResizeToContents,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter),
IModel._column('Name', lambda x: x['Name'],
QHeaderView.ResizeMode.ResizeToContents,
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter),
IModel._column('Description', lambda x: x['desc'],
QHeaderView.ResizeMode.ResizeToContents,
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter),
])
def select(self, what='ALL'):
self.beginResetModel()
items = [
{'ID': 1, 'Name': 'foo', 'desc': 'Something'},
{'ID': 2, 'Name': 'fie', 'desc': 'Something else'},
{'ID': 3, 'Name': 'faa', 'desc': 'Another'},
{'ID': 4, 'Name': 'fum', 'desc': 'Another one'},
]
self._rows = items
self.endResetModel()
def id(self, idx: int):
return self._rows[idx]['ID']
def value(self, idx: int):
return self._rows[idx]
class Storage(QWidget):
def __init__(self, *args, **kwargs):
self.items: Optional[QTableView] = None
self.storage: Optional[QTableView] = None
super().__init__(*args, **kwargs)
uic.loadUi("Storage.ui", self)
self.item_model = ItemModel()
self.item_proxy = QSortFilterProxyModel()
self.item_proxy.setSourceModel(self.item_model)
self.items.setModel(self.item_proxy)
self.item_model.set_hints(self.items)
self.items.setSortingEnabled(True)
self.items.sortByColumn(1, Qt.SortOrder.AscendingOrder)
self.items.reset()
@pyqtSlot()
def on_add_clicked(self):
sel = self.items.currentIndex()
if sel.isValid():
idx = self.item_model.id(sel.row())
print(idx)
@pyqtSlot()
def on_del_clicked(self):
sel = self.items.currentIndex()
if sel.isValid():
idx = self.item_model.id(sel.row())
print(idx)
if __name__ == '__main__':
from PyQt6.QtWidgets import QMainWindow, QApplication
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.storage = Storage()
self.setCentralWidget(self.storage)
app = QApplication([])
win = MainWindow()
win.show()
from sys import exit
exit(app.exec())
Problem is in on_[add|del]_clicked()
I need to map back from sorted rows (returned by self.items.current_index()
) to the index in the original, unsorted ItemModel._rows
.
How can I achieve this?
I found out. I need to use
QSortFilterProxyModel.mapToSource()
in my case something like: