其他分享
首页 > 其他分享> > QWidget探索

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