This question is almost identical to this question. However, the solution did not work for me and the question is an older version of Qt. I am using a QStyledItemDelegate
to color certain cells based on their value. However, after these cells are colored, the text in them shifts far to the upper left. Using Alignment flags helps the issue but they are still far to the left (maybe only a space or two). In the code below, a table is created and the columns next to GOAT
are highlighted to demonstrate this. We can see the text is 'more' to the left than the other cells. How do I fix this? translate
moves the whole cell, not just the text. Is there a way to just move the text?
import sys
import pandas as pd
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtGui import QPen, QBrush, QColor
from PySide6.QtWidgets import QStyledItemDelegate
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._dfDisplay = data
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data[index.column()][index.row()]
return str(value)
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, col, orientation, role):
if orientation == Qt.Orientation.Horizontal:
if role == Qt.ItemDataRole.DisplayRole:
return str(self._dfDisplay.columns[col])
return None
class MyDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
super().paint(painter, option, index)
painter.setPen(QPen(Qt.GlobalColor.black, 3))
# Highlight our bm and tbm ROE cells
if index.siblingAtColumn(0).data() in ["GOAT"]:
if index.column() > 0:
# Set the fill color
brush = QBrush(QColor('#90EE90'))
brush.setStyle(Qt.SolidPattern)
painter.setBrush(brush)
# Set the outline color
painter.setPen(Qt.GlobalColor.white)
painter.drawRect(option.rect)
# Draw the text
painter.setPen(Qt.GlobalColor.black)
painter.translate(1, 0)
painter.drawText(option.rect,Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, str(index.data()))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.delegate = MyDelegate()
self.table.setItemDelegate(self.delegate)
header_view = CustomHeaderView(QtCore.Qt.Orientation.Horizontal)
self.table.setHorizontalHeader(header_view)
data = pd.DataFrame([["GOAT", "Giraffe", "Potatoe", "Another"],
[77, 33, 111111, 233],
[50, 70, 89, 100000]
])
print(data)
self.model = TableModel(data)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Your issue is mostly conceptual.
If you just want to alter the background and foreground color of an item, overriding the paint()
function of an item delegate is usually not the correct choice.
If the appearance has to be consistent from the model part and within all its connected views, then you must properly implement the data()
function of the model:
class TableModel(QtCore.QAbstractTableModel):
...
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return
elif role == Qt.DisplayRole:
value = self._data[index.column()][index.row()]
return str(value)
elif (
role in (Qt.BackgroundRole, Qt.ForegroundRole)
and index.column() > 0
):
leftValue = self._data[0][index.row()]
if leftValue == 'GOAT':
if role == Qt.BackgroundRole:
return QColor('#90EE90')
else:
return QColor(Qt.white)
If you don't want to alter the behavior of the model and only change the appearance in specific views, then, again, don't try to alter the painting, but just rely on the initStyleOption()
of the delegate:
class MyDelegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
if index.column() and index.siblingAtColumn(0).data() == 'GOAT':
opt.backgroundBrush = QBrush(QColor('#90EE90'))
opt.palette.setColor(QPalette.Text, QColor(Qt.white))
The above approach is not only more accurate, but also safer.
The paint()
function of an item delegate strongly relies on the current QStyle implementation, which internally uses QStyle functions to compute appropriate margins and spacings within each item.
Each QStyle accesses its internal functions in different ways, most of the times by calling those functions internally, so there is absolutely no certain way to know where the internal objects of an item will be finally shown. Some styles use public functions that can be overridden, but others do that privately, and while there are some "safer" ways to draw item objects that normally work in most circumstances, trying to manually do that drawing is, most of the times, a "leap of faith".
Considering the above, your attempt in overriding paint()
has a lot of issues:
SolidPattern
, so there's no point in redundantly setting it;save()
and restore()
functions should be used instead, before and after applying such important changes to the painting context;At the very least, a proper delegate paint()
function that alters the default behavior should do the following:
initStyleOption()
with that option and the given index;option.widget
or the QApplication instance);PE_PanelItemViewItem
;I understand that the above may seem a big complication, but that's how Qt allows complex drawing of items in views, and if you need deeply custom behavior you must follow those aspects if you want to provide a look that is as close as possible to the default behavior.
I strongly suggest you to never underestimate all these aspects: even if you end up never using them, being aware of them is quite mandatory. So, take your time to review the whole documentation about: