漫談QWidget及其派生類(二)
轉自:http://blog.csdn.net/dbzhang800/article/details/6741344?reload
-
上一部分漫談QWidget及其派生類(一) 介紹了QWidget及其派生類,分:視窗、普通控制元件兩種型別(其實有個Qt::SubWindow沒有提,不過本系列中也沒有介紹它的打算,因為我不熟)。
本文接下來試圖看看 QLayout 與視窗的幾何尺寸控制。
注意:本文只是試圖解釋,QLayout其實沒有任何神祕的東西,它所有的功能離開它你也都可以做。但這並不是鼓勵大家不使用QLayout。
始終記住一點:要改變一個Widget的大小,只有move()、resize()、setGeometry()這3個東西可用,當然,對於帶裝飾器的頂級視窗,你還可以通過滑鼠等改變大小或移動視窗位置(但這個不在本文討論範圍內)。
幾何尺寸
一個QWidget 或其派生類
- 放置到什麼位置?
- 需要佔多大的地盤?
對於一個視窗(Window)來說,還要區分:
- 帶視窗裝飾器後的位置和大小
- 不帶裝飾器(客戶區域?繪圖區域?)的位置和大小
看起來還蠻複雜的哈,列個表看看。
frameGeometry() |
幾何尺寸(位置+大小) |
對於視窗,包含視窗裝飾器 |
x() |
只包含位置資訊(左上角座標) |
|
move() |
只移動位置 |
|
geometry() |
幾何尺寸(位置+大小) |
不包含視窗裝飾器 |
width() |
只包含大小資訊 |
|
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的最佳大小變化時 |
讓父物件的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()->layout) { layout->widgetEvent(e); } } bool consumed = receiver->event(e); ...
而後layout開始工作
void QLayout::widgetEvent(QEvent *e) { switch (e->type()) { case QEvent::Resize: if (d->activated) { QResizeEvent *r = (QResizeEvent *)e; d->doResize(r->size()); } else { activate(); } break; ...
恩,注意看上面程式碼:如果reciever是widget而且有layout,該事件先送到layout的widgetEvent()中。然後才會通過event()派發到達大家熟悉的resizeEvent()等函式。
QEvent::LayoutRequest
前面的resize比較容易理解,如果子widget的大小想變化,如何通知layout呢?
通過void QWidget::updateGeometry ()函式。
void QWidgetPrivate::updateGeometry_helper(bool forceUpdate) { ... if (!q->isWindow() && !q->isHidden() && (parent = q->parentWidget())) { if (parent->d_func()->layout) parent->d_func()->layout->invalidate(); else if (parent->isVisible()) QApplication::postEvent(parent, new QEvent(QEvent::LayoutRequest)); }
看看這段程式碼,如果parent有layout佈局,直接讓佈局無效(強制Layout重新計算大小)。
而如果parent沒有佈局呢?恩,思想也比較簡單:它給父widget傳送 LayoutRequest 事件。注意:如果我們不使用佈局的話,面對這種情況,我們就要自己處理這個事件嘍。
sizeHint等
這個其實似乎是最有趣的,QLayout如果知道它負責控制的各個widget該有多大的大小呢?
QWidget::sizePolicy() |
這3個東西為layout提供一些大小資訊 |
QWidget::sizeHint() |
|
QWidget::minimumSizeHint() |
熟悉這3個東西,以及各個QLayout派生類的使用,不然,你可能會抱怨——QLayout太難用了
“混用”會如何?
比如:前面的例子,我們64個按鈕,如果32個使用QGridLayout進行管理,32個不用layout進行管理。結果會怎麼樣?
其實不會怎麼樣。QGridLayout 負責對它管理的widget呼叫setGeometry,而你負責對自己管理的呼叫setGeometry。想怎麼放就怎麼放。(但是你要注意:最好別讓它們重合,不然...)
QMainWindow
QMainWindow 上面放置很多的Widget:
選單欄 |
QMenuBar |
這些全是QWidget的派生類 |
工具欄 |
QToolBar |
|
狀態列 |
QStatusBar |
|
停靠視窗 |
QDockWidget |
|
中心窗體 |
... |
其實沒有什麼神祕的,一堆widget放置到了QMainWindow中,而且還會自動隨著QMainWindow變化。你很容易想到它預設就已經設定了一個QLayout!
class QMainWindowLayout : public QLayout { Q_OBJECT ...
這是一個私有類,你不必關心細節,但是可以考慮:平時如何使用QLayout的?是不是要將你的widget加入到layout中??
在QMainWindow中,QMenuBar、QToolBar等等都已經加入到了它的layout中,而且layout中為你留了一個位置,就是中心窗體。
在QMainWindow,QMainWindowLayout管理的這些子widget佈滿了幾乎整個窗體。所以:有人抱怨
-
為什麼在 MainWindow::paintEvent() 中畫的東西總是不成功? 不是不成功,是被上面的選單欄、中心窗體等擋住了。
-
為什麼MainWindow::mousePressEvent()中收不到滑鼠事件?? 同上...
-
為什麼new QPushButton(this)建立的按鈕總是在左上方? 既沒有加入到layout中,又沒有手動呼叫setGeometry()或move(),當然如此了
- ...
-
當然還有更隱蔽的,訊號槽不起作用? 見http://hi.baidu.com/cyclone
參考
相關文章
- 派生類
- 泛型類派生子類泛型
- 效能優化漫談之二優化
- 繼承 基類與派生類繼承
- 【森城市】GIS資料漫談(二)
- 類的繼承和派生繼承
- Qt QWidget Must construct a QApplication before a QWidgetQTStructAPP
- WPF 控制元件類派生關係控制元件
- UIAppearance漫談UIAPP
- Flink漫談
- C++派生類的拷貝構造C++
- 顧客類的派生(C#程式碼)C#
- 基本資料型別及其包裝類(二)資料型別
- 漫談逆向工程
- 漫談全景分割
- Alink漫談(八) : 二分類評估 AUC、K-S、PRC、Precision、Recall、LiftChart 如何實現
- Qt三大視窗基類的差別QMainWindow QWidget QDialogQTAI
- 漫談 React 元件庫開發(二):元件庫最佳實踐React元件
- 動作與射擊漫談:俯視角射擊(二)
- 生成擴散模型漫談(二):DDPM = 自迴歸式VAE模型
- 漫談負載均衡負載
- Hadoop Map Reduce 漫談Hadoop
- 隨機數漫談隨機
- 漫談CUDA優化優化
- 第11章 使用類——再談過載:向量類(二)
- C++物件切片探秘:派生類物件如何被‘切割’?C++物件
- 淺談session及其安全Session
- ProgressBar及其子類
- 【MySQL】四、Insert buffer 漫談MySql
- 漫談 SLAM 技術(上)SLAM
- PHP安全性漫談PHP
- iOS APP 架構漫談iOSAPP架構
- GIS資料漫談(三)
- C++,繼承,基類和派生類指標間賦值等知識C++繼承指標賦值
- 新特性:postgresql的vacuum漫談SQL
- [前端漫談_1] 從 for of 聊到 Generator前端
- 漫談Web快取架構Web快取架構
- 效能優化漫談之一優化
- 漫談計算機架構計算機架構