本文轉自:《Qt編程指南》 作者:奇先生 Qt編程指南,Qt新手教程,Qt Programming Guide 6.5 控件尺寸調整策略
6.5.1 布局器工作原理
這些函數和屬性我們在 6.1.1 小節專門介紹過,設置最小尺寸和最大尺寸是沒啥技術含量的,比較簡單,這里不贅述了。 6.5.2 QSizePolicy 之一:伸展因子
|
枚舉標志位 | 數值 | 描述 |
QSizePolicy::GrowFlag | 1 | 可增長標志,如果有必要的話,可以在建議尺寸之外繼續增長。 |
QSizePolicy::ExpandFlag | 2 | 盡量擴展標志,能占多大空間就占多大。 |
QSizePolicy::ShrinkFlag | 4 | 可收縮標志,如果有必要的話,可以在縮小到建議尺寸之后繼續縮小。 |
QSizePolicy::IgnoreFlag | 8 | 忽略建議尺寸,這個增長方式最野蠻,能占多大空間就占多大空間 |
建議尺寸就是通過控件的 sizeHint() 函數獲取的尺寸,這個尺寸通常由 Qt 類庫自己根據要顯示的內容計算。建議尺寸是伸展策略的基準。
控件通常不會直接設置策略的基本標志位,因為沒有這方面的設置函數。基本標志位的用途,是為了組合成為實用的策略枚舉常量,也就是下面第二層級的內容。
伸展策略的枚舉常量由 QSizePolicy::?Policy 類型枚舉,有七個定義好的常量,用于設置控件的水平和垂直伸展策略:
枚舉常量 | 數值 | 拉伸特點 | 描述 |
QSizePolicy::Fixed | 0 | 固定 | 以建議尺寸固定住,對于水平方向是固定寬度,垂直方向是固定高度。 |
QSizePolicy::Minimum | GrowFlag | 被動拉大 | 以建議尺寸為最小尺寸,如果有多余的空間就拉伸,沒有多余的空間就保持建議尺寸。被動擴張。 |
QSizePolicy::Maximum | ShrinkFlag | 被動縮小 | 以建議尺寸為最大尺寸,窗口縮小時,如果其他控件需要,該控件可以盡量縮小為其他控件騰出空間。 |
QSizePolicy::Preferred | GrowFlag | ShrinkFlag | 被動伸縮 | 以建議尺寸為最佳尺寸,能屈能伸,窗口縮小時可以為其他控件騰出空間,窗口變大時,也可以占據其他控件不需要的空閑空間?;?QWidget 默認是這種策略。被動擴張。 |
QSizePolicy::Expanding | GrowFlag | ShrinkFlag | ExpandFlag | 主動擴張 | 建議尺寸僅僅是明智的建議,但控件基本不采用。這個模式也是能屈能伸,但它傾向于主動擴張,它會盡可能占據新增的區域。 |
QSizePolicy::MinimumExpanding | GrowFlag | ExpandFlag | 主動擴張 | 以建議尺寸作為最小尺寸,主動擴張,盡可能占據新增的區域。 |
QSizePolicy::Ignored | ShrinkFlag | GrowFlag | IgnoreFlag | 野蠻擴張 | 忽略建議尺寸,雖然能屈能伸,但是它會盡最大可能占據空間。 |
我們對七個策略常量大致分兩類,第一類是固定、單向縮小、單向拉大的,相同布局情景中,占據的尺寸大小排序為:
QSizePolicy::Maximum ≤ QSizePolicy::Fixed ≤ QSizePolicy::Minimum ≤ QSizePolicy::MinimumExpanding 。
第二類是能屈能伸的,如果在相同布局情景中,占據尺寸大小排序為:
QSizePolicy::Preferred ≤ QSizePolicy::Expanding ≤ QSizePolicy::Ignored 。
七個策略枚舉常量,最常用到的只有如下四個,我們考慮它們在相同布局場景中,占據的尺寸大小進行不嚴格排序(有例外):
QSizePolicy::Fixed ≤ QSizePolicy::Preferred ≈ QSizePolicy::Minimum ≤ QSizePolicy::Expanding
雖然 Preferred 和 Expanding 都是能屈能伸的類型,但實際情況是只有窗口縮小到特別小的情況,這兩個才會比 Fixed 小。
窗口如果特別小,那么窗口的可用性顯然受限,這通常屬于不合理的設置,因此正常情況下不會遇到 Preferred 和 Expanding 比 Fixed 占用空間小的情況。
以上的策略枚舉常量是用于尺寸策略的設置函數中,控件和窗口的伸展策略細分為水平方向和垂直方向,通過如下兩個函數分別設置:
- void QSizePolicy::?setHorizontalPolicy(Policy policy) //設置水平策略
- void QSizePolicy::?setVerticalPolicy(Policy policy) //設置垂直策略
水平和垂直策略在大多數情況下都是不相關的,各自管各自的維度。除了調用函數,設計師和 QtCreator 設計模式也可以直接設置控件的 sizePolicy 屬性兩個子屬性:"水平策略" 和 "垂直策略" 。
伸展策略的常量有七個,每個常量都有各自的特性,我們在這里把它們簡化一下,在實際使用中可以按照下面三條建議來運用策略的枚舉常量:
① 如果希望控件尺寸在水平或垂直方向固定住,那么把該維度的策略設置為 QSizePolicy::Fixed。
② 如果希望控件被動拉伸,其他控件不需要空間時這個控件才會占據新增區域,那么可以用 QSizePolicy::Preferred (尺寸下限是隱含的最小建議尺寸)或者 QSizePolicy::Minimum(尺寸下限是建議尺寸)。
③ 如果希望控件盡量拉伸,主動擴張,那就把策略設置為 QSizePolicy::Expanding。
Qt 里面的控件默認策略也是基本符合上面三條建議的,所以希望大家記住這三條建議,因為比較實用。
QSizePolicy 除了上面兩小節的伸展因子和伸展策略,還有一些其他的內容,這部分補充內容應用會比較少,但是會影響界面的一些細節,有必要在這講一下。
QSizePolicy::ControlType 枚舉類型有一大堆枚舉常量,大部分的 Qt 控件都對應一個枚舉常量,比如按壓按鈕對應的常量為 QSizePolicy::PushButton,單行編輯控件對應的枚舉常量是 QSizePolicy::LineEdit,類似的還 有很多,這里不列舉了。
QSizePolicy 類中關于控件類型獲取和設置的函數為:
- ControlType QSizePolicy::?controlType() const
- void QSizePolicy::?setControlType(ControlType type)
這個控件類型應用比較少,不是什么時候都生效的,而且對界面布局影響很小,只是一些細節有差異??丶愋椭粫灰恍┨囟ǖ?Qt 界面風格(可以查詢 QStyle 類文檔)采用,比如蘋果系統風格的 QMacStyle,不同的控件類型會影響各個控件之間的默認間隙。比如 Mac OS X Aqua 指導方針中指出按壓按鈕之間需要 12 像素的間隙,而垂直方向排布的單選按鈕間隔是 6 像素。因為控件類型影響很小,所以通常可以忽略這個設置。
多數情況下建議尺寸 sizeHint() 的高度和寬度是不相關的,但有些特殊情況,比如能夠自動換行的標簽控件、菜單欄(后面章節講解),比如一行長文本自動換行變成兩行時,高度是雙倍的,如果把標簽拉寬,當兩 行文本恢復成一行的時候,高度就變成單行的。這種控件越寬,它高度相對低一些,越窄,高度就高一些。因此這些控件的建議尺寸計算時,高度和寬度是相關的。
可以通過如下函數設置在計算建議尺寸時,高度和寬度相關:
void QSizePolicy::?setHeightForWidth(bool dependent) //設置高度依賴寬度
dependent 如果為 true,那么控件的建議尺寸高度就和寬度相關。如果 dependent 為 false 那么就是無關的。
如果要獲知建議尺寸的高度是否與寬度相關,可以用如下函數:
bool QSizePolicy::?hasHeightForWidth() const //判斷高度是否依賴寬度
另外還有一對相反功能的函數,計算建議尺寸時,寬度可能會依賴高度,這個設置只對 QGraphicsLayout 的子類有用,一般是用不到的:
- void QSizePolicy::?setWidthForHeight(bool dependent) //設置寬度依賴高度
- bool QSizePolicy::?hasWidthForHeight() const //判斷寬度是否依賴高度
HeightForWidth 和 WidthForHeight 二者最多只能有一個生效,不能雙向依賴的。
注意無論是 HeightForWidth 還是 WidthForHeight ,都只對建議尺寸的計算有影響,不會直接影響窗口或控件的高度和寬度拉伸比例。如果希望窗口或控件的高度和寬度保持一定比例,比如 2 : 3 ,那么這些函數是完全沒用的,因為根本不是一個概念。
程序運行時,控件都可以通過函數 hide() 隱藏自己。在控件隱藏時,控件是否還占據布局器里的空間,這是可以設置的:
- void QSizePolicy::?setRetainSizeWhenHidden(bool retainSize) //設置控件在隱藏時是否仍占據布局器空間
- bool QSizePolicy::?retainSizeWhenHidden() const //判斷隱藏控件是否占據布局器空間
默認情況下,控件調用 hide() 隱藏之后,就不會在通過布局器分配空間了,因為沒有必要。
如果遇到特殊情況需要保留隱藏控件在布局器里的占用的空間,可以用上述函數設置。
如果設置保留隱藏控件的空間,那么布局器會留下一塊空白區域,就是控件在隱藏前應該占據的區域。
為了應對可能的屏幕旋轉操作,QSizePolicy 提供了一個快捷函數,能夠把水平方向的伸展因子、伸展策略與垂直方向上的伸展因子、伸展策略完全互換過來:
void QSizePolicy::?transpose()
這個函數讀者可以根據實際情況試試。
關于控件的尺寸調整策略就介紹這么多,下面通過兩個例子試一試伸展因子和伸展策略的功效。
這一小節通過兩個窗口來對比常用到的四個伸展策略枚舉常量,主界面的窗口里面是六個按鈕,點擊里面的第一個按鈕會彈出第二個示范窗口,第二個示范窗口里面放置六個 單行編輯控件。兩個窗口的布局器和各個控件的水平伸展策略都是一樣的,我們一方面對比四個常用策略的拉伸特性,另一方面對比按鈕和單行編輯控件對布局器和策略的不 同反應。
打開 QtCreator,新建一個 Qt Widgets Application 項目,在新建項目的向導里填寫:
①項目名稱 comparepolicies,創建路徑 D:\QtProjects\ch06,點擊下一步;
②套件選擇里面選擇全部套件,點擊下一步;
③基類選擇 QWidget,點擊下一步;
④項目管理不修改,點擊完成。
建好項目之后,打開窗體 widget.ui 文件,進入設計模式,按照下圖拖入六個按鈕:
我們按從上到下、從左到右順序說明一下六個按鈕的屬性:
① 文本 "Fixed" ,對象名稱 pushButtonFixed ,水平策略選擇 Fixed 。
② 文本 "Preferred" ,對象名稱 pushButtonPreferred,水平策略選擇 Preferred 。
③ 文本 "Preferred2" ,對象名稱 pushButtonPreferred2,水平策略選擇 Preferred 。
④ 文本 "Minimum" ,對象名稱 pushButtonMinimum,水平策略選擇 Minimum 。
⑤ 文本 "Minimum2" ,對象名稱 pushButtonMinimum2,水平策略選擇 Minimum 。
⑥ 文本 "Expanding" ,對象名稱 pushButtonExpanding,水平策略選擇 Expanding 。
設置好六個按鈕的屬性之后,我們對每個行進行布局,先對第一行兩個按鈕水平布局,第二行和第三行也一樣用水平布局,得到的效果如下:
然后我們點擊主窗體空白區域,不選中任何控件和子布局器(其實就是唯一選中主界面窗口自身),直接點擊上面的垂直布局按鈕,自動為窗口設置主布局器:
這樣主界面的布局就設置完畢。下面我們要為第一個 "Fixed" 按鈕添加一個 clicked() 信號的槽函數,等會我們在槽函數添加代碼來彈出第二個示范窗口:
添加好槽函數之后,保存界面文件,我們回到代碼編輯模式,首先是編輯主窗體頭文件 widget.h ,我們要為第二個示范窗口添加成員指針和創建第二個示范窗口的函數:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private slots: void on_pushButtonFixed_clicked(); private: Ui::Widget *ui; //第二個示范窗口,全部放置 QLineEdit QWidget *m_pWidget; //通過代碼構造第二個示范窗口 void CreateWidget(); }; #endif // WIDGET_H
m_pWidget 是我們自己用代碼構造的第二個示范窗口,CreateWidget() 就是負責構造第二個示范窗口的函數。頭文件里其他代碼行都是自動添加的。
主窗體的界面不需要調整,我們剛才在設計模式都設置好了。我們下面編輯源代碼文件 widget.cpp 主要是為了構建第二個示范窗口的內容,并通過 "Fixed" 按鈕的槽函數彈窗顯示。
在 widget.cpp 文件中,首先是頭文件包含和主窗體的構造函數:
#include "widget.h" #include "ui_widget.h" #include <QLineEdit> //單行編輯器 #include <QHBoxLayout> //水平布局器 #include <QVBoxLayout> //垂直布局器 #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //按鈕的建議尺寸和最小建議尺寸 qDebug()<<tr("Preferred 按鈕:") <<ui->pushButtonPreferred->sizeHint() <<ui->pushButtonPreferred->minimumSizeHint(); qDebug()<<tr("Expanding 按鈕:") <<ui->pushButtonExpanding->sizeHint() <<ui->pushButtonExpanding->minimumSizeHint(); m_pWidget = NULL; //初始空指針 CreateWidget(); //構建第二個示范窗口 }
我們新增了三個頭文件包含,是第二個示范窗口要用到的單行編輯器和水平布局器、垂直布局器。
在構造函數里面,我們首先打印了 "Preferred" 按鈕和 "Expanding" 按鈕的建議尺寸和最小建議尺寸。
因為這兩個按鈕都是能伸能屈的,建議尺寸是最優的尺寸,但是在需要伸縮的情況下,這兩個按鈕可能變大也可能變小。
我們在設計模式沒有設置按鈕的最小尺寸 minimumSize,在這種情況下,minimumSizeHint() 尺寸會自動成為尺寸下限,按鈕縮小到這個最小建議尺寸,就不會再縮小。
打印按鈕的信息之后,我們將 m_pWidget 初始化為空指針,然后調用 CreateWidget() 構建第二個示范窗口的界面。CreateWidget() 函數代碼等會講解。我們先看看析構函數的內容:
- Widget::~Widget()
- {
- //刪除第二個窗口
- if(m_pWidget != NULL)
- {
- delete m_pWidget; m_pWidget = NULL;
- }
- //刪除ui
- delete ui;
- }
析構函數中,我們對 m_pWidget 做了判斷,如果是非空指針,就刪除第二個示范窗口,并把 m_pWidget 設為空。
析構函數最后一句是原本自動生成的代碼,不用變。
接下來是 CreateWidget() 函數,就是構建第二個示范窗口的代碼,我們沒有用新的 ui 文件,直接用代碼構建該示范窗口。這個函數內容比較多,我們按照幾個小塊來分開講解,首先是窗口新建和主布局器新建:
//建立第二個示范窗口,包括內部的單行編輯控件和布局器
- //仿造主界面的架構,只是把按鈕換成單行編輯器
- void Widget::CreateWidget()
- {
- //構建第二個示范窗口
- m_pWidget = new QWidget(this, Qt::Window); //獨立窗口
- m_pWidget->resize(480, 360);
- m_pWidget->setWindowTitle(tr("單行編輯器的布局"));
- //主布局器是垂直排列的三行
- QVBoxLayout *mainLayout = new QVBoxLayout(m_pWidget);
- //待續
主界面是不同水平伸展策略的按鈕,我們這第二個示范窗口則是不同水平伸展策略的單行編輯器。
CreateWidget() 函數內,第一句是新建一個獨立的子窗口用于彈窗,注意 QWidget() 構造函數第二個參數,是窗口類型標志位,如果不設置標志位,那么 m_pWidget 會作為大部件顯示在主窗體內部,而在設置標志位是 Qt::Window 或 Qt::Dialog ,那么新建的 m_pWidget 才是獨立的子窗口,不會放在主窗體內部。
然后我們把第二個示范窗口大小重置為 480*360 ,并設置該窗口標題為 "單行編輯器的布局" 。
接著新建了主布局器,用于容納三個水平行的布局器。主布局器構造時指定了 m_pWidget 為父窗口指針。
下面來看看第一行的控件和布局器代碼:
//構建六個單行編輯器,分成三行做對比 //第一行 //第一個是固定尺寸的 QLineEdit *leFixed = new QLineEdit(m_pWidget); leFixed->setText(tr("Fixed")); QSizePolicy sp = leFixed->sizePolicy(); //修改第一個的水平策略為 Fixed sp.setHorizontalPolicy(QSizePolicy::Fixed); leFixed->setSizePolicy(sp); //第二個編輯器 QLineEdit *lePreferred = new QLineEdit(m_pWidget); lePreferred->setText(tr("Preferred")); sp = lePreferred->sizePolicy(); //修改第二個的水平策略為 Preferred sp.setHorizontalPolicy(QSizePolicy::Preferred); lePreferred->setSizePolicy(sp); //第一行的布局器 QHBoxLayout *lay1 = new QHBoxLayout(); lay1->addWidget(leFixed); //添加第一個編輯器 lay1->addWidget(lePreferred); //添加第二個編輯器 //把第一行的布局器添加到主布局器 mainLayout->addLayout(lay1); //待續
第一個單行編輯器新建時是指定 m_pWidget 為父窗口,這樣該控件會由 m_pWidget 窗口管理。
然后設置第一個單行編輯器的文本為 "Fixed" ,并把水平策略修改為 QSizePolicy::Fixed 。
第一個編輯器的尺寸會固定為建議尺寸,不會被拉寬。
第二個單行編輯器新建時也是指定 m_pWidget 為父窗口,然后設置文本為 "Preferred",修改水平策略為 QSizePolicy::Preferred,這個編輯器會被動地拉大或縮小。雖然沒有指定該編輯器最小尺寸,但是等會程序運行時我們會看到 "Preferred" 編輯器以隱含的最小建議尺寸為下限。
然后我們新建了第一行的水平布局器 lay1,注意這個布局器沒有指定父窗口。
我們把第一行的兩個編輯器添加給 lay1 ,然后把 lay1 添加到主布局器。
下面看看第二行控件和布局器的代碼:
//第二行 //第三個編輯器 QLineEdit *lePreferred2 = new QLineEdit(m_pWidget); lePreferred2->setText(tr("Preferred2")); sp = lePreferred->sizePolicy(); //修改第三個的水平策略為 Preferred sp.setHorizontalPolicy(QSizePolicy::Preferred); lePreferred2->setSizePolicy(sp); //第四個編輯器 QLineEdit *leMinimum = new QLineEdit(m_pWidget); leMinimum->setText(tr("Minimum")); sp = leMinimum->sizePolicy(); //修改第三個的水平策略為 Minimum sp.setHorizontalPolicy(QSizePolicy::Minimum); leMinimum->setSizePolicy(sp); //第二行的布局器 QHBoxLayout *lay2 = new QHBoxLayout(); lay2->addWidget(lePreferred2); lay2->addWidget(leMinimum); //添加到主布局器 mainLayout->addLayout(lay2); //待續
第三個單行編輯器也是以 m_pWidget 為父窗口,設置文本為 "Preferred2",修改水平策略為 QSizePolicy::Preferred。
第四個單行編輯器也是以 m_pWidget 為父窗口,設置文本為 "Minimum",修改水平策略為 QSizePolicy::Minimum。
然后新建了第二行的布局器 lay2 ,這個布局器沒有指定父窗口指針。
接著將 lePreferred2 和 leMinimum 兩個編輯器添加給布局器 lay2 ,再把第二行布局器 lay2 添加到主布局器 mainLayout 。
接下來是第三行控件和布局器的代碼:
//第三行 //第五個編輯器 QLineEdit *leMinimum2 = new QLineEdit(m_pWidget); leMinimum2->setText(tr("Minimum2")); sp = leMinimum2->sizePolicy(); //修改第五個的水平策略為 Minimum sp.setHorizontalPolicy(QSizePolicy::Minimum); leMinimum2->setSizePolicy(sp); //第六個編輯器 QLineEdit *leExpanding = new QLineEdit(m_pWidget); leExpanding->setText(tr("Expanding")); sp = leExpanding->sizePolicy(); //修改第六個的水平策略為 Expanding sp.setHorizontalPolicy(QSizePolicy::Expanding); leExpanding->setSizePolicy(sp); //第三行的布局器 QHBoxLayout *lay3 = new QHBoxLayout(); lay3->addWidget(leMinimum2); lay3->addWidget(leExpanding); mainLayout->addLayout(lay3); //待續
第五個單行編輯器也是以 m_pWidget 為父窗口,設置文本為 "Minimum2" ,修改水平策略為 QSizePolicy::Minimum。
第六個單行編輯器也是以 m_pWidget 為父窗口,設置文本為 "Expanding",修改水平策略為 QSizePolicy::Expanding。
然后新建了第三行的布局器 lay3 ,這個布局器沒有指定父窗口。
接著把第三行的兩個編輯器 leMinimum2、leExpanding 添加到 布局器 lay3 ,并把 lay3 添加到主布局器 mainLayout。
注意上面關于控件的代碼寫法:
單行編輯控件的父窗口是 m_pWidget ,而不是布局器,也不能是布局器。
布局器僅僅是輔助布局的手段,布局器不是實際的功能控件。
布局器基類是 QLayoutItem,不能獨立存在,它需要依附于其他實體功能控件或窗口,但不會擁有任何控件,也就是不能作為父窗口。
實體功能控件的基類是 QWidget,可以獨立存在,也可以借助布局器進行布局,QWidget 派生類控件可以作為其他控件的父窗口,可以擁有子控件。
CreateWidget() 函數最后一小部分代碼是設置主布局器和打印調試信息的:
- //設置該窗口的主布局器
- m_pWidget->setLayout(mainLayout);
- //如果只有一個布局器的 parent 設置為該窗口,那么可以不調用 setLayout()
- //上面的 setLayout() 一句其實可以省略,mainLayout 自動是主布局器
- //打印信息
- qDebug()<<tr("Fixed 編輯器建議尺寸:")<<leFixed->sizeHint();
- qDebug()<<tr("Preferred 編輯器建議尺寸:")<<lePreferred->sizeHint();
- qDebug()<<tr("Preferred 編輯器最小建議尺寸:")<<lePreferred->minimumSizeHint();
- qDebug()<<tr("Minimum 編輯器建議尺寸:")<<leMinimum->sizeHint();
- qDebug()<<tr("Expanding 編輯器建議尺寸:")<<leExpanding->sizeHint();
- qDebug()<<tr("Expanding 編輯器最小建議尺寸:")<<leExpanding->minimumSizeHint();
- }
通常設置某個窗口的主布局器就是調用它的 setLayout() 函數,這是主動設置窗口主布局器的方式。
還有第二種設置主布局器的方式,就是 ui_*.h 里面采用的方式,當有且只有一個布局器將父窗口指針設置為窗口 m_pWidget 時,這個布局器就自動成為 m_pWidget 的主布局器了。
我們之前代碼里的 lay1、lay2、lay3 構造時都沒有父窗口指針,只有 mainLayout 指定了父窗口為 m_pWidget,其實 mainLayout 已經成為 m_pWidget 主布局器了,因此上面的 setLayout() 一句代碼可以省略。
最后幾句 qDebug() 打印了 "Fixed" 和 "Minimum" 編輯器的建議尺寸,并打印了 "Preferred" 和 "Expanding" 編輯器的建議尺寸和最小建議尺寸。
CreateWidget() 函數代碼就是上面那些。 程序兩個窗口的控件個數和布局格式是一樣的,但第二個示范窗口的代碼其實已經不少了。而我們通過設計模式生成的主界面窗口,就沒編寫什么代碼。可見通過 Qt 設計師和 QtCreator 設計圖形界面是很方便的,節省了很多關于新建控件、布局器等重復無聊的代碼。
源代碼文件 widget.cpp 最后一部分是我們主界面第一個按鈕的槽函數代碼:
- //點擊按鈕彈出第二個窗口
- void Widget::on_pushButtonFixed_clicked()
- {
- if(m_pWidget != NULL)
- {
- m_pWidget->show(); //顯示
- }
- }
這個槽函數非常簡單,就是判斷 m_pWidget 是否非空,如果非空就彈出該窗口。
操作指針之前判斷一下指針非空,有利于增加程序的健壯性,以免操作空指針。
整個例子的代碼到這里就完整了。主界面的窗口其實完全靠 QtCreator 設計模式完成的,而第二個示范窗口是完全靠手動編寫代碼實現的。下面生成并運行例子看看,先看主界面窗口:
在主界面窗口比較大時,明顯看到第一行的 "Fixed" 按鈕比較窄,而 "Preferred" 按鈕比較寬。因為 "Fixed" 按鈕寬度是固定的,不能拉伸,因此水平布局器只能嘗試拉伸 "Preferred" 按鈕,正好 "Preferred" 按鈕能夠被動拉伸,就被拉寬了。
第二行的 "Preferred" 按鈕和 "Minimum" 按鈕是等寬的,因為二者都是被動拉伸,誰也不占上風,就平均拉伸。
第三行的 "Minimum" 按鈕屬于被動拉伸,而 "Expanding" 按鈕屬于主動拉伸,所有額外的空間都被 "Expanding" 按鈕占據了,"Minimum" 按鈕僅保持了建議尺寸。
我們把主界面窗口縮到最小尺寸,可以看到下圖效果:
注意,"Fixed" 按鈕和 "Minimum" 按鈕的尺寸下限是建議尺寸 sizeHint(),
而 "Preferred" 和 "Expanding" 按鈕尺寸下限是最小建議尺寸 minimumSizeHint()。
上圖中六個按鈕最小情況下都是一樣大的,因為按鈕的建議尺寸和最小建議尺寸是一樣大的,我們可以在輸出面板看到按鈕的尺寸信息:
- "Preferred 按鈕:" QSize(75, 23) QSize(75, 23)
- "Expanding 按鈕:" QSize(75, 23) QSize(75, 23)
對于按鈕組成的布局器,四種常用伸展策略是符合下面公式的:
QSizePolicy::Fixed ≤ QSizePolicy::Preferred ≈ QSizePolicy::Minimum ≤ QSizePolicy::Expanding
主窗口的三行按鈕對比完畢。我們下面點擊主界面第一個 "Fixed" 按鈕進行彈窗,看到第二個示范窗口:
在窗口比較大時,六個編輯器的拉伸特性和主窗口沒區別,都符合上面的公式。
下面我們把第二個示范窗口縮到最小,再看看效果:
這時候就明顯看到 "Fixed" 和 "Minimum" 編輯器保持一個建議尺寸,不會再變小。
而 "Preferred" 和 "Expanding" 編輯器已經縮到比建議尺寸更小了,這兩個可伸縮的編輯器的尺寸下限是由最小建議尺寸指定的。我們可以在 調試輸出信息里看到:
- "Fixed 編輯器建議尺寸:" QSize(133, 20)
- "Preferred 編輯器建議尺寸:" QSize(133, 20)
- "Preferred 編輯器最小建議尺寸:" QSize(39, 18)
- "Minimum 編輯器建議尺寸:" QSize(133, 20)
- "Expanding 編輯器建議尺寸:" QSize(133, 20)
- "Expanding 編輯器最小建議尺寸:" QSize(39, 18)
單行編輯器的建議尺寸都是 133*20,最小建議尺寸是 39*18 。
因為單行編輯器的最小建議尺寸比普通的建議尺寸更小,因此上面的公式在窗口特別小的時候不成立。
不過窗口特別小的時候,控件的可用性已經大大降低了,單行編輯器連一個單詞都顯示不完整了,這種情況在實際應用中是很少遇見的。
本小節的例子就是為了印證 6.5.3 小節關于常用伸展策略的三條建議,伸展策略的枚舉常量原本有七個,我們把七個策略常量簡化為三條使用建議,希望大家記住這三條建議,到實際布局時會比較實用。
本小節是對上一章 5.6.3 小節的圖標使用示例進行布局,我們簡單示范一下布局器的伸展因子和伸展策略用法。
我們復制 D:\QtProjects\ch05\ 目錄里面的 helloqrc 文件夾,粘貼到第 6 章的示例目錄 D:\QtProjects\ch06\ ,然后進行下面操作:
① 把新的 helloqrc 文件夾重命名為 helloqrcnew ,并刪除里面的 helloqrc.pro.user 用戶文件。
② 把 helloqrcnew 文件夾里面的 helloqrc.pro 重命名為 helloqrcnew.pro 。
③ 用記事本打開新的 helloqrcnew.pro 文件,修改里面的 TARGET 一行,變成下面這句:
TARGET = helloqrcnew
這樣就得到新項目 helloqrcnew ,我們用 QtCreator 打開這個新項目,在配置項目界面選擇所有套件并點擊 "Configure Project" ,配置好項目后,打開 widget.ui 界面文件,進入 QtCreator 設計模式:
這個界面的布局思路大概是這樣的,我們把上面三行用網格布局器排布,第一列和第二列的伸展因子比例設置為 1:3 ,這樣在窗口拉大時,標簽占 1/4,右邊輸入控件占 3/4。我們之前 6.3.2 和 6.4.2 小節的個人信息收集示例布局都出現了標簽控件很窄,而右邊輸入控件很寬,看著有點別扭。通過網格布局器的伸展因子比例設置,可以把標簽也拉寬些。這樣在窗口比較大時,整體 界面不會太別扭。
第四行的兩個按鈕,我們用水平布局器封裝,然后把網格布局器與按鈕布局器組合成一個垂直布局器,作為窗口的主布局器。
下面我們開始操作:
(1)選中兩個單選按鈕,點擊上面的水平布局按鈕,把單選按鈕組合到一塊:
(2)我們選中前三行的控件和單選按鈕布局器,點擊上面的網格布局按鈕,實現網格布局:
實現網格布局后,把該網格布局器的列伸展因子 layoutColumnStretch 調整為1,3這樣標簽占 1/4 ,右邊一列占 3/4 。
(3)我們選中兩個按壓按鈕,點擊上面的水平布局工具按鈕,實現水平布局:
(4) 我們點擊主窗體空白區域,不選中任何控件(其實就是唯一選中主界面窗口自身),直接點擊上面的垂直布局按鈕,得到下圖的效果:
目前這個界面不是我們想看到的,首先是下面的按鈕布局器太高了,而且按鈕也拉得太寬。
(5)下面按鈕部分微調:
我們選中 "提交" 按鈕,把它的水平策略調整為 Fixed;
我們選中 "取消" 按鈕,把它的水平策略也調整為 Fixed。得到下圖效果:
然后我們點擊主窗體的空白區域,選中主窗體本身,可以看到主布局器屬性欄,把垂直布局器的 layoutStretch 伸展因子設置為:3,1這樣讓網格布局器占據垂直方向大部分區域:
現在下面兩個按鈕的布局問題算是解決了。上面網格布局器還是有問題,第二行的單選按鈕布局器占據了太大空間,而不是我們希望的每行各占三分之一。
(6)網格布局器細節調整
對于上面的網格布局器,我們希望三行各占三分之一,最直觀的想法是設置垂直方向的伸展因子。
因為界面里多個布局器嵌套,選中某個子布局器的操作其實不好實現。
我們直接在右上角的布局樹,點擊 gridLayout 條目,就可以很快捷地選中該網格布局器,然后把垂直方向的行伸展因子 layoutRowStretch 設置為1,1,1得到下圖的效果:
這里看到設置三個行的伸展因子并沒有效果,因為右邊一列的單行編輯控件和組合框在垂直方向都是固定高度的,而中間的單選按鈕布局器默認是盡量拉伸,所以即使設置了 行的伸展因子也沒有用。
如果我們修改右邊單行編輯控件和組合框的垂直伸展策略為主動擴張,那么這兩個控件會被拉得很高,但是只顯示一行文本,從顯示上看會非常別扭。
我們的視野不要局限在右邊一列,可以從左邊一列的標簽控件來打主意。
標簽在垂直方向默認都是 Preferred 被動拉伸的策略,如果我們把左邊一列三個標簽的垂直策略全部設置成 Expanding,讓三個標簽都去主動占據垂直方向的空間,那么會有驚喜出現:
這樣就得到了我們想要的布局效果了。
我們保存界面文件,然后生成運行例子,看看運行效果:
最后順便提一下,如果讀者希望標簽的文本在水平方向居中,是可以設置標簽控件的 alignment 屬性的,調整為水平居中 AlignHCenter 即可。修改界面后要點擊 QtCreator 菜單【構建-->重新構建 "項目名"】,然后就可以看到文本水平居中的標簽。讀者可以自行測試一下,這里不再截圖示范了。
本小節的例子同時示范了伸展策略和伸展因子的運用,另外,布局時不但可以從我們需要調整的列考慮,還可以從相鄰的列角度考慮問題,比如要調整第二列的輸入控件布 局,其實可以調整第一列標簽控件的垂直策略來實現我們想要的效果。思維可以開闊一些,旁敲側擊有時候也是好方法。
|