Is there a way to change the stylesheet of just OK button of all the QMessageBox?

Viewed 722

I would like to have the OK button of all the QMessageBox of my GUI (which is quite complex and so there are a lot of them) set a different color with respect to the other buttons (Cancel, No etc.).

I don't want to set its color every time I create a new QMessageBox but I would like to set it through the stylesheet of my application once and for all.

I tried different options but none of them worked:

 - QMessageBox#okButton {background-color:blue}
 - QMessageBox::okButton {...}
 - QMessageBox:okButton {...}
 - QMessageBox#ok-button {...}
 - QMessageBox QPushButton#okButton {...}

and others...

Is there a way or do I have to give up?

2 Answers

A possible solution is to set the objectName() to be a selector for each button and for this you can use the notify() method of the QApplication:

from PySide2.QtCore import QEvent
from PySide2.QtWidgets import *


class Application(QApplication):
    def notify(self, receiver, event):
        if isinstance(receiver, QMessageBox) and event.type() == QEvent.Show:
            for button in receiver.buttons():
                sb = receiver.standardButton(button)
                if not button.objectName():
                    button.setObjectName(sb.name.decode())
                    button.style().unpolish(button)
                    button.style().polish(button)
        return super().notify(receiver, event)


def main():
    app = Application()
    app.setStyle("fusion")

    app.setStyleSheet(
        """
    QPushButton#Ok { background-color: green }
    QPushButton#Cancel { background-color: red }
    """
    )

    QMessageBox.critical(
        None, "Title", "text", buttons=QMessageBox.Ok | QMessageBox.Cancel
    )

    msgBox = QMessageBox()
    msgBox.setText("The document has been modified.")
    msgBox.setInformativeText("Do you want to save your changes?")
    msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
    msgBox.exec_()


if __name__ == "__main__":
    main()

Unfortunately, there's no direct solution, since QDialogButtonBox (which is what QMessageBox uses for its buttons) doesn't provide selectors for button roles.

For staticly created messagebox, the only way is to use the [text="button text"] selector, but that would just be a guess, which could change depending on the localization and the style (which could set a mnemonic, and you cannot even know what that would be for sure). Also, this requires setting the stylesheet for the QApplication.

A possible application wide stylesheet for these situations would look like this:

app.setStyleSheet('''
    QDialogButtonBox > QPushButton[text="&OK"] {
        background-color: orange;
    }
    QDialogButtonBox > QPushButton[text="&Cancel"] {
        background-color: green;
    }
''')

Note that I used the Parent > Class selector in order to ensure that only buttons of QDialogButtonBox are styled with those rules, otherwise any "Ok" button would be orange, etc.

Nonetheless, in the case above on my computer it only works for the Ok button, since my localization (Italian) uses "&Annulla" for the other.

On the other hand, if you're creating QMessageBox instances, there's more freedom and flexibility using selectors based on object names.

The only issue is that since the object name is set after the creation, the stylesheet is not applied, so the buttons must be "unpolished" after instantiation.

A simple subclass could provide a standard interface without complicating things too much:

colored buttons

class ColoredMessageBox(QtWidgets.QMessageBox):
    StandardNames = {
        QtWidgets.QMessageBox.Ok: 'Ok', 
        QtWidgets.QMessageBox.Cancel: 'Cancel', 
        QtWidgets.QMessageBox.Save: 'Save', 
        # ...
    }
    def exec_(self):
        for button in self.buttons():
            objName = self.StandardNames.get(self.standardButton(button))
            if objName:
                button.setObjectName(objName)
            self.style().unpolish(button)
        return super().exec_()

import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet('''
    QMessageBox > QDialogButtonBox > QPushButton#Ok {
        background-color: orange;
    }
    QMessageBox > QDialogButtonBox > QPushButton#Cancel {
        background-color: blue;
    }
''')
w = ColoredMessageBox(QtWidgets.QMessageBox.Information, 'Hello', 'How are you?', 
    QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
w.exec_()
Related