Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

[PySide6] GraphicsView not showing - I'm evidently doing it wrong!



  • Hello!

    I'm trying to set a QGraphicsView's background using a SVG image. I've thus extended it overwriting the drawBackground method (see PoC at the bottom).

    I currently create a scene, add a "stupid" rectangle, create a GraphicsView using the scene in the constructor and add the view to the layout of the application. The result is the following:

    6c511357-acef-4ae0-9566-19f151766ba9-immagine.png

    Which is... not what I wanted :D (I'd expect a window with a GraphicsScene with at least a rectangle with a custom view's background). What am I doing wrong?

    I've prepared a one-file PoC you can copy and try out.

    In the PoC I've created a click event listener to print out what's being clicked, and when you click on the darker area, it says "QObject" (because there is no object name, see code). I'd expect the MyGraphicsView to be logged.

    Thanks :)

    import sys
    from typing import Union
    from PySide6 import QtCore, QtGui
    from PySide6.QtSvg import QSvgRenderer
    from PySide6.QtWidgets import QApplication, QGraphicsScene, QGraphicsView, QMainWindow, QVBoxLayout, QWidget
    
    
    class MyGraphicsView(QGraphicsView):
        def drawBackground(self, painter: QtGui.QPainter, rect: Union[QtCore.QRectF, QtCore.QRect]) -> None:
            svg_data = '''
    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
        <rect width="100%" height="100%" fill="#434343" fill-opacity="1" />
        <g fill-rule="evenodd">
            <g fill="#979797" fill-opacity="1">
                <path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z" />
                <path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z" />
            </g>
        </g>
    </svg>
            '''.encode('utf8')
    
            renderer = QSvgRenderer(contents=svg_data, parent=self)
    
            renderer.render(painter, rect)
    
    
    class WhatsUnderMouse(QtCore.QObject):
        def eventFilter(self, watched: QtCore.QObject, event: QtCore.QEvent) -> bool:
            if event.type() == QtCore.QEvent.MouseButtonPress:
                click_name = watched.objectName().strip() or type(watched)
                print(f'Clicked: {click_name}')
    
            return super().eventFilter(watched, event)
    
    
    if __name__ == '__main__':
        app = QApplication()
        app.setObjectName('The application')
        mouseEventFilter = WhatsUnderMouse()
        app.installEventFilter(mouseEventFilter)
    
        main = QMainWindow()
        main.setObjectName('Main window')
    
        central_widget = QWidget()
        layout = QVBoxLayout()
        central_widget.setLayout(layout)
        main.setCentralWidget(central_widget)
    
        rect = QtCore.QRect()
        rect.setWidth(400)
        rect.setHeight(400)
        main.setGeometry(rect)
    
        scene = QGraphicsScene()
        scene.setObjectName('The scene')
        rect = QtCore.QRectF(10, 10, 100, 100)
        scene.addRect(rect)
        view = MyGraphicsView(main)
        view.setObjectName('The graphics view')
        layout.addWidget(view)
    
        main.show()
    
        sys.exit(app.exec())
    
    

  • Lifetime Qt Champion

    You could make a brush out of your SVG image.

    See QGraphicsView::backgroundBrush property


  • Lifetime Qt Champion

    Hi,

    I don't have a machine a hand so here are some suggestions:

    • Nuke all QMainWindow related code, you don't need it. Just show the graphics view.
    • You should check that your renderer is valid. You might be drawing a black rectangle on a black background.

    Once you have ensured that the renderer is valid and if you are still getting a black background, you can just call the base class implementation of drawBackground to check that it is working properly.



  • Hello @SGaist ,

    thanks for replying :)

    I've simplified as much as possible down to this:

    import sys
    from PySide6 import QtCore
    from PySide6.QtWidgets import QApplication, QGraphicsScene, QGraphicsView
    
    if __name__ == '__main__':
        app = QApplication()
    
        scene = QGraphicsScene()
        rect = QtCore.QRectF(10, 10, 100, 100)
        scene.addRect(rect)
        view = QGraphicsView()
    
        view.show()
    
        sys.exit(app.exec())
    
    

    The resulting view is still empty:
    7a8ffeb2-8336-45bf-951d-ba0460dc38de-immagine.png

    So, supposing that the default renderer works as expected, I'm evidently missing something else :(

    Thank you very much for the support :)



  • Ok, got some hints reading the code again:

    import sys
    from typing import Union
    from PySide6 import QtCore, QtGui
    from PySide6.QtSvg import QSvgRenderer
    from PySide6.QtWidgets import QApplication, QGraphicsScene, QGraphicsView
    
    
    class MyGraphicsView(QGraphicsView):
        svg_renderer: QSvgRenderer
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            svg_data = '''
    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
        <rect width="100%" height="100%" fill="#434343" fill-opacity="1" />
        <g fill-rule="evenodd">
            <g fill="#979797" fill-opacity="1">
                <path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z" />
                <path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z" />
            </g>
        </g>
    </svg>
            '''.encode('utf8')
            background_content = QtCore.QByteArray(svg_data)
            self.svg_renderer = QSvgRenderer(background_content)
            self.svg_renderer.setAspectRatioMode(
                QtCore.Qt.AspectRatioMode.KeepAspectRatio)
    
        def drawBackground(self, painter: QtGui.QPainter, rect: Union[QtCore.QRectF, QtCore.QRect]) -> None:
    
            self.svg_renderer.render(painter, rect)
    
    
    class WhatsUnderMouse(QtCore.QObject):
        def eventFilter(self, watched: QtCore.QObject, event: QtCore.QEvent) -> bool:
            if event.type() == QtCore.QEvent.MouseButtonPress:
                click_name = watched.objectName().strip() or type(watched)
                print(f'Clicked: {click_name}')
    
            return super().eventFilter(watched, event)
    
    
    if __name__ == '__main__':
        app = QApplication()
        app.setObjectName('The application')
        mouseEventFilter = WhatsUnderMouse()
        app.installEventFilter(mouseEventFilter)
    
        scene = QGraphicsScene()
        scene.setObjectName('The scene')
        pen = QtGui.QPen(QtGui.Qt.GlobalColor.yellow)
        brush = QtGui.QBrush(QtGui.Qt.GlobalColor.red)
        scene.addRect(10, 10, 100, 100, pen, brush)
        view = MyGraphicsView(scene)
        view.setObjectName('The graphics view')
    
        view.show()
    
        sys.exit(app.exec())
    
    

    Now it "works" just fine. The only thing missing now is that the drawBackground won't repeat the SVG pattern, but will render it once only, enlarging the graphic to match the size of the graphic view.



  • Ok, kind of got it working.

    This is the SVG background graphic view:

    class MyGraphicsView(QGraphicsView):
        svg_renderer: QSvgRenderer
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            svg_data = '''
    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
        <rect width="100%" height="100%" fill="#434343" fill-opacity="1" />
        <g fill-rule="evenodd">
            <g fill="#979797" fill-opacity="1">
                <path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z" />
                <path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z" />
            </g>
        </g>
    </svg>
            '''.encode('utf8')
            background_content = QtCore.QByteArray(svg_data)
            self.svg_renderer = QSvgRenderer(background_content)
            self.svg_renderer.setAspectRatioMode(
                QtCore.Qt.AspectRatioMode.KeepAspectRatio)
    
        def drawBackground(self, painter: QtGui.QPainter, rect: Union[QtCore.QRectF, QtCore.QRect]) -> None:
            pattern_size = self.svg_renderer.defaultSize()
            p_width = pattern_size.width()
            p_height = pattern_size.height()
    
            left_steps = math.ceil(rect.width() / pattern_size.width())
            top_steps = math.ceil(rect.height() / pattern_size.height())
    
            for w in range(0, left_steps):
                for h in range(0, top_steps):
                    # print(w, h)
                    left = w * p_width
                    top = h * p_height
    
                    # print(top, left)
    
                    rect = QtCore.QRect()
                    rect.setTop(top)
                    rect.setLeft(left)
                    rect.setWidth(p_width)
                    rect.setHeight(p_height)
    
                    self.svg_renderer.render(painter, rect)
    
            self.svg_renderer.render(painter, rect)
    
    

    Last step: it seems that the scene is centered, though all the surface seems to be the QGraphicsView (clicking on the surface with the click event handler):

    ec079d3d-b869-4de4-bf9c-8ee7ccd185d0-immagine.png

    Any hint on how to let the renderer paint on all the surface, and not only from the center downwards?

    EDIT: I kinda sorted it out, but I'm unsure whether it's the correct solution (due to the cyclic complexity). This is the drawBackground overwritten method:

        def drawBackground(self, painter: QtGui.QPainter, rect: Union[QtCore.QRectF, QtCore.QRect]) -> None:
                pattern_size = self.svg_renderer.defaultSize()
                p_width = pattern_size.width()
                p_height = pattern_size.height()
        
                left_steps = math.ceil(rect.width() / pattern_size.width())
                top_steps = math.ceil(rect.height() / pattern_size.height())
        
                for w in range(0, left_steps):
                    for h in range(0, top_steps):
                        left = w * p_width
                        top = h * p_height
                        for top_multiplier in [1, -1]:
                            for left_multiplier in [1, -1]:
                                rect = QtCore.QRect()
                                rect.setTop(top_multiplier * top)
                                rect.setLeft(left_multiplier * left)
                                rect.setWidth(p_width)
                                rect.setHeight(p_height)
        
                                self.svg_renderer.render(painter, rect)
    
    

  • Lifetime Qt Champion

    You could make a brush out of your SVG image.

    See QGraphicsView::backgroundBrush property



  • Thanks @SGaist for the hint :) I made a way better solution for what I want to achieve. The code follows for future reference :)

    class BlueprintGraphicsView(QGraphicsView):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            svg_data = Path(__file__).parent.joinpath(
                'images', 'background', 'graph-paper.svg').read_bytes()
            background_content = QtCore.QByteArray(svg_data)
            svg_renderer = QSvgRenderer(background_content)
            svg_renderer.setAspectRatioMode(
                QtCore.Qt.AspectRatioMode.KeepAspectRatio)
    
            brush_pixmap = QtGui.QPixmap(svg_renderer.defaultSize())
    
            brush_painter = QtGui.QPainter(brush_pixmap)
            svg_renderer.render(brush_painter, brush_pixmap.rect())
            brush_painter.end()
    
            self.setBackgroundBrush(QtGui.QBrush(brush_pixmap))
    
    

  • Lifetime Qt Champion

    Great !

    Since you have it working now, please mark the thread as solved using the "Topic Tools" button or the three dotted menu beside the answer you deem correct so that other forum users may know a solution has been found :-)