虽然标准布局可以帮助我们解决很多问题,但是有时候需要一些特殊的布局管理器来管理控件,所以我们需要制作一个自己的自定义布局管理器,本节小豆君就来给大家分享如何自定义布局管理器。
我们看到所有的标准布局都直接或间接继承于QLayout,那么我们的自定义布局也要继承QLayout。
QLayout继承于QObject和QLayoutItem,QLayoutItem是一个抽象类
一般的,在继承类中,我们重点要关注这个基类中的虚函数,下面列出了QLayout的虚函数:
//添加一个QLayoutItem,当调用addWidget时,也会调用此函数
//在布局中,每添加一个控件,就会相应的增加一个QLayoutItem,
//用来管理每个控件在布局中的大小策略
virtual void addItem(QLayoutItem *) = 0;
//用于计算每个子控件在父窗口中的位置和大小
virtual void setGeometry(const QRect&) Q_DECL_OVERRIDE;
//获取第index个QLayoutItem
virtual QLayoutItem *itemAt(int index) const = 0;
//出栈一个QLayoutItem,并且返回该QLayoutItem的指针
virtual QLayoutItem *takeAt(int index) = 0;
//返回QLayoutItem*的个数
virtual int count() const = 0;
//大小提示
//这个虚函数是属于QLayoutItem的接口
//关于大小提示的信息,可查看上一节标准布局管理器三中的布局原理
QSize sizeHint() const;
3.2.1 金字塔布局
这个布局会在第一行放置一个控件,第二行放置两个控件。。。以此类推。
效果图:
image.png好,直接上代码。
新建项目CustomLayout,类名为CustomLayout,基类为QWidget,不需要创建ui文件
customlayout.h
#ifndef CUSTOMLAYOUT_H
#define CUSTOMLAYOUT_H
#include <QWidget>
class CustomLayout : public QWidget
{
Q_OBJECT
public:
CustomLayout(QWidget *parent = 0);
~CustomLayout();
};
#endif // CUSTOMLAYOUT_H
customlayout.cpp
#include <QLabel>
#include "customlayout.h"
#include "pyramidlayout.h"
CustomLayout::CustomLayout(QWidget *parent)
: QWidget(parent)
{
PyramidLayout* layout = new PyramidLayout(this, 0, 0, 0);
for (int i = 0; i < 15; ++i)
{
layout->addWidget(new QLabel(tr(" ")));
}
//再给我们的每个label加个颜色,看起来像一块儿砖
setStyleSheet("QLabel{background-color:#ffc000;border:2px solid #ff3000}");
}
CustomLayout::~CustomLayout()
{
}
添加一个新C++类PyramidLayout
pyramidlayout.h
#ifndef PYRAMIDLAYOUT_H
#define PYRAMIDLAYOUT_H
#include <QLayout>
#include <QWidget>
class PyramidLayout: public QLayout
{
public:
PyramidLayout(QWidget *parent, int margin = 9,
int hSpacing = 9, int vSpacing = 9);
PyramidLayout(int margin = 9, int hSpacing = 9, int vSpacing = 9);
~PyramidLayout();
void addItem(QLayoutItem *item) override ;
void setGeometry(const QRect &rect) override ;
QLayoutItem *itemAt(int index) const override;
QLayoutItem *takeAt(int index) override;
int count() const;
QSize minimumSize() const override;
QSize sizeHint() const override;
int rowCount() const;
private:
QList<QLayoutItem *> itemList;
int m_hSpace;
int m_vSpace;
};
#endif // PYRAMIDLAYOUT_H
pyramidlayout.cpp
#include <QtMath>
#include "pyramidlayout.h"
PyramidLayout::PyramidLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
PyramidLayout::PyramidLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
PyramidLayout::~PyramidLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void PyramidLayout::addItem(QLayoutItem *item)
{
itemList.append(item);
}
int PyramidLayout::count() const
{
return itemList.size();
}
QSize PyramidLayout::minimumSize() const
{
QLayoutItem *item = itemList.first();
QSize size;
if (item)
{
int r = rowCount();
size.setWidth((item->sizeHint().width()+m_hSpace)*r- m_hSpace);
size.setHeight((item->sizeHint().height()+m_vSpace)*r- m_vSpace);
}
size += QSize(2*margin(), 2*margin());
return size;
}
QSize PyramidLayout::sizeHint() const
{
return minimumSize();
}
//用于计算金字塔高度
int PyramidLayout::rowCount() const
{
//(1+r)*r = cnt
//r为金字塔高度,cnt为子控件总个数
//求解一元二次方程
double r = (qSqrt(1+8*count())-1)/2.;
int ir = r;
if (r > ir)
{
r = ir + 1;
}
return r;
}
QLayoutItem *PyramidLayout::itemAt(int index) const
{
return itemList.value(index);
}
QLayoutItem *PyramidLayout::takeAt(int index)
{
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return 0;
}
void PyramidLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = 0;
int y = 0;
int cnt = count();
//i表示行,j表示列,k表示第k个子控件
for (int i = 0, k = 0; k < cnt; ++i)
{
for (int j = 0; j < i+1 && k < cnt; ++j, ++k)
{
QLayoutItem *item = itemList.at(k);
QLayoutItem *aboveItem = 0;//每个控件的头上控件,用于定位item
if (i == j)//最右侧控件
{
if (i == 0)//第一个控件,水平居中
{
double r = rowCount();
x = effectiveRect.center().x() - item->sizeHint().width()/2;
y = effectiveRect.center().y()-r/2*item->sizeHint().height()-(r-1)/2*m_vSpace;
}
else//选择它头上的左侧控件作为头控件
{
aboveItem = itemList.at(k-i-1);
x = aboveItem->geometry().right()-item->sizeHint().width()/2+m_hSpace/2;
y = aboveItem->geometry().bottom()+m_vSpace;
}
}
else//选择它头上的右侧控件作为头控件
{
aboveItem = itemList.at(k-i);
x = aboveItem->geometry().left()-item->sizeHint().width()/2-m_hSpace/2;
y = aboveItem->geometry().bottom()+m_vSpace;
}
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
}
}
编译运行程序,如图:
image.png改变窗口大小,金字塔也会居中显示。
总的来说,布局管理就是当父窗口的大小被改变时,将其中的每个子控件进行重新排放,至于如何排放,就在setGeometry中进行计算。你可以将每个子控件想象成积木,如何摆,那就看你心情了。
这个金字塔还可以使用Qt的绘图,第三方库制作,我们以后再讲。
好了,关于自定义布局的内容今天先讲到这里。
更多分享,请关注微信公众号:小豆君,只要关注,便可加入我的C++\Qt交流群一起学习。
网友评论