You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

正确实现方案

问题根源

  1. 直接嵌套时重写sizeHint会干扰QSplitter的自动布局逻辑,导致拉伸因子失效;
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 10:15:50