PyQt5 : how to Sort a QTableView when you click on the headers of a QHeaderView?
Asked Answered
S

1

6

I want to sort a QTableView when I click on the headers of my QHeaderView. I've found a several code sample on the internet like this one: Sort QTableView in pyqt5 but it doesn't work for me. I also look in the Rapid Gui programming Book from Summerfield but I could find something that was working either.

In this quick example how could I sort the table by name or age?

Here is my code :

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd
import operator

# This class was generated from the Qt Creator
class Ui_tableView_ex(object):
    def setupUi(self, tableView_ex):
        tableView_ex.setObjectName("tableView_ex")
        tableView_ex.resize(800, 600)
        self.centralwidget = QWidget(tableView_ex)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.myTable = QTableView(self.centralwidget)
        self.myTable.setObjectName("monTablo")
        self.gridLayout.addWidget(self.myTable, 0, 0, 1, 1)
        tableView_ex.setCentralWidget(self.centralwidget)

        self.retranslateUi(tableView_ex)
        QMetaObject.connectSlotsByName(tableView_ex)

    def retranslateUi(self, tableView_ex):
        _translate = QCoreApplication.translate
        tableView_ex.setWindowTitle(_translate("tableView_ex", "MainWindow"))


class TableTest(QMainWindow, Ui_tableView_ex):
    def __init__(self, parent=None):
        super(TableTest, self).__init__(parent)
        self.setupUi(self)

        self.model = TableModel()
        self.myTable.setModel(self.model)
        self.myTable.setShowGrid(False)

        self.hView = HeaderView(self.myTable)
        self.myTable.setHorizontalHeader(self.hView)
        self.myTable.verticalHeader().hide()

        # adding alternate colours
        self.myTable.setAlternatingRowColors(True)
        self.myTable.setStyleSheet("alternate-background-color: rgb(209, 209, 209)"
                                   "; background-color: rgb(244, 244, 244);")

        # self.myTable.setSortingEnabled(True)
        # self.myTable.sortByColumn(1, Qt.AscendingOrder)


class HeaderView(QHeaderView):
    def __init__(self, parent):
        QHeaderView.__init__(self, Qt.Horizontal, parent)
        self.model = TableModel()
        self.setModel(self.model)

        # Setting font for headers only
        self.font = QFont("Helvetica", 12)
        self.setFont(self.font)

        # Changing section backgroud color. font color and font weight
        self.setStyleSheet("::section{background-color: pink; color: green; font-weight: bold}")

        self.setSectionResizeMode(1)
        self.setSectionsClickable(True)


class TableModel(QAbstractTableModel):

    def __init__(self):
        QAbstractTableModel.__init__(self)
        super(TableModel, self).__init__()

        self.headers = ["Name", "Age", "Grades"]
        self.stocks = [["George", "26", "80%"],
                       ["Bob", "16", "95%"],
                       ["Martha", "22", "98%"]]
        self.data = pd.DataFrame(self.stocks, columns=self.headers)

    def update(self, in_data):
        self.data = in_data

    def rowCount(self, parent=None):
        return len(self.data.index)

    def columnCount(self, parent=None):
        return len(self.data.columns.values)

    def setData(self, index, value, role=None):
        if role == Qt.EditRole:
            row = index.row()
            col = index.column()
            column = self.data.columns.values[col]
            self.data.set_value(row, column, value)
            self.update(self.data)
            return True

    def data(self, index, role=None):
        if role == Qt.DisplayRole:
            row = index.row()
            col = index.column()
            value = self.data.iloc[row, col]
            return value

    def headerData(self, section, orientation, role=None):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return self.data.columns.values[section]

# -----------------NOT WORKING!!!---------------
# =================================================

    def sort(self, Ncol, order):
        """Sort table by given column number.
        """
        self.layoutAboutToBeChanged.emit()
        self.data = sorted(self.data, key=operator.itemgetter(Ncol))
        if order == Qt.DescendingOrder:
            self.data.reverse()
        self.layoutChanged.emit()
# =================================================


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create("Fusion"))
    main_window = TableTest()
    main_window.show()
    app.exec_()
    sys.exit()

I've try to modify a few things, but nothing worked out and when I uncomment the setSortingEnabled(True) line the window doesn't even open. So there is no way I can know if I'm closer to the solution or not! I would also like to change the text color depending on the grade(red under 50 an green over 50 for example). For that, I haven't search that much, so i will try it by myself before asking aquestion but if you have any hint It would be much appreciated!

Thank you for your help!

Swedish answered 17/10, 2016 at 18:48 Comment(1)
a side note: it seems that the TableModel is created twice, once for the TableTest and another for the HeaderView?Ruche
S
7

Your sort function is the problem, you are not using the pandas DataFrame sorting functions, and self.data become a python list, then other functions fail and the program crash.

To correctly sort the DataFrame use the sort_values function like this:

def sort(self, Ncol, order):
    """Sort table by given column number."""
    self.layoutAboutToBeChanged.emit()
    self.data = self.data.sort_values(self.headers[Ncol],
                                      ascending=order == Qt.AscendingOrder)
    self.layoutChanged.emit()
Shrum answered 18/10, 2016 at 9:38 Comment(2)
Thank you, it's working perfectly! From what I was able to find, everyone seems to use a list of list in their model when they are working with QtableView. Are they any advantages to use a DataFrame instead of a list of list in a model?Swedish
Unless you need some DataFrame functionality the list of list is probably simpler to manipulate and (unless is used elsewhere) avoid the dependency with pandas.Shrum

© 2022 - 2024 — McMap. All rights reserved.