Martin Hosken
Martin Hosken

Reputation: 71

pyqt stylesheet based styling of TableView cells based on content

I've found a way to do css based cell styling in a TableView based on contents in the cell. The following code shows an example:

#!/usr/bin/python3

from PyQt5 import QtWidgets, QtGui, QtCore

class_values = ["zero", "one", "two"]

class Cell(QtWidgets.QWidget):
    def initFromItem(self, item):
        self.setProperty('dataClass', class_values[int(item.text())])

class TDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, *a):
        super(TDelegate, self).__init__(*a)
        self.cell = Cell(self.parent())

    def paint(self, painter, option, index):
        item = index.model().itemFromIndex(index)
        self.cell.initFromItem(item)
        self.initStyleOption(option, index)
        style = option.widget.style() if option.widget else QtWidgets.QApplication.style()
        style.unpolish(self.cell)
        style.polish(self.cell)
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter, self.cell)

class TTableModel(QtGui.QStandardItemModel):
    def __init__(self, parent=None):
        super(TTableModel, self).__init__(parent)
        for i in range(5):
            self.appendRow([QtGui.QStandardItem(str((x+i) % 3)) for x in range(5)])

class TTableView(QtWidgets.QTableView):
    def __init__(self, parent=None):
        super(TTableView, self).__init__(parent)
        self.setItemDelegate(TDelegate(self))

class Main(QtWidgets.QMainWindow):
    def __init__(self):
        super(Main, self).__init__()
        self.table = TTableView(self)
        self.model = TTableModel(self)
        self.table.setModel(self.model)
        self.setCentralWidget(self.table)

if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet("""
Cell[dataClass=zero]::item { background-color: gray; }
Cell[dataClass=one]::item { background-color: green; font-style: italic }
Cell[dataClass=two]::item { font-weight: bold }
""")
    mainWin = Main()
    mainWin.show()
    sys.exit(app.exec_())

This generates a table like this:

TableView with per cell styling

The problem is that while the colours work, the font styling has no effect. What am I doing wrong? How could I improve my code? And how does it work? For example, why does the CSS selector have to include the ::item. All answers gratefully received. But please bear in mind that the need for CSS based styling is essential to the project.

Upvotes: 2

Views: 1805

Answers (1)

Martin Hosken
Martin Hosken

Reputation: 71

This is due to a bug in qt (v5.9.5) that ignores all font styling information when creating a CE_ItemViewItem (see QStyleSheetStyle::drawControl). Cheating by creating something else like a CE_ToolBoxTabLabel (which does correct handling of fonts in drawControl) does get you font formatting, but gets you on the colour because the rendering uses the button face palette, not the one specified in the option (or associated CSS). So you can have one or the other but not both. I know of no workaround.

As to how this works. In QStyleSheetStyle::drawControl for a CE_ItemViewItem the CSS for the subrole of ::item is looked up and if present, applied to a copy of the option (but not the font styling), and then the Item is drawn based on the updated option and its updated palette. Unfortunately there is no way to break into this code since there is no way to apply stylesheets from PyQt (since QStyleSheet is not part of the public API of Qt).

Upvotes: 1

Related Questions