Реализация методов QAbstractProxyModel

У меня есть QSqlTableModel, который имеет примерно эту структуру:

| ID |  Name  |
---------------
|  0 |  Xxxx  |
|  2 |  Yyyy  |
|  5 |  Zzzz  |

Как видите, идентификаторы (которые являются уникальными) являются последовательными, но их можно пропустить; доступный диапазон всегда от 0 до 1023. Мне нужно создать таблицу, которая заполняет пробелы до 1024 строк, без изменения макета исходной модели. Окончательный результат будет примерно таким:

|  0 |  Xxxx  |
|    |        |
|  2 |  Yyyy  |
|    |        |
|    |        |
|  5 |  Zzzz  |
      ...
|1023|  Xyz   |

"Пустые" элементы не будут редактируемыми, но пользователь сможет использовать перетаскивание для изменения порядка (что будет реализовано внутренним интерфейсом с моделью SQL) и добавления / удаления элементов, при этом размер таблицы всегда будет составлять 1024 строки.

Я пытался реализовать QAbstractProxyModel, но у меня есть некоторые проблемы. Например, флаги не соблюдаются, и каждый элемент не редактируется и не может быть выбран. Даже если я специально вернусь ItemIsEnabled|ItemIsSelectable|ItemIsEditableничего не меняется. Кроме того, нажатие на элементы дает странные результаты для выделения заголовка.

Я полагаю, я делаю что-то не так с mapToSource/mapFromSource, но я не уверен.

Это пример, который я сделал, используя QStandardItemModel вместо QSqlTableModel (результирующее поведение такое же) с 4 строками и прокси с 6 строками.

class BaseModel(QtGui.QStandardItemModel):
    def __init__(self):
        QtGui.QStandardItemModel.__init__(self)
        for id, name in [(1, 'One'), (2, 'Two'), (3, 'Three'), (5, 'Five')]:
            idItem = QtGui.QStandardItem()
            idItem.setData(id, QtCore.Qt.DisplayRole)
            nameItem = QtGui.QStandardItem(name)
            self.appendRow([idItem, nameItem])


class ProxyModel(QtCore.QAbstractProxyModel):
    def data(self, index, role):
        source = self.mapToSource(index)
        if source.isValid():
            return source.data(role)
        return None

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return str(section + 1)
        return QtCore.QAbstractProxyModel.headerData(self, section, orientation, role)

    def setData(self, index, value, role):
        return self.sourceModel().setData(self.mapToSource(index), value, role)

    def index(self, row, column, parent=None):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, row, flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), column)
        return self.createIndex(row, column)

    def parent(self, index):
        return self.sourceModel().index(index.row(), index.column()).parent()

    def flags(self, index):
        source = self.mapToSource(index)
        if source.isValid():
            return source.flags()
        return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable

    def rowCount(self, index):
        return 6

    def columnCount(self, index):
        return 2

    def mapToSource(self, index):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, index.row(), flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), index.column())
        return QtCore.QModelIndex()

    def mapFromSource(self, index):
        if index.row() < 0:
            return QtCore.QModelIndex()
        row = self.sourceModel().index(index.row(), 0).data()
        return self.createIndex(row, index.column())


class Win(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)
        self.model = BaseModel()
        self.proxy = ProxyModel()
        self.proxy.setSourceModel(self.model)
        table = QtWidgets.QTableView()
        table.setModel(self.proxy)
        layout.addWidget(table)

1 ответ

Решение

Нет необходимости реализовывать QAbstractProxyModelв этом случае достаточно использовать QIdentityProxyModel:

class ProxyModel(QtCore.QIdentityProxyModel):
    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return str(section + 1)
        return QtCore.QIdentityProxyModel.headerData(self, section, orientation, role)

    def setData(self, index, value, role=QtCore.Qt.DisplayRole):
        if index.column() == 0:
            if not (0 <= int(value) < self.rowCount()) :
                return False
        return QtCore.QIdentityProxyModel.setData(self, index, value, role)

    def rowCount(self, index=QtCore.QModelIndex()):
        return 6

    def index(self, row, column, parent=QtCore.QModelIndex()):
        return self.createIndex(row, column)

    def mapFromSource(self, sourceIndex):
        if sourceIndex.isValid()  and 0 <= sourceIndex.row() < self.rowCount():
            ix = self.sourceModel().index(sourceIndex.row(), 0)
            return self.index(int(ix.data()), sourceIndex.column())
        return QtCore.QModelIndex()

    def mapToSource(self, proxyIndex):
        res = self.sourceModel().match(self.sourceModel().index(0, 0), QtCore.Qt.DisplayRole, proxyIndex.row(), flags=QtCore.Qt.MatchExactly)
        if res:
            return res[0].sibling(res[0].row(), proxyIndex.column())
        return QtCore.QModelIndex()
Другие вопросы по тегам