PyQt5嵌套QSplitter布局问题:拉伸因子失效与占满需求
嵌套QSplitter布局实现问题及解决方案
需求目标
- 主垂直QSplitter的上下部件高度占比为66%-33%;
- 上方水平QSplitter的左右部件宽度占比为66%-33%;
- 上方QSplitter的子部件能占满父容器,窗口缩放时拉伸比例保持不变。
遇到的问题
- 直接将QSplitter作为另一个QSplitter的子部件时,
setStretchFactor设置的比例失效; - 改用QFrame承载上方QSplitter时,主QSplitter的拉伸比例异常。
测试代码
直接嵌套QSplitter的测试代码
import sys from PyQt5 import QtWidgets, QtCore, QtGui MAIN_WINDOW_HEIGHT = 900 TOP_FRAME_PERCENTAGE = 66 # can try with other percentages: 50, 75, 90... class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMinimumSize(1000, MAIN_WINDOW_HEIGHT) self.setStyleSheet('background-color: magenta'); main_splitter = QtWidgets.QSplitter(self) main_splitter.setOrientation(QtCore.Qt.Vertical) self.setCentralWidget(main_splitter) # try swapping and using a TopSplitter instead of a QFrame # top_frame = QtWidgets.QFrame() top_frame = TopSplitter() self.setStyleSheet('background-color: red'); main_splitter.addWidget(top_frame) self.bottom_panel = QtWidgets.QFrame() main_splitter.addWidget(self.bottom_panel) self.bottom_panel.setStyleSheet("background-color: green"); print(f'main_splitter.indexOf(self.bottom_panel) {main_splitter.indexOf(self.bottom_panel)}') def bp_size_hint(*args): # NB is never called print('YYY') return QtCore.QSize(200, (100 - TOP_FRAME_PERCENTAGE) * int(MAIN_WINDOW_HEIGHT/100)) self.bottom_panel.sizeHint = bp_size_hint main_splitter.setStretchFactor(0, TOP_FRAME_PERCENTAGE) main_splitter.setStretchFactor(1, 100 - TOP_FRAME_PERCENTAGE) print(f'A self.bottom_panel.height() {self.bottom_panel.height()}') def show(self): print(f'B self.bottom_panel.height() {self.bottom_panel.height()}') super().show() print(f'C self.bottom_panel.height() {self.bottom_panel.height()}') # 305 pixels with a QFrame, but 263 pixels with a TopSplitter class TopSplitter(QtWidgets.QSplitter): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setStyleSheet('background-color: cyan'); top_left_panel = QtWidgets.QFrame() top_left_panel.setStyleSheet('background-color: yellow'); self.addWidget(top_left_panel) top_right_panel = QtWidgets.QFrame() top_right_panel.setStyleSheet('background-color: black'); self.addWidget(top_right_panel) # these work exactly as you'd expect, to achieve 66%-33% self.setStretchFactor(0, 20) self.setStretchFactor(1, 10) def sizeHint(self): # NB called frequently print('XXX') return QtCore.QSize(200, TOP_FRAME_PERCENTAGE * int(MAIN_WINDOW_HEIGHT/100)) app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
QFrame承载QSplitter的测试代码
import sys from PyQt5 import QtWidgets, QtCore, QtGui class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMinimumSize(1000, 800) self.main_splitter = QtWidgets.QSplitter(self) self.main_splitter.setOrientation(QtCore.Qt.Vertical) self.setCentralWidget(self.main_splitter) TopFrame(self.main_splitter) self.bottom_panel = QtWidgets.QFrame(self.main_splitter) self.bottom_panel.setStyleSheet("background-color: green"); print(f'self.main_splitter.indexOf(self.bottom_panel) {self.main_splitter.indexOf(self.bottom_panel)}') self.main_splitter.setStretchFactor(0, 20) self.main_splitter.setStretchFactor(1, 10) class TopFrame(QtWidgets.QFrame): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # A self.lr_splitter = QtWidgets.QSplitter(self) self.lr_splitter.setOrientation(QtCore.Qt.Horizontal) # B self.setLayout(QtWidgets.QHBoxLayout()) self.x_panel = QtWidgets.QFrame(self.lr_splitter) self.x_panel.setStyleSheet("background-color: red"); self.y_panel = QtWidgets.QFrame(self.lr_splitter) self.y_panel.setStyleSheet("background-color: cyan"); self.lr_splitter.setStretchFactor(0, 20) self.lr_splitter.setStretchFactor(1, 10) app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
后续尝试的TopFrame实现
class TopFrame(QtWidgets.QSplitter): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setOrientation(QtCore.Qt.Horizontal) h_layout = QtWidgets.QHBoxLayout() self.setLayout(h_layout) # this line causes two errors: 1) can't add layout to splitter; 2) can't add parent widget to its child layout # h_layout.addWidget(self) self.x_panel = QtWidgets.QFrame() self.addWidget(self.x_panel) self.x_panel.setStyleSheet("background-color: red"); self.y_panel = QtWidgets.QFrame() self.addWidget(self.y_panel) self.y_panel.setStyleSheet("background-color: cyan"); self.setStretchFactor(0, 20) self.setStretchFactor(1, 10)
正确实现方案
问题根源
- 直接嵌套时重写
sizeHint会干扰QSplitter的自动布局逻辑,导致拉伸因子失效; - QFrame承载时未将QSplitter添加到布局中,导致QSplitter无法占满容器空间,进而影响主QSplitter的比例计算。
解决代码
import sys from PyQt5 import QtWidgets, QtCore class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMinimumSize(1000, 900) # 主垂直QSplitter main_splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) self.setCentralWidget(main_splitter) # 上方水平QSplitter top_splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) main_splitter.addWidget(top_splitter) # 上方左右面板,设置SizePolicy确保占满容器 top_left = QtWidgets.QFrame() top_left.setStyleSheet("background-color: yellow") top_left.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) top_splitter.addWidget(top_left) top_right = QtWidgets.QFrame() top_right.setStyleSheet("background-color: black") top_right.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) top_splitter.addWidget(top_right) # 上方水平QSplitter拉伸比例(2:1对应66%-33%) top_splitter.setStretchFactor(0, 2) top_splitter.setStretchFactor(1, 1) # 下方面板,设置SizePolicy确保占满容器 bottom_panel = QtWidgets.QFrame() bottom_panel.setStyleSheet("background-color: green") bottom_panel.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) main_splitter.addWidget(bottom_panel) # 主垂直QSplitter拉伸比例(2:1对应66%-33%) main_splitter.setStretchFactor(0, 2) main_splitter.setStretchFactor(1, 1) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
关键要点
- 不要重写QSplitter或其子部件的
sizeHint,QSplitter会根据拉伸因子自动分配空间; - 给所有子部件设置
SizePolicy.Expanding,确保它们能占满父容器的可用空间; - 拉伸因子使用比例数值(如2:1)即可,QSplitter会按比例分配空间,无需使用百分比数值;
- 直接嵌套QSplitter即可,无需额外的QFrame作为中间容器,减少布局层级冗余。
内容的提问来源于stack exchange,提问作者mike rodent
相关产品推荐
相关产品推荐

