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,设置合适的大小。后面的细节还有很多,待续。。。
标签:WA,parent,QWidget,testAttribute,探索,&&,WState,Qt 来源: https://www.cnblogs.com/quinlan-space/p/15703769.html