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
#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)所示效果的佈局,當調整視窗大小時,子部件會自動調整至上一行或下一行,其中子部件不會拉伸或壓縮
#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
#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(); }
本文作者:黃邦勇帥(原名:黃勇)