Search code examples
pythonpyqt5

Highlighting the header of a filtered column in a QTableView - PyQt5


I implement a customProxyModel which filters a QTableView in my main dialog. The code is inspired from the following discussing PyQT5 and Filtering a Table using multiple columns

Here is how I defined my CustomProxyModel(). It keeps track of filtered columns in a list called filtered_columns and sets the header font of filtered columns to bold in the headerData() method.

class CustomProxyModel(QtCore.QSortFilterProxyModel):

    def __init__(self, parent=None):
        super().__init__(parent)
        self._filters = dict()
        self.filtered_columns = list()

    @property
    def filters(self):
        return self._filters

    def setFilter(self, expresion, column):
        if expresion:
            self.filters[column] = expresion
            self.filtered_columns.append(column)
        elif column in self.filters:
            del self.filters[column]
            self.filtered_columns.remove(column)
        self.invalidateFilter()


    def filterAcceptsRow(self, source_row, source_parent):
        for column, expresion in self.filters.items():
            text = self.sourceModel().index(source_row, column, source_parent).data()
            text = str(text)
            regex = QtCore.QRegExp(
                expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
            )
            if regex.indexIn(text) == -1:
                return False
        return True

    
    def headerData(self, section, orientation, role):
        
        # Set font to bold
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.FontRole:
            font = QtGui.QFont()
            if section in self.filtered_columns:
                font.setBold(True)
            return font
        return super().headerData(section, orientation, role)

Here is how I defined my tableView (Note: I implemented in PyQt UI)

class myTableViewDialog(QDialog, myTableView.Ui_Dialog):
    def __init__(self, parent=None):
        super(myTableViewDialog, self).__init__(parent)

        self.setupUi(self)
        
        self.populatemyTableView()
    
    def populatemyTableView(self):
        # Set up the model
        self.model = QSqlTableModel()
        self.model.setTable("MY_TABLE")
        self.model.select()
        
        # Customize the table view
        self.horizontalHeader = self.myTableView.horizontalHeader()
        self.horizontalHeader.setStyleSheet(
            "QHeaderView::section \
            {\
                background-color: #4C6199;\
                color: white; \
            }"
            )

        self.myTableView.verticalHeader().setStyleSheet(
            "QHeaderView::section \
            {\
                background-color: #4C6199;\
                color: white; \
            }"
            )

        # Make the table view read only
        self.myTableView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)

        # Link the table view to the model
        self.myTableView.setModel(self.model)
        self.myTableView.resizeColumnsToContents()


        """
        Testing header filtering
        """

        # self.proxy_model = QSortFilterProxyModel(self)
        self.proxy_model = CustomProxyModel(self)
        self.proxy_model.setSourceModel(self.model)
        self.myTableView.setModel(self.proxy_model)
        self.horizontalHeader = self.myTableView.horizontalHeader()

        self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
       
    @QtCore.pyqtSlot(int)
    def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
        self.logicalIndex = logicalIndex
        self.menuValues = QMenu(self)
        self.signalMapper = QtCore.QSignalMapper(self)  
        valuesUnique = [    self.model.index(row, self.logicalIndex).data()
                            for row in range(self.model.rowCount())
                            ]
        actionAll = QAction("All", self)
        actionAll.triggered.connect(self.on_actionAll_triggered)
        self.menuValues.addAction(actionAll)
        self.menuValues.addSeparator()
        for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):              
            action = QAction(str(actionName), self)
            self.signalMapper.setMapping(action, actionNumber)  
            action.triggered.connect(self.signalMapper.map)  
            self.menuValues.addAction(action)
        self.signalMapper.mapped.connect(self.on_signalMapper_mapped)  
        headerPos = self.myTableView.mapToGlobal(self.horizontalHeader.pos())        
        posY = headerPos.y() + self.horizontalHeader.height()
        posX = headerPos.x() + self.horizontalHeader.sectionViewportPosition(self.logicalIndex)
        self.menuValues.exec_(QtCore.QPoint(posX, posY))


    @QtCore.pyqtSlot(int)
    def on_actionAll_triggered(self):
        filterColumn = self.logicalIndex
        self.proxy_model.setFilter("", filterColumn)
    

    @QtCore.pyqtSlot(int)
    def on_signalMapper_mapped(self, i):
        stringAction = self.signalMapper.mapping(i).text()
        filterColumn = self.logicalIndex
        self.proxy_model.setFilter(stringAction, filterColumn)

My question: How can I modify the code so that instead of making the header text bold of a filtered column, I change the background colour of the header? Slight clarification: I do not want to modify the background color of the entire header so setting a StyleSheet is not appropriate in this case. What I want instead is modifying the background color only of the header of filtered columns.

Thank you for your help.


Solution

  • The common way to set custom colors for the headers is to implement the related roles for the headerData function of the model.

    This will always be ignored, though, if the header view is affected by stylesheets that set color properties.

    While there are various possibilities (including overriding paintSection() of a QHeaderView subclass), the simplest solution for such a case is to completely avoid style sheets for headers and implement headerData() on the proxy:

        def headerData(self, section, orientation, role):
            if role == Qt.BackgroundRole:
                if orientation == Qt.Horizontal and section in self.filtered_columns:
                    return QColor('red')
                return QColor('#4C6199')
            elif role == Qt.ForegroundRole:
                return QColor(Qt.white)
            if orientation == Qt.Horizontal and role == Qt.FontRole:
                if section in self.filtered_columns:
                    font = QFont()
                    font.setBold(True)
                    return font
            return super().headerData(section, orientation, role)
    

    Note that some styles (notably, the default used on Windows) completely disregard header colors coming from the model.

    In such cases, the easiest work around is to set a more compliant style for the headers:

    self.horizontalHeader.setStyle(QStyleFactory.create('fusion'))
    self.verticalHeader.setStyle(QStyleFactory.create('fusion'))