burntchowmein
burntchowmein

Reputation: 469

PyQt: How to stretch the width and height of a QGraphicsView and QGraphicsScene widget?

I am trying to create a grid widget that stretches to the width and height of the window. The size of the grid (currently 14x49) is subject to change but I want the overall size of the widget to stay the same.

Code:

import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLayout, 
QGridLayout, QSizePolicy, QGraphicsScene, QGraphicsView
from PyQt5.QtCore import QCoreApplication, QSize, Qt, QRectF, QPointF, QSizeF
from PyQt5.QtGui import QColor, QPen, QBrush, qRgb

class Map(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.initUI()

    def initUI(self):
        self.setMinimumSize(500, 500) # min-size of the widget
        self.columns = 14 # num of columns in grid
        self.rows = 49 # num of rows in grid

    def resizeEvent(self, event):
        # compute the square size based on the aspect ratio, assuming that the
        reference = self.width() * self.rows / self.columns
        if reference > self.height():
            # the window is larger than the aspect ratio
            # use the height as a reference (minus 1 pixel)
            self.squareSize = (self.height() - 1) / self.rows
        else:
            # the opposite
            self.squareSize = (self.width() - 1) / self.columns

    def paintEvent(self, event):
        grid = QGridLayout()
        self.sceneWithPen(grid)
        self.setLayout(grid)

    # creates the grid of squares
    def sceneWithPen(self, grid):
        scene = QGraphicsScene()
        w = QGraphicsView()
        w.setScene(scene)
        side = self.squareSize
        brush = QBrush(QColor(qRgb(255, 255, 255))) # background color of square
        pen = QPen(Qt.black) # border color of square
        for i in range(self.rows):
            for j in range(self.columns):
                r = QRectF(QPointF(
                    i*side, j*side), QSizeF(side, side)) # each square 
                scene.addRect(r, pen, brush)
        grid.addWidget(w)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    view = Map()
    view.show()
    sys.exit(app.exec_())

What I want the widget to look like:

enter image description here

Upvotes: 1

Views: 2928

Answers (1)

musicamante
musicamante

Reputation: 48231

Your approach is much more complex than it needs, and there are various issues about it (some of them quite important).

First of all, there's no need to compute the size of the squares each time, since QGraphicsView can do that by using fitInView(), which by default ignores the aspect ratio. Just use an arbitrary square size that fits your needs.

Then, you should NEVER create and add widgets in a paintEvent, as it might cause recursion.

Once a layout is set on a widget, you should not set another one, unless absolutely necessary (and, anyway, it shouldn't be done like that).

When adding a rect to a graphicsScene using addRect, there's usually no need to create a QRectF first, as you can just use the convenience function that accepts coordinate and size (Qt will automatically create the QRectF anyway on the C++ side, which might be slightly faster).

On the other hand, since you're creating multiple rectangles of the same size, you could just create a single QRectF and then use translated() to set the coordinates.

Creating a QRectF with QPointF and QSizeF is usually suggested when those are existing variables or constants, otherwise you can just use the basic QRectF(x, y, width, height) constructor.

class Map(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.initUI()

    def initUI(self):
        self.setMinimumSize(500, 500) # min-size of the widget
        self.columns = 14 # num of columns in grid
        self.rows = 49 # num of rows in grid

        grid = QGridLayout(self)
        self.view = QGraphicsView()
        grid.addWidget(self.view)
        self.view.setRenderHints(QPainter.Antialiasing)

        self.scene = QGraphicsScene()
        self.view.setScene(self.scene)

        brush = QBrush(QColor(qRgb(255, 255, 255))) # background color of square
        pen = QPen(Qt.black) # border color of square
        side = 10
        rect = QRectF(0, 0, side, side)
        for i in range(self.rows):
            for j in range(self.columns):
                self.scene.addRect(rect.translated(i * side, j * side), pen, brush)

        # this is required to ensure that fitInView works on first shown too
        self.resizeScene()

    def resizeScene(self):
        self.view.fitInView(self.scene.sceneRect())

    def resizeEvent(self, event):
        # call fitInView each time the widget is resized
        self.resizeScene()

    def showEvent(self, event):
        # call fitInView each time the widget is shown
        self.resizeScene()

Upvotes: 1

Related Questions