如何在 Qt5 中 parent 调整大小后调整方形 children 小部件的大小?

How to resize square children widgets after parent resize in Qt5?

我想用方形小部件做板。当我 运行 编写代码时,它创建了漂亮的板,但在调整大小后它变得很难看。我正在尝试使用 resize Event 调整它的大小,但它存在(可能存在一些错误)。我不知道如何在调整 parent.

后调整 children 的大小

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

这是我想要调整大小的(点击最大化):

最大化后它看起来很难看(我应该更改 children 小部件但是在什么事件上(我认为在 resizeEvent 上但它不起作用)以及如何(从 parent 或 [=56= 设置] 导致程序退出)。

这是我的最小化代码:

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

我应该如何调整正方形 children 的大小以避免空洞?

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

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

显示后():

太宽(保持比例):

太高(保持比例):

更大(它保持比例但 children 未缩放到自由 space - 我仍然不知道如何调整 children 大小?):

改进代码:

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

如何在不崩溃的情况下调整方形 children 的大小?

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

子类布局

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

在这个例子中,我还创建了一个 "container" 和其他小部件来显示正在调整大小。

当window宽度很高时,会以高度为参考,水平居中:

反之,高度越大,垂直居中:

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

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