QStyleItemDelegate 复选框与样式表不匹配
QStyleItemDelegate checkbox doesn't match stylesheet
我制作了一个自定义 StyleItemDelegate,但出于某种原因,当它绘制复选框指示器时,它与我的样式表中定义的内容不匹配。我怎样才能解决这个问题?您可以在应用程序的右侧看到样式表正确地影响了复选框在默认列表视图绘制事件中的显示方式。
更新 #1
我制作了一个自定义样式的项目委托来支持富文本 html 呈现,一切都很好。我需要重新实现复选框,因为我已经覆盖了绘画事件并确保该复选框仍然可用。但是我的文本与复选框重叠,使其无法使用。结果,在尝试绘制复选框指示器时,ListItem 的突出显示被破坏,仅在左侧显示一条细长的蓝色条带。
截图
代码
################################################################################
# imports
################################################################################
import os
import sys
from PySide2 import QtGui, QtWidgets, QtCore
################################################################################
# QStyledItemDelegate
################################################################################
class MyDelegate(QtWidgets.QStyledItemDelegate):
MARGINS = 10
def __init__(self, parent=None, *args):
QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
# overrides
def sizeHint(self, option, index):
'''
Description:
Since labels are stacked we will take whichever is the widest
'''
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
# draw rich text
doc = QtGui.QTextDocument()
doc.setHtml(index.data(QtCore.Qt.DisplayRole))
doc.setDocumentMargin(self.MARGINS)
doc.setDefaultFont(options.font)
doc.setTextWidth(option.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
# methods
def paint(self, painter, option, index):
painter.save()
painter.setClipping(True)
painter.setClipRect(option.rect)
opts = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(opts, index)
style = QtGui.QApplication.style() if opts.widget is None else opts.widget.style()
# Draw background
if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight().color())
else:
painter.fillRect(option.rect, QtGui.QBrush(QtCore.Qt.NoBrush))
# Draw checkbox
if (index.flags() & QtCore.Qt.ItemIsUserCheckable):
cbStyleOption = QtWidgets.QStyleOptionButton()
if index.data(QtCore.Qt.CheckStateRole):
cbStyleOption.state |= QtWidgets.QStyle.State_On
else:
cbStyleOption.state |= QtWidgets.QStyle.State_Off
cbStyleOption.state |= QtWidgets.QStyle.State_Enabled
cbStyleOption.rect = option.rect.translated(self.MARGINS, 0)
style.drawControl(QtWidgets.QStyle.CE_CheckBox, cbStyleOption, painter, option.widget)
# Draw Title
doc = QtGui.QTextDocument()
doc.setHtml(index.data(QtCore.Qt.DisplayRole))
doc.setTextWidth(option.rect.width())
doc.setDocumentMargin(self.MARGINS)
doc.setDefaultFont(opts.font)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
# highlight text
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(option.palette.Text, option.palette.color(option.palette.Active, option.palette.HighlightedText))
else:
ctx.palette.setColor(option.palette.Text, option.palette.color(option.palette.Active, option.palette.Text))
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, option)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
doc.documentLayout().draw(painter, ctx)
# end
painter.restore()
################################################################################
# Widgets
################################################################################
class ListViewExample(QtWidgets.QWidget):
'''
Description:
Extension of listview which supports searching
'''
def __init__(self, parent=None):
super(ListViewExample, self).__init__(parent)
self.setStyleSheet('''
QListView {
color: rgb(255,255,255);
background-color: rgb(60,60,60);
}
QCheckBox, QCheckBox:disabled {
background: transparent;
}
QWidget::indicator {
width: 12px;
height: 12px;
border: 2px solid rgb(90,90,90);
border-radius: 3px;
background: rgb(30,30,30);
}
QWidget::indicator:checked {
border: 2px solid rgb(76,175,80);
background: rgb(0,255,40);
}
''')
self.itemModel = QtGui.QStandardItemModel()
self.checkbox = QtWidgets.QCheckBox('Sample')
self.listView = QtWidgets.QListView()
self.listView.setIconSize(QtCore.QSize(128,128))
self.listView.setModel(self.itemModel)
self.listView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.checkboxA = QtWidgets.QCheckBox('Sample')
self.listViewA = QtWidgets.QListView()
self.listViewA.setIconSize(QtCore.QSize(128,128))
self.listViewA.setModel(self.itemModel)
self.listViewA.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
# layout
self.mainLayout = QtWidgets.QGridLayout()
self.mainLayout.addWidget(self.checkbox,0,0)
self.mainLayout.addWidget(self.listView,1,0)
self.mainLayout.addWidget(self.checkboxA,0,1)
self.mainLayout.addWidget(self.listViewA,1,1)
self.setLayout(self.mainLayout)
################################################################################
# Widgets
################################################################################
def main():
app = QtWidgets.QApplication(sys.argv)
window = ListViewExample()
window.resize(600,400)
window.listView.setItemDelegate(MyDelegate())
window.itemModel.clear()
for i in range(10):
html = '''
<span style="font-size:12px;">
<b> Player <span>•</span> #{}</b>
</span>
<br>
<span style="font-size:11px;">
<b>Status:</b> <span style='color:rgb(255,0,0);'>⬤</span> Active
<br>
<b>Position:</b> WR
<br>
<b>Team:</b> <span style='color:rgb(0,128,255);'>█</span> Wings
</span>
'''.format(i)
item = QtGui.QStandardItem()
item.setData(html, QtCore.Qt.DisplayRole)
item.setCheckable(True)
window.itemModel.appendRow(item)
window.show()
app.exec_()
if __name__ == '__main__':
pass
main()
QStyle 绘画函数假定您正在为 control/primitive 使用该样式的默认行为。这意味着,在大多数情况下,可以忽略最后一个 widget
参数,因为没有考虑覆盖。
当使用样式sheets 时,情况会有所不同。如果小部件或其 parents 的 any (包括 QApplication)具有样式 sheet,则小部件将使用内部 QStyleSheetStyle,继承 QApplication 的行为样式,并被为 parents.
设置的任何样式 sheet 覆盖
在这种情况下,该参数成为强制性的,因为底层 QStyleSheetStyle 将需要检查小部件是否具有(或继承)样式 sheet 并最终“爬上”小部件树以了解 if 和 如何为它设置 任何自定义样式。
您只需将其添加到函数的参数中即可:
style.drawControl(QtWidgets.QStyle.CE_CheckBox, cbStyleOption, painter,
option.widget)
^^^^^^^^^^^^^
以上解决了样式问题,但未解决绘图问题。
虽然复选框在项目视图中的外观通常与 QCheckBox 相同(并且 ::indicator
伪选择器可用于两者),但它们实际上由样式绘制了不同的功能.
您的实施问题是您使用 drawControl
和 CE_CheckBox
绘图,但对于项目视图,您必须使用 drawPrimitive
和 PE_IndicatorItemViewItemCheck
。此外,由于您只是翻译原始选项 rect,结果是 drawControl
将绘制一个与 whole 项目矩形一样大的矩形(因此绘制在选择)。
正确的解决方案是在现有的基础上创建一个新的 QStyleOptionViewItem,并使用由subElementRect
编辑的矩形return和[=22] =].
def paint(self, painter, option, index):
if (index.flags() & QtCore.Qt.ItemIsUserCheckable):
cbStyleOption = QtWidgets.QStyleOptionViewItem(opts)
if index.data(QtCore.Qt.CheckStateRole):
cbStyleOption.state |= QtWidgets.QStyle.State_On
else:
cbStyleOption.state |= QtWidgets.QStyle.State_Off
cbStyleOption.state |= QtWidgets.QStyle.State_Enabled
cbStyleOption.rect = style.subElementRect(
style.SE_ItemViewItemCheckIndicator, opts, opts.widget)
style.drawPrimitive(style.PE_IndicatorItemViewItemCheck,
cbStyleOption, painter, opts.widget)
请注意,您不应在绘画中使用翻译,否则会使其与鼠标交互不一致。
要翻译元素,请改用样式中的 top
和 left
属性 sheet:
QWidget::indicator {
left: 10px;
...
}
另请注意,您正在使用未初始化的原始选项获取文本矩形,因此可能 return 一个无效的矩形;然后您应该使用实际初始化的选项,并使用上面解释的小部件参数:
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText,
opts, opts.widget)
我制作了一个自定义 StyleItemDelegate,但出于某种原因,当它绘制复选框指示器时,它与我的样式表中定义的内容不匹配。我怎样才能解决这个问题?您可以在应用程序的右侧看到样式表正确地影响了复选框在默认列表视图绘制事件中的显示方式。
更新 #1
我制作了一个自定义样式的项目委托来支持富文本 html 呈现,一切都很好。我需要重新实现复选框,因为我已经覆盖了绘画事件并确保该复选框仍然可用。但是我的文本与复选框重叠,使其无法使用。结果,在尝试绘制复选框指示器时,ListItem 的突出显示被破坏,仅在左侧显示一条细长的蓝色条带。
截图
代码
################################################################################
# imports
################################################################################
import os
import sys
from PySide2 import QtGui, QtWidgets, QtCore
################################################################################
# QStyledItemDelegate
################################################################################
class MyDelegate(QtWidgets.QStyledItemDelegate):
MARGINS = 10
def __init__(self, parent=None, *args):
QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
# overrides
def sizeHint(self, option, index):
'''
Description:
Since labels are stacked we will take whichever is the widest
'''
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
# draw rich text
doc = QtGui.QTextDocument()
doc.setHtml(index.data(QtCore.Qt.DisplayRole))
doc.setDocumentMargin(self.MARGINS)
doc.setDefaultFont(options.font)
doc.setTextWidth(option.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
# methods
def paint(self, painter, option, index):
painter.save()
painter.setClipping(True)
painter.setClipRect(option.rect)
opts = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(opts, index)
style = QtGui.QApplication.style() if opts.widget is None else opts.widget.style()
# Draw background
if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight().color())
else:
painter.fillRect(option.rect, QtGui.QBrush(QtCore.Qt.NoBrush))
# Draw checkbox
if (index.flags() & QtCore.Qt.ItemIsUserCheckable):
cbStyleOption = QtWidgets.QStyleOptionButton()
if index.data(QtCore.Qt.CheckStateRole):
cbStyleOption.state |= QtWidgets.QStyle.State_On
else:
cbStyleOption.state |= QtWidgets.QStyle.State_Off
cbStyleOption.state |= QtWidgets.QStyle.State_Enabled
cbStyleOption.rect = option.rect.translated(self.MARGINS, 0)
style.drawControl(QtWidgets.QStyle.CE_CheckBox, cbStyleOption, painter, option.widget)
# Draw Title
doc = QtGui.QTextDocument()
doc.setHtml(index.data(QtCore.Qt.DisplayRole))
doc.setTextWidth(option.rect.width())
doc.setDocumentMargin(self.MARGINS)
doc.setDefaultFont(opts.font)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
# highlight text
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(option.palette.Text, option.palette.color(option.palette.Active, option.palette.HighlightedText))
else:
ctx.palette.setColor(option.palette.Text, option.palette.color(option.palette.Active, option.palette.Text))
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, option)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
doc.documentLayout().draw(painter, ctx)
# end
painter.restore()
################################################################################
# Widgets
################################################################################
class ListViewExample(QtWidgets.QWidget):
'''
Description:
Extension of listview which supports searching
'''
def __init__(self, parent=None):
super(ListViewExample, self).__init__(parent)
self.setStyleSheet('''
QListView {
color: rgb(255,255,255);
background-color: rgb(60,60,60);
}
QCheckBox, QCheckBox:disabled {
background: transparent;
}
QWidget::indicator {
width: 12px;
height: 12px;
border: 2px solid rgb(90,90,90);
border-radius: 3px;
background: rgb(30,30,30);
}
QWidget::indicator:checked {
border: 2px solid rgb(76,175,80);
background: rgb(0,255,40);
}
''')
self.itemModel = QtGui.QStandardItemModel()
self.checkbox = QtWidgets.QCheckBox('Sample')
self.listView = QtWidgets.QListView()
self.listView.setIconSize(QtCore.QSize(128,128))
self.listView.setModel(self.itemModel)
self.listView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.checkboxA = QtWidgets.QCheckBox('Sample')
self.listViewA = QtWidgets.QListView()
self.listViewA.setIconSize(QtCore.QSize(128,128))
self.listViewA.setModel(self.itemModel)
self.listViewA.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
# layout
self.mainLayout = QtWidgets.QGridLayout()
self.mainLayout.addWidget(self.checkbox,0,0)
self.mainLayout.addWidget(self.listView,1,0)
self.mainLayout.addWidget(self.checkboxA,0,1)
self.mainLayout.addWidget(self.listViewA,1,1)
self.setLayout(self.mainLayout)
################################################################################
# Widgets
################################################################################
def main():
app = QtWidgets.QApplication(sys.argv)
window = ListViewExample()
window.resize(600,400)
window.listView.setItemDelegate(MyDelegate())
window.itemModel.clear()
for i in range(10):
html = '''
<span style="font-size:12px;">
<b> Player <span>•</span> #{}</b>
</span>
<br>
<span style="font-size:11px;">
<b>Status:</b> <span style='color:rgb(255,0,0);'>⬤</span> Active
<br>
<b>Position:</b> WR
<br>
<b>Team:</b> <span style='color:rgb(0,128,255);'>█</span> Wings
</span>
'''.format(i)
item = QtGui.QStandardItem()
item.setData(html, QtCore.Qt.DisplayRole)
item.setCheckable(True)
window.itemModel.appendRow(item)
window.show()
app.exec_()
if __name__ == '__main__':
pass
main()
QStyle 绘画函数假定您正在为 control/primitive 使用该样式的默认行为。这意味着,在大多数情况下,可以忽略最后一个 widget
参数,因为没有考虑覆盖。
当使用样式sheets 时,情况会有所不同。如果小部件或其 parents 的 any (包括 QApplication)具有样式 sheet,则小部件将使用内部 QStyleSheetStyle,继承 QApplication 的行为样式,并被为 parents.
设置的任何样式 sheet 覆盖
在这种情况下,该参数成为强制性的,因为底层 QStyleSheetStyle 将需要检查小部件是否具有(或继承)样式 sheet 并最终“爬上”小部件树以了解 if 和 如何为它设置 任何自定义样式。
您只需将其添加到函数的参数中即可:
style.drawControl(QtWidgets.QStyle.CE_CheckBox, cbStyleOption, painter,
option.widget)
^^^^^^^^^^^^^
以上解决了样式问题,但未解决绘图问题。
虽然复选框在项目视图中的外观通常与 QCheckBox 相同(并且 ::indicator
伪选择器可用于两者),但它们实际上由样式绘制了不同的功能.
您的实施问题是您使用 drawControl
和 CE_CheckBox
绘图,但对于项目视图,您必须使用 drawPrimitive
和 PE_IndicatorItemViewItemCheck
。此外,由于您只是翻译原始选项 rect,结果是 drawControl
将绘制一个与 whole 项目矩形一样大的矩形(因此绘制在选择)。
正确的解决方案是在现有的基础上创建一个新的 QStyleOptionViewItem,并使用由subElementRect
编辑的矩形return和[=22] =].
def paint(self, painter, option, index):
if (index.flags() & QtCore.Qt.ItemIsUserCheckable):
cbStyleOption = QtWidgets.QStyleOptionViewItem(opts)
if index.data(QtCore.Qt.CheckStateRole):
cbStyleOption.state |= QtWidgets.QStyle.State_On
else:
cbStyleOption.state |= QtWidgets.QStyle.State_Off
cbStyleOption.state |= QtWidgets.QStyle.State_Enabled
cbStyleOption.rect = style.subElementRect(
style.SE_ItemViewItemCheckIndicator, opts, opts.widget)
style.drawPrimitive(style.PE_IndicatorItemViewItemCheck,
cbStyleOption, painter, opts.widget)
请注意,您不应在绘画中使用翻译,否则会使其与鼠标交互不一致。
要翻译元素,请改用样式中的 top
和 left
属性 sheet:
QWidget::indicator {
left: 10px;
...
}
另请注意,您正在使用未初始化的原始选项获取文本矩形,因此可能 return 一个无效的矩形;然后您应该使用实际初始化的选项,并使用上面解释的小部件参数:
textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText,
opts, opts.widget)