Как изменить размер квадратных дочерних виджетов после изменения размера родительского элемента в Qt5? - PullRequest
0 голосов
/ 01 ноября 2019

Я хочу сделать доску с квадратными виджетами. Когда я запускаю код, он создает красивую доску, но после изменения размера он становится уродливым. Я пытаюсь изменить его размер с помощью Event Resize, но он существует (возможно, некоторые ошибки). Я понятия не имею, как изменить размер дочерних элементов после изменения размера родительского элемента.

Дочерние виджеты должны быть квадратами, поэтому это также проблема, поскольку я не могу использовать автоматическое расширение. Может быть, это простая проблема, но я не могу найти решение. Я часами проверяю разные идеи, но теперь все работает как надо.

Это то, что я хочу изменить размер (нажмите на максимизировать): enter image description here

После максимизации это выглядит некрасиво(Я должен изменить дочерний виджет, но о том, какое событие (я думаю о resizeEvent, но оно не работает) и как (из родительского или дочернего объекта вызывает выход из программы).

enter image description here

Это мой код минимизации:

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

Как мне изменить размер квадратных детей, чтобы избежать дыр?

2-я попытка - улучшенный код, но все же я не представляю, какизменить размер детей

Какая-то новая идея с центрированием, которая работает лучше (без пробелов), но все же я не знаю, как изменить размер детей (без сбоев).

После show ():

enter image description here

Слишком широкий (он сохраняет пропорции):

enter image description here

Слишком высокий(он сохраняет пропорции):

enter image description here

Больше (он сохраняет пропорции, но дети не масштабируются до свободного места - я делаюНе знаете, как еще изменить размер детей?): enter image description here

Улучшен код:

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

Как мне изменить размер квадратных детей без сбоев?

1 Ответ

3 голосов
/ 01 ноября 2019

Существует два возможных решения.
Вы можете использовать Framework View Graphics , который предназначен именно для такого рода приложений, где необходимо учитывать пользовательскую / специфическую графику и позиционирование, в противном случаесоздать подкласс макета. Хотя переопределение макета в этом случае немного простое, вы можете столкнуться с некоторыми проблемами, как только приложение станет более сложным. С другой стороны, каркас графического представления имеет крутую кривую обучения, так как вам необходимо понять, как она работает и как взаимодействует объект.

Подкласс макета

Предполагая, что квадратчисло всегда одинаково, вы можете переопределить свой собственный макет, который будет устанавливать правильную геометрию на основе его содержимого.

В этом примере я также создал «контейнер» с другими виджетами, чтобы показать изменение размера в действии.

Когда ширина окна очень велика, она будет использовать высоту в качестве ориентира и центрировать ее по горизонтали: window very wide

Наоборот, когда высота больше, он будет центрирован по вертикали: window very tall

Имейте в виду, что вы должны , а не добавлять другие виджеты на доску, иначе вы столкнетесь с серьезными проблемами.
Это не было бы невозможно, но его реализация могла бы быть намного более сложной, поскольку макет должен был бы учитывать позиции других виджетов, подсказки по размеру и поВозможное расширение направлений для правильного вычисления новой геометрии.

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