Qt布局管理(5):自定义布局器(QLayout、QLayoutItem、QSpacerItem、QWidgetItem)
本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)
本文出自本人原创著作《Qt5.10 GUI完全参考手册》网盘地址:
https://pan.baidu.com/s/1iqagt4SEC8PUYx6t3ku39Q
《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg
若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。
自定义布局需要使用QLayout和QLayoutItem类(布局项目),其中QLayoutItem类描述了QLayout布局中的项目信息。
5.5.1 QLayout抽象类中的公有成员函数
QLayout继承自QObject和QLayoutItem类,该类是一个抽象类。该类中的成员在前文基本上都见过了
1、QLayout类中的属性
2、QLayout类中的公有函数(其余函数见后文)
5.5.2 QLayoutItem、QSpacerItem、QWidgetItem类
布局项目(QLayoutItem)指的是添加到布局中由布局管理的元素,布局管理器并不能直接管理QWidget类型的子对象,而是管理QLayoutItem及其子类型的对象(为讲解方便,把其称为QLayoutItem对象),对于QWidget这种非QLayoutItem对象的对象,需要把其转换为QLayoutItem对象,才能使用布局进行管理。
QLayoutItem类是用于描述QLayoutItem对象的一个抽象基类,除非需要创建自定义的QLayoutItem对象,否则不需要用户子类化该类,因此该类通常很少被使用,而是使用他的子类QSpacerItem、QWidgetItem、QLayout,自定义布局时通常子类化QLayout类。另外,该类除了纯虚函数(见5.5.3节)外,以下函数被子类(比如QWidgetItem类)重新实现后,也会被用到
QSpacerItem类在前文已讲解并使用过,这个类主要用来创建一个空白项目,以使布局看起来更合理,在此就不重述了。
QWidgetItem类,其主要作用是可以根据QWidget部件产生一个可由布局管理的QWidgetItem对象,使用方法如下:
示例5.22:部件的移除与删除(设计的界面见图5-45)
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class B:public QWidget{ Q_OBJECT
public: QVBoxLayout *pv; QHBoxLayout *ph;
QLayoutItem* pi; QWidget *pw; //用于保存值
QPushButton *pb,*pb1,*pb2,*pb3,*pb4,*pb5,*pb6,*pb7;
B(QWidget* p=0):QWidget(p){ pw=0; pi=0; //初始化为0;
pv=new QVBoxLayout;
pb=new QPushButton("AAA"); pb1=new QPushButton("BBB"); pb2=new QPushButton("CCC");
pb3=new QPushButton("DDD"); pb4=new QPushButton("replace");
pb5=new QPushButton("remove"); pb6=new QPushButton("take"); pb7=new QPushButton("del");
pv->addWidget(pb); pv->addWidget(pb1); pv->addWidget(pb2);
ph=new QHBoxLayout;
ph->addWidget(pb4); ph->addWidget(pb5); ph->addWidget(pb6); ph->addWidget(pb7);
//主布局
QVBoxLayout *pv1=new QVBoxLayout; pv1->addLayout(ph); pv1->addLayout(pv);
setLayout(pv1);
connect(pb4,&QPushButton::clicked,this,&B::f); //替换repaceWidget
connect(pb5,&QPushButton::clicked,this,&B::f1); //移除removeWidget
connect(pb6,&QPushButton::clicked,this,&B::f2); //takeAt
connect(pb7,&QPushButton::clicked,this,&B::f3); } //彻底删除,构造函数结束
public slots:
void f(){ pi=pv->replaceWidget(pb,pb3); //把pb替换为pb3,但pb未被删除。
if(pi->controlTypes()==QSizePolicy::PushButton) { //若pi管理的部件是按钮。
pw=pi->widget();} } //把pi管理的部件赋值给pw
void f1(){ pv->QLayout::removeWidget(pb1); //移除但不删除pb1,
pw=pb1; }
void f2(){ pi=pv->QBoxLayout::takeAt(0); /*使用QBoxLayout类重新实现的takeAt函数,移除但不删除索引为0的部件,注:QLayout并未实现takeAt纯虚函数*/
if(pi->controlTypes()==QSizePolicy::PushButton){ pw=pi->widget();} }
void f3(){ //该函数用于删除项目和部件
//若仅仅删除布局项目pi,并不会把布局所管理的部件pw删除掉,因此部件需明确的删除。
if(pi!=0) {delete pi; pi=0;} if(pw!=0) {delete pw; pw=0;} } };
#endif // M_H
//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){ QApplication a(argc,argv);
B w; w.resize(300,200); w.show(); return a.exec(); }
运行结果及说明见图5-46
5.5.3 自定义布局的实现
注意:布局不能管理非QLayoutItem对象,QWidget对象需转换为QLayoutItem对象才能被布局管理。
虽然纯虚函数可由用户自行实现任意功能,但是这些纯虚函数通常需要由Qt内部调用,因此,若用户实现的纯虚函数不满足Qt内部的要求,则不但达不到预期的效果,还有可能使程序出错。因此纯虚函数的功能也是需要了解的。
要自定义布局,需要重新实现QLayout类中的以下纯虚函数
count(); addItem(); itemAt(); takeAt(); sizeHint(); setGeometry();
其中,setGeometry()函数不是必须重新实现的,但该函数管理着怎样对子部件进行布局(设置其大小、位置等),因此通常还需要重新实现该函数。
1、以下为QLayout类中的纯虚函数(自定义布局时,必须重新实现以下函数)
2、以下函数为QLayout类重新实现的父类QLayoutItem中的纯虚函数
示例5.23:自定义布局
这是一个简单的示例,该示例实现如图(5-47)所示效果的布局,当调整窗口大小时,子部件会自动调整至上一行或下一行,其中子部件不会拉伸或压缩
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class B : public QLayout{
public: B(QWidget *parent): QLayout(parent) {} B() {} ~B();
//声明需要实现的成员函数
void addItem(QLayoutItem *item); QSize sizeHint() const;
int count() const; QLayoutItem *itemAt(int) const;
QLayoutItem *takeAt(int); void setGeometry(const QRect &rect);
void addw(QWidget* pw); /*使用一个自定义的函数向布局中添加QWidget对象,addItem函数不能直接接收QWidget对象,该函数主要起类型转换的作用。*/
QList<QLayoutItem*> list; //使用QList存储布局需要管理的对象。
}; //类B声明结束
void B::addItem(QLayoutItem *item){ list.append(item);} //把元素添加到列表。
void B::addw(QWidget* p){
addItem(new QWidgetItem(p)); //把p转换为QLayoutItem对象,非QLayoutItem对象不能由布局管理。
//addItem((QLayoutItem*)p); /*错误,强制类型转换指针的类型,会使内存的内容被重新解释,这可能会产生内存错误。比如int a=1; int *p=&a; 假设int占4字节,double占8字节,则*p只会读取4字节的内容,但是*(double*)p;则会读取8字节的内容(详见《C++语法详解》一书有关指针的讲解)。*/
}
QLayoutItem *B::itemAt(int i) const{return list.value(i);} //返回索引i处的项目。
QLayoutItem *B::takeAt(int i) //删除索引i处的项目
{ return i >= 0 && i < list.size() ? (QLayoutItem*)list.takeAt(i) : 0; }
int B::count() const{ return list.size(); } //返回布局中的项目数量
B::~B() { //因为QLayoutItem未继承自QObject,因此必须手动删除QLayoutItem对象。
QLayoutItem *item; while ((item = takeAt(0))) delete item;}
void B::setGeometry(const QRect &r) { //布置布局中的子项目
QSize s=parentWidget()->size(); //获取布局所在父部件的大小
int w=sizeHint().width(); int h=sizeHint().height();
int x=0; int y=0; //部件左上角的坐标。
for(int i=0;i<list.size();i++){
list.at(i)->setGeometry(QRect(x,y,w,h));
x=x+w; //第二个项目的水平坐标向后移第一个部件的宽度
if(x+w>s.width()) /*如果新添加的项目占据的位置超过了父部件的大小,则该部件添加到下一行的开头*/
{ y=y+h; x=0;} } }
QSize B::sizeHint() const{ return QSize(77,22); }
#endif // M_H
//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){ QApplication a(argc,argv);
QWidget w;
QPushButton *pb=new QPushButton("AAA"); QPushButton *pb1=new QPushButton("BBB");
QPushButton *pb2=new QPushButton("CCC"); QPushButton *pb3=new QPushButton("DDD");
QPushButton *pb4=new QPushButton("DDD"); QPushButton *pb5=new QPushButton("DDD");
QPushButton *pb6=new QPushButton("DDD"); QPushButton *pb7=new QPushButton("DDD");
B *ph=new B;
ph->addWidget(pb); ph->addWidget(pb1); ph->addWidget(pb2);
ph->addWidget(pb3); ph->addWidget(pb4); ph->addWidget(pb5);
ph->addWidget(pb6); ph->addWidget(pb7);
w.setLayout(ph); w.resize(300,200); w.show(); return a.exec(); }
本文作者:黄邦勇帅(原名:黄勇)