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

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

yuyutoo 2024-10-12 01:30 9 浏览 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来达到这个效果.

相关推荐

网站建设:从新手到高手

现代化网站应用领域非常广泛,从个人形象网站展示、企业商业网站运作、到政府公益等服务网站,各行各业都需要网站建设。大体上可以归结四类:宣传型网站设计、产品型网站制作、电子商务型网站建设、定制型功能网站开...

JetBrains 推出全新 AI 编程工具 Junie,助力高效开发

JetBrains宣布推出名为Junie的全新AI编程工具。这款工具不仅能执行简单的代码生成与检查任务,还能应对编写测试、验证结果等复杂项目,为开发者提供全方位支持。根据SWEBench...

AI也能写代码!代码生成、代码补全、注释生成、代码翻译轻松搞定

清华GLM技术团队打造的多语言代码生成模型CodeGeeX近期更新了新的开源版本「CodeGeeX2-6B」。CodeGeeX2是多语言代码生成模型CodeGeeX的第二代模型,不同于一代CodeG...

一键生成前后端代码,一个36k星的企业级低代码平台

「企业级低代码平台」前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任...

Gitee 代码托管实战指南:5 步完成本地项目云端同步(附避坑要点)

核心流程拆解:远程仓库的搭建登录Gitee官网(注册账号比较简单,大家自行操作),点击“新建仓库”,建议勾选“初始化仓库”和“设置模板文件”(如.gitignore),避免上传临时文件。...

jeecg-boot 源码项目-强烈推荐使用

JEECGBOOT低代码开发平台...

JetBrains推出全新AI编程工具Junie,强调以开发者为中心

IT之家2月1日消息,JetBrains发文,宣布推出一款名为Junie的全新AI编程工具,官方声称这款AI工具既能执行简单的代码生成与检查等基础任务,也能应对“编写测试、验证结...

JetBrains旗下WebStorm和Rider现已加入“非商用免费”阵营

IT之家10月25日消息,软件开发商JetBrains今日宣布,旗下WebStorm(JavaScript开发工具)和Rider(.NET开发工具)现已加入“非商用免费”阵营。如果...

谈谈websocket跨域

了解websocketwebsocket是HTML5的新特性,在客户端和服务端提供了一个基于TCP连接的双向通道。...

websocket调试工具

...

利用webSocket实现消息的实时推送

1.什么是webSocketwebSocket实现实现推送消息WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。以前的推送技术使用Ajax轮询,浏览器需...

Flutter UI自动化测试技术方案选型与探索

...

为 Go 开发的 WebSocket 库

#记录我的2024#...

「Java基础」Springboot+Websocket的实现后端数据实时推送

这篇文章主要就是实现这个功能,只演示一个基本的案例。使用的是websocket技术。...

【Spring Boot】WebSocket 的 6 种集成方式

介绍...

取消回复欢迎 发表评论: