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的知识。

相关推荐
爱码小白8 分钟前
Python 异常处理 完整学习笔记
开发语言·python
芝士就是力量啊 ೄ೨37 分钟前
Python如何编写一个简单的类
开发语言·python
胖虎喜欢静香1 小时前
从零到一快速实现 Mini DeepResearch
人工智能·python·开源
qq_392690661 小时前
Redis怎样应对Redis集群整体宕机带来的雪崩
jvm·数据库·python
zhangrelay1 小时前
三分钟云课实践速通--模拟电子技术-模电--SimulIDE
linux·笔记·学习·ubuntu·lubuntu
木木_王1 小时前
嵌入式Linux学习 | 数据结构 (Day05) 栈与队列详解(原理 + C 语言实现 + 实战实验 + 易错点剖析)
linux·c语言·开发语言·数据结构·笔记·学习
lkforce1 小时前
MiniMind学习笔记(三)--train_pretrain.py(预训练)
笔记·机器学习·ai·预训练·minimind·train_pretrain
Muyuan19981 小时前
22.让 RAG Agent 更像真实产品:聊天页面优化、PDF 上传、知识库重建与检索片段展示
python·django·pdf·fastapi
OSwich1 小时前
【 Godot 4 学习笔记】数组(Array)
笔记·学习·godot
超龄编码人1 小时前
Qt Widgets Designer QTabWidget无法添加布局
开发语言·qt