In the following Python 2.7, PyQt4 example, I generate 2 QTableWidgets. Table1 has no ItemDelegate and the table2 has HTMLDelegate.
Selected background color works if the table has focus, but when the table loses focus, the blue selection turns gray on table2. I want table2 selection to work like table1 when focus is lost.
How can I maintain blue selection appearance regardless of focus when using itemdelegate?
import sip
sip.setapi('QVariant', 2)
from PyQt4 import QtCore, QtGui
import random
from html import escape
words = [
"Hello",
"world",
"Stack",
"Overflow",
"Hello world",
]
class HTMLDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
col = index.column()
row = index.row()
painter.save()
options = QtGui.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
text = index.data()
self.doc.setHtml(text)
options.text = ""
style = (
QtGui.QApplication.style()
)
style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtGui.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText), )
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.Text), )
textRect = (options.rect)
constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtGui.QHBoxLayout()
lay = QtGui.QVBoxLayout(self)
self.table1 = QtGui.QTableWidget(4, 2)
self.table2 = QtGui.QTableWidget(4, 2)
lay.addLayout(hlay)
lay.addWidget(self.table1)
lay.addWidget(self.table2)
# define itemdelegate for table1, but not for table2
self.table2.setItemDelegate(HTMLDelegate(self.table2))
# fill table1
for i in range(self.table1.rowCount()):
for j in range(self.table1.columnCount()):
it = QtGui.QTableWidgetItem(random.choice(words))
self.table1.setItem(i, j, it)
# fill table2
for i in range(self.table2.rowCount()):
for j in range(self.table2.columnCount()):
it = QtGui.QTableWidgetItem(random.choice(words))
self.table2.setItem(i, j, it)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setStyle("Plastique") # set style
stylesheet = """
QPushButton:hover, QComboBox:hover
{
background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #cbc9c5, stop: 1 #b9b7b5);
border: 2px solid #78879b;
border-radius: 3px;
}
QTableWidget::item:selected
{
background: #0078d7;
color: white;
}
QTableWidget
{
font: 9pt "Segoe UI";
}
QHeaderView
{
font: 9pt "Segoe UI";
}
"""
app.setStyleSheet(stylesheet)
myapp = Widget()
myapp.show()
rc = app.exec_()
myapp.close()
sys.exit(rc)
You're using QStyleOptionViewItem
, which in Qt4 is a very basic class, while what you'll need is QStyleOptionViewItemV4
, which implements a lot of useful things, including decorations (as in item icon) and their positioning, the item position according to other items, and, most importantly, the widget the delegate is used in and its full QStyle painting capabilities.
The style.drawControl
method, like most of QStyle methods, also has a widget
argument; it is normally ignored, but is very important in this kind of situations, expecially when there are stylesheets at play.
I suggest you to use the option
's class of the paint()
method as a reference to create your own option, which will automatically use the latest QStyleOption available for view item, and will also make a possible future transition to Qt5 much easier.
Keep in mind that, according to the documentation (which is somehow obscure about that), the widget property is available only from version 3 of QStyleOptionViewItem, but according to my tests the correct background painting will fail anyway with that version. If, for any reason, you're stuck with a very old version of Qt which doesn't provide QStyleOptionViewItemV4, I'm afraid that the only option you'll have is to keep the background color reference somewhere (there's no way to access to stylesheet colors from code, and it doesn't match the QTableView Highlight
palette role) and manually paint the background by yourself.
def paint(self, painter, option, index):
#...
newOption = option.__class__(option)
self.initStyleOption(newOption, index)
#...
style = newOption.widget.style() if newOption.widget else QtGui.QApplication.style()
style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter, newOption.widget)
# ...
PS: I'd suggest you to not use too similar names for objects: I actually lost sometime finding the source of the problem because I often got confused between "option" and "options": that's why I changed it, which is much better for readability and debugging purposes too.