PyQt5 Linux Mint Cinnamon 原生 'delete'、'apply' 按钮

PyQt5 Linux Mint Cinnamon native 'delete', 'apply' buttons

我正在尝试在 Linux mint 19(肉桂色)上创建一个 pyqt5 应用程序。 我有一个 'delete' 和 'apply' 按钮,但我想给它们更相关的外观。喜欢这张照片:

原生 Linux 薄荷纽扣

更多

它是 linux mint 上具有应用或删除角色的按钮的原生外观,我想在我的应用程序中创建这样的按钮,但我还没有找到这样做的方法。

似乎 windows 和 mac 有 qtmacextrasqtwinextras 模块来处理这些东西。 Linux 有某种 qtx11extras,但该模块不提供此类功能。

您可以根据自己的喜好使用样式sheet进行设计。

这是一个独立的例子。当然,您必须根据您喜欢的颜色和尺寸对其进行调整。但这应该已经提供了一个起点。

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout

GREEN_BUTTON_STYLE = """
    QPushButton {
        border: 4px solid black;
        border-radius: 4px;
        color: #ffffff;
        background-color: #6db442;
        font-size: 28px;
        min-height: 40px;
        min-width: 128px;
        }
    QPushButton:pressed {
        background-color: #646464;
        }
    QPushButton:flat {
        border: none;
        }
"""
RED_BUTTON_STYLE = """
    QPushButton {
        border: 4px solid black;
        border-radius: 4px;
        color: #ffffff;
        background-color: #f04a50;
        font-size: 26px;
        min-height: 40px;
        min-width: 128px;
        }
    QPushButton:pressed {
        background-color: #646464;
        }
    QPushButton:flat {
        border: none;
        }
"""


class App(QWidget):

    def __init__(self):
        super().__init__()
        self.title = 'button styles'
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(self.title)
        self.setGeometry(256, 128, 384, 256)

        button1 = QPushButton('Launch')
        button1.setStyleSheet(GREEN_BUTTON_STYLE)
        button1.setFlat(True)
        button1.clicked.connect(self.on_click)

        button2 = QPushButton('Remove')
        button2.setStyleSheet(RED_BUTTON_STYLE)
        button2.setFlat(True)
        button2.clicked.connect(self.on_click)

        layout = QHBoxLayout()
        layout.addWidget(button1, alignment=Qt.AlignHCenter)
        layout.addWidget(button2, alignment=Qt.AlignHCenter)
        self.setLayout(layout)
        self.setStyleSheet("background-color:#333333;")
        self.show()

    def on_click(self):
        print('clicked')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

输出

看起来像这样:

虽然已经提供的解决方案工作得很好,但并不是完全适用于应用程序,尤其是当您想要控制对话框按钮的外观时。

即使您将样式表应用于应用程序,您也需要确保在父项中正确设置选择器:在另一个答案中,例如,设置没有选择器的背景 属性 将清除子项的应用样式表继承。

假设样式表设置为应用程序并且所有其他样式表设置都经过仔细编写,问题来自对话框 windows。

如果对话框是手动创建的,则可以通过为每个需要备用颜色的按钮指定样式表来设置备用颜色,但这对于使用静态函数创建的按钮是不可能的。

在那种情况下,唯一的可能就是使用QProxyStyle。以下是一个可能的实现,它还允许设置自定义颜色和字体,并自动为对话框按钮框的 "negative" 角色(取消、忽略等)设置替代颜色。

在此示例中,我只是将样式应用于应用程序,但消息框是使用 information() 静态函数创建的。 "Alternate" 按钮使用自定义 属性 手动设置: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)