第 2 讲 | 布局管理告别手动摆坐标本讲目标彻底搞懂 PyQt 的布局系统做出一个真正像样的界面并理解控件位置全乱了这类问题的根因。配套代码cases/c02_layout_login.py一、为什么不能用 setGeometry先看一个所有人入门时都写过的代码classMyWindow(QWidget):def__init__(self):super().__init__()self.resize(400,300)self.labelQLabel(用户名,self)self.label.setGeometry(30,40,60,25)# x, y, 宽, 高self.editQLineEdit(self)self.edit.setGeometry(100,40,200,25)self.btnQPushButton(登录,self)self.btn.setGeometry(150,100,100,32)运行起来是对的甚至看起来还挺整齐。但它有三个必然会出问题的地方窗口一放大控件纹丝不动。用户把窗口拉大控件还挤在左上角右边下面一大片空白。文字变了就错位。把用户名“改成请输入用户名”60 像素宽度装不下文字被截断。不同系统/DPI 表现不一。macOS 和 Windows 的默认字体行高不同同一套坐标在两边长得不一样。所以结论是除了做动画和自定义绘制永远不要用 setGeometry 摆控件。那用什么答案是布局管理器Layout。二、三大布局覆盖 95% 的场景1. QVBoxLayout竖向排列layoutQVBoxLayout()layout.addWidget(QLabel(第一行))layout.addWidget(QLabel(第二行))layout.addWidget(QLabel(第三行))控件从上到下依次排开宽度自动撑满高度按各自需求分配。2. QHBoxLayout横向排列同样的写法只是方向变成从左到右。横向排列时每个控件分到多宽取决于它的尺寸策略——这是新手第二个高频困惑点本讲第四节专门讲。3. QFormLayout表单专用最省心formQFormLayout()form.addRow(用户名,self.user_edit)form.addRow(密 码,self.pwd_edit)addRow(标签文字, 控件)一行搞定。它会自动把左边标签列对齐这才是它的核心价值标签列宽度取所有标签里最宽的那个右边控件列撑满剩余空间两列表单就用 QFormLayout别自己拿 QHBoxLayout 拼否则标签长度不一时左边一条锯齿状非常难看。三、布局可以嵌套这是关键单层布局只能应付简单界面。真实界面的做法是大块用竖排小块用横排一块块拼起来。看登录窗口的结构┌─────────────────────────────────┐ │ 欢迎登录QLabel │ ← ① 独立控件 ├─────────────────────────────────┤ │ 用户名[______________] │ ← ② QFormLayout │ 密 码[______________] │ ├─────────────────────────────────┤ │ ☑记住我 忘记密码 │ ← ③ QHBoxLayout ├─────────────────────────────────┤ │ [ 登 录 ] │ ← ④ 独立控件 └─────────────────────────────────┘ 外层QVBoxLayout对应代码# ② 表单区formQFormLayout()form.setSpacing(12)form.addRow(用户名,self.user_edit)form.addRow(密 码,self.pwd_edit)# ③ 选项行横排 弹簧optsQHBoxLayout()opts.addWidget(self.remember)opts.addStretch()# 弹簧opts.addWidget(self.forgot)# 组装外层竖排rootQVBoxLayout(self)root.setContentsMargins(28,20,28,20)root.setSpacing(14)root.addWidget(self.title)root.addLayout(form)# ★ 布局里加布局root.addLayout(opts)root.addWidget(self.login_btn)注意root.addLayout(form)和root.addWidget(...)的区别addWidget()加的是控件addLayout()加的是子布局这两种都能套娃所以再复杂的界面也能拆成一层层方块。四、addStretch那个让界面透气的弹簧addStretch()往布局里插入一个可伸缩的空白。它的作用是把两边的控件顶开。rowQHBoxLayout()row.addWidget(btn_left)row.addStretch()# ← 有了它btn_left 靠左btn_right 靠右row.addWidget(btn_right)不加addStretch()会怎样两个按钮会挨在一起或者平均分配宽度。加上之后btn_left被顶到最左边btn_right被顶到最右边。实用口诀想把控件推到左边 → 后面加addStretch()想推到右边 → 前面加addStretch()想居中 →前后各加一个addStretch()addStretch()还可以带参数表示弹性权重addStretch(2)比addStretch(1)撑开的空间大一倍。五、尺寸策略为什么我的按钮被拉宽了这是问得最多的问题“我在横排布局里放了两个按钮为什么第一个被拉得好宽”原因是控件的尺寸策略QSizePolicy。常见的有策略含义典型控件Fixed尺寸固定不随布局变QPushButton 的默认高度Preferred有自己偏好的尺寸可以被拉伸一点QLabelExpanding尽可能占满可用空间QLineEdit、QTextEdit、QTableViewMinimum不小于最小尺寸能被拉大QComboBoxQLineEdit是Expanding的 —— 所以它会吃掉所有剩余宽度把旁边的按钮挤成最小尺寸。这通常正是我们想要的输入框该宽按钮该窄。如果你想让两个按钮平分宽度btn1.setSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed)btn2.setSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed)或者干脆给布局设比例row.addWidget(btn1,1)# 权重 1row.addWidget(btn2,1)# 权重 1 → 两者平分row.addWidget(btn3,2)# 权重 2 → 它占一半前两个各占四分之一推荐用权重stretch的写法比调 QSizePolicy 直观得多。六、间距的三个层次控件挨得太紧或太散都是这三个参数在控制layout.setContentsMargins(28,20,28,20)# 布局到父控件的四边间距左, 上, 右, 下layout.setSpacing(14)# 相邻控件之间的间距layout.addSpacing(20)# 在当前位置插入一段固定空隙经验值照着用界面不会难看场景值窗口整体四边留白12 ~ 20 px表单行间距10 ~ 14 px普通控件之间8 ~ 12 px分组之间的空隙16 ~ 24 px顺手加两条样式表质感立刻不一样self.title.setStyleSheet(font-size: 20px; font-weight: bold; padding: 6px;)self.login_btn.setMinimumHeight(36)self.login_btn.setStyleSheet(QPushButton { background:#2d7ff9; color:white; border:none; border-radius:4px; font-size:14px; }QPushButton:hover { background:#1c6be0; })七、三个高频坑坑 1布局写了但完全没生效控件还是堆在左上角八成是父子关系搞错了。正确写法是二选一rootQVBoxLayout(self)# 写法 A建布局时把父控件传进去# 或rootQVBoxLayout()root.addWidget(...)self.setLayout(root)# 写法 B最后 setLayout别两个都写也别忘了写。坑 2用 addRow 时控件跑到了标签列QFormLayout.addRow()的参数顺序是(标签, 字段)。参数传反了输入框就跑左边去了。坑 3布局嵌套太深界面间距怎么调都不对一般情况下三层嵌套足够。如果超过四层说明你把该分组的没分组用QGroupBox带标题的分组框代替纯布局嵌套代码会清晰很多groupQGroupBox(数据库设置)innerQFormLayout(group)# 直接以 group 为父控件inner.addRow(主机,QLineEdit())八、完整代码# -*- coding: utf-8 -*-第 2 讲 案例 02布局管理 —— 一个像样的登录窗口 知识点 - QVBoxLayout / QHBoxLayout / QFormLayout 三件套 - 布局嵌套外层竖排内部套表单和横排 - addStretch() 做弹簧把控件挤到两端 - setSpacing / setContentsMargins 控制间距 - 输入框的 placeholder 与密码模式 importsysfrompathlibimportPath sys.path.insert(0,str(Path(__file__).resolve().parent.parent))fromPyQt6.QtCoreimportQt# noqa: E402fromPyQt6.QtWidgetsimport(QCheckBox,QFormLayout,QHBoxLayout,# noqa: E402QLabel,QLineEdit,QPushButton,QVBoxLayout,QWidget)fromutils.envimportrun# noqa: E402classLoginWindow(QWidget):一个结构完整的登录界面重点看布局是怎么一层层套起来的def__init__(self)-None:super().__init__()self.setWindowTitle(用户登录)self.resize(380,260)# 区域 1顶部标题 self.titleQLabel(欢迎登录)self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)self.title.setStyleSheet(font-size: 20px; font-weight: bold; padding: 6px;)# 区域 2表单QFormLayout 自动左右两列对齐 self.user_editQLineEdit()self.user_edit.setPlaceholderText(请输入用户名)self.pwd_editQLineEdit()self.pwd_edit.setPlaceholderText(请输入密码)# 密码模式输入内容显示成圆点self.pwd_edit.setEchoMode(QLineEdit.EchoMode.Password)formQFormLayout()form.setSpacing(12)form.addRow(用户名,self.user_edit)form.addRow(密 码,self.pwd_edit)# 区域 3选项行横排 弹簧 self.rememberQCheckBox(记住我)self.forgotQLabel(a href#忘记密码/a)optsQHBoxLayout()opts.addWidget(self.remember)opts.addStretch()# ← 弹簧把左边和右边顶开opts.addWidget(self.forgot)# 区域 4登录按钮 self.login_btnQPushButton(登 录)self.login_btn.setMinimumHeight(36)self.login_btn.setStyleSheet(QPushButton { background:#2d7ff9; color:white; border:none; border-radius:4px; font-size:14px; }QPushButton:hover { background:#1c6be0; })# 组装外层竖排布局 rootQVBoxLayout(self)root.setContentsMargins(28,20,28,20)# 左 上 右 下root.setSpacing(14)root.addWidget(self.title)root.addLayout(form)# ← 布局可以嵌套布局root.addLayout(opts)root.addWidget(self.login_btn)if__name____main__:sys.exit(run(LoginWindow,shotc02_login,width380,height260))九、小结这一讲的核心就四句话永远不用 setGeometry所有位置交给布局三种布局竖排 QVBoxLayout、横排 QHBoxLayout、表单 QFormLayout布局可以嵌套大块竖排、小块横排一层层拼addStretch() 是你的好朋友想让控件靠边或居中就靠它现在界面能排整齐了但按钮点下去还什么都不发生——因为控件之间还没连上线。下一讲信号与槽。这是 PyQt 最核心的机制也是很多人第一次接触时最懵的地方。我会用一个完整的计算器把connect的三种写法和那个lambda 参数绑定的经典坑讲透。本讲是《PyQt6 客户端编程从入门到实战》专栏的第 2 讲。专栏共 22 讲、22 个可运行案例。
阅读完成 · 觉得有帮助?