百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

QBoxLayout的布局算法及一些影响widget大小的因素

yuyutoo 2024-10-12 01:30 18 浏览 0 评论

QLayout

  • 相关的类:

QLayout继承自QLayoutItem, 常用的子类有QBoxLayout和QGridLayout.
一个布局里会很多个QLayoutItem.

QWidget是通过QWidgetItem这个适配器放到QLayout里到的, QWidgetItem继承自QLayoutItem,
QWidgetItem还有一个子类, QWidgetItemV2, 在QWidgetItem的基础上加了个缓存机制.
现在qt往layout里添加widget时用的就是QwidgetItemV2.

QLayout只是widget的一个布局器, 只和QLayoutItem关联.
不会变成widget的parent, 在父子链中布局器和布局器里的widget是兄弟关系.
但是在往布局器里加widget的时候, 布局器还是会对widget的parent进行调整的.

layout的激活过程

多层layout嵌套的情况:

从最外层的layout开始激活, 如果当前不是最外层的布局就直接转调去激活上一层的布局.

bool QLayout::activate()
{
    if (!d->topLevel)
        return static_cast<QLayout*>(parent())->activate();
    if (d->activated)
        return false;

    activateRecursiveHelper(this);
    
    // 开始真正的布局
    // ...
}

void QLayout::activateRecursiveHelper(QLayoutItem *item)
{
    item->invalidate();     // <== <== <== <== <== <== <== <==

    QLayout *layout = item->layout();
    if (layout) {
        QLayoutItem *child;
        int i=0;
        while ((child = layout->itemAt(i++)))
            activateRecursiveHelper(child);
        layout->d_func()->activated = true;
    }
}

布局过程

先把最外层的大小固定, 再调整里面每一项的大小.
从里往外汇总出整个布局的大小. 再从外往里给每个布局项分配大小, 同时调整每个布局项的位置.

bool QLayout::activate()
{
    // ...

     => mw->setFixedSize(totalSizeHint());

     => d->doResize(mw->size());
             => q->setGeometry(rect);  // QBoxLayout
                     => d->setupGeom();
                        =*= 统计每一项的布局信息(minimumSize/maximumSize/sizeHint)
                        =*= 汇总出整体的布局信息

                     => qGeomCalc(a, 0, n, pos, space);
                        =*= 给每一项分配空间

                     => for (int j = 0; j < n; j++) {
                            QBoxLayoutItem *box = d->list.at(i);
                            box->item->setGeometry(QRect(a.at(i).pos, s.y(), a.at(i).size, s.height()));
                                =*= 调整每一项的位置
                        }

     => mw->updateGeometry();
        =*= 向父widget发LayoutRequest事件, 触发父widget重新布局.
}

QBoxLayout的空间分配算法

void qGeomCalc(QVector<QLayoutStruct> &chain, int start, int count,
               int pos, int space, int spacer)
{
    有三种情况

    一. 可用空间比最小值还小, 所有项按最小值分配都不够分
        从最大的项开始减. 就像给一条往上增长的曲线设置了一个最大值, 超过最大值的部分全部截断为最大值.

    二. 可用空间介于最小值和参考值之间
        把需要压缩的空间平均到每一项上, 每项都压缩相同的值.
        但是会有某一项压缩之后小于最小值的情况, 这时候直接把该项设成最小值, 然后重新计算需要压缩的空间, 剩下的项重新平均分配.

    三. 可用空间大于参考值

        1. 先按比例分配, 同时分配的过程中统计哪些是给多了(实际分配的值和最大值的差)哪些是给少了(实际分配值和参考值的差)
        2. 给少了的就重新调整为最大大小
        3. 给多了的就重新调整为合适大小
        4. 再回到第一步, 还没分配的项继续按比例分配剩下的空间
        5. 循环结束条件: 
            所有项都走了第(2)或者第(3)步的分配,
            或者,
            第(1)步中统计的多分配的和少分配的相平衡(相等).

        理想结果:
            所有项都按比例分配可用空间, 并且都没有超出每项的最小值和最大值的限制范围.

        两个极端:
            最小情况:
                所有项都分值配到了合适的大小
            最大情况:
                所有项都分配到了最大的空间, 还剩下的空间分配给各项的间距.
}

SizeConstraint

【领QT开发教程学习资料,点击→Qt开发(视频教程+文档+代码+项目实战)←莬费领取,先码住不迷路~】

widget大小的约束规则

搜代码, 用到的地方就QLayout::activate一个. 每次触发布局的时候都会根据sizeConstraint调整一下widget的最大值/最小值或者固定值.

用得比较多的是SetFixedSize, 会根据布局的内容算出一个合适的大小, 然后把widget固定到这个大小. 这在某些控件隐藏后自动调整整个widget的大小的时候非常有用.

其他的都只是调整一下最小值和最大值, 不会直接改变widget的大小.

QLayout::activate
{
    switch (d->constraint) {
    case SetFixedSize:
        // will trigger resize
        mw->setFixedSize(totalSizeHint());
        break;
    case SetMinimumSize:
        mw->setMinimumSize(totalMinimumSize());
        break;
    case SetMaximumSize:
        mw->setMaximumSize(totalMaximumSize());
        break;
    case SetMinAndMaxSize:
        mw->setMinimumSize(totalMinimumSize());
        mw->setMaximumSize(totalMaximumSize());
        break;
    case SetDefaultConstraint: {
        bool widthSet = explMin & Qt::Horizontal;
        bool heightSet = explMin & Qt::Vertical;
        if (mw->isWindow()) {
            QSize ms = totalMinimumSize();
            if (widthSet)
                ms.setWidth(mw->minimumSize().width());
            if (heightSet)
                ms.setHeight(mw->minimumSize().height());
            mw->setMinimumSize(ms);
        } else if (!widthSet || !heightSet) {
            QSize ms = mw->minimumSize();
            if (!widthSet)
                ms.setWidth(0);
            if (!heightSet)
                ms.setHeight(0);
            mw->setMinimumSize(ms);
        }
        break;
    }
    case SetNoConstraint:
        break;
    }

    d->doResize(mw->size());
}

SizeHint

widget根据自身的需要给出的参考大小, 布局器会在这个基础上进行调整.

SizePolicy

伸缩策略, 在sizeHint的基础上还可以做哪些调整, 会影响widget的最小大小和最大大小.
而最终widget分配到的大小受限于最大大小和最小大小.

官方定义

QSizePolicy::GrowFlag       1   The widget can grow beyond its size hint if necessary.
QSizePolicy::ExpandFlag     2   The widget should get as much space as possible.
QSizePolicy::ShrinkFlag     4   The widget can shrink below its size hint if necessary.
QSizePolicy::IgnoreFlag     8   The widget's size hint is ignored. The widget will get as much space as possible.

代码里用到的地方

  • ExpandFlag
class Q_WIDGETS_EXPORT QSizePolicy
{
    // ...

    QT_SIZEPOLICY_CONSTEXPR Qt::Orientations expandingDirections() const Q_DECL_NOTHROW {
        return ( (verticalPolicy()   & ExpandFlag) ? Qt::Vertical   : Qt::Orientations() )
             | ( (horizontalPolicy() & ExpandFlag) ? Qt::Horizontal : Qt::Orientations() ) ;
    }

    // ...
};

直接影响QWidgetItem的expandingDirection,
而对于QBoxLayout来说, 只有里面有一项是可以伸展的, 那整个layout就是可伸展的.

最终布局器分配空间的时候会把多余空间平均分配给可伸展的项.

  • ShrinkFlag
Q_WIDGETS_EXPORT QSize qSmartMinSize(const QSize &sizeHint, const QSize &minSizeHint,
                                 const QSize &minSize, const QSize &maxSize,
                                 const QSizePolicy &sizePolicy)
{
    QSize s(0, 0);

    if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) {
        if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag)
            s.setWidth(minSizeHint.width());
        else
            s.setWidth(qMax(sizeHint.width(), minSizeHint.width()));
    }

    if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) {
        if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) {
            s.setHeight(minSizeHint.height());
        } else {
            s.setHeight(qMax(sizeHint.height(), minSizeHint.height()));
        }
    }

    s = s.boundedTo(maxSize);
    if (minSize.width() > 0)
        s.setWidth(minSize.width());
    if (minSize.height() > 0)
        s.setHeight(minSize.height());

    return s.expandedTo(QSize(0,0));
}

这段代码是在用算最小大小, 计算的结果会直接影响到QWidgetItemV2::minimumSize()和QWidgetItem::minimumSize()的返回值,
即最终widget能分配到的大小的下限.

不难看出, 在有ShrinkFlag标记的时候, 直接返回minimumSizeHint, 而这个值又直接来自QWidget::minimumSizeHint();
如果没有ShrinkFlag标记, 就返回minimumSizeHint和sizeHint中较大的.

Shrink, 收缩的意思, 在有这个标记的时候收缩到minimumSizeHint的大小.

  • GrowFlag
Q_WIDGETS_EXPORT QSize qSmartMaxSize(const QSize &sizeHint,
                                 const QSize &minSize, const QSize &maxSize,
                                 const QSizePolicy &sizePolicy, Qt::Alignment align)
{
    if (align & Qt::AlignHorizontal_Mask && align & Qt::AlignVertical_Mask)
        return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX);
    QSize s = maxSize;
    QSize hint = sizeHint.expandedTo(minSize);
    if (s.width() == QWIDGETSIZE_MAX && !(align & Qt::AlignHorizontal_Mask))
        if (!(sizePolicy.horizontalPolicy() & QSizePolicy::GrowFlag))
            s.setWidth(hint.width());

    if (s.height() == QWIDGETSIZE_MAX && !(align & Qt::AlignVertical_Mask))
        if (!(sizePolicy.verticalPolicy() & QSizePolicy::GrowFlag))
            s.setHeight(hint.height());

    if (align & Qt::AlignHorizontal_Mask)
        s.setWidth(QLAYOUTSIZE_MAX);
    if (align & Qt::AlignVertical_Mask)
        s.setHeight(QLAYOUTSIZE_MAX);
    return s;
}

这里是在计算layoutItem的最大大小的时候调的, 计算结果会直接影响QWidgetItemV2::maximumSize()和QWidgetItem::maximumSize()的返回值,
即最终widget能分配到的大小的上限.

GrowFlag是反过来用的, 为0的时候才会把最终的返回值限制到sizeHint, 因为最大大小默认就是一个非常大的值(QWIDGETSIZE_MAX).

多层widget嵌套的情况:

QLayout::activate 
    => Qwidget::updateGeometry
        => parentWidget->layout->invalidate
            => postEvent(parentWidget, QEvent::LayoutRequest)
                => QLayout::widgetEvent
                    => QLayout::activate

单来说就是, 里层widget布局的改变(activate), 会通过LaoutRequest事件逐层往外传, 触发外层widget重新布局.
但是, 布局器在重新布局的时候一般不会重新调整widget的大小, 只是给每个布局项重新分配空间.
所以, 里层widge会先按照已有的大小重新调整好布局, 然后再触发外层widget重新布局,
要是外层widget在布局的时候把里层widget的大小改了, 里层的widget就需要重新布局一次.

强调一点, QApplication在处理LayoutRequest事件时是先经过QLayout::widgetEvent,再经过eventFilter最后才到QWdiget::event().

需要注意的是,
QWidget::setGeometry的时候还是会受最大大小和最小大小限制的.
这就意味着, 布局器在空间不够时虽然会自动压缩布局项, 但是最终还是要走QWidget::setGeometry来调整widget的大小.
所以, 要是压缩后的大小小过widget本身的最小大小, 就会看不到布局器的自动压缩效果, 甚至会出现重叠的情况.

布局器默认情况下不会调整widget的大小(SetDefaultConstraint), 但是在多层widget嵌套的时候我们却能看到里层widget的大小可以随着内容动态调整, 这其实是外层widget的布局器的作用.
里层widget在updateGeometry的时候会往外层发一个LayoutRequest事件, 触发外层widget重新布局,
外层widget在重新布局的时候把里层widget的大小给调整了.
当然, 最外层的widget也可以通过把sizeConstraint设为SetFixedSize来达到这个效果.

相关推荐

从零搭建高可用的 MySQL 主从复制架构(基于 Linux 实战指南)

背景在生产环境中,单点MySQL数据库容易成为性能瓶颈或单点故障源。搭建MySQL主从复制架构,可以实现读写分离、高可用,提升系统的整体稳定性与扩展性。...

国外大神成功让Nexus4吃上安卓6.0:基本可正常使用

IT之家讯10月9日消息谷歌已经于10月6日正式开启了Nexus设备Android6.0Marshmallow系统的OTA升级推送。根据之前报道的消息,老一批的Nexus手机如Nexus4/Ne...

急死!CPU被挖矿了,却找不到哪个进程

CPU起飞了最近有朋友在群里反馈,自己服务器的CPU一直处于高占用状态,但用...

甜甜的安卓5.0却让手机ROOT难度大大增加

IT之家(www.ithome.com):甜甜的安卓5.0却让手机ROOT难度大大增加对设备进行ROOT,毫无疑问,这是安卓最美丽的地方之一,不管是对于消费者来说还是开发者。Root意味着掌握更多的权...

Linux基础知识(linux基础知识点及答案)

系统目录结构/bin:命令和应用程序。/boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。/dev:dev是Device(设备)的缩写,该目录...

Linux 内核 6.15 发布:内存、网络、文件系统全面升级!

核心增强:性能与安全双飞升!Linux内核6.15的正式版!虽然因一个临门一脚的Bug晚了几小时,但最终还是带着一堆硬核更新闪亮登场!...

AlmaLinux 9.6 发布,新增功能亮点纷呈!

距离上一版本AlmaLinux9.5发布六个月后,基于5.14内核的AlmaLinux正式宣布其企业级Linux发行版的9.x系列第六个更新——AlmaLinux9.6(Sag...

理解Linux下的SELinux(linux seccomp)

理解Linux下的SELinux长久以来,每当遇到授权问题或者新安装的主机,我的第一反应是通过setenforce0命令禁用SELinux,来减少产生的权限问题,但是这并不是一个良好的习惯。这篇文章...

3个简单实用的网址导航网站(简洁的网站导航)

在我们使用电脑上网的时候经常会访问某些常用的网站,每一次都去通过搜索访问就比较浪费时间,添加在浏览器收藏夹不方便在其他电脑使用。找一个好用的网址导航网站就可以帮我们把所有常用的网址集合在一个页面,方便...

整点不一样的网站制作教程,教你怎么用网站模板制作网站#...

网站制作教程整点不一样的网站。不要再问我网站制作教程了,今天给你整个怎么用网站模板制作网站的教程。·1、登录账号进入后台。·2、选择模板。自助建站平台通常提供各种各样的网站模板,可以根据自己的需求和喜...

5个最好的外贸独立站模板,让你的网站更加专业

作为外贸行业从业者,一个专业且具有吸引力的网站是必不可少的。然而,建立一个专业的网站需要耗费大量的时间和精力,尤其是在设计和开发方面。为了帮助您缩短网站建设的时间和成本,以下是5个最好的外贸独立站模板...

网站建设模板 **网站建设模板:全面指南与创意构思*

网站建设模板**网站建设模板:全面指南与创意构思**随着互联网技术的迅猛发展,网站已成为企业、机构和个人展示自身形象、传递信息、实现交流的重要平台。本文将详细介绍网站建设的基本模板,并提供创意...

原地封神!一个只用套模板即可制作电子相册的网站

对于忙碌的年轻人来说,一键操作的模板意味着无需复杂的操作步骤,就能轻松制作出精美的电子相册。但是一个好的工具也是事关重要,最近发现了一款非常适合年轻人的模板---FLBOOK在线制作电子杂志平台,只需...

跨屏建站网kpfree免费网站模板2023.1.14发布更新

跨屏建站网kpfree免费网站模板2023.1.14发布更新,摒弃了之前的卡片式设计,采用了移动优先的设计原则,简化了页面设计风格,优化了代码,优化了图片质量,确保网页打开速度。砍掉了一些花哨而无用的...

响应式大型电子企业集团类网站模板源码-青柠资源网qnziyw.cn

模板信息:模板编号:10964模板编码:UTF8模板颜色:红色模板分类:科技、电子、数码设备适合行业:电子设备类企业模板介绍:本模板自带eyoucms内核,无需再下载eyou系统,原创设计、手工书写D...

取消回复欢迎 发表评论: