PyQt5中如何在应用启动时调整QStackedWidget所有子项大小
这个问题其实是Qt对QStackedWidget的优化逻辑导致的:它只会对当前显示的页面进行布局计算和尺寸调整,隐藏的页面默认不会收到Resize或Show事件——毕竟Qt不会去渲染不可见的部件,这是合理的性能优化,但确实会影响到你这种需要提前获取尺寸的场景(比如视频帧绘制、仪表盘布局)。
你之前用threading.Timer的方案报错,是因为PyQt要求所有UI操作必须在主线程执行,而threading.Timer会在新线程里触发代码,导致跨线程操作UI部件,这是严格禁止的。下面给你两种靠谱的解决方案:
方案一:主线程内临时切换所有页面(推荐)
利用Qt的QTimer.singleShot(0),在UI事件循环启动后(确保窗口尺寸已经确定),临时切换到每个页面一次,触发它们的尺寸调整事件,最后切回初始页面。这种方式最贴近真实的用户操作,能确保页面的布局完全更新。
修改你的Window类,添加初始化方法:
class Window(QtWidgets.QMainWindow): def __init__(self, parent=None): super(Window, self).__init__(parent) self.stacked_widget = QtWidgets.QStackedWidget() layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0,0,0,0) layout.addWidget(self.stacked_widget) widget = QtWidgets.QWidget() widget.setLayout(layout) self.setCentralWidget(widget) widget_1 = Main(self.stacked_widget) widget_2 = Page(self.stacked_widget, 1) widget_3 = Page(self.stacked_widget, 2) widget_4 = Page(self.stacked_widget, 3) wid_list = [widget_1, widget_2, widget_3, widget_4] for wid in wid_list: self.stacked_widget.addWidget(wid) self.showMaximized() # 用Qt定时器在主线程触发页面初始化(0延迟表示事件循环空闲时执行) QtCore.QTimer.singleShot(0, self.init_pages) def init_pages(self): # 保存当前页面索引,之后要切回来 current_idx = self.stacked_widget.currentIndex() # 遍历所有页面,切换并强制处理事件 for idx in range(self.stacked_widget.count()): self.stacked_widget.setCurrentIndex(idx) # 强制Qt处理当前页面的所有 pending 事件(比如Resize、Show) QtWidgets.QApplication.processEvents() # 切回初始页面 self.stacked_widget.setCurrentIndex(current_idx)
方案二:手动发送尺寸事件
如果不想切换页面,可以直接给每个隐藏页面手动发送Show和Resize事件,模拟页面被显示和调整大小的过程:
在Window类的__init__最后添加这段代码(showMaximized()之后):
# 用定时器确保窗口尺寸已稳定 QtCore.QTimer.singleShot(0, self.send_size_events) # 定义发送事件的方法 def send_size_events(self): # 获取stacked_widget的最终尺寸 stacked_size = self.stacked_widget.size() # 这里要确保能拿到所有页面的列表,或者直接遍历stacked_widget的子部件 for idx in range(self.stacked_widget.count()): wid = self.stacked_widget.widget(idx) # 发送Show事件 show_event = QtCore.QShowEvent() QtCore.QCoreApplication.sendEvent(wid, show_event) # 发送Resize事件 resize_event = QtCore.QResizeEvent(stacked_size, QtCore.QSize()) QtCore.QCoreApplication.sendEvent(wid, resize_event)
这种方式更高效,不需要切换页面,用QTimer.singleShot(0)包裹能避免窗口还没完成布局就获取尺寸的问题。
为什么你的Timer方案会报错?
threading.Timer是Python标准库的多线程定时器,它会在一个新的非UI线程里执行hello方法,而PyQt的所有UI操作(比如setCurrentIndex)必须在主线程执行,否则就会抛出QObject::setParent: Cannot set parent, new parent is in a different thread的错误。Qt自己的QTimer是基于事件循环的,所有回调都会在主线程执行,所以完全没问题。
内容的提问来源于stack exchange,提问作者Voldemort

