PyQt:QGraphicsScene内嵌QWidget无法自动缩小问题咨询
根本原因
将QWidget通过QGraphicsScene::addWidget()嵌入场景时,Qt会自动创建一个QGraphicsProxyWidget代理对象托管该控件,这层代理默认的尺寸调整逻辑为仅允许尺寸扩张,拦截自动收缩请求:
- 代理会缓存托管QWidget的历史最大尺寸,当内部布局因子控件减少触发缩小请求时,代理不会同步更新自身的boundingRect,也不会把收缩请求传递给托管的QWidget
- 这和QScrollArea的实现逻辑完全不同:QScrollArea直接将托管控件作为视口的子控件,走标准Qt布局的双向尺寸调整流程,没有代理层的尺寸缓存限制,因此可以正常收缩
- 这也是观察到
itemsBoundingRect()只增不减的核心原因:新增按钮时的扩张请求会被代理放行,控件尺寸正常变大;删除按钮时的收缩请求被代理拦截,控件始终保持历史最大尺寸。
修复方案
需要在子控件删除、布局完成重算后,主动重置托管控件和代理的尺寸,步骤如下:
- 保存
addWidget()返回的QGraphicsProxyWidget对象,不要使用匿名调用 - 子控件删除后,等待事件循环完成布局重算,再主动触发控件尺寸调整和代理尺寸同步
修正后的可运行代码如下:
import sys from PyQt6 import QtCore from PyQt6.QtWidgets import (QMainWindow, QApplication, QWidget, QVBoxLayout, QPushButton, QGraphicsView, QGraphicsScene, QSizePolicy) from PyQt6.QtGui import QBrush class Window(QMainWindow): def __init__(self): super(Window,self).__init__() self.setGeometry(300,300,1000,1000) app = QApplication(sys.argv) window = Window() app.setStyle('Fusion') widget = QWidget() widget.setStyleSheet("background:red") widget.setSizePolicy(QSizePolicy.Policy.Minimum,QSizePolicy.Policy.Minimum) vbox = QVBoxLayout() proxy = None gs = None def resize_proxy(): # 按布局最小尺寸调整控件 widget.adjustSize() # 同步代理尺寸 proxy.setGeometry(widget.geometry()) # 同步场景尺寸 gs.setSceneRect(gs.itemsBoundingRect()) print(gs.itemsBoundingRect()) def add_btn(): btn = QPushButton("x") btn.setFixedHeight(40) vbox.addWidget(btn) btn.clicked.connect(lambda: [btn.deleteLater(), QtCore.QTimer.singleShot(0, resize_proxy)]) print(gs.itemsBoundingRect()) btn = QPushButton("add") btn.clicked.connect(add_btn) vbox.addWidget(btn) init_btn = QPushButton("x") init_btn.setFixedHeight(40) vbox.addWidget(init_btn) init_btn.clicked.connect(lambda: [init_btn.deleteLater(), QtCore.QTimer.singleShot(0, resize_proxy)]) gv = QGraphicsView() gv.setSizePolicy(QSizePolicy.Policy.Minimum,QSizePolicy.Policy.Minimum) gs = QGraphicsScene() gs.setBackgroundBrush(QBrush(QtCore.Qt.GlobalColor.blue)) # 保存代理对象引用 proxy = gs.addWidget(widget) gv.setScene(gs) widget.setLayout(vbox) window.setCentralWidget(gv) window.show() sys.exit(app.exec())
代码中使用
QTimer.singleShot(0, resize_proxy)是因为deleteLater()不会立即销毁控件,需要等下一轮事件循环才会完成删除、触发布局重算,此时读取的布局尺寸才是删除子控件后的准确值。
内容的提问来源于stack exchange,提问作者Mason Trainotti
相关产品推荐
相关产品推荐

