ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

QWidget探索

2022-02-19 14:01:57  阅读:252  来源: 互联网

标签:WA parent QWidget testAttribute 探索 && WState Qt


  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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有