Search code examples
pythonpyqtqtableviewqtablewidgetqsortfilterproxymodel

PyQt QTableView non-english character sorting issue


I use QTableWidget in a project. But there is no search feature in QTableWidget.

How to set filter option in qtablewidget

QTableView allows searching. But this time QTableView, non-English characters are not sorted correctly. QTableWidget sort is great. I want to use QTableView. How can I fix the non-English character sorting in QTableView.

enter image description here

QTableWidget Code

class GuiQTableWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("QTableWidget")

        v_box = QVBoxLayout()
        v_box.addWidget(self.prepare_table())
        self.setLayout(v_box)

        self.setMinimumHeight(750)
        self.setFixedWidth(self.width())

    def prepare_table(self):
        letter_list = [ 'A','B','C','Ç','G','Ğ','I','I','O','Ö','P','S','Ş','U','Ü','Y','Z',
                           'a','b','c','ç','g','ğ','ı','i','o','ö','p','s','ş','u','ü','y','z' ]

        table = QTableWidget()
        table.setColumnCount(1)
        table.setHorizontalHeaderLabels(["Letter"])
        table.setRowCount(len(letter_list))

        for row, letter in enumerate(letter_list):
            table.setItem(row, 0, QTableWidgetItem(letter))

        h_header = table.horizontalHeader()
        h_header.setSectionResizeMode(QHeaderView.Stretch)

        table.setSortingEnabled(True)
        table.setAlternatingRowColors(True)

        return table

QTableView

class GuilQTableView(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

        # sort
        self.h_header.sortIndicatorChanged.connect(self.sort)
        # filter
        self.line_search.textChanged.connect(self.filter)

    def init_ui(self):
        self.setWindowTitle("QTableView")

        self.table = QTableView()
        self.h_header = self.table.horizontalHeader()
        self.model = QStandardItemModel()
        self.proxyFilter = QSortFilterProxyModel()

        self.line_search = QLineEdit()
        lbl_search = QLabel("Letter Search")
        h_search = QHBoxLayout()
        h_search.addWidget(lbl_search)
        h_search.addWidget(self.line_search)

        v_box = QVBoxLayout()
        v_box.addLayout(h_search)
        v_box.addWidget(self.prepare_table(self.table, self.model, self.proxyFilter, self.h_header))
        self.setLayout(v_box)

        self.setMinimumHeight(750)
        self.setFixedWidth(self.width())

    def prepare_table(self, table, model, proxyFilter, h_header):
        letter_list = [ 'A','B','C','Ç','G','Ğ','I','I','O','Ö','P','S','Ş','U','Ü','Y','Z',
                           'a','b','c','ç','g','ğ','ı','i','o','ö','p','s','ş','u','ü','y','z' ]

        model.setColumnCount(1)
        model.setRowCount(len(letter_list))
        model.setHorizontalHeaderLabels(['Letter'])

        for row, letter in enumerate(letter_list):
            model.setItem(row, 0, QStandardItem(letter))

        proxyFilter.setSourceModel(model)
        proxyFilter.setFilterKeyColumn(0)
        proxyFilter.setSortCaseSensitivity(Qt.CaseSensitive)

        table.setModel(proxyFilter)
        table.setAlternatingRowColors(True)
        table.setSelectionBehavior(QAbstractItemView.SelectRows)
        table.setSelectionMode(QAbstractItemView.ExtendedSelection)

        h_header.setSectionResizeMode(QHeaderView.Stretch)

        return table

    def sort(self, sortOrder):
        if sortOrder == Qt.AscendingOrder:
            self.model.sort(0, Qt.AscendingOrder)
        else:
            self.model.sort(0, Qt.DescendingOrder)

        self.h_header.setSortIndicatorShown(True)

    def filter(self, text):
        regExp = QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard)
        self.proxyFilter.setFilterRegExp(regExp)

QTableWidget is sorting correctly. QTableView lists the non-English characters (Ç,G,Ğ,İ,Ö,Ş,Ü, ç,ğ,ı,ö,ş,ü) at the bottom of the list. How can I fix the non-English character sorting in QTableView.


Solution

  • In the case of QTableWidget, the operator < of QTableWidgetItem is used, which in this case uses QString that takes as reference the locality of the characters and the case of QSortFilterProxyModel uses the python string that does not take the locality as reference, that's why you see that difference, the solution is to overwrite the lessThan() method of QSortFilterProxyModel

    On the other hand it is not necessary to use the sortIndicatorChanged signal since the QTableView uses it internally advising the model.

    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    
    class SortFilterProxyModel(QSortFilterProxyModel):
        def lessThan(self, left, right):
            leftData = self.sourceModel().data(left)
            rightData = self.sourceModel().data(right)
            return QTableWidgetItem(leftData) < QTableWidgetItem(rightData)
    
    class GuilQTableView(QWidget):
        def __init__(self):
            super().__init__()
            self.init_ui()
            # filter
            self.line_search.textChanged.connect(self.filter)
    
        def init_ui(self):
            self.setWindowTitle("QTableView")
    
            self.table = QTableView()
            self.table.setSortingEnabled(True)
            self.h_header = self.table.horizontalHeader()
            self.model = QStandardItemModel()
            self.proxyFilter = SortFilterProxyModel()
    
            self.line_search = QLineEdit()
            lbl_search = QLabel("Letter Search")
            h_search = QHBoxLayout()
            h_search.addWidget(lbl_search)
            h_search.addWidget(self.line_search)
    
            v_box = QVBoxLayout()
            v_box.addLayout(h_search)
            v_box.addWidget(self.prepare_table(self.table, self.model, self.proxyFilter, self.h_header))
            self.setLayout(v_box)
    
            self.setMinimumHeight(750)
            self.setFixedWidth(self.width())
    
        def prepare_table(self, table, model, proxyFilter, h_header):
            letter_list = [ 'A','B','C','Ç','G','Ğ','I','I','O','Ö','P','S','Ş','U','Ü','Y','Z',
                               'a','b','c','ç','g','ğ','ı','i','o','ö','p','s','ş','u','ü','y','z' ]
    
            model.setColumnCount(1)
            model.setRowCount(len(letter_list))
            model.setHorizontalHeaderLabels(['Letter'])
    
            for row, letter in enumerate(letter_list):
                model.setItem(row, 0, QStandardItem(letter))
    
            proxyFilter.setSourceModel(model)
            proxyFilter.setFilterKeyColumn(0)
            proxyFilter.setSortCaseSensitivity(Qt.CaseSensitive)
    
            table.setModel(proxyFilter)
            table.setAlternatingRowColors(True)
            table.setSelectionBehavior(QAbstractItemView.SelectRows)
            table.setSelectionMode(QAbstractItemView.ExtendedSelection)
    
            h_header.setSectionResizeMode(QHeaderView.Stretch)
    
            return table
    
        def filter(self, text):
            regExp = QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard)
            self.proxyFilter.setFilterRegExp(regExp)
    
    
    if __name__ == '__main__':
        import sys
    
        app = QApplication(sys.argv)
        w = GuilQTableView()
    
        w.show()
        sys.exit(app.exec_())