编程语言
首页 > 编程语言> > QBoxLayout的布局算法及一些影响widget大小的因素

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

作者:互联网

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的激活过程

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.
代码里用到的地方
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就是可伸展的.

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

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的大小.

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