增加 addAction 图标大小 PyQt5

Increasing addAction icon size PyQt5

我有 QLineEdit,我想在它的末尾添加一个清除按钮。我在 QLineEdit 中启用了清除按钮,它工作正常。我需要在 QLineEdit 的末尾添加一个自定义清除按钮,所以我使用了 QLineEdit 的 addAction() 并添加了我的自定义图标。问题是我找不到增加尺寸的解决方案,我尝试增加图像尺寸但它不起作用。

class TextBox(QFrame):
    def __init__(self, parent):
        super(TextBox, self).__init__(parent=parent)
        self.setObjectName("textBox")

        self.isActive = False

        self.lineEdit = QLineEdit()

        self.lineEdit.addAction(QIcon("assets/icons/clear@3x.png"), QLineEdit.TrailingPosition)

QIcon 没有特定的大小,因为它仅由使用它的小部件“决定”。虽然大多数使用图标的小部件都有 iconSize 属性,但 QLineEdit 中的操作图标以不同的方式显示。

直到 Qt 5.11(已排除),如果行编辑小于 34 像素,则大小被硬编码为 16 像素,如果更高则为 32。

从 Qt 5.11 开始,使用样式(通过 pixelMetric())检索大小,并且可以使用代理样式覆盖它:

class Proxy(QtWidgets.QProxyStyle):
    def pixelMetric(self, metric, opt=None, widget=None):
        if (metric == self.PM_SmallIconSize and 
            isinstance(widget, QtWidgets.QLineEdit)):
                size = widget.property('iconSize')
                if size is not None:
                    return size
                return widget.fontMetrics().height()
        return super().pixelMetric(metric, opt, widget)


class LineEdit(QtWidgets.QLineEdit):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setProperty('iconSize', 64)
        # ...

不过,对于以前版本的 Qt,事情有点棘手。我想出的唯一解决方案是在作为行编辑子项的所有 QToolButton 上安装事件过滤器(每个操作都使用内部 QToolButton,包括清除操作),手动设置它们的几何形状(正确的点击操作需要)在事件过滤器中绘制它。

以下包括 proxystyle 实现,以防当前版本如前所述正确支持它:

from PyQt5 import QtWidgets, QtCore, QtGui

if int(QtCore.QT_VERSION_STR.split('.')[1]) > 11:
    IconSizeFix = False
else:
    IconSizeFix = True


class Proxy(QtWidgets.QProxyStyle):
    def pixelMetric(self, metric, opt=None, widget=None):
        if (metric == self.PM_SmallIconSize and 
            isinstance(widget, QtWidgets.QLineEdit)):
                size = widget.property('iconSize')
                if size is not None:
                    return size
                return widget.fontMetrics().height()
        return super().pixelMetric(metric, opt, widget)


class LineEdit(QtWidgets.QLineEdit):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setProperty('iconSize', 64)
        self.setClearButtonEnabled(True)
        self.addAction(QtGui.QIcon("icon.png"), self.TrailingPosition)
        font = self.font()
        font.setPointSize(48)
        self.setFont(font)

    def checkButtons(self):
        for button in self.findChildren(QtWidgets.QToolButton):
            button.installEventFilter(self)

    def actionEvent(self, event):
        super().actionEvent(event)
        if IconSizeFix:
            self.checkButtons()

    def eventFilter(self, source, event):
        if event.type() == QtCore.QEvent.Paint:
            if (source.defaultAction().objectName() == '_q_qlineeditclearaction' and 
                not self.text()):
                    return True
            qp = QtGui.QPainter(source)
            state = QtGui.QIcon.Disabled
            if source.isEnabled():
                state = QtGui.QIcon.Active if source.isDown() else QtGui.QIcon.Normal
            iconSize = QtCore.QSize(*[self.property('iconSize')] * 2)
            qp.drawPixmap(source.rect(), source.icon().pixmap(
                self.windowHandle(), iconSize, state, QtGui.QIcon.Off))
            return True
        return super().eventFilter(source, event)

    def resizeEvent(self, event):
        if not IconSizeFix:
            return
        self.checkButtons()
        buttons = self.findChildren(QtWidgets.QToolButton)
        if not buttons:
            return

        left = []
        right = []
        center = self.rect().center().x()
        for button in buttons:
            geo = button.geometry()
            if geo.center().x() < center:
                left.append(button)
            else:
                right.append(button)
        
        left.sort(key=lambda x: x.geometry().x())
        right.sort(key=lambda x: x.geometry().x())

        iconSize = self.property('iconSize')

        margin = iconSize / 4
        top = (self.height() - iconSize) / 2
        leftMargin = rightMargin = 0
        if left:
            x = margin
            leftEdge = left[-1].geometry().right()
            for button in left:
                geo = QtCore.QRect(x, top, iconSize, iconSize)
                button.setGeometry(geo)
                x += iconSize + margin
            leftMargin = x - leftEdge - margin
        if right:
            rightEdge = self.width() - margin
            x = rightEdge - len(right) * iconSize - (len(right) - 1) * margin
            rightMargin = self.width() - rightEdge + margin
            for button in right:
                geo = QtCore.QRect(x, top, iconSize, iconSize)
                button.setGeometry(geo)
                x += iconSize + margin
        self.setTextMargins(leftMargin, 0, rightMargin, 0)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle(Proxy())
    w = LineEdit()
    w.show()
    sys.exit(app.exec_())

考虑以下因素:

  • 使用 5.11 之前的解决方法,定位不是像素完美的,我试图模仿 QLineEdit 所做的以保持代码尽可能简单;
  • 这幅画 完全 不一样,最重要的是图标在单击时缺少“高亮”阴影,如果样式使用淡入淡出 in/out 效果清除按钮这些效果将不可用;
  • QProxyStyle方法也影响了QLineEdit的sizeHint,所以不能不能小于图标大小,慎用;