Unsolved 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 theQTableView
. 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 theTableModel
and theQTableView
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 anyQAbstractProxyModel
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
-
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 -
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_()
-
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.