Несмотря на то, что уже предоставленное решение работает отлично, оно не является полностью общесоюзным, особенно если вы хотите контролировать внешний вид кнопок диалогов.
Даже если вы применяете таблицу стилей к приложению, вам потребуется чтобы гарантировать правильную установку селекторов в родителях: в другом ответе, например, установка свойства фона без селекторов очистит наследование таблицы стилей приложения от детей.
Предполагается, что таблица стилей установлена для приложения, и все Остальные наборы стилей тщательно написаны, проблема связана с диалогом windows.
Если диалоговое окно создается вручную, можно установить альтернативные цвета, указав таблицу стилей для каждой кнопки, для которой требуется альтернативный цвет, но это невозможно для тех, которые созданы с функциями stati c.
В этом случае единственная возможность - использовать QProxyStyle. Ниже приведена возможная реализация, которая также позволяет устанавливать собственные цвета и шрифты и автоматически устанавливает альтернативный цвет для «отрицательных» ролей полей диалоговых кнопок (отмена, игнорирование и т. Д. c).
В этом пример Я только что применил стиль к приложению, но окно сообщения создается с использованием функции information()
stati c. Кнопка «Альтернатива» устанавливается вручную с помощью пользовательского свойства: 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)