pythonqtpyside2model-view

How can I properly implement QSortFilterProxyModel.parent() to handle a virtual column?


I have the following working code, which opens a QFileDialog with an extra column that shows the file name again (pointless, I know, but it’s a result of simplifying my issue):

from PySide2 import QtCore, QtWidgets


class MyProxyModel(QtCore.QSortFilterProxyModel):

    def __init__(self, parent=None):
        super(MyProxyModel, self).__init__(parent)
        self._parents = {}

    def mapToSource(self, index):
        if index.column() == 4:
            return QtCore.QModelIndex()
        return super(MyProxyModel, self).mapToSource(index)

    def columnCount(self, index):
        return 5

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole and index.column() == 4:
            return self.index(index.row(), 0, self._parents[index]).data(role)
        return super(MyProxyModel, self).data(index, role)

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if section == 4 and orientation == QtCore.Qt.Horizontal \
                and role == QtCore.Qt.DisplayRole:
            return 'My Column'
        return super(MyProxyModel, self).headerData(section, orientation, role)

    def index(self, row, column, parent=QtCore.QModelIndex()):
        if column == 4:
            index = self.createIndex(row, column)
            self._parents[index] = parent
            return index
        return super(MyProxyModel, self).index(row, column, parent)

    def parent(self, index):
        if index.column() == 4:
            return QtCore.QModelIndex()
        return super(MyProxyModel, self).parent(index)


QtWidgets.QApplication([])
dialog = QtWidgets.QFileDialog()
dialog.setOption(dialog.DontUseNativeDialog, True)
dialog.setProxyModel(MyProxyModel(dialog))
dialog.exec_()

As you can see, parent() is returning an invalid index for items of column 4, and instead I’m retrieving the actual parent inside data(), which isn’t ideal. But if I try the following, it exits with an access violation:

(...)
    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole and index.column() == 4:
            # Either return causes access violation.
            return self.index(index.row(), 0, self.parent(index)).data(role)
            return self.index(index.row(), 0, index.parent()).data(role)
            return index.sibling(index.row(), 0).data(role)
        return super(MyProxyModel, self).data(index, role)
(...)
    def parent(self, index):
        if index.column() == 4:
            return self._parents[index]
        return super(MyProxyModel, self).parent(index)
(...)

I also tried leveraging QModelIndex’s internal pointer, with the same result (access violation):

# No __init__() defined; data() exactly like above.
(...)
    def index(self, row, column, parent=QtCore.QModelIndex()):
        if column == 4:
            return self.createIndex(row, column, parent)
        return super(MyProxyModel, self).index(row, column, parent)

    def parent(self, index):
        if index.column() == 4:
            return index.internalPointer()
        return super(MyProxyModel, self).parent(index)
(...)

Pretty sure I’m missing something, but I can’t figure out what it is…


Solution

  • The main problem is that the parent should not be invalid, even for a "virtual" index.

    Also, in order to properly interact with the fake column, the following three aspects must be considered:

    class MyProxyModel(QtCore.QSortFilterProxyModel):
        # ...
        def mapToSource(self, index):
            if index.column() == 4:
                index = index.sibling(index.row(), 0)
            return super(MyProxyModel, self).mapToSource(index)
    
        def index(self, row, column, parent=QtCore.QModelIndex()):
            if column == 4:
                index = self.createIndex(row, column, parent.internalId())
                self._parents[index] = parent
                return index
            return super(MyProxyModel, self).index(row, column, parent)
    
        def parent(self, index):
            if index.column() == 4:
                return self._parents[index]
            return super(MyProxyModel, self).parent(index)
    
        def flags(self, index):
            if index.column() == 4:
                return self.flags(index.sibling(index.row(), 0))
            return super().flags(index)
    
        def sibling(self, row, column, idx):
            if column == 4:
                return self.index(row, column, idx.parent())
            elif idx.column() == 4:
                idx = self.index(idx.row(), 0, idx.parent())
            return super().sibling(row, column, idx)