Qt中的布局管理器

本文详细介绍Qt中的布局管理器,包括垂直/水平布局(QBoxLayout)、网格布局(QGridLayout)、表单布局(QFormLayout)和栈布局(QStackedLayout)等。通过示例代码展示如何使用这些布局管理器实现组件的灵活定位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  Qt中窗口组件的基类QWidget有两组重载函数用于实现窗口组件的绝对定位:

//定位窗口的坐标
void move(const QPoint &) void move(int x, int y)
//定义窗口占据的大小
void resize(const QSize &) void resize(int w, int h)

  存在的问题:
  (1) 程序员需要计算/规划组件的坐标
  (2) 组件的大小和位置不能自适应所在父窗口的变化

1. QLayout的引入

  Qt采用的解决方案是布局管理器QLayout。布局管理器能根据界面组件进行布局管理:(1)能够自动排列窗口中的界面组件;(2)窗口变化后能自动更新界面的大小。

  QLayout是Qt中布局管理器的抽象基类,通过继承QLayout可实现功能各异的布局管理器。在Qt中由QLayout衍生出若干个实现项目开发中常用的布局管理器子类:垂直/水平布局管理器QBoxLayout、网格布局管理器QGridLayout、表单布局管理器QFormLayout、栈式布局管理器QStackedLayout。
这里写图片描述

  QObject类是所以Qt类的基类,也是Qt对象模型的核心;QLayoutItem为布局操作提供了抽象条款。强调:布局管理器不是界面部件,而是界面部件的定位策略。

2. 垂直/水平布局管理器QBoxLayout

这里写图片描述

2.1 垂直布局管理器
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QVBoxLayout>

class Widget : public QWidget
{
    Q_OBJECT

private:
    //定义4个按钮在Widget内
    QPushButton Btn1;
    QPushButton Btn2;
    QPushButton Btn3;
    QPushButton Btn4;

    void VBoxLayout();

public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

Widget::Widget(QWidget *parent) : QWidget(parent), Btn1(this), Btn2(this), Btn3(this), Btn4(this)
{
    this->VBoxLayout();
}

#endif // WIDGET_H

//Widget.cpp
void Widget::VBoxLayout()
{
    //QVBoxLayout vb(this);
    QVBoxLayout *vb = new QVBoxLayout;  //一定要动态分配

    //按钮的提示信息
    Btn1.setText("1");

    //按钮大小的最小值
    Btn1.setMinimumSize(160, 30);

    //设置按钮的大小策略,Expanding表示可随着父窗口的大小变化而发生改变
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将按钮对象添加到布局管理器
    vb->addWidget(&Btn1);
    vb->addWidget(&Btn2);
    vb->addWidget(&Btn3);
    vb->addWidget(&Btn4);

    //设置每个按钮之间的间隔
    vb->setSpacing(30);

    //两种方法设置按钮的变化比例系数
    vb->setStretchFactor(&Btn3, 2);
    vb->setStretch(0, 6);  //0表示index,第一个被加入布局管理器的组件被自动编号为0

    //将当前widget交付布局管理器vb管理
    setLayout(vb);
}

//main.c
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

  运行:
这里写图片描述

  布局管理器对象的生成必须动态分配,若在栈中生成,那么该函数退出后布局对象将被销毁,布局效果将失效。另外动态生成的布局对象并不需要程序员代码实现销毁。将Widget交付布局管理器托管后,管理器的父类对象即为widget,在父类Widget对象销毁后,会自动销毁Widget对象的所有子类,包括动态生成的布局管理器。

  可以在上面VBoxLayout()函数中打印管理器对象的父对象的地址,该地址地位windget对象的地址,即this指针:

qDebug() << this;
qDebug() << vb->parent();

  运行后:
这里写图片描述

2.2 水平布局管理器
#include <QHBoxLayout>

Widget::Widget(QWidget *parent) : QWidget(parent), Btn1(this), Btn2(this), Btn3(this), Btn4(this)
{
    this->HBoxLayout();
}

void Widget::HBoxLayout()
{
    //定义一个水平布局管理器
    //QVBoxLayout vb(this);
    QHBoxLayout *hb = new QHBoxLayout;

    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    hb->setSpacing(30);
    hb->addWidget(&Btn1);
    hb->addWidget(&Btn2);
    hb->addWidget(&Btn3);
    hb->addWidget(&Btn4);

    setLayout(hb);
}

  运行:
这里写图片描述

2.3 水平/垂直布局管理器的嵌套使用

  布局管理器可以相互嵌套,从而形成更加复杂的布局方式,即上述管理器管理的对象除了可以直接管理组件之外,还可以是布局。

  下面程序,用一个垂直布局管理器管理两个水平布局管理器,两个水平布局管理器各自管理两个按钮:

Widget::Widget(QWidget *parent) : QWidget(parent), Btn1(this), Btn2(this), Btn3(this), Btn4(this)
{
    this->VHBoxLayout();
}

void Widget::VHBoxLayout()
{
    //定义两个水平布局管理器
    QHBoxLayout *hb1 = new QHBoxLayout; 
    QHBoxLayout *hb2 = new QHBoxLayout;

    //定义一个垂直布局管理器
    QVBoxLayout *vb = new QVBoxLayout;

    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    //Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    //Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //两个按钮的间隔
    hb1->setSpacing(30);

    //将两个按钮加入水平布局管理器1
    hb1->addWidget(&Btn1);
    hb1->addWidget(&Btn2);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    //Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    //Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //两个按钮的间隔
    hb2->setSpacing(30);

    //将两个按钮加入水平布局管理器2
    hb2->addWidget(&Btn3);
    hb2->addWidget(&Btn4);

    //两个水平布局管理器的间隔
    vb->setSpacing(10);

    //将两个水平布局管理器加入垂直布局管理器管理
    vb->addLayout(hb1);
    vb->addLayout(hb2);

    //设置两个水平布局管理器的变化比例系数
    vb->setStretchFactor(hb1, 5);   //setStretchFactor()是重载函数之一,参数一既可以是QWidget*类型,
    vb->setStretchFactor(hb2, 2);   //也可以是QLayout*类型


    //将当前widget交付垂直布局管理器管理
    setLayout(vb);
}

  运行:
这里写图片描述

  补充一点,组件的初始大小是独立于布局管理器,在窗口组件中设置的,因此即使是设置了组件的比例大小,也不可保证,组件的大小始终符合比例系数。

3. 网格布局管理器

  所谓网格布局管理,即是以二维的方式来管理界面组件。

#include <QGridLayout>

Widget::Widget(QWidget *parent) : QWidget(parent), Btn1(this), Btn2(this), Btn3(this), Btn4(this)
{
    GridLayout1();
}

void Widget::GridLayout1()
{
    //定义网格布局管理器
    QGridLayout* layout = new QGridLayout();

    //定义4个按钮
    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将4个按钮加入网格布局管理。参数三、四可以看做是xy坐标
    layout->addWidget(&Btn1, 0, 0);
    layout->addWidget(&Btn2, 0, 1);
    layout->addWidget(&Btn3, 1, 0);
    layout->addWidget(&Btn4, 1, 1);

    //设置每个组件间的间隔
    layout->setSpacing(10);

    //设置变化比例系数,以行/列来设置比例系数
    //layout->setRowStretch(0, 5);
    layout->setColumnStretch(1, 9);

    //网格布局管理器管理当前widget
    setLayout(layout);
}

  运行:
这里写图片描述

  界面效果与前面垂直/水平布局管理器嵌套的效果一致,但是可见,利用网格布局管理器指定xy坐标来实现相对简单。

  网格管理器中重载了addWidget()使其支持指定组件占据几行几列,如下效果:
这里写图片描述

  利用网格布局管理器轻松实现,在将组件加入管理器时候:

//后面两个参数表示占据几行几列
layout->addWidget(&Btn1, 0, 0, 1, 2);   //占据1行2列
layout->addWidget(&Btn2, 1, 0, 1, 2);   //占据1行2列
layout->addWidget(&Btn3, 3, 0, 1, 1);   //占据1行1列
layout->addWidget(&Btn4, 3, 1, 1, 1);   //占据1行1列

  网格布局管理器同样支持布局嵌套,将水平布局管理嵌入网关布局管理:

void Widget::GridLayout1()
{
    //网格布局
    QGridLayout* layout = new QGridLayout();

    //水平布局
    QHBoxLayout* hlayout = new QHBoxLayout();

    //4个按钮
    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将按钮3、4加入水平布局
    hlayout->addWidget(&Btn3);
    hlayout->addWidget(&Btn4);

    //将按钮1、2加入网格布局
    layout->addWidget(&Btn1, 0, 0);
    layout->addWidget(&Btn2, 0, 1);

    //将水平布局加入网格布局
    layout->addLayout(hlayout, 3, 0);

    layout->setSpacing(10);

    //设置网格布局
    setLayout(layout);
}

  运行:
这里写图片描述

  按钮效果变形了,先不理会。

  网格布局管理器的显示比例是设置是以行或者列为单位设置的:

QGridLayout::setRowStretch (int row, int stretch)
void QGridLayout::setColumnStretch (int column, int stretch)

4. 表单布局管理器

  要实现一个登录框,如下:
这里写图片描述

  可以通过网格布局管理器实现:

void Widget::LoginWidget()
{
    //定义三个显示标签
    QLabel* nameLbl = new QLabel("name: ");
    QLabel* mailLbl = new QLabel("Email:");
    QLabel* addrLbl = new QLabel("Address:");

    //定义三个输入框
    QLineEdit* nameEdit = new QLineEdit();
    QLineEdit* mailEdit = new QLineEdit();
    QLineEdit* addrEdit = new QLineEdit();

    //定义一个网格布局管理器
    QGridLayout* layout = new QGridLayout();

    //将上述组件加入布局管理器
    layout->addWidget(nameLbl, 0, 0);
    layout->addWidget(mailLbl, 1, 0);
    layout->addWidget(addrLbl, 2, 0);
    layout->addWidget(nameEdit, 0, 1);
    layout->addWidget(mailEdit, 1, 1);
    layout->addWidget(addrEdit, 2, 1);

    //设置比例系数
    layout->setColumnStretch(0, 1);
    layout->setColumnStretch(1, 4);

    //设置父窗口标题
    setWindowTitle("login");

    //设置布局管理器
    setLayout(layout);    
}

  像这种表单形式的布局,Qt中还有一个专门的布局管理器,即表单布局管理器QFormLayout。QFormLayout中的标签和组件是相互对应的。

#include <QFormLayout>

void Widget::FormLayout()
{
    //定义3个输入框
    QLineEdit* nameEdit = new QLineEdit();
    QLineEdit* mailEdit = new QLineEdit();
    QLineEdit* addrEdit = new QLineEdit();

    //定义一个表单布局管理器
    QFormLayout *layout = new QFormLayout();

    //将标签和组件按行添加到布局中
    layout->addRow("Name:", nameEdit);
    layout->addRow("Email:", mailEdit);
    layout->addRow("Address:", addrEdit);

    //设置提示策略
    //layout->setRowWrapPolicy(QFormLayout::WrapLongRows);     //WrapAllRows分两行显示,DontWrapRows强调标签
    layout->setLabelAlignment(Qt::AlignHCenter);               //标签的对其方式
    setLayout(layout);
}

  setRowWrapPolicy()函数用于设置显示策略,WrapLongRows是默认设置,WrapAllRows表示设置将标签和输入框分两行显示,DontWrapRows强调显示标签,即当屏幕小的时候,会缩小输入框的大小,以供标签的完整显示。这个设置在嵌入式设备中常用。

  QFormLayout管理器用addRow()来增加管理的行对象,addRow()的重载函数有:

void QFormLayout::addRow (QWidget * label, QWidget * field)
void QFormLayout::addRow (QWidget * label, QLayout * field)
void QFormLayout::addRow (const QString & labelText, QWidget * field)
void QFormLayout::addRow (const QString & labelText, QLayout * field)
oid QFormLayout::addRow (QWidget * widget)
oid QFormLayout::addRow (QLayout * layout)

  QFormLayout同样支持其他布局管理器作为子布局而被其管理。

5. 栈布局管理器

  栈布局管理器管理的子组件都垂直于显示屏中:
这里写图片描述

  每次只有一个组件会被显示在屏幕上,也就是说最顶层的组件会被显示。

void Widget::StackLayout()
{
    QStackedLayout *layout = new QStackedLayout();

    //定义四个按钮
    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将4个按钮加入栈布局管理器中
    layout->addWidget(&Btn1);
    layout->addWidget(&Btn2);
    layout->addWidget(&Btn3);
    layout->addWidget(&Btn4);

    //设置当前显示在显示器的组件
    //layout->setCurrentIndex(0);
    //layout->setCurrentWidget(&Btn4);

    //显示当前显示的组件的编号
    qDebug() << "index = " << layout->currentIndex();

    setLayout(layout);
}

  运行显示的是按钮1:
这里写图片描述

  栈布局管理器的特点如下:
  (1) 管理器中的组件大小一致且充满父组件的显示区域,每次只能显示一个组件
  (2) 它只能管理组件,不能直接嵌套其他布局管理器
  (3) 能够通过函数自由切换要显示的组件

  栈布局管理器不能直接嵌套其他布局管理器,只能一个组件一个组件管理,那我要在一个显示面上显示两个按钮怎么办??

  实现方法是:定义一个Widget,并将其设置为我们要显示的两个按钮的父组件,通过水平(或者其他想要的)布局管理器来管理这个Widget,最后再将这个Widget交由QStackedLayout管理即可。在QStackedLayout看来这个含有两个按钮的widget还是一个Widget,所以可以通过:

void Widget::StackLayout()
{
    QStackedLayout *layout = new QStackedLayout();
    QHBoxLayout *h = new QHBoxLayout();
    QWidget* w = new QWidget();

    //定义按钮1、2
    Btn1.setText("1");
    Btn1.setMinimumSize(160, 30);
    Btn1.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn2.setText("2");
    Btn2.setMinimumSize(160, 30);
    Btn2.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将按钮1、2设置为Widget的子组件
    Btn2.setParent(w);
    Btn1.setParent(w);

    //将按钮加入水平布局管理器
    h->addWidget(&Btn1);
    h->addWidget(&Btn2);

    //将Widget交由水平布局管理器管理
    w->setLayout(h);   

    //定义按钮3、4
    Btn3.setText("3");
    Btn3.setMinimumSize(160, 30);
    Btn3.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    Btn4.setText("4");
    Btn4.setMinimumSize(160, 30);
    Btn4.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将Widget、按钮3、4加入栈布局管理器
    layout->addWidget(w);
    layout->addWidget(&Btn3);
    layout->addWidget(&Btn4);

    //layout->setCurrentIndex(0);
    layout->setCurrentWidget(w);

    setLayout(layout);
}

  运行效果:
这里写图片描述

  如上代码,每次程序运行只能显示一个组件。若要实现切换可以加上Qt定时器,或者是按钮点击实现加以切换。

  在平时安装软件程序中,经常是有“下一步”或者“上一步”的操作,其内部的实现机制,就是使用栈布局管理器加上垂直布局管理器实现的。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值