PyQt5 在更改样式表时使用默认图像图标

PyQt5 use default image icon when changing stylesheet

我正在使用 PyQt5 为 QTreeWigdet 创建自定义样式表。有没有办法使用默认的 RightArrow/LeftArrow 图标,而不是为箭头加载自定义图像?我试过下面的代码无济于事。

AnnotationTree = """
    QTreeWidget::branch:selected:closed {
        background: rgb(75,75,75);
        image: Qt::RightArrow;
    }
    QTreeWidget::branch:selected:open {
        background: rgb(75,75,75);
        image: Qt::DownArrow;
    }
    QTreeWidget::item:selected {
        background: rgb(75,75,75);
        color: white;
        border-left: 2px solid orange;
    }
"""

在复杂的小部件上设置样式表(尤其是在使用伪选择器时)会导致通过忽略默认样式来完全覆盖元素的绘制。

Qt 不提供对分支等原始元素的访问,但可以使用样式绘制来绘制元素的像素图,只要已知它们的大小在所有情况下都是固定的即可。

一个可能的解决方案是在启动应用程序时创建所有必需的图标(通过子类化 QApplication)并将它们保存在 SO 临时文件夹中。

class MyWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet("""
            QTreeWidget::branch:selected:closed {{
                background: rgb(75,75,75);
                image: url({iconPath}/closed.png);
            }}
            QTreeWidget::branch:selected:open {{
                background: rgb(75,75,75);
                image: url({iconPath}/open.png);
            }}
            QTreeWidget::item:selected {{
                background: rgb(255,255,255);
                color: white;
                border-left: 2px solid orange;
            }}
        """.format(iconPath=QtWidgets.QApplication.instance().iconPath))
        # ...


class Application(QtWidgets.QApplication):
    _iconsDone = True
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        tmpDir = QtCore.QDir(QtCore.QStandardPaths.writableLocation(
            QtCore.QStandardPaths.TempLocation))
        iconDirName = '__qticons'
        if not tmpDir.exists(iconDirName):
            tmpDir.mkpath(iconDirName)
        self.iconPath = tmpDir.absoluteFilePath(iconDirName)

    def setStyle(self, style):
        if isinstance(style, str):
            self.makeIcons(QtWidgets.QStyleFactory.create(style))
        else:
            self.makeIcons(style)
        super().setStyle(style)

    def makeIcons(self, style):
        iconSize = style.pixelMetric(style.PM_SmallIconSize) - 1
        opt = QtWidgets.QStyleOptionViewItem()
        opt.palette = style.standardPalette()
        opt.rect = QtCore.QRect(0, 0, iconSize, iconSize)
        opt.state |= style.State_Enabled
        baseState = style.State(opt.state)

        iconStates = (
            ('closed', style.State_Children), 
            ('open', style.State_Children | style.State_Open), 
        )

        for iconName, state in iconStates:
            opt.state = baseState | state
            pm = QtGui.QPixmap(iconSize, iconSize)
            pm.fill(QtCore.Qt.transparent)
            qp = QtGui.QPainter(pm)
            style.drawPrimitive(style.PE_IndicatorBranch, opt, qp)
            qp.end()
            pm.save(QtCore.QDir(self.iconPath).absoluteFilePath(
                '{}.png'.format(iconName)))

        self._iconsDone = True

    def exec(self):
        if not self._iconsDone:
            self.makeIcons(self.style())
            self._iconsDone = True
        return super().exec()

    def exec_(self):
        return self.exec()

if __name__ == '__main__':
    import sys
    app = Application(sys.argv)
    w = MyWindow()
    w.show()
    app.exec_()

但是,有一个问题:您完全依赖当前的绘画风格。在您的样式表中,您使用的是非常暗的背景色,而默认样式通常假定项目视图的背景非常亮(通常是白色)。结果是默认箭头(黑色)可能几乎不可见。例如,这是我系统上的结果:

因此,只要您提供自定义颜色,就不能依赖当前样式(您不知道它是什么)来绘制系统基元,这是 Qt StyleSheets 正确避免使用的另一个原因标准绘画作为后备:如果您想要自定义样式,您必须提供所有内容,包括分支图标。