简体   繁体   English

使用 PyQt5 绘制正确的网格

[英]Draw a correct grid with PyQt5

I'm struggling a bit with PyQt5: I have to implement Conway's Game of Life and I started out with the GUI general setup.我在 PyQt5 上有点挣扎:我必须实现 Conway's Game of Life,我从 GUI 常规设置开始。 I thought about stacking (vertically) two widgets, one aimed at displaying the game board and another one containing the buttons and sliders.我想过堆叠(垂直)两个小部件,一个用于显示游戏板,另一个包含按钮和滑块。

This is what I came up with (I'm a total noob)这就是我想出的(我是个菜鸟)

在此处输入图片说明

I'd like to fit the grid correctly with respect to the edges.我想相对于边缘正确地拟合网格。 It looks like it builds the grid underneath the dedicated canvas: it would be great to fix the canvas first and then paint on it but this whole thing of layouts, widgets and all that blows my mind.看起来它在专用画布下方构建了网格:首先修复画布然后在其上绘画会很棒,但是布局、小部件和所有这些都让我大吃一惊。

This is my (fastly and poorly written) code这是我的(写得很快,写得不好)代码

import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QPushButton, QWidget
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtGui import QPixmap, QColor, QPainter

WINDOW_WIDTH, WINDOW_HEIGHT = 800, 600
SQUARE_SIDE = 20
ROWS, COLS = int(WINDOW_HEIGHT/SQUARE_SIDE), int(WINDOW_WIDTH/2*SQUARE_SIDE)

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        buttons_layout = QHBoxLayout()
        self.label = QLabel()
        self.label.setContentsMargins(0,0,0,0)
        self.label.setStyleSheet('background-color: white; ')
        self.label.setAlignment(Qt.AlignCenter)
        slider = QSlider(Qt.Horizontal)
        start_button = QPushButton('Start')
        pause_button = QPushButton('Pause')
        reset_button = QPushButton('Reset')
        load_button = QPushButton('Load')
        save_button = QPushButton('Save')
        layout.addWidget(self.label)
        buttons_layout.addWidget(start_button)
        buttons_layout.addWidget(pause_button)
        buttons_layout.addWidget(reset_button)
        buttons_layout.addWidget(load_button)
        buttons_layout.addWidget(save_button)
        buttons_layout.addWidget(slider)
        layout.addLayout(buttons_layout)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.make_grid()

    def make_grid(self):
        _canvas = QPixmap(WINDOW_WIDTH, WINDOW_HEIGHT)
        _canvas.fill(QColor("#ffffff"))
        self.label.setPixmap(_canvas)
        painter = QPainter(self.label.pixmap())
        for c in range(COLS):
            painter.drawLine(SQUARE_SIDE*c, WINDOW_HEIGHT, SQUARE_SIDE*c, 0)
        for r in range(ROWS):
            painter.drawLine(0, SQUARE_SIDE*r, WINDOW_WIDTH, SQUARE_SIDE*r)



if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT)
    window.setWindowTitle("Conway's Game of Life")
    window.show()
    app.exec_()

Thank you for your help, have a nice day!感谢您的帮助,祝您有美好的一天!

The reason for the pixmap not being show at its full size is because you're using WINDOW_WIDTH and WINDOW_HEIGHT for both the window and the pixmap.像素图未以完整尺寸显示的原因是因为您对窗口像素图都使用了WINDOW_WIDTHWINDOW_HEIGHT Since the window also contains the toolbar and its own margins, you're forcing it to be smaller than it should, hence the "clipping out" of the board.由于窗口还包含工具栏和它自己的边距,因此您强制它比它应该的小,因此“剪掉”了板。

The simpler solution would be to set the scaledContents property of the label:更简单的解决方案是设置标签的scaledContents属性:

    self.label.setScaledContents(True)

But the result would be a bit ugly, as the label will have a size slightly smaller than the pixmap you drawn upon, making it blurry.但结果会有点难看,因为标签的尺寸会比您绘制的像素图略小,使其变得模糊。

Another (and better) possibility would be to set the fixed size after the window has been shown, so that Qt will take care of the required size of all objects:另一种(更好)的可能性是在窗口显示设置固定大小,以便 Qt 处理所有对象所需的大小:

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
#    window.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT)
    window.setWindowTitle("Conway's Game of Life")
    window.show()
    window.setFixedSize(window.size())
    app.exec_()

Even if it's not part of your question, I'm going to suggest you a slightly different concept, that doesn't involve a QLabel.即使它不是您问题的一部分,我也会向您建议一个略有不同的概念,它不涉及 QLabel。

With your approach, you'll face two possibilities:使用您的方法,您将面临两种可能性:

  1. continuous repainting of the whole QPixmap: you cannot easily "clear" something from an already painted surface, and if you'll have objects that move or disappear, you will need that整个 QPixmap 的连续重绘:您无法轻易从已绘制的表面“清除”某些内容,如果您有移动或消失的对象,您将需要它
  2. adding custom widgets that will have to be manually moved (and computing their position relative to the pixmap will be a serious PITA)添加必须手动移动的自定义小部件(并且计算它们相对于像素图的位置将是一个严重的 PITA)

A better solution would be to avoid at all the QLabel, and implement your own widget with custom painting.更好的解决方案是完全避免使用 QLabel,并使用自定义绘画实现您自己的小部件。

Here's a simple example:这是一个简单的例子:

class Grid(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setMinimumSize(800, 600)
        self.columns = 40
        self.rows = 30

        # some random objects
        self.objects = [
            (10, 20), 
            (11, 21), 
            (12, 20), 
            (12, 22), 
        ]

    def resizeEvent(self, event):
        # compute the square size based on the aspect ratio, assuming that the
        # column and row numbers are fixed
        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):
        qp = QPainter(self)
        # translate the painter by half a pixel to ensure correct line painting
        qp.translate(.5, .5)
        qp.setRenderHints(qp.Antialiasing)

        width = self.squareSize * self.columns
        height = self.squareSize * self.rows
        # center the grid
        left = (self.width() - width) / 2
        top = (self.height() - height) / 2
        y = top
        # we need to add 1 to draw the topmost right/bottom lines too
        for row in range(self.rows + 1):
            qp.drawLine(left, y, left + width, y)
            y += self.squareSize
        x = left
        for column in range(self.columns + 1):
            qp.drawLine(x, top, x, top + height)
            x += self.squareSize

        # create a smaller rectangle
        objectSize = self.squareSize * .8
        margin = self.squareSize* .1
        objectRect = QRectF(margin, margin, objectSize, objectSize)

        qp.setBrush(Qt.blue)
        for col, row in self.objects:
            qp.drawEllipse(objectRect.translated(
                left + col * self.squareSize, top + row * self.squareSize))

Now you don't need make_grid anymore, and you can use Grid instead of the QLabel.现在您不再需要make_grid ,您可以使用Grid代替 QLabel。

Note that I removed one pixel to compute the square size, otherwise the last row/column lines won't be shown, as happened in your pixmap (consider that in a 20x20 sided square, a 20px line starting from 0.5 would be clipped at pixel 19.5).请注意,我删除了一个像素来计算正方形大小,否则将不会显示最后一行/列线,就像您的像素图中发生的那样(考虑在 20x20 边正方形中,将从 0.5 开始的 20px 线将在像素处剪裁19.5)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM