Qt布局管理(5):自定义布局器

Qt布局管理(5):自定义布局器(QLayout、QLayoutItem、QSpacerItem、QWidgetItem)

若对C++语法不熟悉,建议参阅《C++语法详解》一书,电子工业出版社出版

自定义布局需要使用QLayout和QLayoutItem类(布局项目),其中QLayoutItem类描述了QLayout布局中的项目信息。

5.5.1 QLayout抽象类中的公有成员函数
QLayout继承自QObject和QLayoutItem类,该类是一个抽象类。该类中的成员在前文基本上都见过了
1、QLayout类中的属性

sizeConstraint:SizeConstraint   		//大小约束,详见前文
spacing:int      					//设置部件之间的空白距离,详见前文。

2、QLayout类中的公有函数(其余函数见后文)

   ①、QLayout();		QLayout(QWidget* parent);   //构造函数
②、void addWidget(QWidget *w);  	//将w添加到布局,该函数使用addItem()
③、QWidget* parentWidget() const;
	返回该布局的父部件,若未安装在任何部件上则返回0。若该布局是子布局,则返回父布局的父部件。
④、void removeItem(QLayoutItem* item);
void removeWidget(QWidget* widget);
	以上函数表示,从布局中移除部件widget(注意:并未删除)或布局项目item,在调用之后,调用者有责任给widget一个合理的几何尺寸,或者把该部件放回布局中,或者明确隐藏。对于item,则调用方有责任将其删除。注意:widget的所有权与添加时相同。item可以是布局(因为QLayout是QLayoutItem的子类)。
⑤、QLayoutItem* replaceWidget(QWidget* from, QWidget* to, 
Qt::FindChildOptions options = Qt::FindChildrenRecursively);   //qt5.2 
	使用部件to替换部件from(注意:from并未被删除),如果成功,则返回包含部件from的布局项目,若options为Qt::FindChildrenRecursively(默认值),则还会搜索子布局,因此返回的布局项目有可能不属性此布局,而是属于子布局的。返回的布局项目不再属于此布局,应把该项目删除或插入到其他布局中,部件from不再由布局管理,可能需要删除或隐藏,from的父部件保持不变。此函数适用于Qt内部调用,可能不适用于自定义布局。
⑥、bool isEnabled() const;
void setEnabled(bool enable);
	以上函数用于获取和设置布局是否启用。若布局被禁用,其行为就像该布局不存在一样,默认情况下布局都可用。
⑦、QRect contentsRect() const;   	//返回布局的几何尺寸(即布局的大小),包括内容边距。
⑧、QMargins contentsMargins() const;
void getContentsMargins(int *left, int *top , int *right, int *bottom) const;  
void setContentsMargins(int left, int top, int right, int bottom);
void setContentsMargins(const QMargins &margins);
	以上函数用于设置或获取内容边距(即页边距),大多数平台上,所有方向的边距默认都为11像素。
⑨、bool setAlignment(QWidget* w , Qt::Alignment alignment);
bool setAlignment(QLayout* l, Qt::Alignment alignment);
	将部件w或布局l的对齐方式设置为alignment,若在该布局(不包括子布局)中找到w或l,则返回true,否则返回false。
⑩、void setMenuBar(QWidget* widget);  
	把菜单栏部件widget,放置在parentWidget()的QWidget::contentsMargins()之外的顶部,所有子窗口部件都放置在菜单栏底部的下方。
⑪、QWidget* menuBar() const;   	//返回布局的菜单栏,若没有菜单栏则返回0。
⑫、void update();
bool activate()
	以上函数分别表示,更新或重新设置parentWidget()的布局,以上函数在合适的时候会自动调用,因此通常不需调用。若布局重设,则activate()返回true。
⑬、virtual int indexOf(QWidget* widget) const;   //虚拟的
	返回widget的索引,若没有找到,则返回−1,默认实现使用itemAt()迭代所有项。

5.5.2 QLayoutItem、QSpacerItem、QWidgetItem类
布局项目(QLayoutItem)指的是添加到布局中由布局管理的元素,布局管理器并不能直接管理QWidget类型的子对象,而是管理QLayoutItem及其子类型的对象(为讲解方便,把其称为QLayoutItem对象),对于QWidget这种非QLayoutItem对象的对象,需要把其转换为QLayoutItem对象,才能使用布局进行管理。
QLayoutItem类是用于描述QLayoutItem对象的一个抽象基类,除非需要创建自定义的QLayoutItem对象,否则不需要用户子类化该类,因此该类通常很少被使用,而是使用他的子类QSpacerItem、QWidgetItem、QLayout,自定义布局时通常子类化QLayout类。另外,该类除了纯虚函数(见5.5.3节)外,以下函数被子类(比如QWidgetItem类)重新实现后,也会被用到

①、virtual QWidget *QLayoutItem::widget() //虚函数,返回布局项目所管理的QWidget类型的部件
   ②、virtual QSizePolicy::ControlTypes QLayoutItem::controlTypes() const;    //虚函数
返回布局项目所管理的部件的类型,使用QSizePolicy::ControlType枚举见表5-13

在这里插入图片描述

QSpacerItem类在前文已讲解并使用过,这个类主要用来创建一个空白项目,以使布局看起来更合理,在此就不重述了。
QWidgetItem类,其主要作用是可以根据QWidget部件产生一个可由布局管理的QWidgetItem对象,使用方法如下:

	QWidget *w = new QWidget;    
	QWidgetItem *pi = new QWidgetItem(w);   //把w转换为QWidgetItem对象,并存储在pi中。

使用QWidgetItem类重新实现的widget()函数可实现上述相反的转换,比如

	QWidget *pw = pi->widget();   //返回由布局项目pi管理的部件

示例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类中的纯虚函数(自定义布局时,必须重新实现以下函数)

①、virtual int count() const = 0;    //纯虚函数,返回布局中项目的数量。
②、virtual void addItem(QLayoutItem* item)=0;   //纯虚函数
	把项目item添加到布局中,该函数必须在子类中重新实现,以添加特定于每个子类的项目,该函数通常不需要在程序中调用,因为QLayout的子类提供了相关的函数,比如要添加布局,可使用子类的addLayout()函数,要添加部件,使用addWidget()函数等。
③、virtual QLayoutItem itemAt(int index) const = 0;  //纯虚函数
	返回索引index处的布局项目,若没有这样的项目,则必须返回0,项目从0开始编号,若项目被删除,则其他项目将被重新编号。
④、virtual QLayoutItem* takeAt(int index) = 0;    //纯虚函数
	从布局中删除索引index处的布局项目,并返回该项目。若没有这样的项目,该函数必须什么也不做并返回0,项目的编号从0开始,若项目被删除,则其他项目会被重新编号。下面是安全删除布局中项目的方法
QLayoutItem *c;    while((c = layout -> takeAt(0)) != 0) {…….; delete c;}

2、以下函数为QLayout类重新实现的父类QLayoutItem中的纯虚函数
注意:QLayoutItem类共有7个纯虚函数,但QLayout只实现了6个,其中sizeHint()函数未重新实现。

①、virtual maximumSize() const;   ②、virtual QSize minimumSize() const;   ③、virtual bool isEmpty() const;
④、virtutal QRect geometry() const; 	⑤、virtual setGeometry(const QRect& r); 

当实现自定义布局时,setGeometry()函数的作用就是对布局中的子部件进行布局,即子部件的大小、排列等都在此函数内完成的,因此若要实现自定义布局,则此函数需重新实现。

⑥、virtual Qt::Orientations expandingDirections() const;  
设置此布局的拉伸方式(即是否可以获得比QLayoutItem::sizeHint()更多的空间),若值为Qt::Vertical或Qt::Horizontal则只能在垂直或水平方向拉伸,若值为Qt::Vertical | Qt::Horizontal(默认)则可在两个方向拉伸。子类会根据子部件的大小策略,重新实现该函数,以返回有意义的值。

示例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();	}

本文作者:黄邦勇帅(原名:黄勇)

猜你喜欢

转载自blog.csdn.net/hyongilfmmm/article/details/83029446
今日推荐