QT進階之路 : 佈局詳解

轉載自:http://blog.csdn.net/fanyun_01/article/details/52623777

Qt佈局管理手冊:

http://doc.qt.io/qt-5/qtwidgets-index.html#styles

http://doc.qt.io/qt-5/qtwidgets-index.html#widgets

http://doc.qt.io/qt-5/qtwidgets-index.html#layouts

以下是Qt手冊中的《佈局管理》的譯文:

在一個Widget中,Qt佈局管理系統提供了一個簡單而有效的方式來自動組織子widget,以保證他們能夠很好地利用可用空間。

介紹:

Qt包含一個佈局管理類的集合,它們被用來描述widgets如何在應用程序的用戶界面中呈現的。當可用空間發生變化時,這些佈局將自動調整widgets的位置和大小,以確保它們佈局的一致性和用戶界面主體可用。

所有QWidget的子類都可以用佈局來管理它們的子類。QWidget::setLayout()函數給widget提供一個佈局。當佈局通過這種方式設置到widget,它將負責以下任務:

1.子widget的定位

2.窗口的合理默認空間

3.窗口的合理最小空間

4.調整大小處理

5.當內容發生變化時自動調整

6.字體、大小或者內容變化

7.顯示或 隱藏widget

8.移除子widget

Qt的佈局類:

QGraphicsAnchorLayout

Layout where one can anchor widgets together in Graphics View

在製圖視圖中佈局widget

QGraphicsAnchor

Represents an anchor between two items in a QGraphicsAnchorLayout

表示一個QGraphicsAnchorLayout中兩個圖元之間的anchor

QBoxLayout

Lines up child widgets horizontally or vertically

水平或垂直整理子widget

QHBoxLayout

Lines up widgets horizontally

水平整理子控件

QVBoxLayout

Lines up widgets vertically

垂直整理子控件

QFormLayout

Manages forms of input widgets and their associated labels

label-inputwidget模式表單佈局

QGridLayout

Lays out widgets in a grid

網格佈局

QLayout

The base class of geometry managers

佈局,幾何管理的基類

QLayoutItem

Abstract item that a QLayout manipulates

管理的抽象元素

QSpacerItem

Blank space in a layout

空白區域佈局

QWidgetItem

Layout item that represents a widget

佈局元素

QSizePolicy

Layout attribute describing horizontal and vertical resizing policy

大小策略

QStackedLayout

Stack of widgets where only one widget is visible at a time

棧模式佈局,一次只顯示一個

QButtonGroup

Container to organize groups of button widgets

管理按鈕的容器

QGroupBox

Group box frame with a title

帶標題的組箱框架

QStackedWidget

Stack of widgets where only one widget is visible at a time

棧模式的widget,一次只顯示一個

水平、垂直、網格和表格佈局:

Qt佈局類之間的關係如圖1所示:

QT進階之路 : 佈局詳解

圖1 Qt佈局類之間的關係

給widgets一個很好佈局的最好方式是使用內置的佈局管理器: QHBoxLayout, QVBoxLayout, QGridLayout, and QFormLayout. 這些類都從QLayout繼承而來,它們都來源於QObject(而不是QWidget)。創建更加複雜的佈局,可以讓它們彼此嵌套完成。

QHBoxLayout:水平佈局

QVBoxLayout:垂直佈局

QGridLayout: 表格佈局

QGridLayout::addWidget()語法

layout->addWidget(widget, row, column, rowSpan, columnSpan);

參數widget:為插入到這個佈局的子控件;

參數(row,column)為控件佔據的左上角單元格位置;

參數rowSpan是控件佔據的行數,

參數colunmSpan是控件佔據的列的個數。

(rowSpan和colunmSpan默認值為1)

Stacked Layouts:分組佈局

QStackedLayout類把子控件進行分組或者分頁,一次只顯示一組或者一頁,隱藏其他組或者頁上的控件。

使用這些Qt佈局管理類的另一個原因是,在程序、系統改變字體,語言或者在不同的平臺上運行時,佈局管理器能夠自動調整窗體裡所有控件的大小和尺寸。

其他可進行佈局管理的類:這些類的共同特點是提供了更加靈活的佈局管理,在一定程度上用戶能夠控制窗體內控件的大小。

QSplitter,QScrollArea,QMainWindow,QWorkspace(對多文檔的支持)

2) 佈局管理中結合控件的sizePolicy屬性,進行調整

結合控件的SizePolicy屬性,來控制佈局管理中的控件的尺寸自適應方式。

控件的sizePolicy說明控件在佈局管理中的縮放方式。Qt提供的控件都有一個合理的缺省sizePolicy,但是這個缺省值有時不能適合所有的佈局,開發人員經常需要改變窗體上的某些控件的sizePolicy。一個QSizePolicy的所有變量對水平方向和垂直方向都適用。下面列舉了一些最長用的值:

A. Fixed:控件不能放大或者縮小,控件的大小就是它的sizeHint。

B. Minimum:控件的sizeHint為控件的最小尺寸。控件不能小於這個sizeHint,但是可以

放大。

C. Maximum:控件的sizeHint為控件的最大尺寸,控件不能放大,但是可以縮小到它的最小

的允許尺寸。

D. Preferred:控件的sizeHint是它的sizeHint,但是可以放大或者縮小

E. Expandint:控件可以自行增大或者縮小

注:sizeHint(佈局管理中的控件默認尺寸,如果控件不在佈局管理中就為無效的值)

1.QHBoxLayout是水平佈局,將從左往右(orright to left for right-to-left languages )widget佈局成水平行

QT進階之路 : 佈局詳解

2.QVBoxLayout是垂直佈局,從頂部到底部

QT進階之路 : 佈局詳解

3.QGridLayout 是二位的網格佈局。它可以容納多個單元格:

QT進階之路 : 佈局詳解

4.QFormLayout是兩列label-field式的表單佈局

QT進階之路 : 佈局詳解

代碼舉例:

下面代碼創建QHBoxLayout來管理5個QPushButtons的幾何圖形:

QWidget *window= new QWidget;
QPushButton *button1= new QPushButton("One");
QPushButton *button2= new QPushButton("Two");
QPushButton *button3= new QPushButton("Three");
QPushButton *button4= new QPushButton("Four");
QPushButton *button5= new QPushButton("Five");
QHBoxLayout *layout= new QHBoxLayout;
layout->addWidget(button1);

layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->setLayout(layout);
window->show();

QGridLayout示例如下:

 QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");

QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);

window->setLayout(layout);
window->show();

QFormLayout示例如下:

 QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QLineEdit *lineEdit1 = new QLineEdit();
QPushButton *button2 = new QPushButton("Two");
QLineEdit *lineEdit2 = new QLineEdit();
QPushButton *button3 = new QPushButton("Three");
QLineEdit *lineEdit3 = new QLineEdit();

QFormLayout *layout = new QFormLayout;
layout->addRow(button1, lineEdit1);
layout->addRow(button2, lineEdit2);
layout->addRow(button3, lineEdit3);

window->setLayout(layout);
window->show();

佈局技巧:

當使用佈局的時候,在創建子widget時,沒必要給它傳遞父類。佈局會自動重新定義它們的父類(通過QWidget::setParent())以確保它們是裝載佈局的widget的子類。

注意1:佈局中的控件是裝載佈局控件的子控件,不是佈局的子控件。控件只能以其他控件作為父類,不可以以佈局作為父類。在佈局上,可以使用addLayout來嵌套佈局;被嵌套的佈局,將變成上層佈局的子佈局。

向佈局添加widgets:

添加布局到widgets時,佈局過程執行如下:

1.所有widgets將根據它們的 QWidget::sizePolicy() and QWidget::sizeHint()首先分配一些空間。

2. 如果有widgets設置了大於0的拉伸係數,接下來它們將按照拉伸係數的比例來分配空間。

3. 如果有widgets設置的拉伸係數是0,它將在沒有其他widgets需要空間時獲取更多空間。其中,帶Expanding大小策略的widget將首先獲得空間。

4. 所有分配了小於最小空間(或者設置了最小的sizehint)的widget將按要求分配最小空間。(在拉伸係數成為決定因子時,widgets沒必要再用最小值或者最小hint)。

5. 任何分配了大於最大空間的widget將按要求分配最大空間。(拉伸係數起著決定作用)

拉伸係數:

通常,widgets創建的時候沒有設置拉伸係數。當widget整理到一個佈局中時,它們將根據QWidget::sizePolicy()或者最小大小hint(取決於誰更大)分配一定空間。拉伸係數被用於按比例改變widget的分配空間。

如果3個widget用QHBoxLayout 來佈局,不帶拉伸係數,它們將得到像下面的佈局:

QT進階之路 : 佈局詳解

如果帶上拉伸係數,情況將變成這樣:

QT進階之路 : 佈局詳解

自定義widget的佈局:

當編寫自定義widget類時,需要顯示提供它的佈局屬性。如果widget有Qt自帶的佈局,它能夠自己滿足自己。如果沒有任何子佈局,或者使用手動佈局,可以通過下面的機制來改變widget的行為:

1.實現QWidget::sizeHint() 來返回首先大小

2.實現QWidget::minimumSizeHint()來返回widget可以擁有的最小空間

3.調用QWidget::setSizePolicy來描述widget所需的空間

當size hint、minimum size或size policy改變時,調用QWidget::updateGeometry()。這將促使佈局重新進行計算。連續多次調用QWidget::updateGeometry()只會發生一次佈局重新計算。

即便實現了QWidget::heightForWidth(),也有必要提供合理的sizeHint()。

進一步瞭解,參見:Trading Height for Width.

佈局問題:

The use of rich text in a label widget can introduce some problemsto the layout of its parent widget. Problems occur due to the way rich text ishandled by Qt's layout managers when the label is word wrapped.

Incertain cases the parent layout is put into QLayout::FreeResize mode, meaningthat it will not adapt the layout of its contents to fit inside small sizedwindows, or even prevent the user from making the window too small to beusable. This can be overcome by subclassing the problematic widgets, andimplementing suitable sizeHint() andminimumSizeHint() functions.

Insome cases, it is relevant when a layout is added to a widget. When you set thewidget of a QDockWidget ora QScrollArea (with QDockWidget::setWidget() andQScrollArea::setWidget()), the layout mustalready have been set on the widget. If not, the widget will not be visible.

在QLabel中使用富文本會給佈局的父類widget帶來一些問題。問題發生的原因是因為當label被文字環繞時,富文本被Qt的佈局管理器控制。

在某些情況下,父類佈局被放入QLayout::FreeResize模式,這意味著它將不適應內容佈局所設置的最小窗口,或者甚至阻止用戶讓窗口小到不可用的情況。這個可以通過將問題控件作為子類來解決,並實現合適的sizeHint()和minimumSizeHint()函數。

在一些情況下,當佈局被添加到widget時需要特別注意。當設置QDockWidget ora QScrollArea widget時(用QDockWidget::setWidget() andQScrollArea::setWidget()),佈局必須已經被設置到widget上。否則,這些widget將不可見。

手動佈局:

如果想自定義一個獨特的佈局,可以按 如上所述地自定義一個widget。實現QWidget::resizeEvent()來計算所需的大小分配並在每個子類中調用setGeometry() 。

需要佈局需要重新計算大小時,widget將提供一個事件接口QEvent::LayoutRequest 。實現QWidget::event()來接收QEvent::LayoutRequest事件。

自定義佈局管理:

自定義佈局的唯一方法是繼承QLayout來完成自己佈局管理器。Border Layout 和Flow Layout 例子將說明如何來完成。

下面將舉個例子來說明。CardLayout 類,受同名java佈局管理的啟發。它分層管理每個元素,每個元素的通過QLayout::spacing()來設置位移量。

編寫自定義佈局類,必須定義以下內容:

由佈局控制的存放元素的數據結構。每個元素都是一個QLayoutItem。在這個例子中,我們將使用QList 。

1. addItem(),描述如何添加元素到佈局。

2.setGeometry(),描述如何完成佈局

3.sizeHint(),佈局的首選大小

4.itemAt(),描述如何遞歸佈局

5.takeAt(),描述如何移除佈局中的元素。

在大多數情況下,還需要實現minimumSize()。


頭文件

card.h
#ifndef CARD_H
#define CARD_H

#include <qtgui>
#include <qlist>

class CardLayout : public QLayout
{
public:
CardLayout(QWidget *parent, int dist): QLayout(parent, 0, dist) {}
CardLayout(QLayout *parent, int dist): QLayout(parent, dist) {}
CardLayout(int dist): QLayout(dist) {}
~CardLayout();

void addItem(QLayoutItem *item);
QSize sizeHint() const;
QSize minimumSize() const;
int count() const;
QLayoutItem *itemAt(int) const;
QLayoutItem *takeAt(int);
void setGeometry(const QRect &rect);

private:
QList<qlayoutitem> list;
};
#endif

實現文件

card.cpp
#include "card.h"
int CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in the list

return list.size();
}

int CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in the list
return list.size();
}

int CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in the list
return list.size();
}

CardLayout::~CardLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}

void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);

if (list.size() == 0)
return;

int w = r.width() - (list.count() - 1) * spacing();
int h = r.height() - (list.count() - 1) * spacing();
int i = 0;
while (i < list.size()) {
QLayoutItem *o = list.at(i);
QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
o->setGeometry(geom);
++i;
}
}
QSize CardLayout::sizeHint() const
{
QSize s(0,0);
int n = list.count();
if (n > 0)
s = QSize(100,70); //start with a nice default size
int i = 0;
while (i < n) {
QLayoutItem *o = list.at(i);
s = s.expandedTo(o->sizeHint());
++i;

}
return s + n*QSize(spacing(), spacing());
}

QSize CardLayout::minimumSize() const
{
QSize s(0,0);
int n = list.count();
int i = 0;
while (i < n) {
QLayoutItem *o = list.at(i);
s = s.expandedTo(o->minimumSize());
++i;
}
return s + n*QSize(spacing(), spacing());
}

/<qlayoutitem>/<qlist>/<qtgui>

進一步說明:自定義佈局沒有控制寬和高。

忽略了 QLayoutItem::isEmpty(),這意味著佈局將把隱藏widget作為可見的。

對於複雜佈局,通過緩存計算將大大提高速度。在那種情況下,實現QLayoutItem::invalidate() 來標記數據是髒數據。

調用QLayoutItem::sizeHint()等的代價比較大。在通過函數中,需要再次使用,最好將結果保存在本地變量中。

在同樣函數的同一個元素中,不應該調用兩次 QLayoutItem::setGeometry()。 這個調用將耗費巨大,如果它用幾個子widget,因為佈局管理器每次都要做一個完整的佈局。替代方法:先計算geometry,然後再設置(這種事情,不僅應該在佈局時注意,在實現resizeEvent()時也需要按同樣方法來做)。

另外:

作為QLayout的父類,QLayoutItem提供了下列方法,包括繪製和範圍的信息:

virtual QSize sizeHint() const = 0
virtual QRect geometry() const = 0
virtual void invalidate()
virtual QLayout * layout()
Qt::Alignment alignment() const

QLayout提供的信息就比較多了:提供了子頁面、子Layout的添加接口,設置邊界、菜單項等的接口

virtual void addItem(QLayoutItem *item) = 0
void addWidget(QWidget*w)
void setContentsMargins(intleft, int top, int right, int bottom)
void setMenuBar(QWidget*widget)

QBoxLayout作為QLayout的子類,提供了一些額外的信息:提供元素的拉伸比例,添加空元素等

void addLayout(QLayout*layout, int stretch = 0)
void addSpacerItem(QSpacerItem*spacerItem)
void addSpacing(intsize)
bool setStretchFactor(QWidget*widget, int stretch)
bool setStretchFactor(QLayout*layout, int stretch)

界面的繼承情況,在http://doc.qt.io/qt-5/qlayout.html有詳細的介紹,不做額外的介紹。

使用的過程的例子介紹一些:

// 設置邊界為0

QHBoxLayout *pLayout1 = new QHBoxLayout(); 

QHBoxLayout *pLayout2 = new QHBoxLayout();
pLayout1->setMargin(0);
pLayout2->setMargin(0);

// 設置拉伸比例2:3

pLayoutMain->addLayout(pLayout1);
pLayoutMain->addLayout(pLayout2);
pLayoutMain->setStretch(0, 2);
pLayoutMain->setStretch(1, 3);

// 底面邊距設0

int left = 0, right = 0, top = 0, bottom = 0;
pMainLayout->getContentsMargins(&left, &top, &right, &bottom);
pMainLayout->setContentsMargins(left, right, top, 0);

// 添加填充彈簧條

pLayout1->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));

// 元素設置位置:左對齊,上下居中

pLabel->setAlignment(Qt::AlignLeft|Qt::AlignVCenter);

// 設置固定高度

pLabel->setFixedHeight(21);

// 設置固定寬度

pLabel->setFixedWidth(21);


分享到:


相關文章: