QComboBox 在 Qt.UIEffect.UI_AnimateCombo 设置为 False 时更改样式表时出现意外行为

QComboBox has unexpected behavior when changing stylesheets while Qt.UIEffect.UI_AnimateCombo is set to False

我的目标是制作一个仅当鼠标悬停在其上方时才显示向下箭头的组合框。为此,我继承了 QComboBox 并重新实现了 enterEvent 和 leaveEvent,这将相应地更改其样式表。它按预期工作,当 window 显示时,向下箭头隐藏,当鼠标悬停在组合框上方时,向下箭头显示。然后,当鼠标单击组合框文本时,下拉列表会出现,向下箭头再次变得不可见(我可以处理,但如果有人有解决方案,我会洗耳恭听)。主要问题是当鼠标单击下拉箭头时,它不仅隐藏了下拉箭头,就像鼠标单击文本时那样,它还隐藏了文本和下拉箭头。我做了一些测试以找出导致问题的原因,似乎在 QApplication 上调用 setEffectEnabled(Qt.UIEffect.UI_AnimateCombo, False) 是问题所在,尽管我不知道禁用下拉动画与单击时使文本消失有何关系在下拉箭头上。另外,如果有人有更优雅的方式做我想做的事,我再次洗耳恭听:)。这是示例代码:

import sys

from PyQt5.QtCore import QEvent, Qt
from PyQt5.QtGui import QEnterEvent
from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox, QWidget


class TestComboBox(QComboBox):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.default_stylesheet = ''' 
        QComboBox {
            background-color: rgba(0, 0, 0, 0);
        }
        QComboBox QAbstractItemView {
            background-color: white;
            min-width: 150px;
        }'''

        self.hide_down_arrow_stylesheet = '''QComboBox::down-arrow { \
                                             background-color: rgba(0, 0, 0, 0);}'''

        self.setStyleSheet(self.default_stylesheet + self.hide_down_arrow_stylesheet)

    def enterEvent(self, event: QEnterEvent) -> None:
        self.setStyleSheet(self.default_stylesheet)
        super().enterEvent(event)

    def leaveEvent(self, event: QEvent) -> None:
        self.setStyleSheet(self.default_stylesheet + self.hide_down_arrow_stylesheet)
        super().leaveEvent(event)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.widget = QWidget(self)
        self.setCentralWidget(self.widget)

        self.combobox = TestComboBox(self.widget)
        self.combobox.resize(180, 30)
        self.combobox.insertItem(0, "item 1")
        self.combobox.insertItem(0, "item 2")

        self.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setEffectEnabled(Qt.UIEffect.UI_AnimateCombo, False)
    window = MainWindow()
    sys.exit(app.exec())

可能存在一些内部和复杂的错误,这与您只设置了 单个 sub-control 属性,但 documentation专门针对这方面:

Note: With complex widgets such as QComboBox and QScrollBar, if one property or sub-control is customized, all the other properties or sub-controls must be customized as well.

请注意,在大多数情况下,这也与未设置 layout manager 这一事实有关,并且在我的大多数测试中,只要使用适当的布局(如您总是应该,即使你只使用一个子部件)。

class MainWindow(QMainWindow):
    def __init__(self):
        # ...
        layout = QVBoxLayout(self.widget)
        layout.addWidget(self.combobox)

现在,即使有布局,我仍然能够在某些条件下重现问题。这是因为一开始解释的:如果你设置一个属性一个复杂的widget,你必须设置所有的;否则可能会导致意外行为。

在这种特定情况下,我认为 QStyleSheetStyle(这是自动设置在受 QSS 影响的任何小部件(包括继承的小部件)上的私有样式)无法恢复画家的笔,并且它使用箭头的颜色对于组合标签,但它仅在某些(可能是“随机”)条件下这样做。

重点仍然存在:有些属性没有设置,特别是 drop-down 伪元素。
这样做的结果是,为该伪元素设置 border 会导致将箭头图像重置为空白,但我们可以将其用作实际优势;因为我们知道设置边框会导致无箭头图像,所以我们可以相应地更新样式 sheet:

class TestComboBox(QComboBox):
    default_stylesheet = ''' 
        TestComboBox {
            color: black;
            selection-color: black;
        }
        
        TestComboBox QAbstractItemView {
            background-color: white;
            min-width: 150px;
        }
        '''

    non_hover_stylesheet = '''
        TestComboBox::drop-down {
            border: none;
        }
        '''

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setHoverState(False)

    def setHoverState(self, state):
        qss = self.default_stylesheet
        if not state:
            qss += self.non_hover_stylesheet
        self.setStyleSheet(qss)

    def enterEvent(self, event):
        super().enterEvent(event)
        self.setHoverState(True)

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.setHoverState(False)

补充说明:

  • 我删除了 selection-background-colorbackground-color 属性,因为它们会导致某些样式的行为不一致(特别是“Fusion”样式,它被认为是 QSS 的参考样式,应该总是 被检查)因为这些属性经常被忽略 alpha 级别;我无法用“WindowsVista”风格测试它,但重点仍然是:如果你想要一个透明的背景色,它必须与实际背景一致;考虑使用 QApplication 的 palette() 来获取 HighlightHighlightedText 角色的颜色,并最终使用这些颜色(没有 alpha 级别)来设置实际背景;
  • 我将 QSS“模板”作为 class 属性:因为我们可以假设样式 sheet 设置为 class 默认值,将其定义为实例属性使得没什么意义;
  • 这显然(也不能)考虑继承风格sheet;虽然我特别使用 TestComboBox class 选择器而不是 QComboBox,但请记住,如果您设置样式 sheet(即 扩展 其他 QComboBox 属性或伪 elements/states) 到任何父级或应用程序,您可能会得到不一致和意外的行为;
  • (不相关,但仍然重要)__init__ 中调用 self.show()(或 self.setVisible(True))是个好习惯小部件;

我通过阅读文档 https://doc.qt.io/qt-5/stylesheet-syntax.html#sub-controls 设法解决了这个问题。这是解决方案:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QComboBox, QWidget, QVBoxLayout


class TestComboBox(QComboBox):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.default_stylesheet = ''' 
        QComboBox {
            color: black;
            selection-color: black;
            selection-background-color: rgba(0, 0, 0, 0);
            background-color: rgba(0, 0, 0, 0);
        }
        
        QComboBox QAbstractItemView {
            background-color: white;
            min-width: 150px;
        }
        
        QComboBox:!hover::down-arrow {
        color: rgba(0, 0, 0, 0);
        }
        '''

        self.setStyleSheet(self.default_stylesheet)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.widget = QWidget(self)
        self.setCentralWidget(self.widget)

        self.layout = QVBoxLayout(self.widget)

        self.combobox = TestComboBox()
        self.combobox.resize(180, 30)
        self.combobox.insertItem(0, "item 1")
        self.combobox.insertItem(0, "item 2")
        self.combobox.insertItem(0, "item 3")
        self.combobox.insertItem(0, "item 4")
        self.combobox.insertItem(0, "item 5")
        self.layout.addWidget(self.combobox)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setEffectEnabled(Qt.UIEffect.UI_AnimateCombo, False)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())