滚动区域内QGridLayout清空后添加控件显示异常问题
问题描述
在QScrollArea内使用QGridLayout布局,要求最多显示5列200行。点击网格按钮时需清空现有控件并添加新控件,但出现以下异常:
- 初始加载50个(10×5)控件后,清空再添加1000个(200×5)控件,界面无任何显示
- 添加750个(150×5)控件时,所有控件显示极小
- 直接初始加载1000个控件时,显示完全正常
可复现代码
import sys from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * class GridButton(QPushButton): def __init__(self, parent, tab, grid) -> None: super().__init__(parent) self.tab = tab self.grid = grid self.grid_wid = parent def onclick(self): self.tab.clear() for i in range(150): for j in range(5): btn = GridButton(self.grid_wid, self.tab, self.grid) btn.setText(f"nButton: {i}") btn.clicked.connect(btn.onclick) self.grid.addWidget(btn, i, j) class Test(QWidget): def __init__(self, parent) -> None: super().__init__(parent) self.sc = QScrollArea(self) self.sc.setGeometry(0, 0, 1024, 600) self.grid_wid = QWidget() self.grid = QGridLayout(self.grid_wid) for i in range(10): for j in range(5): btn = GridButton(self.grid_wid, self, self.grid) btn.setText(f"Button: {i}") btn.clicked.connect(btn.onclick) self.grid.addWidget(btn, i, j) self.sc.setWidget(self.grid_wid) def clear(self): for i in reversed(range(self.grid.count())): widgetToRemove = self.grid.itemAt(i).widget() widgetToRemove.setParent(None) widgetToRemove.deleteLater() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.tabs = Test(self) self.setWindowTitle("GUI") self.setFixedSize(QSize(1024, 600)) self.setCentralWidget(self.tabs) self.show() def main(): app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec_()) if __name__ == "__main__": main()
问题截图
- 50个控件初始显示状态:

- 点击按钮后添加1000个控件的异常显示:

解决方案
问题根源
- QGridLayout在删除控件后,会保留之前的行/列尺寸约束(比如初始10行的高度限制),新增大量控件时布局无法正确计算尺寸,导致控件被压缩或无法显示。
- QScrollArea未开启
widgetResizable,且内部widget的尺寸策略未设置为可扩展,无法适配布局变化后的大小。
修改步骤
- 在
clear方法中,删除所有控件后重新创建QGridLayout,彻底清除旧的布局约束; - 给QScrollArea开启
setWidgetResizable(True),让滚动区域自动适配内部widget的大小; - 给内部的
grid_wid设置合适的尺寸策略,确保布局扩展时widget能同步调整。
修改后的完整代码
import sys from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * class GridButton(QPushButton): def __init__(self, parent, tab) -> None: super().__init__(parent) self.tab = tab self.grid_wid = parent def onclick(self): self.tab.clear() # 重新获取当前的布局 grid = self.grid_wid.layout() for i in range(150): for j in range(5): btn = GridButton(self.grid_wid, self.tab) btn.setText(f"nButton: {i}") btn.clicked.connect(btn.onclick) grid.addWidget(btn, i, j) # 强制更新布局 self.grid_wid.updateGeometry() class Test(QWidget): def __init__(self, parent) -> None: super().__init__(parent) self.sc = QScrollArea(self) self.sc.setGeometry(0, 0, 1024, 600) # 开启滚动区域自动适配widget大小 self.sc.setWidgetResizable(True) self.grid_wid = QWidget() # 设置widget的尺寸策略为可扩展 self.grid_wid.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.grid = QGridLayout(self.grid_wid) for i in range(10): for j in range(5): btn = GridButton(self.grid_wid, self) btn.setText(f"Button: {i}") btn.clicked.connect(btn.onclick) self.grid.addWidget(btn, i, j) self.sc.setWidget(self.grid_wid) def clear(self): # 删除所有控件 for i in reversed(range(self.grid.count())): widgetToRemove = self.grid.itemAt(i).widget() widgetToRemove.setParent(None) widgetToRemove.deleteLater() # 删除旧布局并重新创建 del self.grid self.grid = QGridLayout(self.grid_wid) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.tabs = Test(self) self.setWindowTitle("GUI") self.setFixedSize(QSize(1024, 600)) self.setCentralWidget(self.tabs) self.show() def main(): app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec_()) if __name__ == "__main__": main()
关键修改说明
- 移除了
GridButton中对grid的直接引用,改为从grid_wid动态获取布局,避免布局重建后引用失效; clear方法中新增了删除旧布局并重建的逻辑,彻底清除旧的尺寸约束;- 给QScrollArea添加
setWidgetResizable(True),确保滚动区域能跟随内部widget大小变化; - 给
grid_wid设置SizePolicy.Expanding,让widget能充分利用滚动区域的空间; - 在添加新控件后调用
updateGeometry(),强制触发布局更新。
内容的提问来源于stack exchange,提问作者3nws
相关产品推荐
相关产品推荐

