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定时器,或者是按钮点击实现加以切换。
在平时安装软件程序中,经常是有“下一步”或者“上一步”的操作,其内部的实现机制,就是使用栈布局管理器加上垂直布局管理器实现的。