How to reimplement LessThan() method in QSortFIlterProxyModel subclass for Sorting QTableView
-
Hello,
I Have a QTableView, this is my (basic) architecture of my models setup
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, parent=None): ProxyModel = ProxyModel() TableModel = TableModel() ProxyModel.setSourceModel(TableModel) self.MyTableView.setModel(ProxyModel)
I've achieved the filtering with success by reimplementing
FilterAcceptsrow()
in ProxyModel class (QSortFilterProxyModel Subsclass). I want to reimplementlessThan()
method in ProxyModel to achieve sorting. However, even though been documenting on it, I still can't figure out how to do it.Can someone explain to me the basis of how to reimplement this method so I can sort my QTableView on the 4th column for example.
Thanks.Here's the ProxModel and TableModel implementation:
class ProxyModel(QtCore.QSortFilterProxyModel): def __init__(self,parent=None): super(ProxyModel, self).__init__() self._filter = "Aucun" def setFilterColumn(self, header): if header == "Aucun": self.Filtre("Aucun") return True for col in range(self.sourceModel().columnCount()): if self.sourceModel().headerData(col) == header: self.setFilterKeyColumn(col) return True return False def Filtre(self, valeur): self._filter = str(valeur) self.invalidateFilter() 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 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=False): if not row: row = self.rowCount() self.beginInsertRows(QtCore.QModelIndex(), row, row) self._items.append(rowObject) self.endInsertRows() self.layoutChanged.emit()
-
def lessThan(source_left: QModelIndex, source_right: QModelIndex) -> bool: return source_left.data() < source_right.data()
But that's the default implementation anyway if you don't override it. See https://doc-snapshots.qt.io/qtforpython-dev/PySide2/QtCore/QSortFilterProxyModel.html#PySide2.QtCore.PySide2.QtCore.QSortFilterProxyModel.lessThan for what it can handle. You only need to override if you have a more complex data object or criteria for sorting. Since you say nothing about what your "4th column" is I cannot say further.
You might write something along the lines of:
def lessThan(source_left: QModelIndex, source_right: QModelIndex) -> bool: if source_left.column() == 3: return special_comparison(source_left, source_right) # or perhaps val_left = source_left.data(some_role_returning_native_data_object) val_right = source_right.data(some_role_returning_native_data_object) return val_left.lessThan(val_right) return source_left.data() < source_right.data() # or return QSortFIlterProxyModel.lessThan(source_left, source_right)
-
Thanks for your answer. Well, What I really want to achieve is sorting on 3 columns, my column 1 hold boolean values, col 2 holds string values and third one holds Integer values. You can see the example in the image attached to get more understanding.
The aim of my post is to get an understanding about the lessThan() function, (the left and right inputs, what does those mean ? I get that those are indexes, but of what ?)
If I understand how the sorting according to 1 column works, maybe I can implement the sorting I want.
-
@hachbani
Since the data you show looks perfectly sortable --- a boolean, a string, an integer columns --- I would not epect you to need to overridelessThan
at all here. Why do you think you need to? You just ought set up sorting by columns on theQTableView
(QTableView::setSortingEnabled(true)
) and off you go?As for the parameters to
lessThan(source_left, source_right)
, if you do choose to implement it. Don't worry about the names, left/right. This function will be called by Qt repeatedly. Each time these parameters are justQModelIndex
es into your data. The rows will differ, but the columns will always be the same, your sort column index. Your only job is to return abool
according as you want the data at the first index to be treated as "less than", for the purpose of sorting, the data at the second one. -
After adding all the data I want to display to my QtableView, I want to display them in a specific order, hence the urge to wanting to implement a lessThan() method, so I can do it programmatically.
To my understanding, If I set up the sorting in my TableView (setSortingEnabled(true)), This will give the option to sort the columns by clicking on the header, which is still something that I'd like to have in my app, but it doesn't solve my problem of displaying the data in the order I want.
I might be wrong, as I'm just trying to learn about sorting in Qt, what do you think about my understanding of this ?
-
@hachbani
OK, then you don't want to enable column click sorting (for now). So just callQSortFilterProxyModel::sort(int column, Qt::SortOrder order = Qt::AscendingOrder)
on the 4th column. And implementlessThan()
the way I showed above, for your "specific order" that you want on the 4th column.I don't know what else there is to say, just do it.
-
Hey ! I've done that, It was calling the
QSortFilterProxyModel::sort(int column, Qt::SortOrder order = Qt::AscendingOrder)
That I was missing to get an understanding of how this works ! I've tried to sort on a single column and It worked. Thanks !!I've read somewhere that in order to get multiple columns sorting, you need to call the sorting method on every column.
self.MyTableView.model().sort(2, QtCore.Qt.AscendingOrder) self.MyTableView.model().sort(3, QtCore.Qt.AscendingOrder)
I've tried that, and, as expected, the sorting on the second column is no more on.
I'll look through how to achieve multiple column sorting.
Thanks again @JonB !
-
@hachbani
You cannot sort by "secondary", multiple columns by callingsort()
multiple times. The latest call simply replaces any earlier calls.Here indeed is where you will need to implement
lessThan()
correctly and in a more complex scenario.Take your apparent example of column #2 is the "primary" sort column and column #3 is the "secondary" sort column, meaning it only comes into play when two rows have the same value in column #2. Now you will need code like:
def lessThan(source_left: QModelIndex, source_right: QModelIndex) -> bool: if source_left.column() == 2: if source_left.data() < source_right.data() return True; elif source_left.data() > source_right.data() return False; else return lessThan(source_left.siblingAtColumn(3), source_right.siblingAtColumn(3)) return QSortFIlterProxyModel.lessThan(source_left, source_right)
To implement "I want to only apply the sorting on the rows 1 --> 20" you would have to do something like look at the
row()
numbers in the passed in indexes and act accordingly. This would be source (not proxy) model row numbers. And you'll have to return something when asked to compare rows > 20. Plus, don't forget, your rows in the source are also filtered to only include certain ones, so the row numbers will not be what you might expect. You may not be able to preserve the order you would not have seen without sorting. You cannot instruct the sort not to happen on certain rows. It's a bit hokey, a sort is a sort, but you can implement whatever you like.