Reputation: 469
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.
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_())
Upvotes: 1
Views: 2928
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