Использование PyQt5/Pyside2 для установки повторяющегося шаблона SVG в качестве фона главного окна/Qwidget

Я сгенерировал CSS-код SVG через http://www.heropatterns.com/ и пытаюсь использовать его в качестве фона для моего главного окна/Qwidget. Я хочу, чтобы размер фона изменялся по мере увеличения или уменьшения окна. Я попытался вызвать Form.setStyleSheet() с передачей сгенерированного css в качестве аргумента, но я получаю только один из двух цветов (фоновый цвет) в шаблоне. Каков правильный способ отображения SVG в качестве фона главного окна QWidget и просмотра полного шаблона? Я знаю, что QSvgRenderer существует, однако я не уверен, создайте объект QSvgRenderer, откуда я иду, чтобы сделать SVG фоном с изменяемым размером. Мне сказали использовать свойство background-repeat: repeat; в таблице стилей, однако это ничего не изменило.

Вот минимальный, полный и проверяемый пример, который я написал:

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_())

Как сейчас выглядит форма:

введите здесь описание изображения

Как должна выглядеть форма:

введите здесь описание изображения

XML-представление шаблона SVG

<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>

Пример изменения фона кнопки:

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_())

person Mrcitrusboots    schedule 17.07.2018    source источник


Ответы (1)


QSS не поддерживает этот тип url, обходным путем является сохранение содержимого во временном файле, который удаляется при закрытии приложения, для этого используем QTemporaryFile.

С другой стороны, URL-адрес имеет следующий формат: data:image/svg+xml,<CONTENT>, это контент, который вы должны использовать.

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_())

введите здесь описание изображения


Кажется, что PySide2 немного особенный, и он больше похож на Qt, так как эта проблема ждала его в C++, а не в Python. Чтобы виджет поддерживал QSS, необходимо реализовать paintEvent с помощью QStyle, но для этого мы должны создать класс widget, так как класс Ui_Form не является виджетом, это просто класс, который служит для заполнения виджета. Ниже я показываю работающий код.

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_())

Плюс:

В QSS есть правила применения стилей, указанные в следующих ссылках:

В вашем случае, чтобы он применялся только к текущему виджету, вы должны использовать objectName:

введите здесь описание изображения

Form.setStyleSheet("""QWidget#Form{background-color: #000000;
                      background-image: url(%s);}""" % file.fileName())
person eyllanesc    schedule 17.07.2018
comment
Я использовал тот же самый код, который вы предоставили, но я все еще получаю только фон, а не передний план. Я использую Pyside2 вместо PyQt5, но не вижу, как это изменит ситуацию, поскольку раньше этого не было. Я обновил исходный пост необработанным XML-представлением SVG. Возможно, есть способ отобразить SVG с использованием необработанного предоставленного XML, который мог бы работать с Pyside2 и не был бы обходным путем? - person Mrcitrusboots; 17.07.2018
comment
@Mrcitrusboots хорошо, я понимаю то же самое, на что вы указываете, поэтому вы должны использовать теги разумно, не думайте, что все, что работает в PyQt5, работает в PySide2, пожалуйста, используйте правильный тег. - person eyllanesc; 17.07.2018
comment
Не могли бы вы разбить эту строку: self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, self) ? - person Mrcitrusboots; 17.07.2018
comment
@Mrcitrusboots почему? - person eyllanesc; 17.07.2018
comment
Я не уверен, зачем нужен каждый аргумент, и я думаю, что лучше понять логику, используемую в вашей собственной программе. - person Mrcitrusboots; 18.07.2018
comment
@Mrcitrusboots переходить к подробностям по этому вопросу не имеет смысла, если вы хотите сначала прочитать объяснение о классе QStyle - person eyllanesc; 18.07.2018
comment
Я также заметил, что решение применяет фон svg не только к QWidget главного окна, но и к любому Qwidget. Поэтому, если у меня есть QPushButtons, QListWidget или что-то еще поверх главного окна, svg также отображается как фон этих виджетов. Есть ли способ просто указать основной фон Qwidget? - person Mrcitrusboots; 18.07.2018
comment
Я обновил нижнюю часть исходного сообщения тем же кодом, который вы написали, но с простым QPushButton в главном окне. Не совсем понятно, почему это происходит, поскольку кажется, что перегруженный paintEvent повлияет только на экземпляр класса Widget, которого всего 1. - person Mrcitrusboots; 18.07.2018
comment
Ааа, оказывается, нужно укажите экземпляр объекта и класса, как вы делаете это с обычным CSS - person Mrcitrusboots; 18.07.2018
comment
@Mrcitrusboots Ваш вопрос отличается от исходного, я обычно не отвечаю на него, потому что он искажает исходный вопрос, но я вижу, что вы нашли решение, и я рад, что обновил свой ответ, чтобы добавить ссылки. - person eyllanesc; 18.07.2018