Search code examples
pythonpyside2

Sort on Multiple columns in QTableView


I'm using Pyside2, Python 3.8

I have a QTableView, with a QSortFilterProxyModel Model. I managed to sort my rows on a single column. What I want to achieve is sort myTableView on the column 3 (contains String data), then on column 2 (contains Bool data) and then on Column 4 (contains integer data). See the below picture for an example enter image description here

I've been trying about different why to do this, It seems that the hack my be in the lessThan() method, but It's very confusing to me.

Can someone give me a hint on how should I proceed?

Here's some samples of my code, If it helps any one.

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self, parent=None):

        ProxyModel = ProxyModel()
        TableModel = TableModel()
        ProxyModel.setSourceModel(TableModel)

        self.MyTableView.setModel(ProxyModel)

class ProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self,parent=None):
        super(ProxyModel, self).__init__()
        self._filter = "Aucun"

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if self._filter == "Aucun": return True

        sourceModel = self.sourceModel()
        id = sourceModel.index(sourceRow, self.filterKeyColumn(), sourceParent)
        if sourceModel.data(id) == self._filter:
            return True
        return False

    def lessThan(self, left, right):
        print(left.row(), ' vs ',right.row())
        
        if left.column() == 3:
            leftData = int(self.sourceModel().data(left))
            rightData = int(self.sourceModel().data(right))
        if left.column() == 2:
            leftData = str(self.sourceModel().data(left))
            rightData = str(self.sourceModel().data(right))
        return leftData < rightData
        
class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, mlist=None):
        super(TableModel, self).__init__()
        self._items = [] if mlist == None else mlist
        self._header = []
    
    def rowCount(self, parent = QtCore.QModelIndex):
        return len(self._items)

    def columnCount(self, parent = QtCore.QModelIndex):
        return len(self._header)

    def data(self, index, role = QtCore.Qt.DisplayRole):
        if not index.isValid():
           return None
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            return self._items[index.row()][index.column()]
        return None
    
    def setData(self, index, value, role = QtCore.Qt.EditRole):
        if value is not None and role == QtCore.Qt.EditRole:
            self._items[index.row()-1][index.column()] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def addRow(self, rowObject):
        row = self.rowCount()
        self.beginInsertRows(QtCore.QModelIndex(), row, row)
        self._items.append(rowObject)
        self.endInsertRows()
        self.layoutChanged.emit()

Solution

  • QSortFilterProxyModel calls lessThan only once for each row pair with indexes set to index(left_row, sort_column) and index(right_row, sort_column), so implementation must take this into account. Ignore column part and access columns you interested in.

    def lessThan(self, left, right):
        row1 = left.row()
        row2 = right.row()
        model = left.model()
        for col in [3,2,4]:
            a = model.data(model.index(row1, col))
            b = model.data(model.index(row2, col))
            if a < b:
                return True
            elif a > b:
                return False
        return True