【问题标题】:Draw a correct grid with PyQt5使用 PyQt5 绘制正确的网格
【发布时间】:2020-04-18 15:07:21
【问题描述】:

我在使用 PyQt5 时遇到了一些困难:我必须实现 Conway 的 Game of Life,我从 GUI 常规设置开始。我考虑过(垂直)堆叠两个小部件,一个旨在显示游戏板,另一个包含按钮和滑块。

这就是我想出的(我完全是菜鸟)

我想根据边缘正确拟合网格。看起来它在专用画布下方构建了网格:先修复画布然后在上面绘画会很棒,但是布局、小部件和所有这些都让我大吃一惊。

这是我的(写得很快但写得不好)代码

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

感谢您的帮助,祝您有愉快的一天!

【问题讨论】:

    标签: python user-interface layout pyqt5 conways-game-of-life


    【解决方案1】:

    像素图没有以完整尺寸显示的原因是您在窗口像素图上都使用了WINDOW_WIDTHWINDOW_HEIGHT。由于窗口还包含工具栏和它自己的边距,因此您强制它比应有的小,因此会“剪裁”板子。

    更简单的解决方案是设置标签的scaledContents 属性:

        self.label.setScaledContents(True)
    

    但结果会有点难看,因为标签的大小会比您绘制的像素图略小,使其模糊。

    另一种(更好的)可能性是在窗口显示后设置固定大小,以便 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_()
    

    即使这不是您的问题的一部分,我也会向您建议一个稍微不同的概念,它不涉及 QLabel。

    使用您的方法,您将面临两种可能性:

    1. 整个 QPixmap 的连续重绘:你不能轻易地从已经绘制的表面上“清除”某些东西,如果你有移动或消失的对象,你将需要它
    2. 添加必须手动移动的自定义小部件(计算它们相对于像素图的位置将是一个严重的 PITA)

    更好的解决方案是完全避免使用 QLabel,并使用自定义绘画实现您自己的小部件。

    这是一个简单的例子:

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

    现在您不再需要make_grid,您可以使用Grid 代替QLabel。

    请注意,我删除了一个像素来计算正方形大小,否则将不会显示最后一行/列线,就像您的像素图中发生的那样(考虑在 20x20 边的正方形中,从 0.5 开始的 20px 线将是剪辑在像素 19.5)。

    【讨论】:

    • 非常感谢!我将听从您的建议,并更仔细地重新考虑整个事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-29
    • 1970-01-01
    • 2011-05-10
    • 2016-02-01
    • 1970-01-01
    相关资源
    最近更新 更多