QWidget探索


  QWidget繼承自QObject和QPaintDevice,QObject前篇已有部分介紹,QPaintDevice跟繪制系統相關,以后再看,先看看它的構造函數。

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
    : QObject(*new QWidgetPrivate, nullptr), QPaintDevice()
{
    QT_TRY {
        d_func()->init(parent, f);
    } QT_CATCH(...) {
        QWidgetExceptionCleaner::cleanup(this, d_func());
        QT_RETHROW;
    }
}

new 了一個QWidgetPrivate, 繼承自QOBjectPrivate, impl實現機制。qt大部分類的實現都是這樣的機制。先看下有哪些字段,

 

 這里只展示了部分,因為太多了。是個很復雜的類,記錄了focus_next,focus_prev, layout,margin等等,注意size_policy(QSizePolicy::Preferred, QSizePolicy::Preferred),默認的QWidget的尺寸策略在這里就設置了;然后照常調用init函數,

設置parent,和windowFlags. 在init函數里,有個allWidgets變量,並把當前widget插入進去了。

QWidgetMapper *QWidgetPrivate::mapper = nullptr; // widget with wid
QWidgetSet *QWidgetPrivate::allWidgets = nullptr; // widgets with no wid。

會記錄所有創建的widget;並將private的data,賦值給widget的QWidgetData *data,然后初始化data,這個data跟QObjectData是不一樣的,QObjectData是所有private的基類,保存了一些QObject相關的信息,而這個保存了QWIdget要用到的相關信息。data里保存了低位attribute信息。high_attributes保存了高位信息,默認都設置為0. 還有個關鍵的TLExtra 字段,會在第一次使用到的時候,創建該字段(凡是需要調用到topData()的地方),又包含一個topextra(QTLWE)字段。可以看到TLExtra 包含了一些qt的屬性,而topextra保存了一些平台相關的屬性,

 

 

 

 

 

 

 首先判斷有沒有MSWindowsOwnDC標記,這個標記給widget一個本地顯示上下文,具有native性質,根據qt文檔的介紹,很少用到,且不推薦在有多個顯示器的情況下使用,尤其分辨率還不同。設置了一個WA_QuitOnClose,猜大致的意思是程序退出時,關閉窗口。一些的其他的標記文檔都有詳細說明,這里設置了一個WA_WState_Hidden,默認隱藏,以及設定widget大小,有無parent影響大小。

 if ((f & Qt::WindowType_Mask) == Qt::Desktop)
        q->create();
    else if (parentWidget)
        q->setParent(parentWidget, data.window_flags);
    else {
        adjustFlags(data.window_flags, q);
        resolveLayoutDirection();
        // opaque system background?
        const QBrush &background = q->palette().brush(QPalette::Window);
        setOpaque(q->isWindow() && background.style() != Qt::NoBrush && background.isOpaque());
    }
    data.fnt = QFont(data.fnt, q);

    q->setAttribute(Qt::WA_PendingMoveEvent);
    q->setAttribute(Qt::WA_PendingResizeEvent);

  接着判斷Qt::Desktop標記,如果是的話,在這里就直接創建窗口了,調用win32的接口了,其他的情況一般是在show()的時候判斷是否要create();可以看到Desktop屬性與設置parent互斥。順着看一下setParent里面干了啥?

    const bool resized = testAttribute(Qt::WA_Resized);
    const bool wasCreated = testAttribute(Qt::WA_WState_Created);
    QWidget *oldtlw = window();

    if (f & Qt::Window) // Frame geometry likely changes, refresh.
        d->data.fstrut_dirty = true;

    QWidget *desktopWidget = nullptr;
    if (parent && parent->windowType() == Qt::Desktop)
        desktopWidget = parent;
    bool newParent = (parent != parentWidget()) || desktopWidget;

    if (newParent && parent && !desktopWidget) {
        if (testAttribute(Qt::WA_NativeWindow) && !QCoreApplication::testAttribute(Qt::AA_DontCreateNativeWidgetSiblings))
            parent->d_func()->enforceNativeChildren();//將兄弟窗口變為native,這個只是設置相應屬性,並沒有真正創建WinId,方便后續createWinId 創建真正的windId else if (parent->d_func()->nativeChildrenForced() || parent->testAttribute(Qt::WA_PaintOnScreen))
            setAttribute(Qt::WA_NativeWindow);//如果父窗口強制子窗口native或 parent 具有Qt::WA_PaintOnScreen屬性
if (wasCreated) {
        if (!testAttribute(Qt::WA_WState_Hidden)) {
            hide();//如果當前窗口已經創建過,且沒有state_hidden屬性,則主動hide,
            setAttribute(Qt::WA_WState_ExplicitShowHide, false);//這個屬性文檔沒有介紹。
        }
        if (newParent) {
            QEvent e(QEvent::ParentAboutToChange);
            QCoreApplication::sendEvent(this, &e);//給當前窗口發送一個parent改變的事件,同步處理。
        }
    }
    // If we get parented into another window, children will be folded
    // into the new parent's focus chain, so clear focus now.
    if (newParent && isAncestorOf(focusWidget()) && !(f & Qt::Window))
        focusWidget()->clearFocus();//清除當前focus

先看有沒有resized屬性 和 WA_WState_Created 這兩個默認都是沒有的。desktopWidget 作為parent 優先級最高。如果有parent,且沒有desktopWidget,而且具有WA_NativeWindow屬性,則會使childwidget 也變為native。setParent_sys()函數如下:

if (parent) {//在設置新的parent之前,如果之前已經存在一個parent
        QObjectPrivate *parentD = parent->d_func();
        if (parentD->isDeletingChildren && wasDeleted
            && parentD->currentChildBeingDeleted == q) {//isDeletingChildchildren會在deleteChildren()設置為true,wasDeleted在析構時設置 // don't do anything since QObjectPrivate::deleteChildren() already
            // cleared our entry in parentD->children.不用管這個,已經脫離關系了,都被刪了,還設置啥parent???
        } else {
            const int index = parentD->children.indexOf(q);
            if (index < 0) {
                // we're probably recursing into setParent() from a ChildRemoved event, don't do anything
            } else if (parentD->isDeletingChildren) {//如果正在delete children
                parentD->children[index] = nullptr;//解除舊parent的關系
            } else {
                parentD->children.removeAt(index);//這一塊邏輯怎么都覺得奇怪,總之就是會通知之前的parent if (sendChildEvents && parentD->receiveChildEvents) {//滿足條件 發送相應的事件
                    QChildEvent e(QEvent::ChildRemoved, q);
                    QCoreApplication::sendEvent(parent, &e);
                }
            }
        }
    }
//后面建立新的父子關系,同樣發送事件通知,注意一點的是parent 要處於同一線程
if (parent != newparent) {
        QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function???連自己人都吐槽了,將就着看吧
        if (q->windowHandle()) {
            q->windowHandle()->setFlags(f);//判斷當前widget是否是native, 將對應的QWindow 也設置flags。
            QWidget *parentWithWindow =
                newparent ? (newparent->windowHandle() ? newparent : newparent->nativeParentWidget()) : nullptr;
            if (parentWithWindow) {
                QWidget *topLevel = parentWithWindow->window();
                if ((f & Qt::Window) && topLevel && topLevel->windowHandle()) {//如果存在頂層窗口
                    q->windowHandle()->setTransientParent(topLevel->windowHandle());//為甚么要設置這個trasient parent
                    q->windowHandle()->setParent(nullptr);
                } else {
                    q->windowHandle()->setTransientParent(nullptr);
                    q->windowHandle()->setParent(parentWithWindow->windowHandle());
                }
            } else {
                q->windowHandle()->setTransientParent(nullptr);
                q->windowHandle()->setParent(nullptr);// windowHandle 設置parent 有什么意義??
            }
        }
    }
//WindowHandle 的child 也要重新設置parent ,parent為頂層窗口

adjustFlags()函數 設置相關窗口屬性,就是對應win32的那些窗口屬性。

Qt::WA_WState_Created The widget has a valid winId().
Qt::WA_WState_Visible The widget is currently visible.
Qt::WA_WState_Hidden The widget is hidden。

adjustFlags(f, q);
    data.window_flags = f;
    q->setAttribute(Qt::WA_WState_Created, false);//設置完parent ,為什么要設置這些屬性為false.
    q->setAttribute(Qt::WA_WState_Visible, false);
    q->setAttribute(Qt::WA_WState_Hidden, false);

    if (newparent && wasCreated && (q->testAttribute(Qt::WA_NativeWindow) || (f & Qt::Window)))
        q->createWinId();//不知道這里的調用有什么意義,如果wasCreated為true,還重復創建winId干嘛。。這里的邏輯處理也感覺怪怪的,不是很清晰。

createWinId():

const bool forceNativeWindow = q->testAttribute(Qt::WA_NativeWindow);//判斷是否有native屬性, if (!q->testAttribute(Qt::WA_WState_Created) || (forceNativeWindow && !q->internalWinId())) {//沒有被創建過,且是native屬性, if (!q->isWindow()) {//是否有Qt::window屬性 
            QWidget *parent = q->parentWidget();
            QWidgetPrivate *pd = parent->d_func();
            if (forceNativeWindow && !q->testAttribute(Qt::WA_DontCreateNativeAncestors))
                parent->setAttribute(Qt::WA_NativeWindow);
            if (!parent->internalWinId()) {
                pd->createWinId();//創建父窗口的winId
            }

            for (int i = 0; i < pd->children.size(); ++i) {
                QWidget *w = qobject_cast<QWidget *>(pd->children.at(i));
                if (w && !w->isWindow() && (!w->testAttribute(Qt::WA_WState_Created)
                                            || (!w->internalWinId() && w->testAttribute(Qt::WA_NativeWindow)))) {
                    w->create();//將具有native屬性的兄弟窗口 創建winId.
                }
            }
        } else {
            q->create();//create()和createWinId()的區別應該在於create只創建自己的winId.
        }
    }
if (q->isWindow() || (!newparent || newparent->isVisible()) || explicitlyHidden)
        q->setAttribute(Qt::WA_WState_Hidden);//這里的設置hide邏輯也很奇怪,后面又根據parentWidget的visible狀態設置這個屬性,果然是不同的開發都只專注寫自己的邏輯,看的太詳細容易掉他們的挖的溝里。
    q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden);

setParent的時候,還會同步stylesheet, enable狀態。setParent 在初始化調用,和之后的時機調用應該是有一些區別的,這個需要注意一下。其實setParent里面干了很多的事,短時間內看不過來,不強求。init的最后幾步干了這些:

    q->setAttribute(Qt::WA_PendingMoveEvent);
    q->setAttribute(Qt::WA_PendingResizeEvent);

    if (++QWidgetPrivate::instanceCounter > QWidgetPrivate::maxInstances)
        QWidgetPrivate::maxInstances = QWidgetPrivate::instanceCounter;

    QEvent e(QEvent::Create);
    QCoreApplication::sendEvent(q, &e);
    QCoreApplication::postEvent(q, new QEvent(QEvent::PolishRequest));

這些就不解釋了,至此init函數執行完畢。

  接着看下resize函數,在創建完QWidget后,調用resize函數。

void QWidget::resize(const QSize &s)
{
    Q_D(QWidget);
    setAttribute(Qt::WA_Resized);//設置相應屬性 if (testAttribute(Qt::WA_WState_Created)) {//是否創建過,只有在創建之后才會真正的去設置窗口的大小,並重繪,單獨構建完QWidget是不會標記創建的,只有show的時候才會真正創建
        d->fixPosIncludesFrame();
        d->setGeometry_sys(geometry().x(), geometry().y(), s.width(), s.height(), false);
        d->setDirtyOpaqueRegion();
    } else {
        const auto oldRect = data->crect;
        data->crect.setSize(s.boundedTo(maximumSize()).expandedTo(minimumSize()));//限定了大小范圍。這時只是用個字段去記錄size而已。 if (oldRect != data->crect)//只有不一樣時,才會發送resize事件。
            setAttribute(Qt::WA_PendingResizeEvent);//可能存在隱藏時,會resize,那么會先做個標記。
    }
}

  接着看看QWidget的show 函數實際干了什么。本質上還是調了setVisible。在該函數內有個判斷:

if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) == !visible)
        return;//WA_WState_ExplicitShowHide 根據parent的狀態來判斷,在第一次show的時候設為true,以后都用不到了,除非重新設置parent,關鍵的是后面這個判斷。

  // Remember that setVisible was called explicitly 顯示調用嗎?還能有不顯示調用的?
  setAttribute(Qt::WA_WState_ExplicitShowHide);

if (visible) { // show
        // Designer uses a trick to make grabWidget work without showing
        if (!q->isWindow() && q->parentWidget() && q->parentWidget()->isVisible()
            && !q->parentWidget()->testAttribute(Qt::WA_WState_Created))
            q->parentWidget()->window()->d_func()->createRecursively();//根據條件判斷是否遞歸創建parent //create toplevels but not children of non-visible parents
        QWidget *pw = q->parentWidget();
        if (!q->testAttribute(Qt::WA_WState_Created)
            && (q->isWindow() || pw->testAttribute(Qt::WA_WState_Created))) {//這里的判斷揭示了如果q不是window,那么它一定有parent。
            q->create();//創建native 窗口。QWidgetWindow,封裝win32窗口創建,真正的創建在WindowCreateData.create()

      //創建完平台窗口后,設置backingStore。提供繪圖區域。在后續的繪圖系統中再看。

      QBackingStore類為QWindow提供了一個繪圖區域。QBackingStore允許使用QPainter在帶有類型的QWindow上繪制RasterSurface。

      呈現到QWindow的另一種方式是通過使用OpenGL的QOpenGLContext。

      一個QBackingStore包含一個窗口內容的緩沖表示,因此通過使用QPainter只更新窗口內容的一個子區域來支持部分更新。

      }

bool wasResized = q->testAttribute(Qt::WA_Resized); Qt::WindowStates initialWindowState = q->windowState(); // polish if necessary q->ensurePolished();

 對於一個頂層窗口,還需要一個QWidgetRepaintManager,保存在topData里,之后設置模態屬性,標題,圖標等。create執行完畢。繼續執行setVisible,ensurepolish,sendEvent polish , 並polish children.

// whether we need to inform the parent widget immediately
        bool needUpdateGeometry = !q->isWindow() && q->testAttribute(Qt::WA_WState_Hidden);//當widget不是window,並且隱藏時,更新尺寸 // we are no longer hidden
        q->setAttribute(Qt::WA_WState_Hidden, false);

        if (needUpdateGeometry)
            updateGeometry_helper(true);//更新parent的layout.  

        // activate our layout before we and our children become visible
        if (layout)
            layout->activate();//這里干了很多事,根據layout的constraint(setSizeConstraint),默認為widget設置最小尺寸.計算layout里的所有item的最大最小。已便設置相應的widget的最大值或最小值之類的。在這個函數里,
調用layout的任何尺寸相關函數(注意是layout的),都會先檢查dirty字段,判斷是否需要重新計算,從而調用layout的setupGeom函數,為每個item創建一個QLayoutStruct數據結構,並填充,包含了item的最大最小,sizeHINT等尺寸信息。
setupGeom最終更新了layout的這些成員數據:geomArray(
QLayoutStruct的數組),expanding(方向),minSize,maxSize,sizeHint.
widget在設置最小setMinimumSize,會更新extra的minw,minh字段。
if (!q->isWindow()) { QWidget *parent = q->parentWidget(); while (parent && parent->isVisible() && parent->d_func()->layout  && !parent->data->in_show) { parent->d_func()->layout->activate(); if (parent->isWindow()) break; parent = parent->parentWidget(); } if (parent) parent->d_func()->setDirtyOpaqueRegion(); }

也會逐一show自己的children。在layout里的activate函數里,會執行doResize(),判斷menuBar的尺寸,調用widget的setGeometry, widget的setGeometry又會調用layout的setGeometry, 在layout的setGeometry里才會真正的去改變尺寸大小。設置給widget的rect,layout也會記錄下來。調用qGeomCalc(跟setupGeom成對應關系)計算每個item合適的大小(依據setupGeom建立的所有item的最大值,最小,sizehint等信息),最后再對每個item逐一setGeometry.不同的layout,setGeometry都是不一樣的,setupGeom里會參考item的QSizePolicy的信息,通過調用qSmartMinSize,設置合適的大小。后面的細節還有很多,待續。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM