PyQt6 - 在 QComboBox 中,显示居中的 QIcon,不带文本

问题描述 投票:0回答:1

使用 PyQt6,我尝试显示以

QIcon(QPixmap)
为中心的图像,没有带有
QComboBox.addItem()
的文本。

> help(QComboBox.addItem)
Help on built-in function addItem:

addItem(...) method of PyQt6.sip.wrappertype instance
    addItem(self, str, userData: Any = None)
    addItem(self, QIcon, str, userData: Any = None)

到目前为止,我认为我必须使用

QStyledItemDelegate
作为下拉列表,但我仍然使用以下方法得到了一个奇怪的结果:

class MyComboBox(QComboBox):
    def __init__(self, *args, **kwargs):
        super().__init__()
        delegate = AlignDelegate(self)
        self.setItemDelegate(delegate)

class AlignDelegate(QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        option.features &= ~QStyleOptionViewItem.ViewItemFeature.HasDisplay
        option.decorationAlignment = Qt.AlignmentFlag.AlignCenter
        option.decorationPosition = QStyleOptionViewItem.Position.Top

下拉QIcon水平居中,但垂直居中,但我尝试使用以下行删除文本,但除非字符串是

""
,否则文本仍然在某处。

option.features &= ~QStyleOptionViewItem.ViewItemFeature.HasDisplay

所选行也是如此,我不知道如何没有文本(除了

""
)并让 QIcon 居中而不是文本。

class MyComboBox(QComboBox):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.setEditable(True)
        self.lineEdit().setReadOnly(True)
        self.lineEdit().setAlignment(Qt.AlignmentFlag.AlignCenter)
python pyqt alignment qcombobox qstyleditemdelegate
1个回答
0
投票

玩起来有两个问题:

  • 委托中元素的定位始终基于当前样式,并且大多数样式根据显示文本的可能性假定图标的位置,即使没有显示文本;
  • 委托仅管理项目在弹出窗口中的显示方式:当前项目在组合框的正常显示中的显示方式完全忽略委托;

弹出显示

由于 Qt 小部件(包括委托)的大多数功能始终依赖于当前样式,这意味着确保图标始终居中的唯一方法是重写委托的

paint()
函数。

请注意,我们应该始终尝试遵循默认行为:QComboBox 的默认委托是 QItemDelegate,而不是 QStyledItemDelegate。

QItemDelegate 对于项目的每个元素都有单独的函数;因为我们只想显示图标,所以我们只需要调用

drawBackground()
(它也绘制突出显示项目的选定状态),然后绘制图标。

class AlignDelegate(QItemDelegate):
    def drawCenteredIcon(self, qp, opt, index):
        icon = index.data(Qt.ItemDataRole.DecorationRole)

        if isinstance(icon, QIcon):
            if not opt.state & QStyle.StateFlag.State_Enabled:
                mode = QIcon.Mode.Disabled
            elif opt.state & QStyle.StateFlag.State_Selected:
                mode = QIcon.Mode.Selected
            else:
                mode = QIcon.Mode.Normal
            icon = icon.pixmap(opt.rect.size(), mode)
            rect = opt.rect

        elif isinstance(icon, QPixmap):
            rect = QRect(QPoint(), icon.size().scaled(opt.rect.size(), 
                Qt.AspectRatioMode.KeepAspectRatio))
            rect.moveCenter(opt.rect.center())

        else:
            return

        qp.drawPixmap(rect, icon)

    def paint(self, qp, opt, index):
        self.drawBackground(qp, opt, index)
        self.drawCenteredIcon(qp, opt, index)

    def sizeHint(self, opt, index):
        # return the default size
        return super().sizeHint(opt, QModelIndex())

如果你还想使用QStyledItemDelegate的功能(包括样式表),那么我们可以通过调用样式的相关函数来实现类似的方法,即

drawPrimitive()
PE_PanelItemViewItem

class StyledAlignDelegate(QStyledItemDelegate):
    ...

    def paint(self, qp, opt, index):
        self.initStyleOption(opt, index)
        style = opt.widget.style()
        style.drawPrimitive(QStyle.PrimitiveElement.PE_PanelItemViewItem, 
            opt, qp, opt.widget)

        self.drawCenteredIcon(qp, opt, index)

    ...

组合显示

如上所述,当弹出窗口不可见时,委托对组合框的显示方式没有影响。

为了更改这种显示,我们需要通过覆盖

paintEvent()
来遵循 QComboBox 的默认行为,并将原始 C++ 代码“移植”到 Python 中。

图标的正确居中是通过使用 subControlRect()

 子控件调用当前样式 
SC_ComboBoxEditField
 函数来实现的,这确保了我们获得显示“标签”的正确矩形,不包括箭头按钮组合框。

请注意,这意味着我们可能会破坏默认行为,以防任何未来的 Qt 版本改变默认实现,这可能是由于错误报告/修复、QComboBox 绘制的更改甚至 QStyle 实现造成的。

class MyComboBox(QComboBox):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setItemDelegate(AlignDelegate(self))
        # alternatively, for the styled delegate
        # self.setItemDelegate(StyledAlignDelegate(self))

    def paintEvent(self, event):
        qp = QStylePainter(self)
        qp.setPen(self.palette().color(QPalette.ColorRole.Text))

        opt = QStyleOptionComboBox()
        self.initStyleOption(opt)

        if self.currentIndex() < 0 and self.placeholderText():
            opt.palette.setBrush(QPalette.ColorRole.ButtonText, 
                opt.palette.placeholderText())
            opt.currentText = self.placeholderText()
        elif opt.currentText:
            opt.currentText = ''

        style = self.style()
        qp.drawComplexControl(style.ComplexControl.CC_ComboBox, opt)

        if not opt.currentIcon.isNull():
            rect = style.subControlRect(
                style.ComplexControl.CC_ComboBox, opt, 
                style.SubControl.SC_ComboBoxEditField, self
            )
            style.drawItemPixmap(qp, rect, Qt.AlignmentFlag.AlignCenter, 
                opt.currentIcon.pixmap(rect.size()))

请注意,如果您担心未来的变化(如上所述),您可以实施安全措施:只需为装饰创建一个等于或高于

Qt::UserRole
的自定义数据角色,将其用于中的
index.data()
getter
drawCenteredIcon
功能,并始终通过使用
setItemData(index, icon, customrole)
设置图标来添加新项目。
通过这种方式,您只需要使用基本的
paintEvent()
覆盖
super().paintEvent(event)
(不会绘制图标或文本),然后最终使用该图标/像素图进行图标绘制,如上面最后一个代码的最后部分所示自定义角色。

© www.soinside.com 2019 - 2024. All rights reserved.