虽然已经提供的解决方案可以完美运行,但并不完全适用于应用程序,特别是如果您想控制对话框按钮的外观。
即使您将样式表应用于应用程序,您也需要确保正确设置父项中的选择器:例如,在另一个答案中,设置没有选择器的背景属性将清除子项的应用程序样式表继承。
假设样式表设置为应用程序并且所有其他样式表设置都经过仔细编写,那么问题就出在对话框窗口上。
如果对话框是手动创建的,则可以通过指定样式表来为您想要替换颜色的每个按钮设置替换颜色,但这对于使用静态函数创建的那些是不可能的。
在这种情况下,唯一的可能是使用 QProxyStyle。以下是一个可能的实现,它还允许设置自定义颜色和字体,并自动为对话框按钮框的“负面”角色(取消、忽略等)设置替代颜色。
在此示例中,我只是将样式应用于应用程序,但消息框是使用information()
静态函数创建的。“备用”按钮是使用自定义属性手动设置的:button.setProperty('alternateColor', True)
.
class ColorButtonStyle(QtWidgets.QProxyStyle):
def __init__(self, *args, **kwargs):
if isinstance(kwargs.get('buttonFont'), QtGui.QFont):
self._buttonFont = kwargs.pop('buttonFont')
else:
self._buttonFont = QtWidgets.QApplication.font()
self._buttonFont.setPointSize(20)
super().__init__(*args, **kwargs)
self._buttonFontMetrics = QtGui.QFontMetrics(self._buttonFont)
self._defaultButtonColor = QtGui.QColor(109, 180, 66)
self._defaultTextColor = QtGui.QColor(QtCore.Qt.white)
self._alternateButtonColor = QtGui.QColor(240, 74, 80)
self._alternateTextColor = None
self._alternateRoles = set((
QtWidgets.QDialogButtonBox.RejectRole,
QtWidgets.QDialogButtonBox.DestructiveRole,
QtWidgets.QDialogButtonBox.NoRole,
))
def _polishApp(self):
self.polish(QtWidgets.QApplication.instance())
def buttonFont(self):
return QtGui.QFont(self._buttonFont)
@QtCore.pyqtSlot(QtGui.QFont)
def setButtonFont(self, font):
if not isinstance(font, QtGui.QFont) or font == self._buttonFont:
return
self._buttonFont = font
self._buttonFontMetrics = QtGui.QFontMetrics(self._buttonFont)
self._polishApp()
def defaultButtonColor(self):
return QtGui.QColor(self._defaultButtonColor)
@QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
@QtCore.pyqtSlot(QtGui.QColor)
def setDefaultButtonColor(self, color):
if isinstance(color, QtCore.Qt.GlobalColor):
color = QtGui.QColor(color)
elif not isinstance(color, QtGui.QColor):
return
self._defaultButtonColor = color
self._polishApp()
def alternateButtonColor(self):
return QtGui.QColor(self._alternateButtonColor)
@QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
@QtCore.pyqtSlot(QtGui.QColor)
def setAlternateButtonColor(self, color):
if isinstance(color, QtCore.Qt.GlobalColor):
color = QtGui.QColor(color)
elif not isinstance(color, QtGui.QColor):
return
self._alternateButtonColor = color
self._polishApp()
def alternateRoles(self):
return self._alternateRoles
def setAlternateRoles(self, roles):
newRoles = set()
for role in roles:
if isinstance(role, QtWidgets.QDialogButtonBox.ButtonRole):
newRoles.add(role)
if newRoles != self._alternateRoles:
self._alternateRoles = newRoles
self._polishApp()
def setAlternateRole(self, role, activate=True):
if isinstance(role, QtWidgets.QDialogButtonBox.ButtonRole):
if activate and role in self._alternateRoles:
self._alternateRoles.add(role)
self._polishApp()
elif not activate and role not in self._alternateRoles:
self._alternateRoles.remove(role)
self._polishApp()
def defaultTextColor(self):
return QtGui.QColor(self._defaultTextColor)
@QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
@QtCore.pyqtSlot(QtGui.QColor)
def setDefaultTextColor(self, color):
if isinstance(color, QtCore.Qt.GlobalColor):
color = QtGui.QColor(color)
elif not isinstance(color, QtGui.QColor):
return
self._defaultTextColor = color
self._polishApp()
def alternateTextColor(self):
return QtGui.QColor(self._alternateTextColor or self._defaultTextColor)
@QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
@QtCore.pyqtSlot(QtGui.QColor)
def setAlternateTextColor(self, color):
if isinstance(color, QtCore.Qt.GlobalColor):
color = QtGui.QColor(color)
elif not isinstance(color, QtGui.QColor):
return
self._alternateTextColor = color
self._polishApp()
def drawControl(self, element, option, painter, widget):
if element == self.CE_PushButton:
isAlternate = False
if widget and isinstance(widget.parent(), QtWidgets.QDialogButtonBox):
role = widget.parent().buttonRole(widget)
if role in self._alternateRoles:
isAlternate = True
elif widget.property('alternateColor'):
isAlternate = True
if isAlternate:
color = self.alternateButtonColor()
textColor = self.alternateTextColor()
else:
color = self.defaultButtonColor()
textColor = self.defaultTextColor()
if not option.state & self.State_Enabled:
color.setAlpha(color.alpha() * .75)
textColor.setAlpha(textColor.alpha() * .75)
# switch the existing palette with a new one created from it;
# this shouldn't be necessary, but better safe than sorry
oldPalette = option.palette
palette = QtGui.QPalette(oldPalette)
palette.setColor(palette.ButtonText, textColor)
# some styles use WindowText for flat buttons
palette.setColor(palette.WindowText, textColor)
option.palette = palette
# colors that are almost black are not very affected by "lighter"
if color.value() < 32:
lightColor = QtGui.QColor(48, 48, 48, color.alpha())
else:
lightColor = color.lighter(115)
darkColor = color.darker()
if option.state & self.State_MouseOver:
# colors that are almost black are not very affected by "lighter"
bgColor = lightColor
lighterColor = lightColor.lighter(115)
darkerColor = darkColor.darker(115)
else:
bgColor = color
lighterColor = lightColor
darkerColor = darkColor
if option.state & self.State_Raised and not option.state & self.State_On:
topLeftPen = QtGui.QPen(lighterColor)
bottomRightPen = QtGui.QPen(darkerColor)
elif option.state & (self.State_On | self.State_Sunken):
if option.state & self.State_On:
bgColor = bgColor.darker()
else:
bgColor = bgColor.darker(125)
topLeftPen = QtGui.QPen(darkColor)
bottomRightPen = QtGui.QPen(lighterColor)
else:
topLeftPen = bottomRightPen = QtGui.QPen(bgColor)
painter.save()
painter.setRenderHints(painter.Antialiasing)
painter.translate(.5, .5)
rect = option.rect.adjusted(0, 0, -1, -1)
painter.setBrush(bgColor)
painter.setPen(QtCore.Qt.NoPen)
painter.drawRoundedRect(rect, 2, 2)
if topLeftPen != bottomRightPen:
roundRect = QtCore.QRectF(0, 0, 4, 4)
painter.setBrush(QtCore.Qt.NoBrush)
# the top and left borders
tlPath = QtGui.QPainterPath()
tlPath.arcMoveTo(roundRect.translated(0, rect.height() - 4), 225)
tlPath.arcTo(roundRect.translated(0, rect.height() - 4), 225, -45)
tlPath.arcTo(roundRect, 180, -90)
tlPath.arcTo(roundRect.translated(rect.width() - 4, 0), 90, -45)
painter.setPen(topLeftPen)
painter.drawPath(tlPath)
# the bottom and right borders
brPath = QtGui.QPainterPath(tlPath.currentPosition())
brPath.arcTo(roundRect.translated(rect.width() - 4, 0), 45, -45)
brPath.arcTo(
roundRect.translated(rect.width() - 4, rect.height() - 4), 0, -90)
brPath.arcTo(
roundRect.translated(0, rect.height() - 4), 270, -45)
painter.setPen(bottomRightPen)
painter.drawPath(brPath)
if option.state & self.State_HasFocus:
focusColor = QtGui.QColor(textColor).darker()
focusColor.setAlpha(focusColor.alpha() * .75)
painter.setPen(focusColor)
painter.setBrush(QtCore.Qt.NoBrush)
painter.drawRoundedRect(rect.adjusted(2, 2, -2, -2), 2, 2)
painter.setFont(self._buttonFont)
oldMetrics = option.fontMetrics
option.fontMetrics = self._buttonFontMetrics
self.drawControl(self.CE_PushButtonLabel, option, painter, widget)
painter.restore()
# restore the original font metrics and palette
option.fontMetrics = oldMetrics
option.palette = oldPalette
return
super().drawControl(element, option, painter, widget)
def sizeFromContents(self, contentsType, option, size, widget=None):
if contentsType == self.CT_PushButton:
if option.text:
textSize = option.fontMetrics.size(
QtCore.Qt.TextShowMnemonic, option.text)
baseWidth = size.width() - textSize.width()
baseHeight = size.height() - textSize.height()
text = option.text
else:
baseWidth = size.width()
baseHeight = size.height()
text = 'XXXX' if not option.icon else ''
buttonTextSize = self._buttonFontMetrics.size(
QtCore.Qt.TextShowMnemonic, text)
if not widget or widget.font() != QtWidgets.QApplication.font():
buttonTextSize = buttonTextSize.expandedTo(
QtWidgets.QApplication.fontMetrics().size(
QtCore.Qt.TextShowMnemonic, text))
margin = self.pixelMetric(self.PM_ButtonMargin, option, widget)
newSize = QtCore.QSize(
buttonTextSize.width() + baseWidth + margin * 2,
buttonTextSize.height() + baseHeight + margin)
return newSize.expandedTo(
super().sizeFromContents(contentsType, option, size, widget))
return super().sizeFromContents(contentsType, option, size, widget)
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ColorButtonStyle())
# ...
从这里开始,您还可以添加其他属性以更好地控制样式,例如圆角矩形的半径(只需在边框图中将每个“4”替换为您的变量,并使用一半绘制背景):
painter.drawRoundedRect(rect, self._radius / 2, self._radius / 2)
if topLeftPen != bottomRightPen:
roundRect = QtCore.QRectF(0, 0, self._radius, self._radius)
painter.setBrush(QtCore.Qt.NoBrush)
# the top and left borders
tlPath = QtGui.QPainterPath()
tlPath.arcMoveTo(roundRect.translated(
0, rect.height() - self._radius), 225)
tlPath.arcTo(roundRect.translated(
0, rect.height() - self._radius), 225, -45)