Qt 布局管理
Qt 主要提供了 QLayout 类及其子类来作为布局管理器。QLayout 类继承于 QObject 类与 QLayoutItem 类。
[TOC]
1 布局管理系统
所有 QWidget 的子类对象都可以使用布局管理器管理位于它们之中的子部件,QWidget::setLayout
可以在一个部件上应用布局管理器。
在一个部件上设置了布局管理器,可以完成如下功能:
1 2 3 4 5
| 定位子部件 感知窗口默认大小 感知窗口最小大小 窗口大小变化时进行处理 当内容改变时自动更新
|
1.1 简介
QLayout 是布局管理器的基类,它是一个抽象类,继承于 QObject 类和 Q LayoutItem 类。QLayout 有几个子类:
QBoxLayout (基本布局管理器)
QGridLayout (栅格布局管理器)
QFormLayout (窗体布局管理器)
QStackedLayout (栈布局管理器)
其中QStackedLayout与上一讲的QStackedWidget用法相似,不再讲述。
1.2 基本布局管理器
QBoxLayout可以使子部件在水平方向或者垂直方向排成一列。
它将所有空间分成一行box,然后把每个部件放入一个box中。
- 它有两个子类:
QHBoxLayout(水平布局管理器)和QVBoxLayout(垂直布局管理器)。
布局管理器有如下常用属性:
| 属性 |
名称 |
| layoutName |
布局管理器名称 |
| layoutLeftMargin |
布局管理器到左边界距离 |
| 。layoutTopMargin |
布局管理器到左上边界距离 |
| layoutRightMargin |
布局管理器到右边界距离 |
| layoutBottomMargin |
布局管理器到下边界距离 |
| layoutSpacing |
布局管理器中各个子部件之间的距离 |
| layoutStretch |
伸缩因子 |
| layoutSizeConstraint |
设置大小的约束条件 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| void setContentsMargins(int left, int top, int right, int bottom);
void getContentsMargins(int *left, int *top, int *right, int *bottom) const;
void setSpacing(int spacing);
int spacing() const;
void setSizeConstraint(QLayout::SizeConstraint constraint);
QLayout::SizeConstraint sizeConstraint() const;
void setGeometry(const QRect &rect);
QRect geometry() const;
void invalidate();
bool activate();
void addWidget(QWidget *widget);
void addLayout(QLayout *layout);
|
示例:
1 2 3 4 5
| QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(10, 10, 10, 10); layout->setSpacing(8); layout->addWidget(new QLabel("用户名", this)); layout->addWidget(new QLineEdit(this));
|
表示的含义:
1 2 3
| 整个布局距离窗口边缘 10px 控件之间间隔 8px 控件按垂直方向排列
|
1.3 栅格布局管理器
QGridLayout将部件在网格中布局,将所有的空间分为行和列,交叉处形成单元格,将部件放入一个特定的单元格中。
核心概念
1 2 3 4 5 6
| row / column 控件所在的行和列 spacing 行列之间的间距 stretch 窗口放大时的空间分配比例 minimum size 行或列允许的最小尺寸 rowSpan 跨越的行数 columnSpan 跨越的列数
|
常用属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| void setHorizontalSpacing(int spacing);
int horizontalSpacing() const;
void setVerticalSpacing(int spacing);
int verticalSpacing() const;
void setSpacing(int spacing);
void setContentsMargins(int left, int top, int right, int bottom);
void getContentsMargins(int *left, int *top, int *right, int *bottom) const;
void setRowStretch(int row, int stretch);
void setColumnStretch(int column, int stretch);
int rowStretch(int row) const;
int columnStretch(int column) const;
void setRowMinimumHeight(int row, int minSize);
void setColumnMinimumWidth(int column, int minSize);
void addWidget(QWidget *widget, int row, int column);
void addWidget(QWidget *widget, int row, int column, int rowSpan, int columnSpan);
void addLayout(QLayout *layout, int row, int column);
void addLayout(QLayout *layout, int row, int column, int rowSpan, int columnSpan);
int rowCount() const;
|
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| QWidget *window = new QWidget;
QLabel *userLabel = new QLabel("用户名:", window); QLineEdit *userEdit = new QLineEdit(window);
QLabel *passwordLabel = new QLabel("密码:", window); QLineEdit *passwordEdit = new QLineEdit(window); passwordEdit->setEchoMode(QLineEdit::Password);
QPushButton *loginButton = new QPushButton("登录", window); QPushButton *cancelButton = new QPushButton("取消", window);
QGridLayout *layout = new QGridLayout(window);
layout->addWidget(userLabel, 0, 0); layout->addWidget(userEdit, 0, 1);
layout->addWidget(passwordLabel, 1, 0); layout->addWidget(passwordEdit, 1, 1);
layout->addWidget(loginButton, 2, 0); layout->addWidget(cancelButton, 2, 1);
window->setLayout(layout); window->show();
|
上述代码的流程是:
- 创建一个顶级窗口 window
- 创建一个布局 layout
- 往 layout 里面添加控件
- 把 layout 安装到 window 上
- window 显示时,layout 负责摆放控件
1.4 窗体布局管理器
eg.
1 2 3 4 5
| 用户名: [____________]
密码: [____________]
邮箱: [____________]
|
写法:
1 2 3 4 5
| QFormLayout *layout = new QFormLayout;
layout->addRow("用户名:", new QLineEdit); layout->addRow("密码:", new QLineEdit); layout->addRow("邮箱:", new QLineEdit);
|
常用函数有:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void addRow(const QString &labelText, QWidget *field);
void addRow(QWidget *label, QWidget *field);
void addRow(QWidget *widget);
void addRow(QLayout *layout);
|
常用属性和枚举量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
void setLabelAlignment(Qt::Alignment alignment);
Qt::AlignLeft; Qt::AlignRight; Qt::AlignHCenter; Qt::AlignTop; Qt::AlignVCenter; Qt::AlignCenter;
void setFormAlignment(Qt::Alignment alignment);
Qt::AlignLeft; Qt::AlignRight; Qt::AlignHCenter; Qt::AlignTop; Qt::AlignBottom; Qt::AlignCenter;
void setRowWrapPolicy(QFormLayout::RowWrapPolicy policy);
QFormLayout::DontWrapRows; QFormLayout::WrapLongRows; QFormLayout::WrapAllRows;
void setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy policy);
QFormLayout::FieldsStayAtSizeHint; QFormLayout::ExpandingFieldsGrow; QFormLayout::AllNonFixedFieldsGrow;
void setHorizontalSpacing(int spacing);
void setVerticalSpacing(int spacing);
void setSpacing(int spacing);
void setContentsMargins(int left, int top, int right, int bottom);
|
可以发现,Qt中很多时候都是传入某个枚举类型的枚举值。
1.5 综合使用
1 2 3 4 5 6 7 8 9 10 11
| ┌────────────────────────────┐ │ 标题 │ │ │ │ API Key: [____________] │ ← QFormLayout │ 模型: [____________] │ │ │ │ [选项A] [选项B] │ ← QGridLayout │ [选项C] [选项D] │ │ │ │ [取消] [保存] │ ← QHBoxLayout └────────────────────────────┘
|
如:
1 2 3 4 5 6 7 8 9 10 11 12
| ┌────────────────────────────────────────────┐ │ LLM 设置 │ │ │ │ API Key: [__________________________] │ │ 模型: [__________________________] │ │ Base URL: [__________________________] │ │ │ │ [ ] 启用搜索 [ ] 流式输出 │ │ [ ] 保存历史 [ ] 开机启动 │ │ │ │ [取消] [保存] │ └────────────────────────────────────────────┘
|
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| QWidget *window = new QWidget;
QVBoxLayout *mainLayout = new QVBoxLayout(window);
QLabel *title = new QLabel("LLM 设置", window); mainLayout->addWidget(title);
QFormLayout *formLayout = new QFormLayout; formLayout->addRow("API Key:", new QLineEdit(window)); formLayout->addRow("模型:", new QLineEdit(window)); formLayout->addRow("Base URL:", new QLineEdit(window)); mainLayout->addLayout(formLayout);
QGridLayout *gridLayout = new QGridLayout; gridLayout->addWidget(new QCheckBox("启用搜索", window), 0, 0); gridLayout->addWidget(new QCheckBox("流式输出", window), 0, 1); gridLayout->addWidget(new QCheckBox("保存历史", window), 1, 0); gridLayout->addWidget(new QCheckBox("开机启动", window), 1, 1); mainLayout->addLayout(gridLayout);
QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(new QPushButton("取消", window)); buttonLayout->addWidget(new QPushButton("保存", window)); mainLayout->addLayout(buttonLayout);
window->show();
|
1.6 部件大小
1.6.1 大小提示和最小大小提示
所有继承于QWidget的类都有这两个属性。
sizeHint保存了建议大小,不同的部件默认有不同的sizeHint。通过调用sizeHint()可以获取其值。
minimumSizeHint保存了最小建议大小。通过调用minimumSizeHint()可以获取其值。
1.6.2 QSizePolicy 大小策略属性
以下是QSizepolicy的所有枚举值:
| 常量 |
含义 |
| QSizePolicy::Fixed |
只使用sizeHint()提供是值,不能伸缩 |
| QSizePolicy::Preferred |
sizeHint()提供的是最佳大小,可以伸缩 |
| QSizePolicy::Expanding |
sizeHint()提供的是最佳大小,可以伸缩,有多余空间就自动被拉伸 |
| QSizePolicy::Ignored |
sizeHint()值被忽略,可以伸缩,有多少空间全部拉伸 |
| QSizePolicy::Minimum |
sizeHint()值为最小值,可以伸缩 |
| QSizePolicy::Maximum |
sizeHint()值为最大值,可以伸缩 |
| QSizePolicy::MinimumExpanding |
sizeHint()值为最小值,可以伸缩,有多余空间就自动被拉伸 |
1.6.3 缩放因子 Stretch Factor
缩放因子不是控件自己的大小,而是不同控件之间的比例分配。
例:
1 2 3 4 5 6 7
| QHBoxLayout *layout = new QHBoxLayout(this);
QPushButton *btn1 = new QPushButton("A", this); QPushButton *btn2 = new QPushButton("B", this);
layout->addWidget(btn1, 1); layout->addWidget(btn2, 2);
|
1 和 2 就是缩放因子。
btn1 : btn2 = 1 : 2,窗口变宽时,多余空间按 1:2 分配。
如果是
1 2
| layout->addWidget(btn1); layout->addWidget(btn2);
|
相当于
1 2
| layout->addWidget(btn1, 0); layout->addWidget(btn2, 0);
|
则根据sizeHint()、sizePolicy()来划分。
常用函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void QBoxLayout::addWidget(QWidget *widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment());
void QBoxLayout::addLayout(QLayout *layout, int stretch = 0);
void QBoxLayout::addStretch(int stretch = 0);
void QBoxLayout::setStretch(int index, int stretch);
int QBoxLayout::stretch(int index) const;
|
1.7 可扩展窗口
一个窗口的功能是很多的,其中一部分只在需要的时候显示出来或者消失,这就是可扩展窗口。
eg.
1 2 3 4 5 6 7
| 展开前:
┌────────────────────────┐ │ 搜索内容: [________] │ │ │ │ [更多 >>] [搜索] │ └────────────────────────┘
|
1 2 3 4 5 6 7 8 9 10 11
| 展开后:
┌────────────────────────┐ │ 搜索内容: [________] │ │ │ │ 大小写敏感: [ ] │ │ 全词匹配: [ ] │ │ 使用正则: [ ] │ │ │ │ [更少 <<] [搜索] │ └────────────────────────┘
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <QDialog> #include <QVBoxLayout> #include <QHBoxLayout> #include <QFormLayout> #include <QLineEdit> #include <QCheckBox> #include <QPushButton> #include <QWidget>
class SearchDialog : public QDialog { public: explicit SearchDialog(QWidget *parent = nullptr) : QDialog(parent) { setWindowTitle("搜索");
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QFormLayout *formLayout = new QFormLayout; QLineEdit *searchEdit = new QLineEdit(this); formLayout->addRow("搜索内容:", searchEdit);
extensionWidget = new QWidget(this); QVBoxLayout *extensionLayout = new QVBoxLayout(extensionWidget); extensionLayout->setContentsMargins(0, 0, 0, 0);
QCheckBox *caseCheck = new QCheckBox("大小写敏感", extensionWidget); QCheckBox *wordCheck = new QCheckBox("全词匹配", extensionWidget); QCheckBox *regexCheck = new QCheckBox("使用正则", extensionWidget);
extensionLayout->addWidget(caseCheck); extensionLayout->addWidget(wordCheck); extensionLayout->addWidget(regexCheck);
extensionWidget->hide();
QHBoxLayout *buttonLayout = new QHBoxLayout; QPushButton *moreButton = new QPushButton("更多 >>", this); QPushButton *searchButton = new QPushButton("搜索", this);
buttonLayout->addStretch(); buttonLayout->addWidget(moreButton); buttonLayout->addWidget(searchButton);
mainLayout->addLayout(formLayout); mainLayout->addWidget(extensionWidget); mainLayout->addLayout(buttonLayout);
connect(moreButton, &QPushButton::clicked, this, [=]() { bool visible = extensionWidget->isVisible(); extensionWidget->setVisible(!visible); moreButton->setText(visible ? "更多 >>" : "更少 <<"); adjustSize(); }); }
private: QWidget *extensionWidget = nullptr; };
|
1.8 拆分器 QSplitter
不说概念了,直接上图
1 2 3 4 5 6 7
| ┌───────────────┬──────────────────────┐ │ 左侧列表 │ 右侧内容区 │ │ │ │ │ │ │ └───────────────┴──────────────────────┘ ↑ 这里可以拖动
|
典型用途:
1 2 3 4
| 文件管理器:左侧目录树 + 右侧文件列表 代码编辑器:左侧项目树 + 右侧编辑区 聊天软件:左侧会话列表 + 右侧聊天内容 设置窗口:左侧分类导航 + 右侧设置页面
|
- 事实上,QSplitter类继承于QFrame类即继承于QWidget类。换言之,QSplitter类具有QWidget类的特性,可以像QFrame类一样设置边框。
常用函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| QSplitter(Qt::Orientation orientation, QWidget *parent = nullptr);
void addWidget(QWidget *widget);
void insertWidget(int index, QWidget *widget);
void setOrientation(Qt::Orientation orientation);
Qt::Orientation orientation() const;
void setSizes(const QList<int> &list);
QList<int> sizes() const;
void setCollapsible(int index, bool collapse);
void setChildrenCollapsible(bool collapse);
|
其中,Qt::Orientation有两个枚举值:
1 2 3 4 5
| Qt::Horizontal
Qt::Vertical
|
2 伙伴 buddy
buddy 机制提出于QLabel类。让一个标签 QLabel 和另一个输入控件建立关联,
用户按快捷键时,焦点自动跳到对应控件。
例如:
1 2
| 用户名(&U): [____________] 密码(&P): [____________]
|
这里 用户名(&U) 这个 label 的伙伴就是右边的 QLineEdit。
Q: &U是什么?
A:&U 表示快捷键助记符,也叫 mnemonic。用户按:
Qt 就会触发这个 label 的快捷键。
实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| QWidget *window = new QWidget;
QLabel *userLabel = new QLabel("用户名(&U):", window); QLineEdit *userEdit = new QLineEdit(window);
QLabel *passwordLabel = new QLabel("密码(&P):", window); QLineEdit *passwordEdit = new QLineEdit(window); passwordEdit->setEchoMode(QLineEdit::Password);
userLabel->setBuddy(userEdit); passwordLabel->setBuddy(passwordEdit);
QGridLayout *layout = new QGridLayout(window); layout->addWidget(userLabel, 0, 0); layout->addWidget(userEdit, 0, 1); layout->addWidget(passwordLabel, 1, 0); layout->addWidget(passwordEdit, 1, 1);
window->show();
|
3 设计Tab键顺序
核心函数:
1
| static void QWidget::setTabOrder(QWidget *first, QWidget *second);
|
意思是:按 Tab 时,从 first 跳到 second
例如登录窗口:
1 2 3 4 5 6 7 8
| QLineEdit *userEdit = new QLineEdit(this); QLineEdit *passwordEdit = new QLineEdit(this); QPushButton *loginButton = new QPushButton("登录", this); QPushButton *cancelButton = new QPushButton("取消", this);
QWidget::setTabOrder(userEdit, passwordEdit); QWidget::setTabOrder(passwordEdit, loginButton); QWidget::setTabOrder(loginButton, cancelButton);
|
这样按 Tab 的顺序就是:
1
| userEdit → passwordEdit → loginButton → cancelButton
|
设置链式顺序时要按顺序写:setTabOrder(a, b); setTabOrder(b, c); setTabOrder(c, d);,不要乱序。