自动调整 pyqt 小部件的大小而不将其放入布局中

Auto resize pyqt widget without putting it in a layout

我正在尝试叠加两个 QWidget,并让它们在 window 大小改变时自动调整大小。

背景 QWidget 显示带有 co-ordinates 的网格,前景 QWidget 包含其他小部件(按钮等)。这些都设置为 children 到同一个 QWidget。如果我不使用布局,我可以让它们重叠,但是当 parent 小部件调整大小时,我失去了有用的自动调整 child 小部件的大小。

我的总体目标是创建一个编辑器,让用户可以为仪表板设计自己的小部件。网格背景将是网格布局,前景是小部件。

import sys

from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QWidget, \
        QGridLayout


class Grid(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.columns = columns
        self.rows = rows
        self.layout = QGridLayout()
        self.layout.setContentsMargins(0,0,0,0)
        self.layout.setSpacing(0)
        for ii in range(self.rows):
                for jj in range(self.columns):
                        label = QLabel('{}, {}'.format(ii,jj))
                        label.setStyleSheet("border: 1px solid grey; color: grey")
                        self.layout.addWidget(label, ii, jj, 1, 1)
        self.setLayout(self.layout)

class Overlay(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rows = rows
        self.columns = columns
        self.layout = QGridLayout()
        # self.layout.setContentsMargins(0,0,0,0)
        # self.layout.setSpacing(0)
        button_1 = QPushButton('yep')
        button_2 = QPushButton('nope')
        top_left_label = QLabel('tl')
        bottom_right_label = QLabel('br')
        self.layout.addWidget(button_1, 1, 1, 1, 4)
        self.layout.addWidget(button_2, 4, 4, 2, 2)
        self.layout.addWidget(top_left_label, 0, 0, 1, 1)
        self.layout.addWidget(bottom_right_label, self.rows, self.columns, 1, 1)
        self.setLayout(self.layout)

class Parent_Dashboard_Template(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rows = rows
        self.columns = columns
        self.setMinimumSize(400, 200)
        self.layout = QGridLayout()
        self.layout_2 = QGridLayout()
        self.grid = Grid(self.rows, self.columns, parent=self)
        # self.layout.addWidget(self.grid)
        # self.setLayout(self.layout)
        # self.grid.showMaximized()
        self.overlay = Overlay(self.rows, self.columns, parent=self)
        # self.layout_2.addWidget(self.overlay)
        # self.setLayout(self.layout_2)



if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Parent_Dashboard_Template(10, 10)
    window.show()
    app.exec_()

QGridLayout 具有能够重叠占据相同“单元格”的项目的“隐藏”功能,因此您可以将这两个小部件添加到相同的 row/column:

class Parent_Dashboard_Template(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        # ...
        layout = QGridLayout(self)
        self.grid = Grid(self.rows, self.columns)
        self.overlay = Overlay(self.rows, self.columns)
        layout.addWidget(self.grid, 0, 0)
        layout.addWidget(self.overlay, 0, 0)

由于那些覆盖的小部件需要与网格正确对齐,更好的可能性是为主要小部件创建一个网格布局并在其中添加所有内容,然后使用上述“技巧”添加覆盖的小部件。考虑到这一点,由于 Qt 按钮具有最小尺寸提示和固定的垂直策略,您还应该根据可能的最小尺寸显式覆盖尺寸提示,并设置 Preferred 垂直尺寸策略。

class Parent_Dashboard_Template(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rows = rows
        self.columns = columns
        self.setMinimumSize(400, 200)
        layout = QGridLayout(self)

        for ii in range(self.rows):
            for jj in range(self.columns):
                label = QLabel('{}, {}'.format(ii,jj))
                label.setStyleSheet("border: 1px solid grey; color: grey")
                layout.addWidget(label, ii, jj)

        button_1 = QPushButton('yep')
        button_2 = QPushButton('nope')

        # just a reference based on the last created label
        minSize = label.sizeHint()
        button_1.sizeHint = lambda: minSize
        button_2.sizeHint = lambda: minSize
        button_1.setSizePolicy(
            QSizePolicy.Preferred, QSizePolicy.Preferred)
        button_2.setSizePolicy(
            QSizePolicy.Preferred, QSizePolicy.Preferred)

        top_left_label = QLabel('tl')
        bottom_right_label = QLabel('br')
        layout.addWidget(button_1, 1, 1, 1, 4)
        layout.addWidget(button_2, 4, 4, 2, 2)
        layout.addWidget(top_left_label, 0, 0)
        layout.addWidget(
            bottom_right_label, self.rows - 1, self.columns - 1)

最后,考虑到网格可能需要在一个单独的 class 中,您可以覆盖主窗口小部件的调整大小事件,然后根据单元格设置覆盖窗口小部件的几何形状它们的矩形,映射到顶级小部件。

class Parent_Dashboard_Template(QWidget):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rows = rows
        self.columns = columns
        self.setMinimumSize(400, 200)
        layout = QVBoxLayout(self)

        self.grid = Grid(self.rows, self.columns)
        layout.addWidget(self.grid)

        self.button_1 = QPushButton('yep', self)
        self.button_2 = QPushButton('nope', self)
        self.top_left_label = QLabel('tl', self)
        self.bottom_right_label = QLabel('br', self)
        self.widgetCells = {
            self.button_1: (1, 1, 1, 4), 
            self.button_2: (4, 4, 2, 2), 
            self.top_left_label: (0, 0, 1, 1), 
            self.bottom_right_label: (
                self.rows - 1, self.columns - 1, 1, 1), 
        }

    def resizeEvent(self, event):
        super().resizeEvent(event)
        layout = self.grid.layout
        # required for first show
        layout.activate()
        for w, (row, column, rSpan, cSpan) in self.widgetCells.items():
            geo = layout.cellRect(row, column)
            geo |= layout.cellRect(
                row + rSpan - 1, column + cSpan - 1)
            geo.moveTopLeft(self.grid.mapTo(self, geo.topLeft()))
            w.setGeometry(geo)

注意:

  • layout() 是所有 QWidget class 的现有 dynamic 属性,你不应该覆盖它;
  • 如果它们只是 1,则不需要显式添加 row/column 范围,因为这是默认值;
  • 您可以通过在布局构造函数中指定它来自动在小部件上“安装”QLayout(就像我对 QGridLayout(self) 所做的那样);