2

I've generated the SVG css code through http://www.heropatterns.com/ and I'm trying to use it as the background for my main window/Qwidget. I want the background to resize as the window grows bigger or shrinks. I tried calling Form.setStyleSheet() with the generated css being passed in as an argument, but I only get the one of the two colors(the backround color) in the pattern. What's the proper way to display a SVG as the backround of the main QWidget window and see the complete pattern? I know QSvgRenderer exists, however, I'm not sure once I create the QSvgRenderer object where I go from there to make the SVG a resizable background. I was told to use background-repeat: repeat; property in the style sheet, however, that didn't change anything.

Here's the Minimal, Complete, and Verifiable example I wrote:

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.horizontalLayout_2.addLayout(self.horizontalLayout)
        self.retranslateUi(Form)
        Form.setStyleSheet("""background-repeat: repeat; background-color: #000000;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='%23b0b0b0' fill-opacity='0.4' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");""")
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())

How the form currently Looks:

enter image description here

How the form should look:

enter image description here

XML representation of SVG pattern

<svg xmlns="http://www.w3.org/2000/svg" width="28" height="49" viewBox="0 0 28 49"><g fill-rule="evenodd"><g id="hexagons" fill="#000" fill-rule="nonzero"><path d="M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z"/></g></g></svg>

Example of button background being changed:

from PySide2 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.horizontalLayout_2.addLayout(self.horizontalLayout)

        self.Start_Stop_button = QtWidgets.QPushButton(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.Start_Stop_button.sizePolicy().hasHeightForWidth())
        self.Start_Stop_button.setSizePolicy(sizePolicy)
        self.Start_Stop_button.setMinimumSize(QtCore.QSize(0, 0))
        self.Start_Stop_button.setBaseSize(QtCore.QSize(0, 0))
        self.Start_Stop_button.setIconSize(QtCore.QSize(16, 16))
        self.Start_Stop_button.setFlat(False)
        self.Start_Stop_button.setObjectName("Start_Stop_button")
        contents = b"<svg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'><g fill-rule='evenodd'><g id='hexagons' fill='#b0b0b0' fill-opacity='0.4' fill-rule='nonzero'><path d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/></g></g></svg>"
        file = QtCore.QTemporaryFile(Form)
        if file.open():
            file.write(contents)
            file.flush()
            Form.setStyleSheet("""background-color: #000000;
                                  background-image: url(%s);""" % file.fileName())

        #Form.show()
        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
        self.Start_Stop_button.setText(QtWidgets.QApplication.translate("Form", "Start", None, -1))

class Widget(QtWidgets.QWidget, Ui_Form):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.setupUi(self)

    def paintEvent(self, event):
        opt = QtWidgets.QStyleOption()
        opt.init(self)
        painter = QtGui.QPainter(self)
        self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, self)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())
Mrcitrusboots
  • 317
  • 3
  • 14

1 Answers1

2

QSS does not support this type of url, a workaround is to save the content in a temporary file that is deleted when the application is closed, for this we use QTemporaryFile.

On the other the url has the following format: data:image/svg+xml,<CONTENT>, that is the content you should use.

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.horizontalLayout_2.addLayout(self.horizontalLayout)
        self.retranslateUi(Form)

        contents = b"<svg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'><g fill-rule='evenodd'><g id='hexagons' fill='#b0b0b0' fill-opacity='0.4' fill-rule='nonzero'><path d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/></g></g></svg>"
        file = QtCore.QTemporaryFile(Form)
        if file.open():
            file.write(contents)
            file.flush()
            Form.setStyleSheet("""background-color: #000000;
                                  background-image: url(%s);""" % file.fileName())
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())

enter image description here


It seems that PySide2 is a bit special, and it looks more like Qt since this problem was waiting for it in C ++ but not in Python. For a widget to support QSS should be implemented paintEvent using QStyle but for this we must create the class widget since the class Ui_Form is not a widget, it is just a class that serves to fill the widget. Below I show the workable code.

from PySide2 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.horizontalLayout_2.addLayout(self.horizontalLayout)
        self.retranslateUi(Form)

        contents = b"<svg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'><g fill-rule='evenodd'><g id='hexagons' fill='#b0b0b0' fill-opacity='0.4' fill-rule='nonzero'><path d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/></g></g></svg>"
        file = QtCore.QTemporaryFile(Form)
        if file.open():
            file.write(contents)
            file.flush()
            Form.setStyleSheet("""background-color: #000000;
                                  background-image: url(%s);""" % file.fileName())

        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))


class Widget(QtWidgets.QWidget, Ui_Form):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.setupUi(self)

    def paintEvent(self, event):
        opt = QtWidgets.QStyleOption()
        opt.init(self)
        painter = QtGui.QPainter(self)
        self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, self)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

Plus:

QSS has rules to apply the styles that are indicated in the following links:

In your case so that it only applies to the current widget you must use the objectName:

enter image description here

Form.setStyleSheet("""QWidget#Form{background-color: #000000;
                      background-image: url(%s);}""" % file.fileName())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I used the exact same code you provided, but I'm still only getting the background, no foreground. I'm using Pyside2 instead of PyQt5, but I don't see how that would make a difference, since it never has before. I've updated the original post with the raw XML representation of the SVG. Is there perhaps a way to display the SVG using the raw provided XML that might work with Pyside2 and be less of a workaround? – Mrcitrusboots Jul 17 '18 at 08:28
  • @Mrcitrusboots okay, I get the same thing that you point out, so you should use the tags in an intelligent way, do not assume that everything that works in PyQt5 works in PySide2, please use the proper tag. – eyllanesc Jul 17 '18 at 08:34
  • Would you mind breaking down this line: `self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, self)` ? – Mrcitrusboots Jul 17 '18 at 20:55
  • @Mrcitrusboots why? – eyllanesc Jul 17 '18 at 20:58
  • I'm not sure why each argument is needed and I think it's best to understand the logic being used in your own program. – Mrcitrusboots Jul 17 '18 at 21:02
  • @Mrcitrusboots go to details on this question does not make sense, if you want an explanation first read about the QStyle class – eyllanesc Jul 17 '18 at 21:04
  • I also noticed that the solution doesn't just apply the svg background to the Main window QWidget, but to any Qwidget. Therefore, if I have QPushButtons, QListWidget, or whatever on top of the main window, the svg appears as the background of those widgets as well. Is there a way to just specify the main background Qwidget? – Mrcitrusboots Jul 18 '18 at 04:40
  • I updated the bottom of the original post with the same exact code you wrote, but with a simple `QPushButton` on the main window. It's not quite clear why this is happening since since it seems like the overloaded `paintEvent` would only affect the instance of the class `Widget`, of which there is only 1. – Mrcitrusboots Jul 18 '18 at 05:35
  • Aah, it turns out you need to [specify the object and class instance](https://stackoverflow.com/questions/12116413/how-do-i-style-a-qt-widget-not-its-children-with-stylesheets) like you do with normal CSS – Mrcitrusboots Jul 18 '18 at 10:51
  • @Mrcitrusboots Your question is different from the original, I would not usually answer it because it denatures the original question, but I see that you have found the solution and I am glad, I have updated my answer to add references. – eyllanesc Jul 18 '18 at 14:29