找回密码
 立即注册
首页 业界区 业界 详谈 QLayout::SizeConstraint 和 QSizePolicy 对 QWidg ...

详谈 QLayout::SizeConstraint 和 QSizePolicy 对 QWidget 尺寸的影响

决任愧 前天 09:34
QT 窗口布局常用的设置有 QSizePolicy 和 QLayout::SizeConstraint ,当窗口大小调整时,哪个配置会生效或者都会生效?先说一个简单的结论:QSizePolicy 与 QLayout::SizeConstraint 都用于 QLayout 的自动布局,父 widget 尺寸发生变化时,layout 会根据子 widget 的 QSizePolicy 与 QLayout 的方向调整子 widget 的位置及大小;子 widget 显示、隐藏、minimumSize、maximumSize 发生变化时,会触发父 widget 的 layout 重新计算(新尺寸的计算由 QLayout::SizeConstraint 来决定),从而改变父 widget 尺寸。QLayout::SizeConstraint 确定了主 widget (layout 所在 widget) 的尺寸变化策略。如果父 widget 没有使用 layout ,子 widget 的 QSizePolicy 不起任何作用。下面从帮助文档和源码分别分析一下。
1. 帮助文档

1.1 QWidget

默认情况下组合窗口的大小根据子窗口的大小来调整。widget 的 size policy 用于父窗口 layout 的布局管理。
1.png

sizeHint 属性是 widget 的推荐大小。如果设置了 layout 该属性的值由 layout 计算,如果没设置 layout 该属性的值默认不可用。
2.png

minimumSizeHint 属性是 widget 推荐的最小尺寸。如果设置了 layout 该属性的值由 layout 计算,如果没设置 layout 该属性的值默认不可用。如果要设置比 minimumSizeHint 更小的尺寸,则必须设置 QSizePolicy::Ignore。
3.png

maximumSize 属性是 widget 推荐的最大尺寸, 宽高的默认值都是 16777215。
4.png

adjustSize() 用于调整 widget 的大小到适合子控件大小。如果 sizeHint 属性可用,则使用sizeHint提供的大小,否则,使用所有子控件尺寸的并集(可以涵盖所有子控件的尺寸)来调整尺寸。
5.png

1.2 QSizePolicy

通过 QWidget 的 sizePolicy() 方法可以设置 widget 的 sizePolicy。但是该设置是 widget 未设置 layout 时的布局配置,如果设置了 layout 该 widget 使用 layout 的 size policy。layout 的 size policy 通过方法 setSizeConstraint() 和 setContentsMargins() 设置。
6.png

1.3 QLayout

QLayout 的大小调整由 SizeConstraint 配置,它有以下6种预置配置。从源码中可以确认文档中提到的minimumSize()、sizeHint()、maxmumSize() 指的是 QLayout 类中的方法,不是 Qwidget中的方法。 文档中的 main widget 指定的是 QLayout:Layout(QWidget *parent = nullptr) 实例化时指定的 widget,或调用QWidget::setLayout(QLayout *layout) 方法的 widget。
从文档可以看出只有设置为 QLayout::SetNoConstraint 时,QLayout::SizeConstraint 不会影响 widget 的大小。当设置为 QLayout::SetDefaultConstraint 时,widget 自己的minimunSize() 才会生效。
7.png

QLayout::activate() 会重新计算父 widget 布局,一般情况下这个方法会自动执行。
8.png

2. 源码分析

2.1 消息的传递

对于 widget 控件, QApplication 在分发消息时会优先发送给该 widget 的 layout 布局,然后再由 widget 处理。QLayout 中的 widgetEvent() 方法处理了 QEvent::Resize、QEvent::ChildRemoved、QEvent:ayoutRequest 消息。也就是说,当 widget 生子控件移除或大小变动时,layout 优先处理,优先调整大小。layout 调整大小时主要由 activate() 方法处理。
  1. // qapplication.cpp
  2. bool QApplication::notify(QObject *receiver, QEvent *e)
  3. {
  4.     ...
  5.     if (isWidgetType) {
  6.         QWidget * w = static_cast<QWidget *>(receiver);
  7.         switch (e->type()) {
  8.         ...
  9.         default:
  10.             res = d->notify_helper(receiver, e);
  11.             break;
  12.         }
  13.     } else {
  14.         res = d->notify_helper(receiver, e);
  15.     }
  16.     return res;
  17. }
  18. bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
  19. {
  20.     if (receiver->isWidgetType()) {
  21.         QWidget *widget = static_cast<QWidget *>(receiver);
  22.         ....
  23.         // 如果使用了 layout 布局,消息先发送给 layout 处理
  24.         if (QLayout *layout=widget->d_func()->layout) {
  25.             layout->widgetEvent(e);
  26.         }
  27.     }
  28.     // send to all receiver event filters
  29.     if (sendThroughObjectEventFilters(receiver, e)) {
  30.         filtered = true;
  31.         return filtered;
  32.     }
  33.     // 消息再发送给 widget 处理
  34.     // deliver the event
  35.     consumed = receiver->event(e);
  36.     QCoreApplicationPrivate::setEventSpontaneous(e, false);
  37.     return consumed;
  38. }
复制代码
  1. // qlayout.cpp
  2. void QLayout::widgetEvent(QEvent *e)
  3. {
  4.     Q_D(QLayout);
  5.     const QEvent::Type type = e->type();
  6.     if (!d->enabled && type != QEvent::ChildRemoved)
  7.         return;
  8.     switch (type) {
  9.     case QEvent::Resize:
  10.         if (d->activated)
  11.             d->doResize();
  12.         else
  13.             activate();
  14.         break;
  15.     case QEvent::ChildRemoved:
  16.         {
  17.             QChildEvent *c = (QChildEvent *)e;
  18.             QObject *child = c->child();
  19.             QObjectPrivate *op = QObjectPrivate::get(child);
  20.             if (op->wasWidget) {
  21. #if QT_CONFIG(menubar)
  22.                 if (child == d->menubar)
  23.                     d->menubar = nullptr;
  24. #endif
  25.                 removeWidgetRecursively(this, child);
  26.             }
  27.         }
  28.         break;
  29.     case QEvent::LayoutRequest:
  30.         if (static_cast<QWidget *>(parent())->isVisible())
  31.             activate();
  32.         break;
  33.     default:
  34.         break;
  35.     }
  36. }
复制代码
2.2 QWidget 调整大小

QWidget 在执行 show() 方法时会计算 widget 大小,show() 方法调用 setVisible() 计算widget 布局并显示 widget。调整 widget 尺寸由 adjustSize() 方法执行,调整尺寸时采用 sizeHint() 推荐的大小。sizeHint() 使用 layout 计算的推荐尺寸。
由此可见,当 widget 是组合 widget 时(即该 widget 包含其它 widget),它的尺寸由 layout 来计算,Size Policy 不起作用当 widget 是单一控件时(不包含其它 widget),必须重写 sizeHint() 方法,否则,该控件不会显示。
  1. // qwidget.cpp
  2. void QWidget::show()
  3. {
  4.     ...
  5.     if (!isWindow()) {
  6.         setVisible(true);
  7.     } else {
  8.         ...
  9.         if (defaultState == Qt::WindowFullScreen)
  10.             showFullScreen();
  11.         else if (defaultState == Qt::WindowMaximized)
  12.             showMaximized();
  13.         else
  14.             setVisible(true);
  15.     }
  16. }
  17. void QWidget::setVisible(bool visible)
  18. {
  19.     ...
  20.     d->setVisible(visible);
  21. }
  22. void QWidgetPrivate::setVisible(bool visible)
  23. {
  24.     ...
  25.     if (visible) { // show
  26.         ...
  27.         // 向父窗口发送布局更改事件
  28.         if (needUpdateGeometry)
  29.             updateGeometry_helper(true);
  30.         // 重新计算 layout 布局
  31.         // activate our layout before we and our children become visible
  32.         if (layout)
  33.             layout->activate();
  34.         
  35.         if (!q->isWindow()) {
  36.             QWidget *parent = q->parentWidget();
  37.             // 依次调整上层 widget 尺寸
  38.             while (parent && parent->isVisible() && parent->d_func()->layout  && !parent->data->in_show) {
  39.                 parent->d_func()->layout->activate();
  40.                 if (parent->isWindow())
  41.                     break;
  42.                 parent = parent->parentWidget();
  43.             }
  44.             if (parent)
  45.                 parent->d_func()->setDirtyOpaqueRegion();
  46.         }
  47.         // 如果是 window 或上级 widget 未设置 layout 调整大小
  48.         // adjust size if necessary
  49.         if (!wasResized
  50.             && (q->isWindow() || !q->parentWidget()->d_func()->layout))  {
  51.             if (q->isWindow()) {
  52.                 // 调整 window 大小
  53.                 q->adjustSize();
  54.                 if (q->windowState() != initialWindowState)
  55.                     q->setWindowState(initialWindowState);
  56.             } else {
  57.                // 调整 widget 大小
  58.                 q->adjustSize();
  59.             }
  60.             q->setAttribute(Qt::WA_Resized, false);
  61.         }
  62.         q->setAttribute(Qt::WA_KeyboardFocusChange, false);
  63.         if (q->isWindow() || q->parentWidget()->isVisible()) {
  64.             show_helper();// 显示 widget
  65.             qApp->d_func()->sendSyntheticEnterLeave(q);
  66.         }
  67.         QEvent showToParentEvent(QEvent::ShowToParent);
  68.         QCoreApplication::sendEvent(q, &showToParentEvent);
  69.     }else{ // hide
  70.             ...
  71.         // 隐藏 widget
  72.         if (!q->testAttribute(Qt::WA_WState_Hidden)) {
  73.             q->setAttribute(Qt::WA_WState_Hidden);
  74.             hide_helper();
  75.         }
  76.         // 向父 widget 发送布局改变消息
  77.         // invalidate layout similar to updateGeometry()
  78.         if (!q->isWindow() && q->parentWidget()) {
  79.             if (q->parentWidget()->d_func()->layout)
  80.                 q->parentWidget()->d_func()->layout->invalidate();
  81.             else if (q->parentWidget()->isVisible())
  82.                 QCoreApplication::postEvent(q->parentWidget(), new QEvent(QEvent::LayoutRequest));
  83.         }
  84.         QEvent hideToParentEvent(QEvent::HideToParent);
  85.         QCoreApplication::sendEvent(q, &hideToParentEvent);
  86.     }
  87. }
  88. void QWidgetPrivate::updateGeometry_helper(bool forceUpdate)
  89. {
  90.     Q_Q(QWidget);
  91.     if (widgetItem)
  92.         widgetItem->invalidateSizeCache();
  93.     QWidget *parent;
  94.     if (forceUpdate || !extra || extra->minw != extra->maxw || extra->minh != extra->maxh) {
  95.         const int isHidden = q->isHidden() && !size_policy.retainSizeWhenHidden() && !retainSizeWhenHiddenChanged;
  96.         if (!q->isWindow() && !isHidden && (parent = q->parentWidget())) {
  97.             if (parent->d_func()->layout)
  98.                 parent->d_func()->layout->invalidate();
  99.             else if (parent->isVisible())
  100.                 QCoreApplication::postEvent(parent, new QEvent(QEvent::LayoutRequest));
  101.         }
  102.     }
  103. }
  104. void QWidget::adjustSize()
  105. {
  106.     Q_D(QWidget);
  107.     ensurePolished();
  108.     QSize s = d->adjustedSize();
  109.     if (d->layout)
  110.         d->layout->activate();
  111.     if (s.isValid())
  112.         resize(s);
  113. }
  114. QSize QWidgetPrivate::adjustedSize() const
  115. {
  116.     Q_Q(const QWidget);
  117.     // 使用推荐大小
  118.     QSize s = q->sizeHint();
  119.     if (q->isWindow()) {
  120.         Qt::Orientations exp;
  121.         if (layout) {
  122.             if (layout->hasHeightForWidth())
  123.                 s.setHeight(layout->totalHeightForWidth(s.width()));
  124.             exp = layout->expandingDirections();
  125.         } else
  126.         {
  127.             if (q->sizePolicy().hasHeightForWidth())
  128.                 s.setHeight(q->heightForWidth(s.width()));
  129.             exp = q->sizePolicy().expandingDirections();
  130.         }
  131.         if (exp & Qt::Horizontal)
  132.             s.setWidth(qMax(s.width(), 200));
  133.         if (exp & Qt::Vertical)
  134.             s.setHeight(qMax(s.height(), 100));
  135.         QRect screen;
  136.         if (const QScreen *screenAtPoint = QGuiApplication::screenAt(q->pos()))
  137.             screen = screenAtPoint->geometry();
  138.         else
  139.             screen = QGuiApplication::primaryScreen()->geometry();
  140.         s.setWidth(qMin(s.width(), screen.width()*2/3));
  141.         s.setHeight(qMin(s.height(), screen.height()*2/3));
  142.         if (QTLWExtra *extra = maybeTopData())
  143.             extra->sizeAdjusted = true;
  144.     }
  145.     if (!s.isValid()) {
  146.         QRect r = q->childrenRect(); // get children rectangle
  147.         if (r.isNull())
  148.             return s;
  149.         s = r.size() + QSize(2 * r.x(), 2 * r.y());
  150.     }
  151.     return s;
  152. }
  153. QSize QWidget::sizeHint() const
  154. {
  155.     Q_D(const QWidget);
  156.     // 使用 layout 计算的推荐尺寸
  157.     if (d->layout)
  158.         return d->layout->totalSizeHint();
  159.     return QSize(-1, -1);
  160. }
  161. QSize QWidget::minimumSizeHint() const
  162. {
  163.     Q_D(const QWidget);
  164.     // 使用 layout 计算的推荐尺寸
  165.     if (d->layout)
  166.         return d->layout->totalMinimumSize();
  167.     return QSize(-1, -1);
  168. }
  169. void QWidget::resize(const QSize &s)
  170. {
  171.     Q_D(QWidget);
  172.     setAttribute(Qt::WA_Resized);
  173.     if (testAttribute(Qt::WA_WState_Created)) {
  174.         d->fixPosIncludesFrame();
  175.         d->setGeometry_sys(geometry().x(), geometry().y(), s.width(), s.height(), false);
  176.         d->setDirtyOpaqueRegion();
  177.     } else {
  178.         const auto oldRect = data->crect;
  179.         // 根据 minimizeSize 与 maximumSize 进行约束后保存到 data->crect
  180.         data->crect.setSize(s.boundedTo(maximumSize()).expandedTo(minimumSize()));
  181.         if (oldRect != data->crect)
  182.             setAttribute(Qt::WA_PendingResizeEvent);
  183.     }
  184. }
复制代码
2.3 QLayout 布局与 QSizePolicy

在 widget 调整尺寸时,调用了 QLayout::activate() 方法。该方法根据设置的 QLayout::SizeConstraint 调用主 widget 的方法设置对应的尺寸:

  • QLayout::SetFixedSize 调用 QWidget::setFixedSize() 方法,设置 QLayout::totalSizeHint() 计算的尺寸;
  • QLayout::SetMinimumSize 调用 QWidget::setMinimumSize() 方法,设置 QLayout::totalMinimumSize() 计算的尺寸;
  • QLayout::SetMaximumSize 调用 QWidget::SetMaximumSize() 方法,设置 QLayout::totalMaximumSize() 计算的尺寸;
  • QLayout::SetMinAndMaxSize 调用 QWidget::setMinimumSize() 和 QWidget::SetMaximumSize() 方法,设置 QLayout::totalMinimumSize() 和 QLayout::totalMaximumSize() 计算的尺寸;
  • QLayout::SetDefaultConstraint 当 QWidget::minimumSize 属性可用时,使用使用QWidget::minimumSize 指定的尺寸,否则调用 QWidget::setMinimumSize() 方法,设置 QLayout::totalMinimumSize() 计算的尺寸;
  • QLayout::SetNoConstraint 不会修改主 widget 的尺寸
QLayout::totalSizeHint()、QLayout::totalMinimumSize()、QLayout::totalMaximumSize() 会调用子类的 sizeHint()、minimumSize()、maximumSize()。以 QBoxLayout 为例,这些方法中会调用 QLayout::setupGeom() 方法。setupGeom() 方法遍历 layout 中所有 widget 计算 minSize、maxSize、sizeHint。遍历 widget 时,通过 QWidgetItem::expandingDirections() 获取该 widget 的扩展方向。expandingDirections() 方法根据 widget 设置的 size policy 来判断扩展方向。
  1. // qlayout.cpp
  2. bool QLayout::activate()
  3. {
  4.     Q_D(QLayout);
  5.     if (!d->enabled || !parent())
  6.         return false;
  7.     if (!d->topLevel)
  8.         return static_cast<QLayout*>(parent())->activate();
  9.     if (d->activated)
  10.         return false;
  11.     // layout 所属的主widget
  12.     QWidget *mw = static_cast<QWidget*>(parent());
  13.     if (Q_UNLIKELY(!mw)) {
  14.         qWarning("QLayout::activate: %s "%ls" does not have a main widget",
  15.                  metaObject()->className(), qUtf16Printable(objectName()));
  16.         return false;
  17.     }
  18.     activateRecursiveHelper(this);
  19.     QWidgetPrivate *md = mw->d_func();
  20.     uint explMin = md->extra ? md->extra->explicitMinSize : 0;
  21.     uint explMax = md->extra ? md->extra->explicitMaxSize : 0;
  22.     // 根据 QLayout::SizeConstraint 设置主 widget 尺寸
  23.     switch (d->constraint) {
  24.     case SetFixedSize:
  25.         // will trigger resize
  26.         mw->setFixedSize(totalSizeHint());
  27.         break;
  28.     case SetMinimumSize:
  29.         mw->setMinimumSize(totalMinimumSize());
  30.         break;
  31.     case SetMaximumSize:
  32.         mw->setMaximumSize(totalMaximumSize());
  33.         break;
  34.     case SetMinAndMaxSize:
  35.         mw->setMinimumSize(totalMinimumSize());
  36.         mw->setMaximumSize(totalMaximumSize());
  37.         break;
  38.     case SetDefaultConstraint: {
  39.         bool widthSet = explMin & Qt::Horizontal;
  40.         bool heightSet = explMin & Qt::Vertical;
  41.         if (mw->isWindow()) {
  42.             QSize ms = totalMinimumSize();
  43.             if (widthSet)
  44.                 ms.setWidth(mw->minimumSize().width());
  45.             if (heightSet)
  46.                 ms.setHeight(mw->minimumSize().height());
  47.             mw->setMinimumSize(ms);
  48.         } else if (!widthSet || !heightSet) {
  49.             QSize ms = mw->minimumSize();
  50.             if (!widthSet)
  51.                 ms.setWidth(0);
  52.             if (!heightSet)
  53.                 ms.setHeight(0);
  54.             mw->setMinimumSize(ms);
  55.         }
  56.         break;
  57.     }
  58.     case SetNoConstraint:
  59.         break;
  60.     }
  61.     // 根据主 widget 调整后的尺寸计算 layout 的 rect
  62.     d->doResize();
  63.     if (md->extra) {
  64.         md->extra->explicitMinSize = explMin;
  65.         md->extra->explicitMaxSize = explMax;
  66.     }
  67.     // ideally only if sizeHint() or sizePolicy() has changed
  68.     mw->updateGeometry();
  69.     return true;
  70. }
  71. void QLayoutPrivate::doResize()
  72. {
  73.     Q_Q(QLayout);
  74.     QWidget *mw = q->parentWidget();
  75.     QRect rect = mw->testAttribute(Qt::WA_LayoutOnEntireRect) ? mw->rect() : mw->contentsRect();
  76.     const int mbh = menuBarHeightForWidth(menubar, rect.width());
  77.     const int mbTop = rect.top();
  78.     rect.setTop(mbTop + mbh);
  79.     // 对于 QBoxLayout 此处会调整子 widget 大小
  80.     q->setGeometry(rect);
  81. #if QT_CONFIG(menubar)
  82.     if (menubar)
  83.         menubar->setGeometry(rect.left(), mbTop, rect.width(), mbh);
  84. #endif
  85. }
  86. // 此方法在 QLayout 的子类中会重载,完成子 widget 坐标的计算与设置
  87. void QLayout::setGeometry(const QRect &r)
  88. {
  89.     Q_D(QLayout);
  90.     d->rect = r;
  91. }
  92. QSize QLayout::totalSizeHint() const
  93. {
  94.     Q_D(const QLayout);
  95.     int side=0, top=0;
  96.     if (d->topLevel) {
  97.         QWidget *pw = parentWidget();
  98.         pw->ensurePolished();
  99.         QWidgetPrivate *wd = pw->d_func();
  100.         side += wd->leftmargin + wd->rightmargin;
  101.         top += wd->topmargin + wd->bottommargin;
  102.     }
  103.     QSize s = sizeHint();
  104.     if (hasHeightForWidth())
  105.         s.setHeight(heightForWidth(s.width() + side));
  106. #if QT_CONFIG(menubar)
  107.     top += menuBarHeightForWidth(d->menubar, s.width());
  108. #endif
  109.     return s + QSize(side, top);
  110. }
  111. void QLayout::invalidate()
  112. {
  113.     Q_D(QLayout);
  114.     d->rect = QRect();
  115.     update();
  116. }
  117. void QLayout::update()
  118. {
  119.     QLayout *layout = this;
  120.     while (layout && layout->d_func()->activated) {
  121.         layout->d_func()->activated = false;
  122.         if (layout->d_func()->topLevel) {
  123.             Q_ASSERT(layout->parent()->isWidgetType());
  124.             QWidget *mw = static_cast<QWidget*>(layout->parent());
  125.             QCoreApplication::postEvent(mw, new QEvent(QEvent::LayoutRequest));
  126.             break;
  127.         }
  128.         layout = static_cast<QLayout*>(layout->parent());
  129.     }
  130. }
复制代码
  1. // qboxlayout.cpp
  2. QSize QBoxLayout::sizeHint() const
  3. {
  4.     Q_D(const QBoxLayout);
  5.     if (d->dirty)
  6.         const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
  7.     return d->sizeHint;
  8. }
  9. QSize QBoxLayout::minimumSize() const
  10. {
  11.     Q_D(const QBoxLayout);
  12.     if (d->dirty)
  13.         const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
  14.     return d->minSize;
  15. }
  16. QSize QBoxLayout::maximumSize() const
  17. {
  18.     Q_D(const QBoxLayout);
  19.     if (d->dirty)
  20.         const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
  21.     QSize s = d->maxSize.boundedTo(QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX));
  22.     if (alignment() & Qt::AlignHorizontal_Mask)
  23.         s.setWidth(QLAYOUTSIZE_MAX);
  24.     if (alignment() & Qt::AlignVertical_Mask)
  25.         s.setHeight(QLAYOUTSIZE_MAX);
  26.     return s;
  27. }
  28. // 此方法遍历 layout 中所有 widget 计算 minSize、maxSize、sizeHint
  29. void QBoxLayoutPrivate::setupGeom()
  30. {
  31.     if (!dirty)
  32.         return;
  33.     Q_Q(QBoxLayout);
  34.     int maxw = horz(dir) ? 0 : QLAYOUTSIZE_MAX;
  35.     int maxh = horz(dir) ? QLAYOUTSIZE_MAX : 0;
  36.     int minw = 0;
  37.     int minh = 0;
  38.     int hintw = 0;
  39.     int hinth = 0;
  40.     bool horexp = false;
  41.     bool verexp = false;
  42.     hasHfw = false;
  43.     int n = list.size();
  44.     geomArray.clear();
  45.     QList<QLayoutStruct> a(n);
  46.     QSizePolicy::ControlTypes controlTypes1;
  47.     QSizePolicy::ControlTypes controlTypes2;
  48.     int fixedSpacing = q->spacing();
  49.     int previousNonEmptyIndex = -1;
  50.     QStyle *style = nullptr;
  51.     if (fixedSpacing < 0) {
  52.         if (QWidget *parentWidget = q->parentWidget())
  53.             style = parentWidget->style();
  54.     }
  55.     for (int i = 0; i < n; i++) {
  56.         QBoxLayoutItem *box = list.at(i);
  57.         // 获取 layout 内的 widget 的尺寸
  58.         QSize max = box->item->maximumSize();
  59.         QSize min = box->item->minimumSize();
  60.         QSize hint = box->item->sizeHint();
  61.         // 计算扩展方向
  62.         Qt::Orientations exp = box->item->expandingDirections();
  63.         bool empty = box->item->isEmpty();
  64.         int spacing = 0;
  65.         if (!empty) {
  66.             if (fixedSpacing >= 0) {
  67.                 spacing = (previousNonEmptyIndex >= 0) ? fixedSpacing : 0;
  68. #ifdef Q_OS_MAC
  69.                 if (!horz(dir) && previousNonEmptyIndex >= 0) {
  70.                     QBoxLayoutItem *sibling = (dir == QBoxLayout::TopToBottom  ? box : list.at(previousNonEmptyIndex));
  71.                     if (sibling) {
  72.                         QWidget *wid = sibling->item->widget();
  73.                         if (wid)
  74.                             spacing = qMax(spacing, sibling->item->geometry().top() - wid->geometry().top());
  75.                     }
  76.                 }
  77. #endif
  78.             } else {
  79.                 controlTypes1 = controlTypes2;
  80.                 controlTypes2 = box->item->controlTypes();
  81.                 if (previousNonEmptyIndex >= 0) {
  82.                     QSizePolicy::ControlTypes actual1 = controlTypes1;
  83.                     QSizePolicy::ControlTypes actual2 = controlTypes2;
  84.                     if (dir == QBoxLayout::RightToLeft || dir == QBoxLayout::BottomToTop)
  85.                         qSwap(actual1, actual2);
  86.                     if (style) {
  87.                         spacing = style->combinedLayoutSpacing(actual1, actual2,
  88.                                              horz(dir) ? Qt::Horizontal : Qt::Vertical,
  89.                                              nullptr, q->parentWidget());
  90.                         if (spacing < 0)
  91.                             spacing = 0;
  92.                     }
  93.                 }
  94.             }
  95.             if (previousNonEmptyIndex >= 0)
  96.                 a[previousNonEmptyIndex].spacing = spacing;
  97.             previousNonEmptyIndex = i;
  98.         }
  99.         bool ignore = empty && box->item->widget(); // ignore hidden widgets
  100.         bool dummy = true;
  101.         if (horz(dir)) {
  102.             bool expand = (exp & Qt::Horizontal || box->stretch > 0);
  103.             horexp = horexp || expand;
  104.             maxw += spacing + max.width();
  105.             minw += spacing + min.width();
  106.             hintw += spacing + hint.width();
  107.             if (!ignore)
  108.                 qMaxExpCalc(maxh, verexp, dummy,
  109.                             max.height(), exp & Qt::Vertical, box->item->isEmpty());
  110.             minh = qMax(minh, min.height());
  111.             hinth = qMax(hinth, hint.height());
  112.             a[i].sizeHint = hint.width();
  113.             a[i].maximumSize = max.width();
  114.             a[i].minimumSize = min.width();
  115.             a[i].expansive = expand;
  116.             a[i].stretch = box->stretch ? box->stretch : box->hStretch();
  117.         } else {
  118.             bool expand = (exp & Qt::Vertical || box->stretch > 0);
  119.             verexp = verexp || expand;
  120.             maxh += spacing + max.height();
  121.             minh += spacing + min.height();
  122.             hinth += spacing + hint.height();
  123.             if (!ignore)
  124.                 qMaxExpCalc(maxw, horexp, dummy,
  125.                             max.width(), exp & Qt::Horizontal, box->item->isEmpty());
  126.             minw = qMax(minw, min.width());
  127.             hintw = qMax(hintw, hint.width());
  128.             a[i].sizeHint = hint.height();
  129.             a[i].maximumSize = max.height();
  130.             a[i].minimumSize = min.height();
  131.             a[i].expansive = expand;
  132.             a[i].stretch = box->stretch ? box->stretch : box->vStretch();
  133.         }
  134.         a[i].empty = empty;
  135.         a[i].spacing = 0;   // might be initialized with a non-zero value in a later iteration
  136.         hasHfw = hasHfw || box->item->hasHeightForWidth();
  137.     }
  138.     geomArray = a;
  139.     expanding = (Qt::Orientations)
  140.                        ((horexp ? Qt::Horizontal : 0)
  141.                          | (verexp ? Qt::Vertical : 0));
  142.     minSize = QSize(minw, minh);
  143.     maxSize = QSize(maxw, maxh).expandedTo(minSize);
  144.     sizeHint = QSize(hintw, hinth).expandedTo(minSize).boundedTo(maxSize);
  145.     q->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin);
  146.     int left, top, right, bottom;
  147.     effectiveMargins(&left, &top, &right, &bottom);
  148.     QSize extra(left + right, top + bottom);
  149.     minSize += extra;
  150.     maxSize += extra;
  151.     sizeHint += extra;
  152.     dirty = false;
  153. }
  154. struct QBoxLayoutItem
  155. {
  156.     ...
  157.     QLayoutItem *item;
  158.     ...
  159. };
  160. void QBoxLayout::setGeometry(const QRect &r)
  161. {
  162.     Q_D(QBoxLayout);
  163.     if (d->dirty || r != geometry()) {
  164.         QRect oldRect = geometry();
  165.         QLayout::setGeometry(r);
  166.         if (d->dirty)
  167.             d->setupGeom();
  168.         QRect cr = alignment() ? alignmentRect(r) : r;
  169.         int left, top, right, bottom;
  170.         d->effectiveMargins(&left, &top, &right, &bottom);
  171.         QRect s(cr.x() + left, cr.y() + top,
  172.                 cr.width() - (left + right),
  173.                 cr.height() - (top + bottom));
  174.         // d->geomArray 已经在 setupGeom() 完成计算
  175.         QList<QLayoutStruct> a = d->geomArray;
  176.         int pos = horz(d->dir) ? s.x() : s.y();
  177.         int space = horz(d->dir) ? s.width() : s.height();
  178.         int n = a.size();
  179.         if (d->hasHfw && !horz(d->dir)) {
  180.             for (int i = 0; i < n; i++) {
  181.                 QBoxLayoutItem *box = d->list.at(i);
  182.                 if (box->item->hasHeightForWidth()) {
  183.                     int width = qBound(box->item->minimumSize().width(), s.width(), box->item->maximumSize().width());
  184.                     a[i].sizeHint = a[i].minimumSize =
  185.                                     box->item->heightForWidth(width);
  186.                 }
  187.             }
  188.         }
  189.         Direction visualDir = d->dir;
  190.         QWidget *parent = parentWidget();
  191.         if (parent && parent->isRightToLeft()) {
  192.             if (d->dir == LeftToRight)
  193.                 visualDir = RightToLeft;
  194.             else if (d->dir == RightToLeft)
  195.                 visualDir = LeftToRight;
  196.         }
  197.         // 根据 QRect &r 大小,调整子 widget 尺寸
  198.         qGeomCalc(a, 0, n, pos, space);
  199.         bool reverse = (horz(visualDir)
  200.                         ? ((r.right() > oldRect.right()) != (visualDir == RightToLeft))
  201.                         : r.bottom() > oldRect.bottom());
  202.         // 设置子 widget 坐标
  203.         for (int j = 0; j < n; j++) {
  204.             int i = reverse ? n-j-1 : j;
  205.             QBoxLayoutItem *box = d->list.at(i);
  206.             switch (visualDir) {
  207.             case LeftToRight:
  208.                 box->item->setGeometry(QRect(a.at(i).pos, s.y(), a.at(i).size, s.height()));
  209.                 break;
  210.             case RightToLeft:
  211.                 box->item->setGeometry(QRect(s.left() + s.right() - a.at(i).pos - a.at(i).size + 1,
  212.                                              s.y(), a.at(i).size, s.height()));
  213.                 break;
  214.             case TopToBottom:
  215.                 box->item->setGeometry(QRect(s.x(), a.at(i).pos, s.width(), a.at(i).size));
  216.                 break;
  217.             case BottomToTop:
  218.                 box->item->setGeometry(QRect(s.x(),
  219.                                              s.top() + s.bottom() - a.at(i).pos - a.at(i).size + 1,
  220.                                              s.width(), a.at(i).size));
  221.             }
  222.         }
  223.     }
  224. }
复制代码
  1. // qlayoutengine_p.h
  2. struct QLayoutStruct
  3. {
  4.     inline void init(int stretchFactor = 0, int minSize = 0) {
  5.         stretch = stretchFactor;
  6.         minimumSize = sizeHint = minSize;
  7.         maximumSize = QLAYOUTSIZE_MAX;
  8.         expansive = false;
  9.         empty = true;
  10.         spacing = 0;
  11.     }
  12.     int smartSizeHint() {
  13.         return (stretch > 0) ? minimumSize : sizeHint;
  14.     }
  15.     int effectiveSpacer(int uniformSpacer) const {
  16.         Q_ASSERT(uniformSpacer >= 0 || spacing >= 0);
  17.         return (uniformSpacer >= 0) ? uniformSpacer : spacing;
  18.     }
  19.     // parameters
  20.     int stretch;
  21.     int sizeHint;
  22.     int maximumSize;
  23.     int minimumSize;
  24.     int spacing;
  25.     bool expansive;
  26.     bool empty;
  27.     // temporary storage
  28.     bool done;
  29.     // result
  30.     int pos;
  31.     int size;
  32. };
  33. Q_WIDGETS_EXPORT void qGeomCalc(QList<QLayoutStruct> &chain, int start, int count, int pos,
  34.                                 int space, int spacer = -1);
  35. ...
  36. static inline void qMaxExpCalc(int & max, bool &exp, bool &empty,
  37.                                int boxmax, bool boxexp, bool boxempty)
  38. {
  39.     if (exp) {
  40.         if (boxexp)
  41.             max = qMax(max, boxmax);
  42.     } else {
  43.         if (boxexp || (empty && (!boxempty || max == 0)))
  44.             max = boxmax;
  45.         else if (empty == boxempty)
  46.             max = qMin(max, boxmax);
  47.     }
  48.     exp = exp || boxexp;
  49.     empty = empty && boxempty;
  50. }
复制代码
  1. // qlayoutitem.cpp
  2. Qt::Orientations QWidgetItem::expandingDirections() const
  3. {
  4.     if (isEmpty())
  5.         return {};
  6.     Qt::Orientations e = wid->sizePolicy().expandingDirections();
  7.     /*
  8.       If the layout is expanding, we make the widget expanding, even if
  9.       its own size policy isn't expanding.
  10.     */
  11.     // 根据 widget 设置的 size ploicy 来判断扩展方向
  12.     if (wid->layout()) {
  13.         if (wid->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag
  14.                 && (wid->layout()->expandingDirections() & Qt::Horizontal))
  15.             e |= Qt::Horizontal;
  16.         if (wid->sizePolicy().verticalPolicy() & QSizePolicy::GrowFlag
  17.                 && (wid->layout()->expandingDirections() & Qt::Vertical))
  18.             e |= Qt::Vertical;
  19.     }
  20.     if (align & Qt::AlignHorizontal_Mask)
  21.         e &= ~Qt::Horizontal;
  22.     if (align & Qt::AlignVertical_Mask)
  23.         e &= ~Qt::Vertical;
  24.     return e;
  25. }
复制代码
3. 分析总结

通过上述分析,widget 调整尺寸主要由 layout 管理控件进行计算调整;如果没有设置 layout 由 sizeHint() 提供 widget 的大小。当子 widget 尺寸发生变化时,会通知其所在的 layout 进行尺寸计算,从而影响父 widget 的尺寸,此时 layout 的 SizeConstraint 对 widget 的尺寸调整起到决定性作用。当包含 widget 的 widget 的尺寸发生变化时,resize 方法会触发 layout 执行 doResize 方法,setGeometry() 方法中重新计算子 widget 应该占用的空间,此时会根据 QSizePolicy 确定扩展方向。下面简单描述以下 widget 尺寸变化的 3 种情况:
3.1 widget 显示或隐藏对父 widget 的影响

此种情况会触发父 widget 直至顶层 widget 的尺寸调整(需要每层 widget 都使用 layout 布局)。父 widget 未使用 layout 布局时,可以通过处理 QEvent:ayoutRequest 事件来手工调整尺寸。
当 widget 隐藏时,QWidget::setVisible() 方法被调用。该方法在完成 widget 隐藏操作后,会调用父 widget 的 layout->invalidate() 方法(当父 widget 使用 layout 管理布局时),或者向父 widget 发送 QEvent:ayoutRequest 事件(当父 widget 未使用 layout 布局并且可见时)。 widget 未使用 layout 布局时,默认不处理 QEvent:ayoutRequest 事件,也就是说此时父 widget 不会因为子 widget 的隐藏而改变尺寸。 此时,父 widget 可以通过处理 QEvent:ayoutRequest 事件来手工调整尺寸。使用 layout 布局时,layout->invalidate() 方法会调用 layout->update() 方法,采用循环的方式将所有上层 widget 的 layout 的 activated 置为 false,并给顶层 widget 发送 QEvent:ayoutRequest 事件。顶层 widget 的 layout 收到 QEvent:ayoutRequest 事件后将调用 activate() 方法重新计算所有下层 widget 及本身的尺寸。 widget 使用 layout 布局时,只需要设置 QLayout::SizeConstraint ,当子 widget 隐藏时会自动重新计算窗口尺寸。
当 widget 显示时,QWidget::setVisible() 方法被调用。该方法首先通过updateGeometry_helper()将父 widget 的 layout 置为 invalidate()(当父 widget 使用 layout 布局时)或向父 widget 发送 QEvent:ayoutRequest 事件。然后调用本级的 layout->activate() 计算本身级下级 widget 的尺寸。最后,再依次调用上层 widget 的 layout->activate() 重新计算尺寸。该过程中,只要有一层 widget 没有设置 layout ,就会中断,上层 widget 都将无法自动调整。
3.2 widget 调整大小对子 widget 的影响

当调用 resize() 方法或 setGeometry() 方法调整 widget时,或者 widget 是 scrollArea 中的控件(设置了自动调整大小)时,如果使用了 layout 布局,其子 widget 的大小会由 layout 根据子 widget 的 SizePolicy 计算尺寸和位置信息并依次调整。如果未使用 layout 布局,子 widget 不会自动调整,可以通过处理 QResizeEvent 事件手工调整子 widget 大小及位置。
resize() 方法或 setGeometry() 方法都会调用 QWidgetPrivate::setGeometry_sys()方法,在该方法中会判断是否移动或改变大小,并发送 QMoveEvent 和 QResizeEvent 事件。下个事件循环中 QLayout 截获 QEvent::Resize 事件,并调用 d->doResize() 方法,该方法会调用 QLayout 子类的 setGeometry() 方法。QBoxLayout::setGeometry() 方法会根据子 widget 的 SizePolicy 计算其尺寸和位置信息并依次调用子 widget 的 setGeometry() 方法修改其位置及尺寸。如果 widget 未设置 layout 布局,widget 尺寸的调整不会影响子 widget 的位置及尺寸。
3.3 widget 主动调整大小对父 widget 的影响

当调用 resize() 方法或 setGeometry() 方法调整 widget 时,widget 会修改大小或位置,但是父 widget 不会有任何变化。如果父 widget 使用了 layout,当父 widget 变化时,该 widget 的大小由 layout 重新设定。
当调用 setMinimumSize() 方法或 setMaximumSize() 方法调整 widget 时,如果 widget 大小改变, 会触发父 widget 的 layout 重新计算父 widget 尺寸。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册