在 python 中获取 Qt 按钮图标信息的最佳方式?

Best way of getting Qt pushbuttons Icons information in python?

我正在开发一个动态修改按钮图标的 PyQt 应用程序。图标使用 QStyle 创建,存储为 class 参数,然后使用 setIcon() 方法放入按钮。

由于图标是动态修改的,我想要一种优雅的方式来检查当前按钮图标是什么。原来的 Qt documentation 提到了 setIcon() 方法,但我找不到 getIcon() 方法。在我在下面附加的代码中,我可以访问按钮的 icon() 属性,但它似乎不是我要找的,因为它在确定哪个是当前图标时似乎没有用。

import sys

from PyQt5.QtWidgets import QApplication, QGridLayout, QPushButton, QStyle, QWidget


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        self.back_arrow=self.style().standardIcon(getattr(QStyle, "SP_ArrowBack"))

        self.first_pushbutton=QPushButton("first")

        print(self.first_pushbutton.icon())
        self.first_pushbutton.setIcon(self.back_arrow)
        self.first_pushbutton.clicked.connect(self.first_clicked)
        print(self.first_pushbutton.icon())

        layout = QGridLayout()

        layout.addWidget(self.first_pushbutton)

        self.setLayout(layout)

    def first_clicked(self):
        print(self.sender().icon()==self.back_arrow)


app = QApplication(sys.argv)

w = Window()
w.show()

app.exec_()

执行代码后,点击按钮打印

False

在屏幕上,这对我来说似乎不直观。我还尝试将 first_clicked() 函数更改为

print(self.sender().icon().objectName())

导致“AttributeError:'QIcon' 对象没有属性 'objectName'”。尝试 setObjectName 方法会导致类似的错误。

init函数中self.first_pushbutton.setIcon(self.back_arrow)前后的打印总是打印相同的结果,例如

<PyQt5.QtGui.QIcon object at 0x7fc31da8ddc8>
<PyQt5.QtGui.QIcon object at 0x7fc31da8ddc8>

所以我什至不确定我是否更改了 icon() 属性。有没有推荐的方法来检查当前图标?我知道我可以设置它们的初始图标并保证这些图标只会在调用适当的方法时发生变化,但这样做对我来说似乎很草率。

首先,objectName() is a specific property of classes that inherit from QObject. QIcon is not a QObject subclass (as we can clearly see from its documentation,其header描述中没有“Inherits:”字段),因此属性不可用,因为没有Qt 属性 完全支持。

QIcon 对 name() 值的支持有限,可以 return 一个唯一标识符,但不一定,因为它取决于 OS 和当前样式:例如,在我的系统上,我得到 SP_DirIcon"folder" 图标名称,但 SP_DriveCDIcon.
的空字符串 因此,即使考虑到 QStyle,图标名称也不是比较图标的可靠方式。 name 支持完全取决于 Qt 实现、底层 OS、图标引擎的支持和当前主题可用性。

另一个需要考虑的重要方面是,与 QPixmap 类似,当您在小部件上设置 QIcon 时,您实际上并没有设置 that QIcon 实例,而是它。

此外,QIcon 实际上是一种“抽象 object”,它实际上与 QIconEngine 接口,最终 returns QPixmap 或在给定的油漆表面上绘制图标.

这意味着您无法在小部件的“原始”功能和 icon() getter 功能之间进行任何实际比较,因为您总是有两个不同的 object ,即使他们绘制相同的内容。事实上,如果您查看 QIcon documentation, there is no == operator, exactly like QPixmap, and opposite to QImage,它是一张“实际”图像(注意:它实际上比这复杂一点,为了在 Python 中进行解释,我将其简化条款)。

唯一确保两个QIcon完全相同的方法是使用cacheKey()。请注意,如文档所述,将像素图(或文件)添加到 QIcon 会改变密钥,因此即使将 same 像素图添加到两个先前相同的 QIcon,这些密钥也不会匹配了。
考虑到即使是从同一个文件创建的两个 QIcon(如 QIcon('path/to/image'))也会有不同的缓存键。

    def first_clicked(self):
        # this will print True
        print(self.sender().icon().cacheKey() == self.back_arrow.cacheKey())

如果您通过添加其他图像(或使用文件构造函数创建它们)来更改 QIcon,那么唯一的选择是将两个图标的 availableSizes() (and states/modes) of each QIcon, and, if they do match, transform each resulting pixmap() 结果与 QImages 进行比较,然后进行比较图片。
当然,这会降低性能,特别是如果您使用的是高分辨率文件:没有 直接方法可以知道添加了什么 pixmap/file 以及 mode/state 组合它们已经被设置,除非你创建自己的 QIconEngine,所以 QImage 比较将循环遍历所有图标大小、状态和模式(意味着每个添加的最多 8 个 QImage 比较 file/size,直到 任何像素差异已被发现)。

这是上述的可能实现:

def compareIcons(a, b):
    if a.cacheKey() == b.cacheKey:
        return True
    aSizes = a.availableSizes()
    bSizes = b.availableSizes()
    if aSizes != bSizes:
        return False
    for state in (QIcon.Off, QIcon.On):
        for mode in (QIcon.Normal, QIcon.Disabled, QIcon.Active, QIcon.Selected):
            for size in aSizes:
                if (a.pixmap(size, mode, state).toImage() 
                    != b.pixmap(size, mode, state).toImage()):
                        return False
    return True

通过上面的代码,您还可以比较与文件创建的QIcons:

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        self.back_arrow = QIcon('someIcon.png')
        self.other_arrow = QIcon('someIcon.png')

        self.first_pushbutton = QPushButton("first")

        self.first_pushbutton.setIcon(self.back_arrow)
        self.first_pushbutton.clicked.connect(self.first_clicked)

        layout = QGridLayout(self)
        layout.addWidget(self.first_pushbutton)

    def first_clicked(self):
        buttonIcon = self.sender().icon()
        print('compare original icon cache keys:', 
            buttonIcon.cacheKey() == self.back_arrow.cacheKey())
        print('compare different icon cache keys:', 
            buttonIcon.cacheKey() == self.other_arrow.cacheKey())
        print('compare different icon images:', 
            compareIcons(buttonIcon, self.other_arrow))

其实你也可以用上面的函数合成==运算符,只要在你导入的最开始加上,加上下面的:

# somewhere in the first imported modules of your main script:
QIcon.__eq__ = lambda icon, other: compareIcons(icon, other)

Python2 的注释:__ne__(对应于 !=,如 Not Equal) 也必须被覆盖,它应该 return not compareIcons(icon, other).

然后,您实际上可以使用 ==!= 运算符:

class Window(QWidget):
    # ...
    def first_clicked(self):
        # ...
        print('compare different icon images:', 
            buttonIcon == self.other_arrow)