PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略

在 PySide6 中,布局管理器(Layout)是实现界面自适应、控件合理排布的核心,而尺寸策略(Size Policy)则决定了控件在布局中如何响应尺寸变化。二者结合,就可以构建健壮、自适应、可维护的图形界面,从根本上解决手动低效率布局和频繁计算几何尺寸的痛点。


在上一节容器类部件的学习中,有一段代码:

python 复制代码
import sys

from PySide6.QtWidgets import QApplication, QWidget, QPushButton


# 定义一个父容器类,作为主窗口
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QWidget 基础容器示例")
        self.setStyleSheet("background-color: green;")  # 设置容器的显示样式,方便观察
        self.resize(300, 200)

        # 创建 QWidget 作为子容器
        son_container = QWidget(self)
        son_container.setGeometry(10, 10, 280, 180)  # 设置子容器的几何特征,位置为 (10, 10),大小为 (280, 180)
        son_container.setStyleSheet("background-color: red")  # 设置容器的显示样式,方便观察
        button = QPushButton("子容器的按钮", son_container)  # 创建按钮,作为子容器的子控件
        button.setStyleSheet("background-color: gray")  # 设置按钮的显示样式
        button.setGeometry(40, 50, 200, 40)  # 设置按钮的几何特征


if __name__ == "__main__":
    app = QApplication(sys.argv)  # 创建 QApplication 对象
    main_window = MainWindow()  # 创建 MainWindow 实例对象
    main_window.show()  # 显示窗口
    sys.exit(app.exec())  # 退出应用程序

运行后显示如下:

在上面的代码中,使用setGeometry()指定了子部件在父容器中的位置,如果再增加一个新的子部件在父容器中,可能就要重新计算子部件的几何参数;或者动态修改父容器的尺寸,比如,手动拖动父容器的窗口大小,子部件的几何参数并不会动态地随父容器改变:

现在使用布局管理器,并合理设置尺寸策略,重写以上代码:

python 复制代码
import sys

from PySide6 import QtWidgets
from PySide6.QtWidgets import (QApplication, QWidget, QPushButton,
                               QVBoxLayout, QHBoxLayout)
from PySide6.QtCore import Qt

# 定义一个父容器类,作为主窗口
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("布局管理器+尺寸策略示例")
        self.setStyleSheet("background-color: green;")  # 父容器背景色
        self.resize(300, 200)

        # ========== 1. 父容器的主布局(控制子容器的位置/大小) ==========
        main_layout = QVBoxLayout(self)  # 父容器绑定垂直布局
        # 设置父布局边距(对应原代码子容器的10px边距)
        main_layout.setContentsMargins(10, 10, 10, 10)
        
        # ========== 2. 创建按钮并设置尺寸策略(固定大小) ==========
        button = QPushButton("子容器的按钮")
        button.setStyleSheet("background-color: gray")
        # 核心:设置按钮尺寸策略为 Fixed(固定大小)
        button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        # 设置按钮固定尺寸(对应原代码的200x40)
        button.setFixedSize(200, 40)

        # ========== 3. 将按钮添加到布局(实现居中) ==========
        # 添加拉伸项:按钮左右/上下的空白自动填充,实现居中
        son_container = QWidget()
        son_container.setStyleSheet("background-color: red")  # 子容器背景色
        son_layout = QVBoxLayout(son_container)
        son_layout.addStretch()  # 上方拉伸
        son_layout.addWidget(button, alignment=Qt.AlignCenter)  # 水平+垂直居中
        son_layout.addStretch()  # 下方拉伸

        # ========== 4. 将子容器添加到父布局 ==========
        main_layout.addWidget(son_container)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    # 窗口缩放时,按钮仍保持固定大小+居中
    sys.exit(app.exec())

修改后的代码,当手动改变父容器的尺寸,子容器自动随之改变尺寸,保持了固定的四边距,而子容器中的按钮则会随时居中显示并保持固定大小。


下面将详细介绍布局管理器和尺寸策略的应用。

布局管理器

布局管理器,存在的意义远不止 "排列"和"布局",它的本质是将控件的位置 / 尺寸计算逻辑从开发者转移到 Qt 框架,核心价值体现在 "自适应" 和 "自动化":

1. 自动适配窗口缩放与分辨率

手动布局(如 setGeometry(x, y, w, h) 或 move())的控件位置 / 尺寸是固定值,窗口缩放、屏幕分辨率变化(如从 1080p 到 4K)、系统缩放(如 150% DPI)时,界面会出现:

  • 控件重叠、截断;
  • 空白区域过多;
  • 控件超出窗口可视范围。

而布局管理器会实时重新计算所有控件的位置和尺寸,确保:

  • 控件始终在可视范围内;
  • 空间分配合理(如拉伸控件占满空白);
  • 界面比例与内容适配。

一、核心概念

1. 布局管理器的作用

  • 自动调整控件的位置和大小,适配窗口缩放、不同分辨率屏幕
  • 避免手动设置控件坐标(setGeometry)导致的界面错乱
  • 支持控件的动态添加 / 移除,保持布局一致性
python 复制代码
# ❶ 手动布局(反例):窗口缩放后按钮位置固定,界面错乱
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout


class BadExample(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("手动布局(反例)")
        self.resize(300, 200)
        # 固定坐标和尺寸,窗口缩放后无变化
        btn1 = QPushButton("按钮1", self)
        btn1.setGeometry(20, 20, 80, 30)
        btn2 = QPushButton("按钮2", self)
        btn2.setGeometry(120, 20, 80, 30)

# ❷ 布局管理器(正例):窗口缩放后按钮自动拉伸/对齐
class GoodExample(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("布局管理器(正例)")
        self.resize(300, 200)
        layout = QHBoxLayout(self)
        layout.addWidget(QPushButton("按钮1"))
        layout.addWidget(QPushButton("按钮2"))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win1 = BadExample()  # 缩放窗口看效果
    win2 = GoodExample()   # 缩放窗口,按钮自动适配
    win1.show()
    win1.move(500,500)
    win2.show()
    win2.move(900,500)
    sys.exit(app.exec())

  • 常用布局管理器

PySide6 提供 4 种核心布局,均继承自 QLayout,需通过 setLayout() 绑定到父控件(如 QWidget/QMainWindow)。

1. QHBoxLayout (水平布局)

将控件沿水平方向排列(从左到右)。

python 复制代码
import sys
from PySide6.QtWidgets import (QApplication, QWidget, QHBoxLayout,
                               QPushButton, QLabel)

class HBoxExample(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("水平布局示例")
        layout = QHBoxLayout(self)  # 创建水平布局并绑定到当前窗口

        # 添加控件
        layout.addWidget(QLabel("标签1"))
        layout.addWidget(QPushButton("按钮1"))
        layout.addWidget(QPushButton("按钮2"))

        # 布局间距/边距设置
        layout.setSpacing(10)  # 控件之间的间距
        layout.setContentsMargins(20, 20, 20, 20)  # 布局边缘与父控件的边距(左、上、右、下)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = HBoxExample()
    win.show()
    sys.exit(app.exec())

2. QVBoxLayout (垂直布局)

将控件沿垂直方向排列(从上到下),用法与水平布局完全一致,仅排列方向不同:

python 复制代码
layout = QVBoxLayout(self)

替换上述示例中的布局即可

3. QGridLayout (网格布局)

按行 / 列的网格形式排列控件,支持跨行列,是最灵活的布局:

python 复制代码
import sys

from PySide6.QtWidgets import QGridLayout, QLabel, QLineEdit, QPushButton, QApplication, QWidget


class GridExample(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("网格布局示例")
        layout = QGridLayout(self)

        # 添加控件:addWidget(控件, 行, 列, 跨行数, 跨列数)
        layout.addWidget(QLabel("用户名:"), 0, 0)
        layout.addWidget(QLineEdit(), 0, 1)
        layout.addWidget(QLabel("密码:"), 1, 0)
        layout.addWidget(QLineEdit(), 1, 1)
        # 按钮跨2列
        layout.addWidget(QPushButton("登录"), 2, 0, 1, 2)

        # 设置列拉伸因子(第1列拉伸优先级更高)
        layout.setColumnStretch(0, 1)
        layout.setColumnStretch(1, 3)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = GridExample()
    win.show()
    sys.exit(app.exec())

4. QFormLayout (表单布局)

专为标签 + 输入框的表单场景设计,自动对齐:

python 复制代码
import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget, QFormLayout, QLineEdit, QSpinBox, QApplication


class FormExample(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("表单布局示例")
        layout = QFormLayout(self)

        # 添加行:addRow(标签, 输入控件)
        layout.addRow("姓名", QLineEdit())
        layout.addRow("年龄", QSpinBox())
        layout.addRow("邮箱", QLineEdit())

        # 设置对齐方式
        layout.setLabelAlignment(Qt.AlignRight)  # 标签右对齐

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = FormExample()
    win.show()
    sys.exit(app.exec())

5. 布局嵌套

实际开发中需嵌套布局实现复杂界面:

python 复制代码
import sys

from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QGridLayout, QPushButton, \
    QApplication


class NestedLayout(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("布局嵌套示例")
        # 主布局(垂直)
        main_layout = QVBoxLayout(self)

        # 顶部水平布局
        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel("标题:"))
        top_layout.addWidget(QLineEdit())
        main_layout.addLayout(top_layout)  # 布局添加到布局

        # 中间网格布局
        grid_layout = QGridLayout()
        grid_layout.addWidget(QPushButton("按钮1"), 0, 0)
        grid_layout.addWidget(QPushButton("按钮2"), 0, 1)
        main_layout.addLayout(grid_layout)

        # 底部按钮(水平居中)
        bottom_layout = QHBoxLayout()
        bottom_layout.addStretch()  # 左侧拉伸
        bottom_layout.addWidget(QPushButton("确定"))
        bottom_layout.addWidget(QPushButton("取消"))
        bottom_layout.addStretch()  # 右侧拉伸
        main_layout.addLayout(bottom_layout)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = NestedLayout()
    win.show()
    sys.exit(app.exec())

尺寸策略

QSizePolicy 控制控件在布局中的尺寸行为,每个控件默认有尺寸策略,可通过 setSizePolicy() 自定义。

1. 核心参数

尺寸策略包含水平策略垂直策略,可选值:

策略值 含义
QSizePolicy.Fixed 固定尺寸,仅使用 sizeHint(),不缩放
QSizePolicy.Minimum 最小尺寸为 sizeHint(),可放大(但不主动拉伸)
QSizePolicy.Maximum 最大尺寸为 sizeHint(),可缩小
QSizePolicy.Preferred 首选尺寸 sizeHint(),可放大 / 缩小(默认值)
QSizePolicy.Expanding 优先拉伸,尽可能占满可用空间
QSizePolicy.MinimumExpanding 最小尺寸 sizeHint(),优先拉伸
QSizePolicy.Ignored 忽略 sizeHint(),完全由布局控制

2. 几个 关键方法

python 复制代码
# 获取/设置尺寸策略
policy = widget.sizePolicy()
widget.setSizePolicy(horizontal_policy, vertical_policy)

# 设置最小/最大/固定尺寸
widget.setMinimumSize(width, height)
widget.setMaximumSize(width, height)
widget.setFixedSize(width, height)  # 等价于同时设置最小/最大

# 获取首选尺寸(控件自身推荐的尺寸)
hint = widget.sizeHint()

3. 应用实例

python 复制代码
import sys

from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QSizePolicy, QApplication


class SizePolicyExample(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("尺寸策略示例")
        layout = QHBoxLayout(self)

        # 按钮1:固定尺寸
        btn1 = QPushButton("Fixed")
        btn1.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        btn1.setFixedSize(80, 40)
        layout.addWidget(btn1)

        # 按钮2:优先拉伸(Expanding)
        btn2 = QPushButton("Expanding")
        btn2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        layout.addWidget(btn2)

        # 按钮3:仅最小尺寸,可放大但不主动拉伸
        btn3 = QPushButton("Minimum")
        btn3.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        layout.addWidget(btn3)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = SizePolicyExample()
    win.resize(400, 100)
    win.show()
    sys.exit(app.exec())

4. 拉伸因子(Stretch Factor)

布局的 setStretch() 方法可设置控件 / 布局的拉伸优先级:

python 复制代码
layout = QHBoxLayout(self)
btn1 = QPushButton("拉伸因子1")
btn2 = QPushButton("拉伸因子2")
layout.addWidget(btn1)
layout.addWidget(btn2)

# 设置拉伸因子:btn2的拉伸优先级是btn1的2倍
layout.setStretch(0, 1)  # 索引0的控件(btn1)拉伸因子1
layout.setStretch(1, 2)  # 索引1的控件(btn2)拉伸因子2

5. 空白拉伸(addStretch)

addStretch() 向布局中添加空白拉伸项,用于控件对齐:

python 复制代码
layout = QHBoxLayout(self)
# 控件左对齐
layout.addWidget(QPushButton("左对齐"))
layout.addStretch()  # 右侧空白拉伸

# 控件右对齐
layout.addStretch()
layout.addWidget(QPushButton("右对齐"))

# 控件居中
layout.addStretch()
layout.addWidget(QPushButton("居中"))
layout.addStretch()

常见问题与最佳实践

1. 常见坑点

  • QMainWindow 布局:前面的demo都是用QWidget做例子的,实际的编程中,主程序窗口往往是QMainWindow,QMainWindow需先设置 centralWidget,再给 centralWidget 设布局:
python 复制代码
from PySide6.QtWidgets import QWidget, QVBoxLayout, QMainWindow

main_window = QMainWindow()

central = QWidget()

main_window.setCentralWidget(central)

layout = QVBoxLayout(central) # 布局绑定到centralWidget
  • 控件重复添加:一个控件只能属于一个布局,重复添加会导致布局错乱
  • 尺寸策略冲突:同时设置了几种尺寸策略,比如:setFixedSize 和 Expanding 策略,Fixed 优先级更高

2. 最佳实践

  1. 优先使用布局管理器,避免手动设置坐标
  2. 合理嵌套布局,单层布局最好不超过 8 个控件
  3. 用 addStretch 实现控件对齐,而非固定间距
  4. 对关键控件设置最小尺寸,避免缩放过小导致内容截断
  5. 复杂界面拆分多个子控件(QWidget),每个子控件独立布局
  6. 根据需求灵活设置尺寸策略

通过灵活组合布局管理器和尺寸策略,可实现适配不同屏幕、支持缩放的专业级 Qt 界面。


下一节将要学习QMainWindow的知识。

相关推荐
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 8 继承
c++·笔记·学习
2401_834517072 小时前
AD学习笔记-34 PCBlogo的添加
笔记·学习
被考核重击3 小时前
浏览器原理
前端·笔记·学习
Lynnxiaowen3 小时前
今天我们继续学习kubernetes内容Helm
linux·学习·容器·kubernetes·云计算
charlie1145141913 小时前
编写INI Parser 测试完整指南 - 从零开始
开发语言·c++·笔记·学习·算法·单元测试·测试
冬夜戏雪3 小时前
【java学习日记】【12.14】【12/60】
学习
数据科学项目实践3 小时前
建模步骤 3 :数据探索(EDA) — 1、初步了解数据:常用函数
人工智能·python·机器学习·数据挖掘·数据分析·pandas·数据可视化
老华带你飞3 小时前
列车售票|基于springboot 列车售票系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习·spring
Chen--Xing3 小时前
2025鹏城杯 -- Crypto -- RandomAudit详解
python·密码学·ctf·鹏城杯