我正在使用一个简单的
QTableView
/QAbstractTabmeModel
/QSortFilterProxyModel
设置(为简洁起见进行了编辑):
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())
问题出在
on_[add|del]_clicked()
,我需要从已排序的行(由self.items.current_index()
返回)映射回原始未排序的ItemModel._rows
中的索引。
我怎样才能实现这个目标?
我发现了。 我需要使用
QSortFilterProxyModel.mapToSource()
就我而言,类似:
@pyqtSlot()
def on_del_clicked(self):
sel = self.items.currentIndex() <-- this is proxy index
if sel.isValid():
orig = self.item_proxy.mapToSource(sel) <-- this is model index
idx = self.item_model.id(orig.row()) <-- use as needed
self.storage_model.edit(idx, -1)