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

QAbstractTableModel, QTableView : how to sort ?



  • Hi,

    I am looking for help about sorting data in a QTableView.
    I use a QAbstractTableModel model to set data in the table and I set "setSortingEnabled" as true but it does not work.

    Here is an exemple to describe this situation.

    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    headers = ["Scientist name", "Birthdate", "Contribution"]
    rows =    [("Newton", "1643-01-04", "Classical mechanics"),
               ("Einstein", "1879-03-14", "Relativity"),
               ("Darwin", "1809-02-12", "Evolution")]
    
    class TableModel(QAbstractTableModel):
        def get_items(self):
            return rows
        def rowCount(self, parent):
            # How many rows are there?
            return len(rows)
        def columnCount(self, parent):
            # How many columns?
            return len(headers)
        def data(self, index, role):
            if role != Qt.DisplayRole:
                return QVariant()
            # What's the value of the cell at the given index?
            return rows[index.row()][index.column()]
        def headerData(self, section, orientation, role):
            if role != Qt.DisplayRole or orientation != Qt.Horizontal:
                return QVariant()
            # What's the header for the given column?
            return headers[section]
    
    app = QApplication([])
    model = TableModel()
    view = QTableView()
    view.setModel(model)
    view.setSortingEnabled(True)
    
    def get_obj():
        try:
            if view.currentIndex().row() != -1:
                return view.model().get_items()[view.currentIndex().row()]
            return None
        except:
            return None
    def print_row():
        print(get_obj())
        
    view.doubleClicked.connect(print_row)
    
    view.show()
    app.exec_()
    

    Do you have an idea of what is wrong ?
    I would like to continue working with QAbstractTableModel and QTableView if possible.

    Thank you



  • @anto1ne said in QAbstractTableModel, QTableView : how to sort ?:
    QTableView.setSortingEnabled() enables it as far as the view is concerned --- you get the sort-click-indicators on the column headings --- but it doesn't do the model sorting for you!

    In a word, by far the best way is to interpose a QSortFilterProxyModel between your TableModel and the QTableView. Not only do you get sorting, but you optionally get filtering for free too, if you want to use it :)

    That page even shows you the example:

            QTreeView *treeView = new QTreeView;
            MyItemModel *sourceModel = new MyItemModel(this);
            QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
    
            proxyModel->setSourceModel(sourceModel);
            treeView->setModel(proxyModel);
    

    Easy :)

    Oh, here it for PySide2/Python: https://doc.qt.io/qtforpython/PySide2/QtCore/QSortFilterProxyModel.html

    treeView =  QTreeView()
    sourceModel =  MyItemModel(self)
    proxyModel =  QSortFilterProxyModel(self)
    
    proxyModel.setSourceModel(sourceModel)
    treeView.setModel(proxyModel)
    


  • thank you @JonB

    I added the QSortFilterProxyModel between the TableModel and the QTableView and now sorting is working well.

    Nevertheless, the get_obj function is no longer working.
    I don't know how to get the "object" double clicked
    After sorting, items index and tableview index are not the same. I hope you understand what I mean.

    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    headers = ["Scientist name", "Birthdate", "Contribution"]
    rows =    [("Newton", "1643-01-04", "Classical mechanics"),
               ("Einstein", "1879-03-14", "Relativity"),
               ("Darwin", "1809-02-12", "Evolution")]
    
    class TableModel(QAbstractTableModel):
        def get_items(self):
            return rows
        def rowCount(self, parent):
            # How many rows are there?
            return len(rows)
        def columnCount(self, parent):
            # How many columns?
            return len(headers)
        def data(self, index, role):
            if role != Qt.DisplayRole:
                return QVariant()
            # What's the value of the cell at the given index?
            return rows[index.row()][index.column()]
        def headerData(self, section, orientation, role):
            if role != Qt.DisplayRole or orientation != Qt.Horizontal:
                return QVariant()
            # What's the header for the given column?
            return headers[section]
    
    app = QApplication([])
    model = TableModel()
    
    proxymodel = QSortFilterProxyModel()
    proxymodel.setSourceModel(model)
    
    view = QTableView()
    view.setModel(proxymodel)
    view.setSortingEnabled(True)
    
    def get_obj():
        try:
            print(view.currentIndex().row())
            if view.currentIndex().row() != -1:
                #return view.model().get_items()[view.currentIndex().row()]
                #return rows[view.currentIndex().row()]
                return proxymodel.sourceModel().get_items()[view.currentIndex().row()]
            return None
        except:
            return None
    def print_row():
        print(get_obj())
        
    view.doubleClicked.connect(print_row)
    
    view.show()
    app.exec_()
    


  • @anto1ne
    Once you start using any QAbstractProxyModel you may have to map between indexes into the proxy model and indexes into the source model. You cannot use one where the other is required! QTableView->QSortFilterProxyModel<->QAbstractTableModel. Depending on which you have and which one you want. Look at these 4 methods:

    def mapFromSource (sourceIndex)
    def mapSelectionFromSource (selection)
    def mapSelectionToSource (selection)
    def mapToSource (proxyIndex)
    

    on this PySide2 page: https://doc.qt.io/qtforpython/PySide2/QtCore/QAbstractProxyModel.html


  • Lifetime Qt Champion

    Hi,

    Why are you using get_items at all ?
    You should request the data from your model rather that working around it. With index.data you can directly get it.



  • @SGaist
    I use "get_items" because I want to return the tuple from the row I clicked, not the data in the cell


  • Lifetime Qt Champion

    Then use a custom role to retrieve the tuple from the model.



  • Hi @SGaist ,

    I am not familiar with role.
    I used the Qt.UserRole to return the tuple and it works, is it the right thing to do ?

    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    headers = ["Scientist name", "Birthdate", "Contribution"]
    rows =    [("Newton", "1643-01-04", "Classical mechanics"),
               ("Einstein", "1879-03-14", "Relativity"),
               ("Darwin", "1809-02-12", "Evolution")]
    
    class TableModel(QAbstractTableModel):
    #    def get_items(self):
    #        return rows
        def rowCount(self, parent):
            # How many rows are there?
            return len(rows)
        def columnCount(self, parent):
            # How many columns?
            return len(headers)
        def data(self, index, role):
            if role == Qt.UserRole:
                return rows[index.row()]
            if role != Qt.DisplayRole:
                return QVariant()
            # What's the value of the cell at the given index?
            return rows[index.row()][index.column()]
        def headerData(self, section, orientation, role):
            if role != Qt.DisplayRole or orientation != Qt.Horizontal:
                return QVariant()
            # What's the header for the given column?
            return headers[section]
    
    app = QApplication([])
    model = TableModel()
    
    proxymodel = QSortFilterProxyModel()
    proxymodel.setSourceModel(model)
    #proxymodel.mapFromSource(view.model())
    
    view = QTableView()
    view.setModel(proxymodel)
    view.setSortingEnabled(True)
    view.sortByColumn(0,Qt.AscendingOrder)
    
    def get_obj():
        try:
            print(view.currentIndex().row())
            if view.currentIndex().row() != -1:
                #return view.model().get_items()[view.currentIndex().row()]
                #return rows[view.currentIndex().row()]
                #return proxymodel.sourceModel().get_items()[view.currentIndex().row()]
                return view.currentIndex().data(Qt.UserRole)
            return None
        except:
            return None
    def print_row():
        print(get_obj())
        
    view.doubleClicked.connect(print_row)
    
    view.show()
    app.exec_()
    

  • Lifetime Qt Champion

    Yes you can, that's the starting value of the custom roles. What is usually done is to use a custom enumeration start at that value so you have better names when requesting custom data.


Log in to reply