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.
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'))