上一部分漫談QWidget及其衍生類別(一) 介紹了QWidget及其衍生類別,分:視窗、普通控制項兩種類型(其實有個Qt::SubWindow沒有提,不過本系列中也沒有介紹它的打算,因為我不熟)。
本文接下來試圖看看 QLayout 與視窗的幾何尺寸控制。
注意:本文只是試圖解釋,QLayout其實沒有任何神秘的東西,它所有的功能離開它你也都可以做。但這並不是鼓勵大家不使用QLayout。
始終記住一點:要改變一個Widget的大小,只有move()、resize()、setGeometry()這3個東西可用,當然,對於帶裝飾器的最上層視窗,你還可以通過滑鼠等改變大小或移動視窗位置(但這個不在本文討論範圍內)。 幾何尺寸
一個QWidget 或其衍生類別 放置到什麼位置。 需要佔多大的地盤。
對於一個視窗(Window)來說,還要區分: 帶視窗裝飾器後的位置和大小 不帶裝飾器(用戶端區域。繪圖區域。)的位置和大小
看起來還蠻複雜的哈,列個表看看。
| frameGeometry() |
幾何尺寸(位置+大小) |
對於視窗,包含視窗裝飾器 |
| x() y() pos() |
只包含位置資訊(左上方座標) |
| move() |
只移動位置 |
| geometry() |
幾何尺寸(位置+大小) |
不包含視窗裝飾器 |
| width() height() rect() size() |
只包含大小資訊 |
| setGeometry() |
改變 位置+大小 |
| resize() |
只改變大小 |
關鍵記住一點:要程式內改變一個Widget的大小,只有move、resize、setGeometry這3個東西可用。不要被QLayout幹擾,它一點都不神秘,它也只能老老實實去調用這類函數。 例子
用個例子看看吧,如果 在一個 400X400 的Widget上,放置很多其他Widget(比如64個 45X45 的按鈕)
#include <QtGui/QApplication>#include <QtGui/QPushButton>int main(int argc, char *argv[]){ QApplication a(argc, argv); QWidget widget; widget.setGeometry(100, 100, 400, 400); for (int i=0; i<8; ++i) { for (int j=0; j<8; ++j) { QPushButton * btn = new QPushButton(QString("(%1,%2)").arg(i).arg(j), &widget); btn->setGeometry(i*50, j*50, 45, 45); } } widget.show(); return a.exec();}
只需要挨個設定一下幾何尺寸,似乎也不複雜嘛。是吧。 困難是什麼呢。 如果我們用滑鼠拖動來改變視窗Widget的大小(有裝飾器,可以拖動),它上面的這些按鈕卻不會動(我們前面的很黑體字提到的哈)。介面將很難看。
其實這也不是大問題,我們只需要在子類化QWidget,覆蓋(override)它的resizeEvent()函數
void Widget::resizeEvent(QResizeEvent *){}
在這兒重新設定它上面的按鈕的位置和大小就行了。 考慮一個問題,我們如何知道一個widget用多大的大小合適呢。
這是個大問題,單一的widget還好解決,比如一個按鈕,你可以根據文字、按鈕樣式等等計算一個大小。可是對於複合的widget:比如我們例子中的widget中有64個按鈕,如果再將這樣的64個widget放於另外一個widget中,會怎麼樣。
沒有什麼好辦法,仍然是需要我們一個一個進行計算。其實不是太難,但是操作特別繁雜。 再考慮一個問題,如果我們改變一個按鈕上的文字,按鈕的最佳尺寸要變化,如何處理。
只能是按鈕通知其parent(通過LayoutRequest事件),而後parent重新排布子控制項,以獲得最佳顯示效果。
接下來,我們看看 QLayout 是如何解決這三個問題的。 QLayout
layout 做哪些事情呢。
| 初始放置 |
將子widget一個一個放置到父widget上 |
layout 計算各個子widget大小,並調用setGeometry() 來設定 |
| 響應父widget變化 |
父widget大小變化時,子widget相應變化 |
layout 通過監聽父widget的QResizeEvent事件來實現 |
| 響應子widget變化 |
子widget的最佳大小變化時 (比如給按鈕設定新的Text) |
讓父物件的layout 重新計算幾何尺寸 |
QResizeEvent
當一個widget的大小變化後,會產生QResizeEvent事件(這時widget所關聯Layout就開始重新計算嘍...)。
我們知道,事件都是通過QWidget::event()派發的:
bool QWidget::event(QEvent *event){ switch (event->type()) { case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); break;... case QEvent::Move: moveEvent((QMoveEvent*)event); break; case QEvent::Resize: resizeEvent((QResizeEvent*)event); break;...
但是,Qt對QLayout有特殊照顧,在事件到達接收者的event()函數之前,先送到了接收者對應的layout中:
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e){... if (receiver->isWidgetType()) { QWidget *widget = static_cast<QWidget *>(receiver); if (QLayout *layout=widget->d_func()->lay