首页 > 解决方案 > 如何在 Qt5 中调整父级大小后调整方形子级小部件的大小?

问题描述

我想用方形小部件做板。当我运行代码时,它会创建漂亮的板,但在调整大小后它看起来很丑。我正在尝试使用 resize Event 调整它的大小,但它存在(可能是一些错误)。我不知道如何在调整父母大小后调整孩子的大小。

儿童小部件必须是正方形,所以这也是问题,因为我不能使用自动扩展。也许这是一个简单的问题,但我找不到解决方案。我花了几个小时测试不同的想法,但它现在可以正常工作了。

这是我想要调整大小(单击最大化): 在此处输入图像描述

最大化后它看起来很丑(我应该更改子小部件,但是在什么事件上(我认为在 resizeEvent 但它不起作用)以及如何(从父级或子级设置导致程序退出)。

在此处输入图像描述

这是我的最小化代码:

import logging
import sys

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QFont, QPaintEvent, QPainter
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout


class Application(QApplication):
    pass


class Board(QWidget):
    def square_size(self):
        size = self.size()
        min_size = min(size.height(), size.width())
        min_size_1_8 = min_size // 8
        square_size = QSize(min_size_1_8, min_size_1_8)
        logging.debug(square_size)
        return square_size

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        square_size = self.square_size()

        grid = QGridLayout()
        grid.setSpacing(0)

        squares = []
        for x in range(8):
            for y in range(8):
                square = Square(self, (x + y - 1) % 2)
                squares.append(squares)
                square.setFixedSize(square_size)
                grid.addWidget(square, x, y)
        self.squares = squares
        self.setLayout(grid)

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        # how to resize children?
        logging.debug('Resize %s.', self.__class__.__name__)
        logging.debug('Size %s.', event.size())
        super().resizeEvent(event)


class Square(QWidget):
    def __init__(self, parent, color):
        super().__init__(parent=parent)
        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        logging.debug('Resize %s.', self.__class__.__name__)
        logging.debug('Size %s.', event.size())
        super().resizeEvent(event)

    def paintEvent(self, event: QPaintEvent) -> None:
        painter = QPainter()
        painter.begin(self)
        painter.fillRect(self.rect(), self.color)
        painter.end()


def main():
    logging.basicConfig(level=logging.DEBUG)
    app = Application(sys.argv)
    app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

    default_font = QFont()
    default_font.setPointSize(12)
    app.setFont(default_font)

    board = Board()
    board.setWindowTitle('Board')
    # ugly look
    # chessboard.showMaximized()
    # looks nize but resize not works
    board.show()

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

我应该如何调整方形孩子的大小以避免孔?

第二次尝试 - 改进的代码,但我仍然不知道如何调整孩子的大小

一些将其居中的新想法效果更好(现在没有间隙),但我仍然不知道如何调整孩子的大小(没有崩溃)。

在显示()之后:

在此处输入图像描述

太宽(保持比例):

在此处输入图像描述

太高(保持比例):

在此处输入图像描述

更大(它保持比例但孩子没有缩放到可用空间 - 我不知道如何调整孩子的大小?): 在此处输入图像描述

改进的代码:

import logging
import sys

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QFont, QPaintEvent, QPainter
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout


class Application(QApplication):
    pass


class Board(QWidget):
    def square_size(self):
        size = self.size()
        min_size = min(size.height(), size.width())
        min_size_1_8 = min_size // 8
        square_size = QSize(min_size_1_8, min_size_1_8)
        logging.debug(square_size)
        return square_size

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        square_size = self.square_size()

        vertical = QVBoxLayout()
        horizontal = QHBoxLayout()

        grid = QGridLayout()
        grid.setSpacing(0)

        squares = []
        for x in range(8):
            for y in range(8):
                square = Square(self, (x + y - 1) % 2)
                squares.append(squares)
                square.setFixedSize(square_size)
                grid.addWidget(square, x, y)
        self.squares = squares

        horizontal.addStretch()
        horizontal.addLayout(grid)
        horizontal.addStretch()
        vertical.addStretch()
        vertical.addLayout(horizontal)
        vertical.addStretch()
        self.setLayout(vertical)

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        # how to resize children?
        logging.debug('Resize %s.', self.__class__.__name__)
        logging.debug('Size %s.', event.size())
        super().resizeEvent(event)


class Square(QWidget):
    def __init__(self, parent, color):
        super().__init__(parent=parent)
        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        logging.debug('Resize %s.', self.__class__.__name__)
        logging.debug('Size %s.', event.size())
        super().resizeEvent(event)

    def paintEvent(self, event: QPaintEvent) -> None:
        painter = QPainter()
        painter.begin(self)
        painter.fillRect(self.rect(), self.color)
        painter.end()


def main():
    logging.basicConfig(level=logging.DEBUG)
    app = Application(sys.argv)
    app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

    default_font = QFont()
    default_font.setPointSize(12)
    app.setFont(default_font)

    board = Board()
    board.setWindowTitle('Board')
    # ugly look
    # chessboard.showMaximized()
    # looks nice but resize not works
    board.show()

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

我应该如何调整方形孩子的大小而不会崩溃?

标签: pythonpython-3.xpyqtpyqt5qgridlayout

解决方案


有两种可能的解决方案。
您可以使用Graphics View 框架,该框架专门用于必须考虑自定义/特定图形和定位的此类应用程序,否则创建布局子类。虽然在这种情况下重新实现布局有点简单,但一旦应用程序变得更加复杂,您可能会遇到一些问题。另一方面,Graphics View 框架有一个陡峭的学习曲线,因为您需要了解它的工作原理以及对象交互的行为方式。

子类化布局

假设平方数始终相同,您可以重新实现自己的布局,该布局将根据其内容设置正确的几何图形。

在此示例中,我还创建了一个带有其他小部件的“容器”,以显示正在调整大小。

当窗口宽度非常高时,它会以高度为参考,水平居中: 窗户很宽

相反,当高度较大时,它将垂直居中: 窗户很高

请记住,您不应该板上添加其他小部件,否则您会遇到严重的问题。
这并非不可能,但它的实现可能要复杂得多,因为布局需要考虑其他小部件的位置、大小提示和可能的扩展方向,以便正确计算新的几何图形。

from PyQt5 import QtCore, QtGui, QtWidgets

class Square(QtWidgets.QWidget):
    def __init__(self, parent, color):
        super().__init__(parent=parent)
        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black
        self.setMinimumSize(50, 50)

    def paintEvent(self, event: QtGui.QPaintEvent) -> None:
        painter = QtGui.QPainter(self)
        painter.fillRect(self.rect(), self.color)


class EvenLayout(QtWidgets.QGridLayout):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setSpacing(0)

    def setGeometry(self, oldRect):
        # assuming that the minimum size is 50 pixel, find the minimum possible
        # "extent" based on the geometry provided
        minSize = max(50 * 8, min(oldRect.width(), oldRect.height()))
        # create a new squared rectangle based on that size
        newRect = QtCore.QRect(0, 0, minSize, minSize)
        # move it to the center of the old one
        newRect.moveCenter(oldRect.center())
        super().setGeometry(newRect)


class Board(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        layout = EvenLayout(self)
        self.squares = []
        for row in range(8):
            for column in range(8):
                square = Square(self, not (row + column) & 1)
                self.squares.append(square)
                layout.addWidget(square, row, column)


class Chess(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)
        header = QtWidgets.QLabel('Some {}long label'.format('very ' * 20))
        layout.addWidget(header, 0, 0, 1, 3, QtCore.Qt.AlignCenter)
        self.board = Board()
        layout.addWidget(self.board, 1, 1)

        leftLayout = QtWidgets.QVBoxLayout()
        layout.addLayout(leftLayout, 1, 0)
        rightLayout = QtWidgets.QVBoxLayout()
        layout.addLayout(rightLayout, 1, 2)
        for b in range(1, 9):
            leftLayout.addWidget(QtWidgets.QPushButton('Left Btn {}'.format(b)))
            rightLayout.addWidget(QtWidgets.QPushButton('Right Btn {}'.format(b)))

        footer = QtWidgets.QLabel('Another {}long label'.format('very ' * 18))
        layout.addWidget(footer, 2, 0, 1, 3, QtCore.Qt.AlignCenter)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Chess()
    w.show()
    sys.exit(app.exec_())

使用图形视图

结果将在视觉上与前一个相同,但虽然整体定位、绘图和交互在概念上会更容易一些,但了解图形视图、场景和对象的工作方式可能需要一些时间来掌握它。

from PyQt5 import QtCore, QtGui, QtWidgets


class Square(QtWidgets.QGraphicsWidget):
    def __init__(self, color):
        super().__init__()
        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black

    def paint(self, qp, option, widget):
        qp.fillRect(option.rect, self.color)


class Scene(QtWidgets.QGraphicsScene):
    def __init__(self):
        super().__init__()

        self.container = QtWidgets.QGraphicsWidget()
        layout = QtWidgets.QGraphicsGridLayout(self.container)
        layout.setSpacing(0)
        self.container.setContentsMargins(0, 0, 0, 0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.addItem(self.container)
        for row in range(8):
            for column in range(8):
                square = Square(not (row + column) & 1)
                layout.addItem(square, row, column, 1, 1)


class Board(QtWidgets.QGraphicsView):
    def __init__(self):
        super().__init__()
        scene = Scene()
        self.setScene(scene)
        self.setAlignment(QtCore.Qt.AlignCenter)
        # by default a graphics view has a border frame, disable it
        self.setFrameShape(0)
        # make it transparent
        self.setStyleSheet('QGraphicsView {background: transparent;}')

    def resizeEvent(self, event):
        super().resizeEvent(event)
        # zoom the contents keeping the ratio
        self.fitInView(self.scene().container, QtCore.Qt.KeepAspectRatio)


class Chess(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)
        header = QtWidgets.QLabel('Some {}long label'.format('very ' * 20))
        layout.addWidget(header, 0, 0, 1, 3, QtCore.Qt.AlignCenter)
        self.board = Board()
        layout.addWidget(self.board, 1, 1)

        leftLayout = QtWidgets.QVBoxLayout()
        layout.addLayout(leftLayout, 1, 0)
        rightLayout = QtWidgets.QVBoxLayout()
        layout.addLayout(rightLayout, 1, 2)
        for b in range(1, 9):
            leftLayout.addWidget(QtWidgets.QPushButton('Left Btn {}'.format(b)))
            rightLayout.addWidget(QtWidgets.QPushButton('Right Btn {}'.format(b)))

        footer = QtWidgets.QLabel('Another {}long label'.format('very ' * 18))
        layout.addWidget(footer, 2, 0, 1, 3, QtCore.Qt.AlignCenter)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Chess()
    w.show()
    sys.exit(app.exec_())

推荐阅读