Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 reimplement lessThan() 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.

    Capture.PNG

    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 override lessThan at all here. Why do you think you need to? You just ought set up sorting by columns on the QTableView (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 just QModelIndexes 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 a bool 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 call QSortFilterProxyModel::sort(int column, Qt::SortOrder order = Qt::AscendingOrder) on the 4th column. And implement lessThan() 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 !



  • @JonB

    Do you know if there's a way to call only sort method on only a slice of the table: instead of sorting all the table, I want to only apply the sorting on the rows 1 --> 20 for example. Is that possible ?



  • @hachbani
    You cannot sort by "secondary", multiple columns by calling sort() 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.



  • Thanks @JonB for your thoughtful answer. It gave me more insight on the logic of lessThan().
    I'll give this a big try and see where I'll get.

    Wish you a good day !


Log in to reply