[PyQt] (pyqt)set different column font for qtableview

Maurizio Berti maurizio.berti at gmail.com
Fri Apr 26 00:11:59 BST 2019


Il giorno gio 25 apr 2019 alle ore 21:04 Maziar Parsijani <
maziar.parsijani at gmail.com> ha scritto:

> Hi
> I want to know if it is possible to change font for each column in
> Qtableview and if it is possible even more different appearances in columns
> like backgrounds and font colors.
>


There are different possible approaches, and the choice usually depends on
how the table data is filled and if it's editable.
The most common way is to set the QtCore.Qt.ItemDataRole for each field
index, by providing the FontRole, ForegroundRole and BackgroundRole to the
index.setData(value, role).
If you're using a QStandardItemModel, the data is simple and you don't need
interaction, just use setData method on each item.

If you are using any model based on QAbstractItemModel you could subclass
its data method like this:

class SimpleModel(QtGui.QStandardItemModel):
    backgrounds = QtGui.QColor(QtCore.Qt.lightGray),
QtGui.QColor(QtCore.Qt.darkCyan), QtGui.QColor(QtCore.Qt.darkGray)
    foregrounds = QtGui.QColor(QtCore.Qt.darkGreen),
QtGui.QColor(QtCore.Qt.darkBlue), QtGui.QColor(QtCore.Qt.yellow)
    fonts = QtGui.QFont('monospace'), QtGui.QFont(), QtGui.QFont('times')

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.BackgroundRole:
            return self.backgrounds[index.column()]
        elif role == QtCore.Qt.ForegroundRole:
            return self.foregrounds[index.column()]
        elif role == QtCore.Qt.FontRole:
            return self.fonts[index.column()]
        return QtGui.QStandardItemModel.data(self, index, role)

Note that you could also set a QIdentityProxyModel with your original model
(and use the proxy on the table instead) and then implement the data()
method in the same way. Just use the code above with QIdentityProxyModel
instead of QStandardItemModel, do a setSource(originalModel) and you're
done; this is usually better and much more transparent.

If you cannot have that kind of access, the alternative is to create your
own item delegate subclass and implement the paint() method. Be aware that
item painting is not an easy task, expecially if you want to mimic the
default implementation.

class SimpleDelegate(QtWidgets.QStyledItemDelegate):
    backgrounds = QtGui.QColor(QtCore.Qt.lightGray),
QtGui.QColor(QtCore.Qt.darkCyan), QtGui.QColor(QtCore.Qt.darkGray)
    foregrounds = QtGui.QColor(QtCore.Qt.darkGreen),
QtGui.QColor(QtCore.Qt.darkBlue), QtGui.QColor(QtCore.Qt.yellow)
    fonts = QtGui.QFont('monospace'), QtGui.QFont(), QtGui.QFont('times')
    fontData = zip(backgrounds, foregrounds, fonts)

    def paint(self, qp, option, index):
        #painting needs performance, let's get all data at once
        background, foreground, font = self.fontData[index.column()]

        # never reuse the given option argument, always create a new one
based on it!
        # reusing QStyleOptions might create issues and inconsistencies
with item "siblings"
        option = QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(option, index)

        # reset the text so that QStyle won't paint it
        option.text = ''
        option.backgroundBrush = background

        widget = option.widget
        style = widget.style()

        # we could use the drawPrimitive instead, but it won't paint the
decoration (icon),
        # if it exists; drawControl paints everything, that's why I cleared
the text before,
        # otherwise you'll see the text drawn twice
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, qp)
        #style.drawPrimitive(QtWidgets.QStyle.PE_PanelItemViewItem, option,
qp)

        # get the rectangle available for item text and adjust it to
standard margins;
        # one pixel is added for consistency with the original Qt painting
behavior
        textRect = style.subElementRect(style.SE_ItemViewItemText, option,
widget)
        margin = style.pixelMetric(style.PM_FocusFrameHMargin, option,
widget) + 1
        textRect.adjust(margin, 0, -margin, 0)

        # set the foreground color to the palette (not to the painter!)
        option.palette.setColor(option.palette.Active, option.palette.Base,
foreground)
        # if you want to have differrent colors for disabled items, use
again the setColor
        # method with option.palette.Disabled
        #option.palette.setColor(option.palette.Disabled,
option.palette.Base, disabledColor)

        qp.setFont(font)
        style.drawItemText(qp, textRect,
style.visualAlignment(option.direction, option.displayAlignment),
            option.palette, option.state & style.State_Enabled,
            option.fontMetrics.elidedText(index.data(),
option.textElideMode, textRect.width()),
            option.palette.Base)


class MyTable(QtWidget.QTableView):
    def __init__(self, *args, **kwargs):
        QtWidgets.QTableView.__init__(self, *args, **kwargs)
        self.setItemDelegate(SimpleDelegate())


Unfortunately, this is not quite perfect: while it should be fine for most
user cases, it doesn't take into account the word wrapping, meaning that
the text will always be on one line, no matter how much the row height is
big. To fix this, it would take about 60-70 more lines of code, which would
be run at each paintEvent for each visible item. Not always a good idea,
but if you think it's good enough, you can find how it's done from the
calculateElidedText method called by viewItemDrawText in here:
https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html

Cheers,
Maurizio

-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190426/e5ab3304/attachment-0001.html>


More information about the PyQt mailing list