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,設置合適的大小。后面的細節還有很多,待續。。。
