ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-05-31 08:31:47  阅读:182  来源: 互联网

标签:widget QSize QBoxLayout Qt QSizePolicy mw 算法 大小


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

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标记, 就返回minimumSizeHintsizeHint中较大的.

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,QSize,QBoxLayout,Qt,QSizePolicy,mw,算法,大小
来源: https://www.cnblogs.com/snail-0304/p/16329126.html

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

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

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

ICode9版权所有