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 的布局管理。

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

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

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

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

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

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

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

2. 源码分析
2.1 消息的传递
对于 widget 控件, QApplication 在分发消息时会优先发送给该 widget 的 layout 布局,然后再由 widget 处理。QLayout 中的 widgetEvent() 方法处理了 QEvent::Resize、QEvent::ChildRemoved、QEvent::LayoutRequest 消息。也就是说,当 widget 生子控件移除或大小变动时,layout 优先处理,优先调整大小。layout 调整大小时主要由 activate() 方法处理。
// qapplication.cpp
bool QApplication::notify(QObject *receiver, QEvent *e)
{
...
if (isWidgetType) {
QWidget * w = static_cast<QWidget *>(receiver);
switch (e->type()) {
...
default:
res = d->notify_helper(receiver, e);
break;
}
} else {
res = d->notify_helper(receiver, e);
}
return res;
}
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
if (receiver->isWidgetType()) {
QWidget *widget = static_cast<QWidget *>(receiver);
....
// 如果使用了 layout 布局,消息先发送给 layout 处理
if (QLayout *layout=widget->d_func()->layout) {
layout->widgetEvent(e);
}
}
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, e)) {
filtered = true;
return filtered;
}
// 消息再发送给 widget 处理
// deliver the event
consumed = receiver->event(e);
QCoreApplicationPrivate::setEventSpontaneous(e, false);
return consumed;
}
// qlayout.cpp
void QLayout::widgetEvent(QEvent *e)
{
Q_D(QLayout);
const QEvent::Type type = e->type();
if (!d->enabled && type != QEvent::ChildRemoved)
return;
switch (type) {
case QEvent::Resize:
if (d->activated)
d->doResize();
else
activate();
break;
case QEvent::ChildRemoved:
{
QChildEvent *c = (QChildEvent *)e;
QObject *child = c->child();
QObjectPrivate *op = QObjectPrivate::get(child);
if (op->wasWidget) {
#if QT_CONFIG(menubar)
if (child == d->menubar)
d->menubar = nullptr;
#endif
removeWidgetRecursively(this, child);
}
}
break;
case QEvent::LayoutRequest:
if (static_cast<QWidget *>(parent())->isVisible())
activate();
break;
default:
break;
}
}
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() 方法,否则,该控件不会显示。
// qwidget.cpp
void QWidget::show()
{
...
if (!isWindow()) {
setVisible(true);
} else {
...
if (defaultState == Qt::WindowFullScreen)
showFullScreen();
else if (defaultState == Qt::WindowMaximized)
showMaximized();
else
setVisible(true);
}
}
void QWidget::setVisible(bool visible)
{
...
d->setVisible(visible);
}
void QWidgetPrivate::setVisible(bool visible)
{
...
if (visible) { // show
...
// 向父窗口发送布局更改事件
if (needUpdateGeometry)
updateGeometry_helper(true);
// 重新计算 layout 布局
// activate our layout before we and our children become visible
if (layout)
layout->activate();
if (!q->isWindow()) {
QWidget *parent = q->parentWidget();
// 依次调整上层 widget 尺寸
while (parent && parent->isVisible() && parent->d_func()->layout && !parent->data->in_show) {
parent->d_func()->layout->activate();
if (parent->isWindow())
break;
parent = parent->parentWidget();
}
if (parent)
parent->d_func()->setDirtyOpaqueRegion();
}
// 如果是 window 或上级 widget 未设置 layout 调整大小
// adjust size if necessary
if (!wasResized
&& (q->isWindow() || !q->parentWidget()->d_func()->layout)) {
if (q->isWindow()) {
// 调整 window 大小
q->adjustSize();
if (q->windowState() != initialWindowState)
q->setWindowState(initialWindowState);
} else {
// 调整 widget 大小
q->adjustSize();
}
q->setAttribute(Qt::WA_Resized, false);
}
q->setAttribute(Qt::WA_KeyboardFocusChange, false);
if (q->isWindow() || q->parentWidget()->isVisible()) {
show_helper();// 显示 widget
qApp->d_func()->sendSyntheticEnterLeave(q);
}
QEvent showToParentEvent(QEvent::ShowToParent);
QCoreApplication::sendEvent(q, &showToParentEvent);
}else{ // hide
...
// 隐藏 widget
if (!q->testAttribute(Qt::WA_WState_Hidden)) {
q->setAttribute(Qt::WA_WState_Hidden);
hide_helper();
}
// 向父 widget 发送布局改变消息
// invalidate layout similar to updateGeometry()
if (!q->isWindow() && q->parentWidget()) {
if (q->parentWidget()->d_func()->layout)
q->parentWidget()->d_func()->layout->invalidate();
else if (q->parentWidget()->isVisible())
QCoreApplication::postEvent(q->parentWidget(), new QEvent(QEvent::LayoutRequest));
}
QEvent hideToParentEvent(QEvent::HideToParent);
QCoreApplication::sendEvent(q, &hideToParentEvent);
}
}
void QWidgetPrivate::updateGeometry_helper(bool forceUpdate)
{
Q_Q(QWidget);
if (widgetItem)
widgetItem->invalidateSizeCache();
QWidget *parent;
if (forceUpdate || !extra || extra->minw != extra->maxw || extra->minh != extra->maxh) {
const int isHidden = q->isHidden() && !size_policy.retainSizeWhenHidden() && !retainSizeWhenHiddenChanged;
if (!q->isWindow() && !isHidden && (parent = q->parentWidget())) {
if (parent->d_func()->layout)
parent->d_func()->layout->invalidate();
else if (parent->isVisible())
QCoreApplication::postEvent(parent, new QEvent(QEvent::LayoutRequest));
}
}
}
void QWidget::adjustSize()
{
Q_D(QWidget);
ensurePolished();
QSize s = d->adjustedSize();
if (d->layout)
d->layout->activate();
if (s.isValid())
resize(s);
}
QSize QWidgetPrivate::adjustedSize() const
{
Q_Q(const QWidget);
// 使用推荐大小
QSize s = q->sizeHint();
if (q->isWindow()) {
Qt::Orientations exp;
if (layout) {
if (layout->hasHeightForWidth())
s.setHeight(layout->totalHeightForWidth(s.width()));
exp = layout->expandingDirections();
} else
{
if (q->sizePolicy().hasHeightForWidth())
s.setHeight(q->heightForWidth(s.width()));
exp = q->sizePolicy().expandingDirections();
}
if (exp & Qt::Horizontal)
s.setWidth(qMax(s.width(), 200));
if (exp & Qt::Vertical)
s.setHeight(qMax(s.height(), 100));
QRect screen;
if (const QScreen *screenAtPoint = QGuiApplication::screenAt(q->pos()))
screen = screenAtPoint->geometry();
else
screen = QGuiApplication::primaryScreen()->geometry();
s.setWidth(qMin(s.width(), screen.width()*2/3));
s.setHeight(qMin(s.height(), screen.height()*2/3));
if (QTLWExtra *extra = maybeTopData())
extra->sizeAdjusted = true;
}
if (!s.isValid()) {
QRect r = q->childrenRect(); // get children rectangle
if (r.isNull())
return s;
s = r.size() + QSize(2 * r.x(), 2 * r.y());
}
return s;
}
QSize QWidget::sizeHint() const
{
Q_D(const QWidget);
// 使用 layout 计算的推荐尺寸
if (d->layout)
return d->layout->totalSizeHint();
return QSize(-1, -1);
}
QSize QWidget::minimumSizeHint() const
{
Q_D(const QWidget);
// 使用 layout 计算的推荐尺寸
if (d->layout)
return d->layout->totalMinimumSize();
return QSize(-1, -1);
}
void QWidget::resize(const QSize &s)
{
Q_D(QWidget);
setAttribute(Qt::WA_Resized);
if (testAttribute(Qt::WA_WState_Created)) {
d->fixPosIncludesFrame();
d->setGeometry_sys(geometry().x(), geometry().y(), s.width(), s.height(), false);
d->setDirtyOpaqueRegion();
} else {
const auto oldRect = data->crect;
// 根据 minimizeSize 与 maximumSize 进行约束后保存到 data->crect
data->crect.setSize(s.boundedTo(maximumSize()).expandedTo(minimumSize()));
if (oldRect != data->crect)
setAttribute(Qt::WA_PendingResizeEvent);
}
}
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 来判断扩展方向。
// qlayout.cpp
bool QLayout::activate()
{
Q_D(QLayout);
if (!d->enabled || !parent())
return false;
if (!d->topLevel)
return static_cast<QLayout*>(parent())->activate();
if (d->activated)
return false;
// layout 所属的主widget
QWidget *mw = static_cast<QWidget*>(parent());
if (Q_UNLIKELY(!mw)) {
qWarning("QLayout::activate: %s \"%ls\" does not have a main widget",
metaObject()->className(), qUtf16Printable(objectName()));
return false;
}
activateRecursiveHelper(this);
QWidgetPrivate *md = mw->d_func();
uint explMin = md->extra ? md->extra->explicitMinSize : 0;
uint explMax = md->extra ? md->extra->explicitMaxSize : 0;
// 根据 QLayout::SizeConstraint 设置主 widget 尺寸
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;
}
// 根据主 widget 调整后的尺寸计算 layout 的 rect
d->doResize();
if (md->extra) {
md->extra->explicitMinSize = explMin;
md->extra->explicitMaxSize = explMax;
}
// ideally only if sizeHint() or sizePolicy() has changed
mw->updateGeometry();
return true;
}
void QLayoutPrivate::doResize()
{
Q_Q(QLayout);
QWidget *mw = q->parentWidget();
QRect rect = mw->testAttribute(Qt::WA_LayoutOnEntireRect) ? mw->rect() : mw->contentsRect();
const int mbh = menuBarHeightForWidth(menubar, rect.width());
const int mbTop = rect.top();
rect.setTop(mbTop + mbh);
// 对于 QBoxLayout 此处会调整子 widget 大小
q->setGeometry(rect);
#if QT_CONFIG(menubar)
if (menubar)
menubar->setGeometry(rect.left(), mbTop, rect.width(), mbh);
#endif
}
// 此方法在 QLayout 的子类中会重载,完成子 widget 坐标的计算与设置
void QLayout::setGeometry(const QRect &r)
{
Q_D(QLayout);
d->rect = r;
}
QSize QLayout::totalSizeHint() const
{
Q_D(const QLayout);
int side=0, top=0;
if (d->topLevel) {
QWidget *pw = parentWidget();
pw->ensurePolished();
QWidgetPrivate *wd = pw->d_func();
side += wd->leftmargin + wd->rightmargin;
top += wd->topmargin + wd->bottommargin;
}
QSize s = sizeHint();
if (hasHeightForWidth())
s.setHeight(heightForWidth(s.width() + side));
#if QT_CONFIG(menubar)
top += menuBarHeightForWidth(d->menubar, s.width());
#endif
return s + QSize(side, top);
}
void QLayout::invalidate()
{
Q_D(QLayout);
d->rect = QRect();
update();
}
void QLayout::update()
{
QLayout *layout = this;
while (layout && layout->d_func()->activated) {
layout->d_func()->activated = false;
if (layout->d_func()->topLevel) {
Q_ASSERT(layout->parent()->isWidgetType());
QWidget *mw = static_cast<QWidget*>(layout->parent());
QCoreApplication::postEvent(mw, new QEvent(QEvent::LayoutRequest));
break;
}
layout = static_cast<QLayout*>(layout->parent());
}
}
// qboxlayout.cpp
QSize QBoxLayout::sizeHint() const
{
Q_D(const QBoxLayout);
if (d->dirty)
const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
return d->sizeHint;
}
QSize QBoxLayout::minimumSize() const
{
Q_D(const QBoxLayout);
if (d->dirty)
const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
return d->minSize;
}
QSize QBoxLayout::maximumSize() const
{
Q_D(const QBoxLayout);
if (d->dirty)
const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
QSize s = d->maxSize.boundedTo(QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX));
if (alignment() & Qt::AlignHorizontal_Mask)
s.setWidth(QLAYOUTSIZE_MAX);
if (alignment() & Qt::AlignVertical_Mask)
s.setHeight(QLAYOUTSIZE_MAX);
return s;
}
// 此方法遍历 layout 中所有 widget 计算 minSize、maxSize、sizeHint
void QBoxLayoutPrivate::setupGeom()
{
if (!dirty)
return;
Q_Q(QBoxLayout);
int maxw = horz(dir) ? 0 : QLAYOUTSIZE_MAX;
int maxh = horz(dir) ? QLAYOUTSIZE_MAX : 0;
int minw = 0;
int minh = 0;
int hintw = 0;
int hinth = 0;
bool horexp = false;
bool verexp = false;
hasHfw = false;
int n = list.size();
geomArray.clear();
QList<QLayoutStruct> a(n);
QSizePolicy::ControlTypes controlTypes1;
QSizePolicy::ControlTypes controlTypes2;
int fixedSpacing = q->spacing();
int previousNonEmptyIndex = -1;
QStyle *style = nullptr;
if (fixedSpacing < 0) {
if (QWidget *parentWidget = q->parentWidget())
style = parentWidget->style();
}
for (int i = 0; i < n; i++) {
QBoxLayoutItem *box = list.at(i);
// 获取 layout 内的 widget 的尺寸
QSize max = box->item->maximumSize();
QSize min = box->item->minimumSize();
QSize hint = box->item->sizeHint();
// 计算扩展方向
Qt::Orientations exp = box->item->expandingDirections();
bool empty = box->item->isEmpty();
int spacing = 0;
if (!empty) {
if (fixedSpacing >= 0) {
spacing = (previousNonEmptyIndex >= 0) ? fixedSpacing : 0;
#ifdef Q_OS_MAC
if (!horz(dir) && previousNonEmptyIndex >= 0) {
QBoxLayoutItem *sibling = (dir == QBoxLayout::TopToBottom ? box : list.at(previousNonEmptyIndex));
if (sibling) {
QWidget *wid = sibling->item->widget();
if (wid)
spacing = qMax(spacing, sibling->item->geometry().top() - wid->geometry().top());
}
}
#endif
} else {
controlTypes1 = controlTypes2;
controlTypes2 = box->item->controlTypes();
if (previousNonEmptyIndex >= 0) {
QSizePolicy::ControlTypes actual1 = controlTypes1;
QSizePolicy::ControlTypes actual2 = controlTypes2;
if (dir == QBoxLayout::RightToLeft || dir == QBoxLayout::BottomToTop)
qSwap(actual1, actual2);
if (style) {
spacing = style->combinedLayoutSpacing(actual1, actual2,
horz(dir) ? Qt::Horizontal : Qt::Vertical,
nullptr, q->parentWidget());
if (spacing < 0)
spacing = 0;
}
}
}
if (previousNonEmptyIndex >= 0)
a[previousNonEmptyIndex].spacing = spacing;
previousNonEmptyIndex = i;
}
bool ignore = empty && box->item->widget(); // ignore hidden widgets
bool dummy = true;
if (horz(dir)) {
bool expand = (exp & Qt::Horizontal || box->stretch > 0);
horexp = horexp || expand;
maxw += spacing + max.width();
minw += spacing + min.width();
hintw += spacing + hint.width();
if (!ignore)
qMaxExpCalc(maxh, verexp, dummy,
max.height(), exp & Qt::Vertical, box->item->isEmpty());
minh = qMax(minh, min.height());
hinth = qMax(hinth, hint.height());
a[i].sizeHint = hint.width();
a[i].maximumSize = max.width();
a[i].minimumSize = min.width();
a[i].expansive = expand;
a[i].stretch = box->stretch ? box->stretch : box->hStretch();
} else {
bool expand = (exp & Qt::Vertical || box->stretch > 0);
verexp = verexp || expand;
maxh += spacing + max.height();
minh += spacing + min.height();
hinth += spacing + hint.height();
if (!ignore)
qMaxExpCalc(maxw, horexp, dummy,
max.width(), exp & Qt::Horizontal, box->item->isEmpty());
minw = qMax(minw, min.width());
hintw = qMax(hintw, hint.width());
a[i].sizeHint = hint.height();
a[i].maximumSize = max.height();
a[i].minimumSize = min.height();
a[i].expansive = expand;
a[i].stretch = box->stretch ? box->stretch : box->vStretch();
}
a[i].empty = empty;
a[i].spacing = 0; // might be initialized with a non-zero value in a later iteration
hasHfw = hasHfw || box->item->hasHeightForWidth();
}
geomArray = a;
expanding = (Qt::Orientations)
((horexp ? Qt::Horizontal : 0)
| (verexp ? Qt::Vertical : 0));
minSize = QSize(minw, minh);
maxSize = QSize(maxw, maxh).expandedTo(minSize);
sizeHint = QSize(hintw, hinth).expandedTo(minSize).boundedTo(maxSize);
q->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin);
int left, top, right, bottom;
effectiveMargins(&left, &top, &right, &bottom);
QSize extra(left + right, top + bottom);
minSize += extra;
maxSize += extra;
sizeHint += extra;
dirty = false;
}
struct QBoxLayoutItem
{
...
QLayoutItem *item;
...
};
void QBoxLayout::setGeometry(const QRect &r)
{
Q_D(QBoxLayout);
if (d->dirty || r != geometry()) {
QRect oldRect = geometry();
QLayout::setGeometry(r);
if (d->dirty)
d->setupGeom();
QRect cr = alignment() ? alignmentRect(r) : r;
int left, top, right, bottom;
d->effectiveMargins(&left, &top, &right, &bottom);
QRect s(cr.x() + left, cr.y() + top,
cr.width() - (left + right),
cr.height() - (top + bottom));
// d->geomArray 已经在 setupGeom() 完成计算
QList<QLayoutStruct> a = d->geomArray;
int pos = horz(d->dir) ? s.x() : s.y();
int space = horz(d->dir) ? s.width() : s.height();
int n = a.size();
if (d->hasHfw && !horz(d->dir)) {
for (int i = 0; i < n; i++) {
QBoxLayoutItem *box = d->list.at(i);
if (box->item->hasHeightForWidth()) {
int width = qBound(box->item->minimumSize().width(), s.width(), box->item->maximumSize().width());
a[i].sizeHint = a[i].minimumSize =
box->item->heightForWidth(width);
}
}
}
Direction visualDir = d->dir;
QWidget *parent = parentWidget();
if (parent && parent->isRightToLeft()) {
if (d->dir == LeftToRight)
visualDir = RightToLeft;
else if (d->dir == RightToLeft)
visualDir = LeftToRight;
}
// 根据 QRect &r 大小,调整子 widget 尺寸
qGeomCalc(a, 0, n, pos, space);
bool reverse = (horz(visualDir)
? ((r.right() > oldRect.right()) != (visualDir == RightToLeft))
: r.bottom() > oldRect.bottom());
// 设置子 widget 坐标
for (int j = 0; j < n; j++) {
int i = reverse ? n-j-1 : j;
QBoxLayoutItem *box = d->list.at(i);
switch (visualDir) {
case LeftToRight:
box->item->setGeometry(QRect(a.at(i).pos, s.y(), a.at(i).size, s.height()));
break;
case RightToLeft:
box->item->setGeometry(QRect(s.left() + s.right() - a.at(i).pos - a.at(i).size + 1,
s.y(), a.at(i).size, s.height()));
break;
case TopToBottom:
box->item->setGeometry(QRect(s.x(), a.at(i).pos, s.width(), a.at(i).size));
break;
case BottomToTop:
box->item->setGeometry(QRect(s.x(),
s.top() + s.bottom() - a.at(i).pos - a.at(i).size + 1,
s.width(), a.at(i).size));
}
}
}
}
// qlayoutengine_p.h
struct QLayoutStruct
{
inline void init(int stretchFactor = 0, int minSize = 0) {
stretch = stretchFactor;
minimumSize = sizeHint = minSize;
maximumSize = QLAYOUTSIZE_MAX;
expansive = false;
empty = true;
spacing = 0;
}
int smartSizeHint() {
return (stretch > 0) ? minimumSize : sizeHint;
}
int effectiveSpacer(int uniformSpacer) const {
Q_ASSERT(uniformSpacer >= 0 || spacing >= 0);
return (uniformSpacer >= 0) ? uniformSpacer : spacing;
}
// parameters
int stretch;
int sizeHint;
int maximumSize;
int minimumSize;
int spacing;
bool expansive;
bool empty;
// temporary storage
bool done;
// result
int pos;
int size;
};
Q_WIDGETS_EXPORT void qGeomCalc(QList<QLayoutStruct> &chain, int start, int count, int pos,
int space, int spacer = -1);
...
static inline void qMaxExpCalc(int & max, bool &exp, bool &empty,
int boxmax, bool boxexp, bool boxempty)
{
if (exp) {
if (boxexp)
max = qMax(max, boxmax);
} else {
if (boxexp || (empty && (!boxempty || max == 0)))
max = boxmax;
else if (empty == boxempty)
max = qMin(max, boxmax);
}
exp = exp || boxexp;
empty = empty && boxempty;
}
// qlayoutitem.cpp
Qt::Orientations QWidgetItem::expandingDirections() const
{
if (isEmpty())
return {};
Qt::Orientations e = wid->sizePolicy().expandingDirections();
/*
If the layout is expanding, we make the widget expanding, even if
its own size policy isn't expanding.
*/
// 根据 widget 设置的 size ploicy 来判断扩展方向
if (wid->layout()) {
if (wid->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag
&& (wid->layout()->expandingDirections() & Qt::Horizontal))
e |= Qt::Horizontal;
if (wid->sizePolicy().verticalPolicy() & QSizePolicy::GrowFlag
&& (wid->layout()->expandingDirections() & Qt::Vertical))
e |= Qt::Vertical;
}
if (align & Qt::AlignHorizontal_Mask)
e &= ~Qt::Horizontal;
if (align & Qt::AlignVertical_Mask)
e &= ~Qt::Vertical;
return e;
}
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::LayoutRequest 事件来手工调整尺寸。
当 widget 隐藏时,QWidget::setVisible() 方法被调用。该方法在完成 widget 隐藏操作后,会调用父 widget 的 layout->invalidate() 方法(当父 widget 使用 layout 管理布局时),或者向父 widget 发送 QEvent::LayoutRequest 事件(当父 widget 未使用 layout 布局并且可见时)。 widget 未使用 layout 布局时,默认不处理 QEvent::LayoutRequest 事件,也就是说此时父 widget 不会因为子 widget 的隐藏而改变尺寸。 此时,父 widget 可以通过处理 QEvent::LayoutRequest 事件来手工调整尺寸。使用 layout 布局时,layout->invalidate() 方法会调用 layout->update() 方法,采用循环的方式将所有上层 widget 的 layout 的 activated 置为 false,并给顶层 widget 发送 QEvent::LayoutRequest 事件。顶层 widget 的 layout 收到 QEvent::LayoutRequest 事件后将调用 activate() 方法重新计算所有下层 widget 及本身的尺寸。 widget 使用 layout 布局时,只需要设置 QLayout::SizeConstraint ,当子 widget 隐藏时会自动重新计算窗口尺寸。
当 widget 显示时,QWidget::setVisible() 方法被调用。该方法首先通过updateGeometry_helper()将父 widget 的 layout 置为 invalidate()(当父 widget 使用 layout 布局时)或向父 widget 发送 QEvent::LayoutRequest 事件。然后调用本级的 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 尺寸。