漫談QWidget及其派生類(二)

pamxy發表於2013-06-16

轉自: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()
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()->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提供一些大小資訊
對於自定義widget,子類化時你可能需要提供這些資訊

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

參考

 


 

相關文章