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>&#8226;</span> #{}</b>
        </span>
        <br>
        <span style="font-size:11px;">
          <b>Status:</b> <span style='color:rgb(255,0,0);'>&#11044;</span> Active
            <br>
          <b>Position:</b> WR
            <br>
          <b>Team:</b> <span style='color:rgb(0,128,255);'>&#9608;</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 伪选择器可用于两者),但它们实际上由样式绘制了不同的功能.

您的实施问题是您使用 drawControlCE_CheckBox 绘图,但对于项目视图,您必须使用 drawPrimitivePE_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)

请注意,您不应在绘画中使用翻译,否则会使其与鼠标交互不一致。

要翻译元素,请改用样式中的 topleft 属性 sheet:

            QWidget::indicator {
                left: 10px;
                ...
            }

另请注意,您正在使用未初始化的原始选项获取文本矩形,因此可能 return 一个无效的矩形;然后您应该使用实际初始化的选项,并使用上面解释的小部件参数:

    textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, 
        opts, opts.widget)