为了将样式与 PyQt 代码的其余部分分开,我尝试使用 qss 文件进行样式设置。然而,使用它来完成所有的样式似乎很困难(如果不是不可能的话)。
使用自定义 qss 属性可以帮助我实现这一目标。举个例子,我有一个类代表一条可以是水平或垂直的线:
class Line(QWidget):
def __init__(self, direction: Qt.Orientation, thickness: int, parent: Optional[QWidget]):
super().__init__(parent)
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
if direction == Qt.Orientation.Vertical:
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
self.setFixedWidth(thickness)
else:
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setFixedHeight(thickness)
但是,(据我所知)没有办法从 qss 文件设置“厚度”属性。我当然可以从
Line
创建子类: HLine
和 VLine
,然后分别使用高度和宽度属性,设置厚度。然而,这并没有改变这样一个事实:似乎不可能从 qss 文件设置自定义样式属性,例如厚度。如果不可能,实现这一目标的首选方法是什么?
首先,为了设置自定义属性,这些属性必须是“实际”属性。初始化参数是不够的,因为属性可能会在运行时更改,或者由其他方面引起(例如样式,这就是您的情况)。 Qt 确实允许设置属性。请参阅
样式表语法文档从 4.3 及以上版本开始,任何可设计的 Q_PROPERTY 都可以使用 qproperty- 语法设置。单独使用
property
装饰器是不够的,因为 Qt 不了解“底层”Python 绑定。所有可能由样式表设置的自定义属性都必须创建为实际的
Qt 属性,这是通过使用 QtCore 模块中的
pyqtProperty
(PyQt) 和 Property
(PySide) 装饰器来创建的。请注意,最好考虑将 QObject 子类的自定义属性声明为实际的 Qt 属性,因为这使它们在 Qt 上下文中更加一致。class Line(QWidget):
_thickness = 0
_direction = Qt.Orientation.Horizontal
def __init__(self, direction=Qt.Orientation.Horizontal, thickness=1, parent=None, **kwargs):
super().__init__(parent, direction=direction, thickness=thickness, **kwargs)
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
@pyqtProperty(Qt.Orientation)
def direction(self):
return self._direction
@direction.setter
def direction(self, direction):
if self._direction != direction:
self._direction = direction
if direction == Qt.Orientation.Horizontal:
self.setSizePolicy(
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
else:
self.setSizePolicy(
QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
self.updateGeometry()
@pyqtProperty(int)
def thickness(self):
return self._thickness
@thickness.setter
def thickness(self, size):
if size < 1:
return
if self._thickness != size:
self._thickness = size
self.updateGeometry()
def minimumSizeHint(self):
return self.sizeHint()
def sizeHint(self):
if self._direction == Qt.Orientation.Horizontal:
return QSize(1, self._thickness)
return QSize(self._thickness, 1)
请注意,我专门更改了
__init__
语法,以便 PyQt 属性管理在初始化时直接(且正确)设置定义的属性。
通过上述内容,您可以使用基本选择器(例如,使用对象名称)并设置方向和厚度。
self.line = Line(objectName='test')
layout.addWidget(self.line)
...
self.setStyleSheet('''
Line#test {
qproperty-thickness: 2;
qproperty-direction: Vertical;
}
''')
请注意上述文档末尾的注释:
请小心使用 qproperty 语法,因为它会修改正在绘制的小部件。此外,qproperty 语法仅评估一次,即在样式对小部件进行修饰时。这意味着任何在 QPushButton:hover 等伪状态下使用它们的尝试都将不起作用。
这不仅对于所解释的内容很重要,而且因为它还意味着可能在样式表中的其他位置用作选择器的任何属性定义可能不会导致正确的更新。
考虑以下尝试:
app = QApplication([])
app.setStyleSheet('''
Line[direction=Horizontal] {
background: orange;
qproperty-thickness: 4;
}
Line[direction=Vertical] {
background: green;
qproperty-thickness: 12;
}
Line#topSeparator {
qproperty-direction: Vertical;
}
''')
test = QWidget()
l = QGridLayout(test)
l.addWidget(QTextEdit())
l.addWidget(Line(objectName='topSeparator'), 0, 1)
l.addWidget(QTextEdit(), 0, 2)
l.addWidget(Line(objectName='midSeparator'), 1, 0, 1, 3)
l.addWidget(QTextEdit(), 2, 0, 1, 3)
test.show()
app.exec()
虽然
Line#topSeparator
选择器正确设置了垂直线的方向,但颜色和粗细不是预期的。
这是因为从样式表设置 Qt 属性不会使小部件再次评估其当前状态,因此Line[direction=Horizontal]
实际上被忽略。
解决此问题的方法是强制执行样式重新抛光,同时小心避免在同一更改中进行任何进一步的重新抛光:由于样式可能会尝试更改导致更改的属性,因此结果可能是对风格取消/抛光,甚至在某些条件下无限递归。
class Line(QWidget):
_polishRecursionGuard = False
...
@direction.setter
def direction(self, direction):
if self._direction != direction:
... # as above
self._polish()
@thickness.setter
def thickness(self, size):
if size < 1:
return
if self._thickness != size:
self._thickness = size
self.updateGeometry()
self._polish()
def _polish(self):
if not self._polishRecursionGuard:
self._polishRecursionGuard = True
self.style().unpolish(self)
self.style().polish(self)
self._polishRecursionGuard = False
Line[direction="1"]
等。