之前使用 Qt 在界面上创建的控件,都是通过"绝对定位"的方式来设定的
也就是每个控件所在的位置,都需要计算坐标,最终通过 setGeometry 或者 move 方式摆放过去
这种设定方式其实并不方便,尤其是界面如果内容比较多,不好计算。而且一个窗口大小往往是可以调整的,按照绝对定位的方式,也无法自适应窗口大小。
因此 Qt 引入"布局管理器"(Layout)机制,来解决上述问题
垂直布局
使用 QVBoxLayout
表示垂直的布局管理器. V 是 vertical
的缩写.
核心属性:
属性 | 说明 |
---|---|
layoutLeftMargin | 左侧边距 |
layoutRightMargin | 右侧边距 |
layoutTopMargin | 上方边距 |
layoutBottomMargin | 下方边距 |
layoutSpacing | 相邻元素之间的间距 |
Layout 只是用于界面布局,并没有提供信号
代码示例: 使用 QVBoxLayout 管理多个控件
(1) 编写代码,创建布局管理器和三个按钮并且把按钮添加到布局管理器中.
- 使用 addWidget 把控件添加到布局管理器中.
- 使用 setLayout 设置该布局管理器到 widget 中.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
// 创建布局管理器, 并且把按钮添加进去
// 如果创建的时候指定⽗元素为 this, 则后⾯不需要 setLayout ⽅法了.
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);
// 把布局管理器设置到 widget 中
this->setLayout(layout);
}
(2) 运行程序, 可以看到此时界面上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发生改变.
此时三个按钮的尺寸和位置, 都是自动计算出来的.
通过上述代码的方式,只能给这个 widget 设定一个布局管理器。实际上也可以通过 Qt Design 在一个窗口中创建多个布局管理器.
代码示例: 创建两个 QVBoxLayout
(1) 在界面上创建两个 QVBoxLayout ,每个 QVBoxLayout 各放三个按钮
(2) 运行程序, 可以看到这些按钮已经自动排列好. 只不过当前这些按钮的位置不能随着窗口大小自动变化
通过 Qt Designer 创建的布局管理器,其实是先创建了一个 widget,设置过 geometry 属性的。再把这个 layout 设置到这个 widget 中.
实际上,一个 widget 只能包含一个layout
打开 ui 文件的原始 xml,可以看到其中的端倪
这种情况下 layout 并非是窗口 widget 的布局管理器,因此不会随着窗口大小改变
xml
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>140</x>
<y>140</y>
<width>141</width>
<height>331</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
水平布局
使用 QHBoxLayout
表示垂直的布局管理器.H是 horizontal
的缩写!
核心属性 (和 QVBoxLayout 属性是一致的)
代码示例: 使用 QHBoxLayout 管理控件
(1) 编写代码创建布局管理器和三个按钮并且把按钮添加到布局管理器中
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
// 创建布局管理器
QHBoxLayout* layout = new QHBoxLayout();
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);
// 设置 layout 到 widget 上
this->setLayout(layout);
}
(2) 运行程序,可以看到此时界面上的按钮就存在于布局管理器中随着窗口尺寸变化而发生改变
Layout 里面可以再嵌套上其他的 layout, 从而达到更复杂的布局效果
代码示例: 嵌套的 layout
(1) 在代码中创建以下内容
使用 addLayout 给 layout 中添加子 layout.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建顶层 layout
QVBoxLayout* layoutParent = new QVBoxLayout();
this->setLayout(layoutParent);
// 添加两个按钮进去
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
layoutParent->addWidget(btn1);
layoutParent->addWidget(btn2);
// 创建⼦ layout
QHBoxLayout* layoutChild = new QHBoxLayout();
// 添加两个按钮进去
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
layoutChild->addWidget(btn3);
layoutChild->addWidget(btn4);
// 把这个⼦ layout 添加到 ⽗ layout 中
layoutParent->addLayout(layoutChild);
}
(2) 执行程序, 观察结果
结合 QHBoxLayout 和 QVBoxLayout , 就可以做出各种复杂的界面了
网格布局
Qt 中还提供了 QGridLayout
用来实现网格布局的效果可以达到 M*N 的这种网格的效果
核心属性
整体和 QVBoxLayout 以及 QHBoxLayout 相似. 但是设置 spacing 的时候是按照垂直水平两个方向来设置的.
属性 | 说明 |
---|---|
layoutLeftMargin | 左侧边距 |
layoutRightMargin | 右侧边距 |
layoutTopMargin | 上方边距 |
layoutBottomMargin | 下方边距 |
layoutHorizontalSpacing | 相邻元素之间水平方向的间距 |
layoutVerticalSpacing | 相邻元素之间垂直方向的间距 |
layoutRowStretch | 行方向的拉伸系数 |
layoutColumnStretch | 列方向的拉伸系数 |
代码示例: 使用 QGridLayout 管理元素
(1) 代码中创建 QGridLayout 和 4 个按钮.
使用 addWidget 添加控件到布局管理器中. 但是添加的同时会指定两个坐标. 表示放在第几行, 第几列.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 4 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
// 创建⽹格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 1, 0);
layout->addWidget(btn4, 1, 1);
// 设置 layout 到窗⼝中.
this->setLayout(layout);
}
(2) 执行代码, 观察效果. 可以看到当前的这几个按钮是按照 2 行 2 列的方式排列的.
(3) 如果调整行列坐标为下列代码
cpp
// 创建⽹格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 0, 2);
layout->addWidget(btn4, 0, 3);
执行代码, 可以看到这几个按钮都在同一行了. 相当于 QHBoxLayout
(4)) 如果调整行列坐标为下列代码
cpp
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 1, 0);
layout->addWidget(btn2, 2, 0);
layout->addWidget(btn3, 3, 0);
layout->addWidget(btn4, 4, 0);
执行代码, 可以看到这几个按钮都在同⼀列了.相当于 QVBoxLayout
(5) 任意调整行列, 即可看到不同的效果
cpp
// 创建⽹格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 1, 1);
layout->addWidget(btn3, 2, 2);
layout->addWidget(btn4, 3, 3);
(6) 编写代码形如
cpp
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 1, 0);
layout->addWidget(btn3, 2, 0);
layout->addWidget(btn4, 10, 0);
此处也要注意, 设置行和列的时候, 如果设置的是⼀个很大的值, 但是这个值和上一个值之间并没有其他的元素, 那么并不会在中间腾出额外的空间.
虽然把 btn4 设置在第10行,但是由于3-9 行没有元素因此 btn4 仍然会紧挨在 btn3 下方看起来和上面的 123 的情况是相同的
代码示例: 设置 QGridLayout 中元素的大小比例.
(1) 创建 6 个按钮, 按照 2 行 3 列的方式排列
使用 setColumnStretch 设置每⼀列的拉伸系数.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 6 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
QPushButton* btn5 = new QPushButton("按钮5");
QPushButton* btn6 = new QPushButton("按钮6");
// 创建⽹格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 0, 2);
layout->addWidget(btn4, 1, 0);
layout->addWidget(btn5, 1, 1);
layout->addWidget(btn6, 1, 2);
// 设置拉伸⽐例
// 第 0 列拉伸⽐例设为 1;
layout->setColumnStretch(0, 1);
// 第 1 列拉伸⽐例设为 0, 即为固定⼤⼩, 不参与拉伸
layout->setColumnStretch(1, 0);
// 第 2 列拉伸⽐例设为 3, 即为第 2 列的宽度是第 0 列的 3 倍
layout->setColumnStretch(2, 3);
// 设置 layout 到窗⼝中.
this->setLayout(layout);
}
(2) 执行程序, 可以看到每一列的宽度是不同的. 并且随着窗口调整动态变化
另外, QGridLayout 也提供了 setRowStretch 设置行之间的拉伸系数.
上述案例中, 直接设置 setRowStretch 效果不明显, 因为每个按钮的高度是固定的. 需要把按钮的垂直方向的 sizePolicy 属性设置为 QSizePolicy::Expanding 尽可能填充满布局管理器, 才能看到效果.
代码示例: 设置垂直方向的拉伸系数(setSizePolicy)
(1) 编写代码, 创建 6 个按钮, 按照 3 行 2 列方式排列.
使用 setSizePolicy 设置按钮的尺寸策略. 可选的值如下:
QSizePolicy::Ignored
: 忽略控件的尺寸,不对布局产生影响。
QSizePolicy::Minimum
: 控件的最小尺寸为固定值,布局时不会超过该值。
QSizePolicy::Maximum
: 控件的最大尺寸为固定值,布局时不会小于该值。
QSizePolicy::Preferred
: 控件的理想尺寸为固定值,布局时会尽量接近该值。
QSizePolicy::Expanding
: 控件的尺寸可以根据空间调整,尽可能占据更多空间。
QSizePolicy::Shrinking
: 控件的尺寸可以根据空间调整,尽可能缩小以适应空间。
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 6 个按钮
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
QPushButton* btn3 = new QPushButton("按钮3");
QPushButton* btn4 = new QPushButton("按钮4");
QPushButton* btn5 = new QPushButton("按钮5");
QPushButton* btn6 = new QPushButton("按钮6");
// 设置按钮的 sizePolicy, 此时按钮的⽔平⽅向和垂直⽅向都会尽量舒展开
btn1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn4->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn5->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
btn6->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// 创建⽹格布局管理器, 并且添加元素
QGridLayout* layout = new QGridLayout();
layout->addWidget(btn1, 0, 0);
layout->addWidget(btn2, 0, 1);
layout->addWidget(btn3, 1, 0);
layout->addWidget(btn4, 1, 1);
layout->addWidget(btn5, 2, 0);
layout->addWidget(btn6, 2, 1);
// 设置拉伸⽐例
// 第 0 ⾏拉伸⽐例设为 1;
layout->setRowStretch(0, 1);
// 第 1 ⾏拉伸⽐例设为 0, 即为固定⼤⼩, 不参与拉伸
layout->setRowStretch(1, 0);
// 第 2 ⾏拉伸⽐例设为 3, 即为第 2 ⾏的宽度是第 0 ⾏的 3 倍
layout->setRowStretch(2, 3);
// 设置 layout 到窗⼝中.
this->setLayout(layout);
}
(2) 执行代码, 观察效果
此时的按钮垂直方向都舒展开了. 并且调整窗口尺寸, 也会按照设定的比例同步变化.
总的来说, 使用 QGridLayout 能够代替很多 QHBoxLayout 和 QVBoxLayout 嵌套的场景. 毕竟嵌套的代码写起来是比较麻烦的.
另外不要忘了, QGridLayout 里面也能嵌套 QHBoxLayout 和 QVBoxLayout , QHBoxLayout 和 QVBoxLayout 里面也能嵌套 QGridLayout . 灵活使用上述布局管理器, 就可以实现出任意的复杂界面
表单布局
除了上述的布局管理器之外, Qt 还提供了 QFormLayout
, 属于是 QGridLayout
的特殊情况, 专门用于实现两列表单的布局.
这种表单布局多用于让用户填写信息的场景. 左侧列为提示, 右侧列为输入框.
代码示例: 使用 QFormLayout 创建表单.
(1) 编写代码, 创建 QFormLayout , 以及三个 label 和三个 lineEdit
- 使用 addRow 方法来添加一行. 每行包含两个控件. 第一个控件固定是 QLabel / 文本, 第二个控件则可以是任意控件.
- 如果把第一个参数填写为 NULL, 则什么都不显示.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 layout
QFormLayout* layout = new QFormLayout();
this->setLayout(layout);
// 创建三个 label
QLabel* label1 = new QLabel("姓名");
QLabel* label2 = new QLabel("年龄");
QLabel* label3 = new QLabel("电话");
// 创建三个 lineEdit
QLineEdit* lineEdit1 = new QLineEdit();
QLineEdit* lineEdit2 = new QLineEdit();
QLineEdit* lineEdit3 = new QLineEdit();
// 创建⼀个提交按钮
QPushButton* btn = new QPushButton("提交");
// 把上述元素添加到 layout 中
layout->addRow(label1, lineEdit1);
layout->addRow(label2, lineEdit2);
layout->addRow(label3, lineEdit3);
layout->addRow(NULL, btn);
}
(2) 执行程序, 可以看到以下结果.
Spacer
使用布局管理器的时候, 可能需要在控件之间, 添加一段空白. 就可以使用 QSpacerItem
来表示
核心属性:
属性 | 说明 |
---|---|
width | 宽度 |
height | 高度 |
hData | 水平方向的 sizePolicy QSizePolicy::Ignored : 忽略控件的尺寸,不对布局产⽣影响。 QSizePolicy::Minimum : 控件的最小尺寸为固定值,布局时不会超过该值。 QSizePolicy::Maximum : 控件的最大尺寸为固定值,布局时不会小于该值。 QSizePolicy::Preferred : 控件的理想尺寸为固定值,布局时会尽量接近该值。 QSizePolicy::Expanding : 控件的尺寸可以根据空间调整,尽可能占据更多空间。 QSizePolicy::Shrinking : 控件的尺寸可以根据空间调整,尽可能缩小以适应控件 |
vData | 垂直方向的 sizePolicy 选项同上. |
代码示例:创建一组左右排列的按钮
(1) 在界面上创建⼀个 QHBoxLayout , 并添加两个按钮.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
layout->addWidget(btn1);
layout->addWidget(btn2);
}
(2) 直接运行程序, 可以看到两个按钮是紧挨着的.
(3) 在两个按钮中间添加⼀个 spacer
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* btn1 = new QPushButton("按钮1");
QPushButton* btn2 = new QPushButton("按钮2");
// 创建 Spacer
QSpacerItem* spacer = new QSpacerItem(200, 20);
layout->addWidget(btn1);
// 在两个 widget 中间添加空⽩
layout->addSpacerItem(spacer);
layout->addWidget(btn2);
}
(4) 运行程序, 观察代码效果. 可以看到两个按钮之间已经存在了间隔了.
调整 QSpacerItem 不同的尺寸, 即可看到不同的间距
在 Qt Designer 中, 也可以直接给界面上添加 spacer