1. 程式人生 > >Qt佈局管理(5):自定義佈局器

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

本文作者:黃邦勇帥(原名:黃勇)