布局管理器

之前使用 Qt 在界面上创建的控件, 都是通过 "绝对定位" 的方式来设定的 --- 之前把控件放在界面上,都是靠 “手动” 的方式来进行布局的!

也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move 方式摆放过去.

这种设定方式其实并不方便. 尤其是界面如果内容比较多, 不好计算. 而且一个窗口大小往往是可以调整的, 按照绝对定位的方式, 也无法自适应窗口大小.

因此 Qt 引入 "布局管理器" (Layout) 机制, 来解决上述问题.

😼当然, 布局管理器并非 Qt 独有. 其他的 GUI 开发框架, 像 Android, 前端等也有类似的机制.

  1. 垂直布局
  2. 水平布局
  3. 网络布局
  4. 表单布局

垂直布局

使用 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);

    // 创建三个按钮,使用垂直布局管理器,管理起来!
    QPushButton* button1 = new QPushButton("按钮1");//暂时不指定父元素
    QPushButton* button2 = new QPushButton("按钮2");//暂时不指定父元素
    QPushButton* button3 = new QPushButton("按钮3");//暂时不指定父元素

    // 创建布局管理器
    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);

    // 将布局管理器添加到窗口中
    this->setLayout(layout);
}

2.运行程序, 可以看到此时界面上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发和改变.

此时三个按钮的尺十和位置, 都是自动计算出来的

通过上述代码的方式, 每个 Widget 中,只能设置一个布局管理器。

实际上也可以通过 Qt Design 在一个窗口中创建多个布局管理器。

代码示例: 创建两个 QVBoxLayout

1.我们在ui界面上创建两个 QVBoxLayout ,每个 QVBoxLayout 各放三个按钮.

2.运行程序, 可以看到这些按钮已经自动排列好. 只不过当前这些按钮的位置不能随着窗口大小自动变化。

🍕通过 Qt Designer 创建的布局管理器, 其实是先创建了一个 widget, 设置过 geometry 属性的,再把这个layout 设置到这个 widget 中

实际上, 一个 widget 只能包含一个layout.

打开 ui 文件的原始xml, 可以看到其中的端倪.

这种情况下 layout 并非是窗口 widget 的布局管理器, 因此不会随着窗口大小改变.

这是先拖了layout,然后再往layout中拖其他控件,我们也是可以先拖其他控件,然后给这些控件拖到layout!这是一样的,没啥区别!

水平布局

使用 QHBoxLayout 表示垂直的布局管理器.H 是 horizontat 的缩写.

核心属性(和 QVBoxLayout 属性是一致的)
属性 说明
layoutLeftMargin 左侧边距
layoutRightMargin 右侧边距
layoutTopMargin 上方边距
layoutBottomMargin 下方边距
layoutSpacing 相邻元素之间的间距
代码示例: 使用 QHBoxLayout 管理控件

1.编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");

    QHBoxLayout* layout = new QHBoxLayout();
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);

    this->setLayout(layout);
}

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 ,就可以做出各种复杂的界面了.

网格布局

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);

    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);

    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, 1, 0);
    layout->addWidget(button2, 2, 0);
    layout->addWidget(button3, 3, 0);
    layout->addWidget(button4, 4, 0);

执行代码, 可以看到这几个按钮都在同一列了. 相当于 QVBoxLayout

5.任意调整行列, 即可看到不同的效果.

6.编写代码形如

    layout->addWidget(button1, 1, 0);
    layout->addWidget(button2, 2, 0);
    layout->addWidget(button3, 3, 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);

    // 设置水平拉伸系数
    layout->setColumnStretch(0, 1);
    layout->setColumnStretch(1, 1);
    layout->setColumnStretch(2, 2);
    // 次数代码的含义就是这三个列,按照1:1:2的方式来设置宽度!

}

2.执行程序, 可以看到每一列的宽度是不同的. 并且随着窗口调整动态变化.

我们将比例设置为0,就代表这个按钮不参与比例的运算!拉伸的时候,因为尺寸已经是固定的了,不会产生拉伸的变化效果!

🍇另外,QGridLayout 也提供了 setRowStretch 设置行之间的拉伸系数.

上述案例中, 直接设置 setRowStretch 效果不明显, 因为每个按钮的高度是固定的. 需要把按钮的垂直方向的 sizePolicy 属性设置为 QSizePotLicy: :Expanding 尽可能填充满布局管理器, 才能看到效果.

代码示例: 设置垂直方向的拉伸系数

1.编写代码, 创建 6 个按钮, 按照 3 行 2 列方式排列.

我们按照上面的相似代码进行编写的效果是没有的,这里之所以没有拉伸,原因是这里涉及到 setSizePolicy 的影响!

这个是 QWidget 的属性,描述的是当前这个按钮,他的尺寸是按照一个什么样的方式去进行排列的!

使用 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");
    
    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);

    this->setLayout(layout);
    
    // 设置垂直方向的拉伸系数
    layout->setRowStretch(0,1);
    layout->setRowStretch(1,1);
    layout->setRowStretch(2,2);
}

2.执行代码, 观察效果.

此时的按钮垂直方向都舒展开了. 并且调整窗口尺寸, 也会按照设定的比例同步变化.

总的来说, 使用 QGridLayout 能够代替很多 QHBoxLayout 和 QVBoxLayout 嵌套的场景。毕竟嵌套的代码写起来是比较麻烦的。

另外不要忘了,QG ridLayout SMWAERE QHBoxLayout 和 QVBoxLayout ,QHBoxLayout 和 QVBoxLayout BMtHAERE QGridLayout,灵活使用上述布局管理器, 就可以实现出任意的复杂界面.

表单布局

除了上述的布局管理器之外, Qt 还提供了 QFormLayout ,属于是 QGridLayout 的特殊情况, 专门用于实现两列表单的布局.

前端有一个 form 标签,搭配其他的 input 等标签,让网页端用户输入数据,并且提交到服务器!

涉及到填表,填调查问卷!就是N行2列的布局,其中左边这一列是固定的,就是一个label,文本,起到提示说明的作用,右边往往是一个输入框,让用户输入一些内容,也可以是一些下拉菜单,单选按钮......

表单布局就是为了使用于这种场景!

这种表单布局多用于让用户填写信息的场景. 左侧列为提示, 右侧列为输入框

代码示例: 使用 QFormLayout 创建表单

1.编写代码, 创建 QFormLayout ,以及三个label 和三

  • 使用 addRow 方法来添加一行. 每行包含两个控件. 第一个控件固定是 QLabel / 文本, 第二个控件则可以是任意控件.
  • 如果把第一个参数填写为 NULL, 则什么都不显示
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置成3行2列
    QFormLayout* layout = new QFormLayout();
    this->setLayout(layout);

    // 创建三个label作为第一列
    QLabel* label1 = new QLabel("姓名");
    QLabel* label2 = new QLabel("年龄");
    QLabel* label3 = new QLabel("电话");

    // 创建3个输入框作为第二列
    QLineEdit* edit1 = new QLineEdit();
    QLineEdit* edit2 = new QLineEdit();
    QLineEdit* edit3 = new QLineEdit();

    // 将上述控件添加到表单布局中
    layout->addRow(label1, edit1);
    layout->addRow(label2, edit2);
    layout->addRow(label3, edit3);


    QPushButton* button = new QPushButton("提交");
    layout->addRow(nullptr, button);
}

2.执行程序, 可以看到以下结果

Spacer

使用布局管理器的时候, 可能需要在控件之间, 添加一段空白. 就可以使用 QSpacerItem 来表示!

核心属性
属性 说明
width 宽度
height 高度
hData 水平方向的 sizePolicy,选项在下面!
vData  垂直方向的 sizePolicy,选项同上。
  • QSizePolicy::Ignored:忽略控件的尺寸,不对布局产生影响。

  • QSizePolicy::Minimum:控件的最小尺寸为固定值,布局时不会超过该值。

  • QSizePolicy::Maximum:控件的最大尺寸为固定值,布局时不会小于该值。

  • QSizePolicy::Preferred:控件的理想尺寸为固定值,布局时会尽量接近该值。

  • QSizePolicy::Expanding:控件的尺寸可以根据空间调整,尽可能占据更多空间。

  • QSizePolicy::Shrinking:控件的尺寸可以根据空间调整,尽可能缩小以适应空间。

上述属性在构造函数设置即可

代码示例: 创建一组左右排列的按钮.

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);//高度200,宽度20

    layout->addWidget(button1);
    layout->addSpacerItem(spacer);//我们是要将空白添加到两个按钮之间,因此此处add的顺序就是把addspaceritem放在中间
    layout->addWidget(button2);
    
}

4.运行程序, 观察代码效果. 可以看到两个按钮之间已经存在了间隔了.

调整 QSpacerltem 不同的尺寸, 即可看到不同的间距

在 Qt Designer 中, 也可以直接给界面上添加 spacer.

小结

通过上面的学习, 我们就了解了 Qt 中常用控件的使用方法. 对于图形界面开发, 知道有哪些控件, 每个控件有什么作用, 如何使用, 是最核心的部分.

基于上面内容其实我们就已经可以写出一些具有实际意义的图形化程序了.

我们之前学习的每一个控件,都是可扩展的!每一个控件都是Qt内置的一个类,我们在代码中都可以居于这个类,继承出我们自定义的子类,在我们自定义的子类中,又可以添加很多的属性和方法,实现自己的需求场景,还可以在子类中,把多个控件组合在一起!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐