使用PyQt5绘制正确的网格 [英] Draw a correct grid with PyQt5

查看:112
本文介绍了使用PyQt5绘制正确的网格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在PyQt5上有些挣扎:我必须实现Conway的《人生游戏》,我从GUI常规设置开始.我考虑过要(垂直)堆叠两个小部件,一个小部件旨在显示游戏板,另一个小部件包含按钮和滑块.

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. 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!

推荐答案

未完全显示像素图的原因是因为您使用的是 WINDOW_WIDTH WINDOW_HEIGHT 和像素图的code>.由于该窗口还包含工具栏及其自身的页边距,因此您必须将其设置为小于其应有的大小,从而截断"木板.

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

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

The simpler solution would be to set the scaledContents property of the label:

    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.

另一种(更好)的可能性是在窗口显示后设置固定大小,以便 Qt 处理所有对象所需的大小:

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:

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.

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.

采用这种方法,您将面临两种可能性:

With your approach, you'll face two possibilities:

    整个QPixmap的
  1. 连续重绘:您无法轻易地从已绘制的表面上清除"某些东西,如果您有移动或消失的对象,则需要
  2. 添加必须手动移动的自定义小部件(计算相对于像素图的位置将是严重的PITA)
  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
  2. adding custom widgets that will have to be manually moved (and computing their position relative to the pixmap will be a serious PITA)

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

A better solution would be to avoid at all the QLabel, and implement your own widget with custom painting.

这是一个简单的例子:

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.

Now you don't need make_grid anymore, and you can use Grid instead of the QLabel.

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

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

这篇关于使用PyQt5绘制正确的网格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆