QTableWidget ItemDelegate 打破了选择样式
QTableWidget ItemDelegate breaks selection style
在下面的Python2.7、PyQt4例子中,我生成了2个QTableWidgets。 Table1 没有 ItemDelegate,table2 有 HTMLDelegate。
如果 table 具有焦点,则所选背景颜色有效,但当 table 失去焦点时,蓝色选择在 table2 上变为灰色。我希望 table2 选择在失去焦点时像 table1 一样工作。
在使用 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)
您正在使用 QStyleOptionViewItem
,它在 Qt4 中是一个非常基本的 class,而您需要的是 QStyleOptionViewItemV4
,它实现了很多有用的东西,包括装饰(如在项目图标中)及其定位,根据其他项目的项目位置,最重要的是,使用委托的小部件及其完整的 QStyle 绘画功能。
style.drawControl
方法,像大多数 QStyle 方法一样,也有一个 widget
参数;它通常被忽略,但在这种情况下非常重要,尤其是在使用样式表时。
我建议你参考paint()
方法的option
的class来创建你自己的选项,它会自动使用最新的QStyleOption可供查看项目,也将使将来可能更容易过渡到 Qt5。
请记住,根据文档(有些晦涩难懂),小部件 属性 仅在 QStyleOptionViewItem 的版本 3 中可用,但根据我的测试,正确的背景绘制将失败反正那个版本。如果出于任何原因,您遇到 非常 不提供 QStyleOptionViewItemV4 的旧版本 Qt,恐怕您唯一的选择就是保留某处的背景颜色参考(无法从代码访问样式表颜色,并且它与 QTableView Highlight
调色板角色不匹配)并自己手动绘制背景。
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:我建议你不要为对象使用太相似的名称:实际上我有时会找不到问题的根源,因为我经常在 "option" 和 [=34 之间混淆=]:这就是我更改它的原因,这对于可读性和调试目的来说也更好。
在下面的Python2.7、PyQt4例子中,我生成了2个QTableWidgets。 Table1 没有 ItemDelegate,table2 有 HTMLDelegate。
如果 table 具有焦点,则所选背景颜色有效,但当 table 失去焦点时,蓝色选择在 table2 上变为灰色。我希望 table2 选择在失去焦点时像 table1 一样工作。
在使用 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)
您正在使用 QStyleOptionViewItem
,它在 Qt4 中是一个非常基本的 class,而您需要的是 QStyleOptionViewItemV4
,它实现了很多有用的东西,包括装饰(如在项目图标中)及其定位,根据其他项目的项目位置,最重要的是,使用委托的小部件及其完整的 QStyle 绘画功能。
style.drawControl
方法,像大多数 QStyle 方法一样,也有一个 widget
参数;它通常被忽略,但在这种情况下非常重要,尤其是在使用样式表时。
我建议你参考paint()
方法的option
的class来创建你自己的选项,它会自动使用最新的QStyleOption可供查看项目,也将使将来可能更容易过渡到 Qt5。
请记住,根据文档(有些晦涩难懂),小部件 属性 仅在 QStyleOptionViewItem 的版本 3 中可用,但根据我的测试,正确的背景绘制将失败反正那个版本。如果出于任何原因,您遇到 非常 不提供 QStyleOptionViewItemV4 的旧版本 Qt,恐怕您唯一的选择就是保留某处的背景颜色参考(无法从代码访问样式表颜色,并且它与 QTableView Highlight
调色板角色不匹配)并自己手动绘制背景。
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:我建议你不要为对象使用太相似的名称:实际上我有时会找不到问题的根源,因为我经常在 "option" 和 [=34 之间混淆=]:这就是我更改它的原因,这对于可读性和调试目的来说也更好。