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