查看Qt帮助文档,我们可知所有布局继承
QLayout,而QLayout继承QObject and QLayoutItem。
一、QLayoutItem
1.1 QLayoutItem
The QLayoutItem class provides an abstract item that a QLayout manipulates.
QLayoutItem类为QLayout提供了一个抽象基类。各个Layout可实现这些抽象方法。
class Q_WIDGETS_EXPORT QLayoutItem
{
public:
inline explicit QLayoutItem(Qt::Alignment alignment = Qt::Alignment());
virtual ~QLayoutItem();
/*子类中实现它,用来返回本item最佳尺寸*/
virtual QSize sizeHint() const = 0;
/*子类中实现它,用来返回本item最小尺寸*/
virtual QSize minimumSize() const = 0;
/*子类中实现它,用来返回本item最大尺寸*/
virtual QSize maximumSize() const = 0;
/*用来决定是否可以在水平、垂直方向膨胀,即占据比sizeHint()指定的还大的空间*/
virtual Qt::Orientations expandingDirections() const = 0;
/*子类中实现它,用来设置本item的几何尺寸*/
virtual void setGeometry(const QRect&) = 0;
virtual QRect geometry() const = 0;
/*子类中实现它用来判断自己是否为空。例如:用来判断是否含有任何widgets*/
virtual bool isEmpty() const = 0;
/*如果该布局的最佳高度依赖于宽度,则返回真,否则返回假。默认返回假*/
virtual bool hasHeightForWidth() const { return false; }
virtual int heightForWidth(int) const { return -1; }
/*根据已给的宽度返回其自小的高度,默认实现是heightForWidth(w)*/
virtual int minimumHeightForWidth(int) const { return heightForWidth(w); }
/*让item中所有的缓存信息失效*/
virtual void invalidate() {}
/* 如果它管理的是QWidget,则返回此widget,否则返回空*/
virtual QWidget *widget() {return 0;}
/*如果它是QLayout,则返回自己; 否则返回空*/
virtual QLayout *layout() {return 0;}
/*如果它是QSpacerItem,则返回自己,否则返回空*/
virtual QSpacerItem *spacerItem() {return 0;}
Qt::Alignment alignment() const { return align; }
void setAlignment(Qt::Alignment a) {align = alignment;}
/*如果是QWidgetItem,则返回widget的尺寸策略;如果是QLayoutItem,则返回布局内容的尺寸策略*/
virtual QSizePolicy::ControlTypes controlTypes() const {return QSizePolicy::DefaultType;}
protected:
Qt::Alignment align;
};
1.2 QSpacerItem
class Q_WIDGETS_EXPORT QSpacerItem : public QLayoutItem
{
public:
QSpacerItem(int w, int h,
QSizePolicy::Policy hData = QSizePolicy::Minimum,
QSizePolicy::Policy vData = QSizePolicy::Minimum)
: width(w), height(h), sizeP(hData, vData) { }
~QSpacerItem();
void changeSize(int w, int h,
QSizePolicy::Policy hData = QSizePolicy::Minimum,
QSizePolicy::Policy vData = QSizePolicy::Minimum);
QSize sizeHint() const override;
QSize minimumSize() const override;
QSize maximumSize() const override;
Qt::Orientations expandingDirections() const override;
bool isEmpty() const override;
void setGeometry(const QRect&) override;
QRect geometry() const override;
QSpacerItem *spacerItem() override;
QSizePolicy sizePolicy() const { return sizeP; }
private:
int width;
int height;
QSizePolicy sizeP;
QRect rect;
};
QSize QSpacerItem::sizeHint() const
{
return QSize(width, height);
}
QSize QSpacerItem::minimumSize() const
{
return QSize(sizeP.horizontalPolicy() & QSizePolicy::ShrinkFlag ? 0 : width,
sizeP.verticalPolicy() & QSizePolicy::ShrinkFlag ? 0 : height);
}
QSize QSpacerItem::maximumSize() const
{
return QSize(sizeP.horizontalPolicy() & QSizePolicy::GrowFlag ? QLAYOUTSIZE_MAX : width,
sizeP.verticalPolicy() & QSizePolicy::GrowFlag ? QLAYOUTSIZE_MAX : height);
}
Qt::Orientations QSpacerItem::expandingDirections() const
{
return sizeP.expandingDirections();
}
void QSpacerItem::setGeometry(const QRect &r)
{
rect = r;
}
QRect QSpacerItem::geometry() const
{
return rect;
}
QSpacerItem * QSpacerItem::spacerItem()
{
return this;
}
1.3 QWidgetItem
class Q_WIDGETS_EXPORT QWidgetItem : public QLayoutItem
{
Q_DISABLE_COPY(QWidgetItem)
public:
explicit QWidgetItem(QWidget *w) : wid(w) { }
~QWidgetItem();
QSize sizeHint() const override;
QSize minimumSize() const override;
QSize maximumSize() const override;
Qt::Orientations expandingDirections() const override;
bool isEmpty() const override;
void setGeometry(const QRect&) override;
QRect geometry() const override;
QWidget *widget() override;
bool hasHeightForWidth() const override;
int heightForWidth(int) const override;
QSizePolicy::ControlTypes controlTypes() const override;
protected:
QWidget *wid;
};
QSize QWidgetItem::sizeHint() const
{
QSize s(0, 0);
if (!isEmpty()) {
s = wid->sizeHint().expandedTo(wid->minimumSizeHint());
s = s.boundedTo(wid->maximumSize())
.expandedTo(wid->minimumSize());
s = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect)
? toLayoutItemSize(wid->d_func(), s)
: s;
if (wid->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored)
s.setWidth(0);
if (wid->sizePolicy().verticalPolicy() == QSizePolicy::Ignored)
s.setHeight(0);
}
return s;
}
QSize QWidgetItem::minimumSize() const
{
if (isEmpty())
return QSize(0, 0);
return !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect)
? toLayoutItemSize(wid->d_func(), qSmartMinSize(this))
: qSmartMinSize(this);
}
bool QWidgetItem::isEmpty() const
{
return (wid->isHidden() && !wid->sizePolicy().retainSizeWhenHidden()) || wid->isWindow();
}
QSizePolicy::ControlTypes QWidgetItem::controlTypes() const
{
return wid->sizePolicy().controlType();
}
QWidget加入到布局中,会自动创建QWidgetItem,布局引擎会调用QWidgetItem的sizeHint函数,该函数中有代码:wid->sizeHint(),其中wid为QWidget,所以其最终调用的就是QWidget的sizeHint(),整个链条打通。
virtual QSize sizeHint() const {
Q_D(const QWidget);
if (d->layout)
return d->layout->totalSizeHint();
return QSize(-1, -1);
}
QWidget加入到布局中,会自动创建QWidgetItem,布局引擎会调用QWidgetItem的minimumSize函数,该函数中有代码:toLayoutItemSize(wid->d_func(), qSmartMinSize(this))。
toLayoutItemSize在qlayoutitem.cpp中,其实现如下:
inline static QRect toLayoutItemRect(QWidgetPrivate *priv, const QRect &rect)
{
return rect.adjusted(-priv->leftLayoutItemMargin, -priv->topLayoutItemMargin,
priv->rightLayoutItemMargin, priv->bottomLayoutItemMargin);
}
inline static QSize toLayoutItemSize(QWidgetPrivate *priv, const QSize &size)
{
return toLayoutItemRect(priv, QRect(QPoint(0, 0), size)).size();
}
qSmartMinSize在qlayoutengine.cpp中,其实现如下:
QSize qSmartMinSize(const QWidgetItem *i)
{
QWidget *w = const_cast<QWidgetItem *>(i)->widget();
return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(),
w->minimumSize(), w->maximumSize(),
w->sizePolicy());
}
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));
}
从上代码可知,QWidget最小尺寸,最终由其的sizePolicy影响。
1.4 QWidgetItemV2
class Q_WIDGETS_EXPORT QWidgetItemV2 : public QWidgetItem
{
public:
explicit QWidgetItemV2(QWidget *widget);
~QWidgetItemV2();
QSize sizeHint() const override;
QSize minimumSize() const override;
QSize maximumSize() const override;
int heightForWidth(int width) const override;
private:
enum { Dirty = -123, HfwCacheMaxSize = 3 };
inline bool useSizeCache() const;
void updateCacheIfNecessary() const;
inline void invalidateSizeCache() {
q_cachedMinimumSize.setWidth(Dirty);
q_hfwCacheSize = 0;
}
mutable QSize q_cachedMinimumSize;
mutable QSize q_cachedSizeHint;
mutable QSize q_cachedMaximumSize;
mutable QSize q_cachedHfws[HfwCacheMaxSize];
mutable short q_firstCachedHfw;
mutable short q_hfwCacheSize;
void *d;
friend class QWidgetPrivate;
Q_DISABLE_COPY(QWidgetItemV2)
};
QSize QWidgetItemV2::sizeHint() const
{
if (isEmpty())
return QSize(0, 0);
if (useSizeCache()) {
updateCacheIfNecessary();
return q_cachedSizeHint;
} else {
return QWidgetItem::sizeHint();
}
}
int QWidgetItemV2::heightForWidth(int width) const
{
if (isEmpty())
return -1;
for (int i = 0; i < q_hfwCacheSize; ++i) {
int offset = q_firstCachedHfw + i;
const QSize &size = q_cachedHfws[offset % HfwCacheMaxSize];
if (size.width() == width) {
if (q_hfwCacheSize == HfwCacheMaxSize)
q_firstCachedHfw = offset;
return size.height();
}
}
if (q_hfwCacheSize < HfwCacheMaxSize)
++q_hfwCacheSize;
q_firstCachedHfw = (q_firstCachedHfw + HfwCacheMaxSize - 1) % HfwCacheMaxSize;
int height = QWidgetItem::heightForWidth(width);
q_cachedHfws[q_firstCachedHfw] = QSize(width, height);
return height;
}
二、QSizePolicy
The QSizePolicy class is a layout attribute describing horizontal and vertical resizing policy.
The size policy of a widget is an expression of its willingness to be resized in various ways, and affects how the widget is treated by the layout engine. Each widget returns a QSizePolicy that describes the horizontal and vertical resizing policy it prefers when being laid out. You can change this for a specific widget by changing its QWidget::sizePolicy property.
QSizePolicy类是描述水平和垂直调整大小策略的布局属性。widget的QSizePolicy用来描述布局引擎如何分配其尺寸大小。
class Q_WIDGETS_EXPORT QSizePolicy
{
Q_GADGET
public:
Q_ENUM(Policy)
Q_DECLARE_FLAGS(ControlTypes, ControlType)
Q_FLAG(ControlTypes)
QT_SIZEPOLICY_CONSTEXPR QSizePolicy() Q_DECL_NOTHROW : data(0) { }
Policy horizontalPolicy() const Q_DECL_NOTHROW { return static_cast<Policy>(bits.horPolicy); }
Policy verticalPolicy() const Q_DECL_NOTHROW { return static_cast<Policy>(bits.verPolicy); }
ControlType controlType() const Q_DECL_NOTHROW;
void setHorizontalPolicy(Policy d) Q_DECL_NOTHROW { bits.horPolicy = d; }
void setVerticalPolicy(Policy d) Q_DECL_NOTHROW { bits.verPolicy = d; }
void setControlType(ControlType type) Q_DECL_NOTHROW;
Qt::Orientations expandingDirections() const Q_DECL_NOTHROW {
return ( (verticalPolicy() & ExpandFlag) ? Qt::Vertical : Qt::Orientations() )
| ( (horizontalPolicy() & ExpandFlag) ? Qt::Horizontal : Qt::Orientations() ) ;
}
void setHeightForWidth(bool b) Q_DECL_NOTHROW { bits.hfw = b; }
bool hasHeightForWidth() const Q_DECL_NOTHROW { return bits.hfw; }
void setWidthForHeight(bool b) Q_DECL_NOTHROW { bits.wfh = b; }
bool hasWidthForHeight() const Q_DECL_NOTHROW { return bits.wfh; }
bool operator==(const QSizePolicy& s) const Q_DECL_NOTHROW { return data == s.data; }
bool operator!=(const QSizePolicy& s) const Q_DECL_NOTHROW { return data != s.data; }
friend uint qHash(QSizePolicy key, uint seed) Q_DECL_NOTHROW { return qHash(key.data, seed); }
operator QVariant() const;
int horizontalStretch() const Q_DECL_NOTHROW { return static_cast<int>(bits.horStretch); }
int verticalStretch() const Q_DECL_NOTHROW { return static_cast<int>(bits.verStretch); }
void setHorizontalStretch(int stretchFactor) { bits.horStretch = static_cast<quint32>(qBound(0, stretchFactor, 255)); }
void setVerticalStretch(int stretchFactor) { bits.verStretch = static_cast<quint32>(qBound(0, stretchFactor, 255)); }
bool retainSizeWhenHidden() const Q_DECL_NOTHROW { return bits.retainSizeWhenHidden; }
void setRetainSizeWhenHidden(bool retainSize) Q_DECL_NOTHROW { bits.retainSizeWhenHidden = retainSize; }
void transpose() Q_DECL_NOTHROW { *this = transposed(); }
QSizePolicy transposed() const Q_DECL_NOTHROW
{
return QSizePolicy(bits.transposed());
}
private:
union {
Bits bits;
quint32 data;
};
};
QSizePolicy::setRetainSizeWhenHidden,当部件被隐藏时,设置其所在布局是否应保留它的大小。
三、QWidget
class QWidget : public QObject, public QPaintDevice
{
virtual QSize sizeHint() const {
Q_D(const QWidget);
if (d->layout)
return d->layout->totalSizeHint();
return QSize(-1, -1);
}
/*设置widget的属性为真或假*/
void setAttribute(Qt::WidgetAttribute, bool on = true);
inline bool testAttribute(Qt::WidgetAttribute) const;
void setSizePolicy(QSizePolicy policy)
{
Q_D(QWidget);
setAttribute(Qt::WA_WState_OwnSizePolicy);
if (policy == d->size_policy)
return;
if (d->size_policy.retainSizeWhenHidden() != policy.retainSizeWhenHidden())
d->retainSizeWhenHiddenChanged = 1;
d->size_policy = policy;
... ...
updateGeometry();
d->retainSizeWhenHiddenChanged = 0;
if (isWindow() && d->maybeTopData())
d->topData()->sizeAdjusted = false;
}
bool hasHeightForWidth() const
{
Q_D(const QWidget);
return d->layout ? d->layout->hasHeightForWidth() : d->size_policy.hasHeightForWidth();
}
}
void QWidget::setParent(QWidget *parent)
{
if (parent == parentWidget())
return;
setParent((QWidget*)parent, windowFlags() & ~Qt::WindowType_Mask);
}
void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
Q_D(QWidget);
bool resized = testAttribute(Qt::WA_Resized);
bool wasCreated = testAttribute(Qt::WA_WState_Created);
QWidget *oldtlw = window();
if (f & Qt::Window) // Frame geometry likely changes, refresh.
d->data.fstrut_dirty = true;
QWidget *desktopWidget = 0;
if (parent && parent->windowType() == Qt::Desktop)
desktopWidget = parent;
bool newParent = (parent != parentWidget()) || !wasCreated || desktopWidget;
if (newParent && parent && !desktopWidget) {
if (testAttribute(Qt::WA_NativeWindow) && !qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings))
parent->d_func()->enforceNativeChildren();
else if (parent->d_func()->nativeChildrenForced() || parent->testAttribute(Qt::WA_PaintOnScreen))
setAttribute(Qt::WA_NativeWindow);
}
if (wasCreated) {
if (!testAttribute(Qt::WA_WState_Hidden)) {
hide();
setAttribute(Qt::WA_WState_ExplicitShowHide, false);
}
if (newParent) {
QEvent e(QEvent::ParentAboutToChange);
QApplication::sendEvent(this, &e);
}
}
if (newParent && isAncestorOf(focusWidget()))
focusWidget()->clearFocus();
QTLWExtra *oldTopExtra = window()->d_func()->maybeTopData();
QWidgetBackingStoreTracker *oldBsTracker = oldTopExtra ? &oldTopExtra->backingStoreTracker : 0;
d->setParent_sys(parent, f);
QTLWExtra *topExtra = window()->d_func()->maybeTopData();
QWidgetBackingStoreTracker *bsTracker = topExtra ? &topExtra->backingStoreTracker : 0;
if (oldBsTracker && oldBsTracker != bsTracker)
oldBsTracker->unregisterWidgetSubtree(this);
if (desktopWidget)
parent = 0;
#ifndef QT_NO_OPENGL
if (d->textureChildSeen && parent) {
// set the textureChildSeen flag up the whole parent chain
QWidgetPrivate::get(parent)->setTextureChildSeen();
}
#endif
if (QWidgetBackingStore *oldBs = oldtlw->d_func()->maybeBackingStore()) {
if (newParent)
oldBs->removeDirtyWidget(this);
// Move the widget and all its static children from
// the old backing store to the new one.
oldBs->moveStaticWidgets(this);
}
// ### fixme: Qt 6: Remove AA_ImmediateWidgetCreation.
if (QApplicationPrivate::testAttribute(Qt::AA_ImmediateWidgetCreation) && !testAttribute(Qt::WA_WState_Created))
create();
d->reparentFocusWidgets(oldtlw);
setAttribute(Qt::WA_Resized, resized);
const bool useStyleSheetPropagationInWidgetStyles =
QCoreApplication::testAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles);
if (!useStyleSheetPropagationInWidgetStyles && !testAttribute(Qt::WA_StyleSheet)
&& (!parent || !parent->testAttribute(Qt::WA_StyleSheet))) {
d->resolveFont();
d->resolvePalette();
}
d->resolveLayoutDirection();
d->resolveLocale();
// Note: GL widgets under WGL or EGL will always need a ParentChange
// event to handle recreation/rebinding of the GL context, hence the
// (f & Qt::MSWindowsOwnDC) clause (which is set on QGLWidgets on all
// platforms).
if (newParent
#if 0 /* Used to be included in Qt4 for Q_WS_WIN */ || defined(QT_OPENGL_ES)
|| (f & Qt::MSWindowsOwnDC)
#endif
) {
// propagate enabled updates enabled state to non-windows
if (!isWindow()) {
if (!testAttribute(Qt::WA_ForceDisabled))
d->setEnabled_helper(parent ? parent->isEnabled() : true);
if (!testAttribute(Qt::WA_ForceUpdatesDisabled))
d->setUpdatesEnabled_helper(parent ? parent->updatesEnabled() : true);
}
d->inheritStyle();
// send and post remaining QObject events
if (parent && d->sendChildEvents) {
QChildEvent e(QEvent::ChildAdded, this);
QApplication::sendEvent(parent, &e);
}
//### already hidden above ---> must probably do something smart on the mac
// #if 0 // Used to be included in Qt4 for Q_WS_MAC
// extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp
// if(!qt_mac_is_macdrawer(q)) //special case
// q->setAttribute(Qt::WA_WState_Hidden);
// #else
// q->setAttribute(Qt::WA_WState_Hidden);
//#endif
if (parent && d->sendChildEvents && d->polished) {
QChildEvent e(QEvent::ChildPolished, this);
QCoreApplication::sendEvent(parent, &e);
}
QEvent e(QEvent::ParentChange);
QApplication::sendEvent(this, &e);
}
#ifndef QT_NO_OPENGL
//renderToTexture widgets also need to know when their top-level window changes
if (d->textureChildSeen && oldtlw != window()) {
sendWindowChangeToTextureChildrenRecursively(this);
}
#endif
if (!wasCreated) {
if (isWindow() || parentWidget()->isVisible())
setAttribute(Qt::WA_WState_Hidden, true);
else if (!testAttribute(Qt::WA_WState_ExplicitShowHide))
setAttribute(Qt::WA_WState_Hidden, false);
}
d->updateIsOpaque();
#if QT_CONFIG(graphicsview)
// Embed the widget into a proxy if the parent is embedded.
// ### Doesn't handle reparenting out of an embedded widget.
if (oldtlw->graphicsProxyWidget()) {
if (QGraphicsProxyWidget *ancestorProxy = d->nearestGraphicsProxyWidget(oldtlw))
ancestorProxy->d_func()->unembedSubWindow(this);
}
if (isWindow() && parent && !graphicsProxyWidget() && !bypassGraphicsProxyWidget(this)) {
if (QGraphicsProxyWidget *ancestorProxy = d->nearestGraphicsProxyWidget(parent))
ancestorProxy->d_func()->embedSubWindow(this);
}
#endif
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasChanged(this);
}
四、QLayout
The QLayout class is the base class of geometry managers.
This is an abstract base class inherited by the concrete classes QBoxLayout, QGridLayout, QFormLayout, and QStackedLayout.
QLayout是管理几何尺寸的基类。它是一个抽象基类,继承它的类有QBoxLayout, QGridLayout, QFormLayout 和 QStackedLayout。
class Q_WIDGETS_EXPORT QLayout : public QObject, public QLayoutItem
{
Q_OBJECT
Q_DECLARE_PRIVATE(QLayout)
Q_PROPERTY(int margin READ margin WRITE setMargin)
Q_PROPERTY(int spacing READ spacing WRITE setSpacing)
Q_PROPERTY(SizeConstraint sizeConstraint READ sizeConstraint WRITE setSizeConstraint)
public:
Q_ENUM(SizeConstraint)
QLayout(QWidget *parent);
QLayout();
~QLayout();
int margin() const;
int spacing() const;
void setMargin(int);
void setSpacing(int);
void setContentsMargins(int left, int top, int right, int bottom);
void setContentsMargins(const QMargins &margins);
void getContentsMargins(int *left, int *top, int *right, int *bottom) const;
QMargins contentsMargins() const;
QRect contentsRect() const;
bool setAlignment(QWidget *w, Qt::Alignment alignment);
bool setAlignment(QLayout *l, Qt::Alignment alignment);
using QLayoutItem::setAlignment;
void setSizeConstraint(SizeConstraint);
SizeConstraint sizeConstraint() const;
void setMenuBar(QWidget *w);
QWidget *menuBar() const;
QWidget *parentWidget() const;
void invalidate() Q_DECL_OVERRIDE;
QRect geometry() const Q_DECL_OVERRIDE;
bool activate();
void update();
void addWidget(QWidget *w);
virtual void addItem(QLayoutItem *) = 0;
void removeWidget(QWidget *w);
void removeItem(QLayoutItem *);
Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE;
QSize minimumSize() const Q_DECL_OVERRIDE;
QSize maximumSize() const Q_DECL_OVERRIDE;
virtual void setGeometry(const QRect&) Q_DECL_OVERRIDE;
virtual QLayoutItem *itemAt(int index) const = 0;
virtual QLayoutItem *takeAt(int index) = 0;
virtual int indexOf(QWidget *) const;
virtual int count() const = 0;
bool isEmpty() const Q_DECL_OVERRIDE;
QSizePolicy::ControlTypes controlTypes() const Q_DECL_OVERRIDE;
// ### Qt 6 make this function virtual
QLayoutItem *replaceWidget(QWidget *from, QWidget *to, Qt::FindChildOptions options = Qt::FindChildrenRecursively);
int totalHeightForWidth(int w) const;
QSize totalMinimumSize() const;
QSize totalMaximumSize() const;
QSize totalSizeHint() const;
QLayout *layout() Q_DECL_OVERRIDE;
void setEnabled(bool);
bool isEnabled() const;
static QSize closestAcceptableSize(const QWidget *w, const QSize &s);
protected:
void widgetEvent(QEvent *);
void childEvent(QChildEvent *e) Q_DECL_OVERRIDE;
void addChildLayout(QLayout *l);
void addChildWidget(QWidget *w);
bool adoptLayout(QLayout *layout);
QRect alignmentRect(const QRect&) const;
protected:
QLayout(QLayoutPrivate &d, QLayout*, QWidget*);
private:
Q_DISABLE_COPY(QLayout)
static void activateRecursiveHelper(QLayoutItem *item);
friend class QApplicationPrivate;
friend class QWidget;
};
bool QLayout::setAlignment(QWidget *w, Qt::Alignment alignment)
{
int i = 0;
QLayoutItem *item = itemAt(i);
while (item) {
if (item->widget() == w) {
item->setAlignment(alignment);
invalidate();
return true;
}
++i;
item = itemAt(i);
}
return false;
}
bool QLayout::setAlignment(QLayout *l, Qt::Alignment alignment)
{
int i = 0;
QLayoutItem *item = itemAt(i);
while (item) {
if (item->layout() == l) {
item->setAlignment(alignment);
invalidate();
return true;
}
++i;
item = itemAt(i);
}
return false;
}
4.1 SizeConstraint
enum SizeConstraint {
SetDefaultConstraint,
SetNoConstraint,
SetMinimumSize,
SetFixedSize,
SetMaximumSize,
SetMinAndMaxSize
};
4.2 对QLayoutItem的实现
QRect QLayout::geometry() const
{
Q_D(const QLayout);
return d->rect;
}
bool QLayout::isEmpty() const
{
int i = 0;
QLayoutItem *item = itemAt(i);
while (item) {
if (!item->isEmpty())
return false;
++i;
item = itemAt(i);
}
return true;
}
/*****************************************************************************************/
// 以下为QLayoutItem方法的重载
void QLayout::invalidate()
{
Q_D(QLayout);
d->rect = QRect();
update();
}
QLayout * QLayout::layout()
{
return this;
}
4.3 布局中添加组件
virtual void addItem(QLayoutItem *item) = 0;
void QLayout::addWidget(QWidget *w)
{
addChildWidget(w);
addItem(QLayoutPrivate::createWidgetItem(this, w));
}
4.3.1 addChildWidget
void QLayout::addChildWidget(QWidget *w)
{
QWidget *mw = parentWidget();
QWidget *pw = w->parentWidget();
bool needShow = mw && mw->isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide));
if (!pw && mw)
w->setParent(mw);
w->setAttribute(Qt::WA_LaidOut);
if (needShow)
QMetaObject::invokeMethod(w, "_q_showIfNotHidden", Qt::QueuedConnection); //show later
}
由代码可以看到addChildWidget主要就是改变QWidget w的父类。w->setParent(mw) 的分析详细见QWidget类。
4.3.2 createWidgetItem
addItem是一个虚函数,具体实现可参考其一个实现BoxLayout中(5.2 添加组件 ==> 5.2.1 addItem)的 addItem。
QWidgetItem *QLayoutPrivate::createWidgetItem(const QLayout *layout, QWidget *widget)
{
if (widgetItemFactoryMethod)
if (QWidgetItem *wi = (*widgetItemFactoryMethod)(layout, widget))
return wi;
return new QWidgetItemV2(widget);
}
从上面代码可知,布局调用addWidget函数添加QWidget会创建一个QWidgetItemV2的类,来描述QWidget的情况。
五、QBoxLayout
class Q_WIDGETS_EXPORT QBoxLayout : public QLayout
{
Q_OBJECT
Q_DECLARE_PRIVATE(QBoxLayout)
public:
enum Direction { LeftToRight, RightToLeft, TopToBottom, BottomToTop,
Down = TopToBottom, Up = BottomToTop };
explicit QBoxLayout(Direction, QWidget *parent = Q_NULLPTR);
~QBoxLayout();
Direction direction() const;
void setDirection(Direction);
void addSpacing(int size);
void addStretch(int stretch = 0);
void addSpacerItem(QSpacerItem *spacerItem);
void addWidget(QWidget *, int stretch = 0, Qt::Alignment alignment = Qt::Alignment());
void addLayout(QLayout *layout, int stretch = 0);
void addStrut(int);
void addItem(QLayoutItem *) Q_DECL_OVERRIDE;
void insertSpacing(int index, int size);
void insertStretch(int index, int stretch = 0);
void insertSpacerItem(int index, QSpacerItem *spacerItem);
void insertWidget(int index, QWidget *widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment());
void insertLayout(int index, QLayout *layout, int stretch = 0);
void insertItem(int index, QLayoutItem *);
int spacing() const;
void setSpacing(int spacing);
bool setStretchFactor(QWidget *w, int stretch);
bool setStretchFactor(QLayout *l, int stretch);
void setStretch(int index, int stretch);
int stretch(int index) const;
QSize sizeHint() const Q_DECL_OVERRIDE;
QSize minimumSize() const Q_DECL_OVERRIDE;
QSize maximumSize() const Q_DECL_OVERRIDE;
bool hasHeightForWidth() const Q_DECL_OVERRIDE;
int heightForWidth(int) const Q_DECL_OVERRIDE;
int minimumHeightForWidth(int) const Q_DECL_OVERRIDE;
Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE;
void invalidate() Q_DECL_OVERRIDE;
QLayoutItem *itemAt(int) const Q_DECL_OVERRIDE;
QLayoutItem *takeAt(int) Q_DECL_OVERRIDE;
int count() const Q_DECL_OVERRIDE;
void setGeometry(const QRect&) Q_DECL_OVERRIDE;
private:
Q_DISABLE_COPY(QBoxLayout)
};
void QLayout::setGeometry(const QRect &r)
{
Q_D(QLayout);
d->rect = r;
}
5.1 对QLayoutItem的实现
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;
}
Qt::Orientations QBoxLayout::expandingDirections() const
{
Q_D(const QBoxLayout);
if (d->dirty)
const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
return d->expanding;
}
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));
QVector<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.count();
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;
}
qGeomCalc(a, 0, n, pos, space);
bool reverse = (horz(visualDir)
? ((r.right() > oldRect.right()) != (visualDir == RightToLeft))
: r.bottom() > oldRect.bottom());
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));
}
}
}
}
/**************************************************************************************/
// 以下为重载QLayoutItem的函数
bool QBoxLayout::hasHeightForWidth() const
{
Q_D(const QBoxLayout);
if (d->dirty)
const_cast<QBoxLayout*>(this)->d_func()->setupGeom();
return d->hasHfw;
}
int QBoxLayout::heightForWidth(int w) const
{
Q_D(const QBoxLayout);
if (!hasHeightForWidth())
return -1;
int left, top, right, bottom;
d->effectiveMargins(&left, &top, &right, &bottom);
w -= left + right;
if (w != d->hfwWidth)
const_cast<QBoxLayout*>(this)->d_func()->calcHfw(w);
return d->hfwHeight + top + bottom;
}
int QBoxLayout::minimumHeightForWidth(int w) const
{
Q_D(const QBoxLayout);
(void) heightForWidth(w);
int top, bottom;
d->effectiveMargins(0, &top, 0, &bottom);
return d->hasHfw ? (d->hfwMinHeight + top + bottom) : -1;
}
void QBoxLayout::invalidate()
{
Q_D(QBoxLayout);
d->setDirty();
QLayout::invalidate();
}
QBoxLayoutPrivate::setupGeom()
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.count();
geomArray.clear();
QVector<QLayoutStruct> a(n);
QSizePolicy::ControlTypes controlTypes1;
QSizePolicy::ControlTypes controlTypes2;
int fixedSpacing = q->spacing();
int previousNonEmptyIndex = -1;
QStyle *style = 0;
if (fixedSpacing < 0) {
if (QWidget *parentWidget = q->parentWidget())
style = parentWidget->style();
}
for (int i = 0; i < n; i++) {
QBoxLayoutItem *box = list.at(i);
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,
0, 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;
}
5.2 添加组件
5.2.1 addItem
对基类QLayout 中虚函数实现
void QBoxLayout::addItem(QLayoutItem *item)
{
Q_D(QBoxLayout);
QBoxLayoutItem *it = new QBoxLayoutItem(item);
d->list.append(it);
invalidate();
}
QBoxLayoutItem
struct QBoxLayoutItem
{
QBoxLayoutItem(QLayoutItem *it, int stretch_ = 0)
: item(it), stretch(stretch_), magic(false) { }
~QBoxLayoutItem() { delete item; }
// heightForWidth
int hfw(int w) {
if (item->hasHeightForWidth()) {
return item->heightForWidth(w);
} else {
return item->sizeHint().height();
}
}
int mhfw(int w) {
if (item->hasHeightForWidth()) {
return item->heightForWidth(w);
} else {
return item->minimumSize().height();
}
}
// horizontalStretch
int hStretch() {
if (stretch == 0 && item->widget()) {
return item->widget()->sizePolicy().horizontalStretch();
} else {
return stretch;
}
}
// verticalStretch
int vStretch() {
if (stretch == 0 && item->widget()) {
return item->widget()->sizePolicy().verticalStretch();
} else {
return stretch;
}
}
QLayoutItem *item;
int stretch;
bool magic;
};
控件水平、垂直膨胀决定因素为,如果加入的时候有设置膨胀因子,则使用它,否则使用QWidget的策略中的膨胀因子。
5.2.2 addSpacing
void QBoxLayout::addSpacing(int size)
{
insertSpacing(-1, size);
}
void QBoxLayout::insertSpacing(int index, int size)
{
Q_D(QBoxLayout);
if (index < 0) // append
index = d->list.count();
QLayoutItem *b;
if (horz(d->dir))
b = QLayoutPrivate::createSpacerItem(this, size, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
else
b = QLayoutPrivate::createSpacerItem(this, 0, size, QSizePolicy::Minimum, QSizePolicy::Fixed);
QT_TRY {
QBoxLayoutItem *it = new QBoxLayoutItem(b);
it->magic = true;
d->list.insert(index, it);
} QT_CATCH(...) {
delete b;
QT_RETHROW;
}
invalidate();
}
createSpacerItem,其创建了一个固定长度的QSpacerItem
QSpacerItem *QLayoutPrivate::createSpacerItem(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy)
{
if (spacerItemFactoryMethod)
if (QSpacerItem *si = (*spacerItemFactoryMethod)(layout, w, h, hPolicy, vPolicy))
return si;
return new QSpacerItem(w, h, hPolicy, vPolicy);
}
5.2.3 addStretch
void QBoxLayout::addStretch(int stretch)
{
insertStretch(-1, stretch);
}
void QBoxLayout::insertStretch(int index, int stretch)
{
Q_D(QBoxLayout);
if (index < 0) // append
index = d->list.count();
QLayoutItem *b;
if (horz(d->dir))
b = QLayoutPrivate::createSpacerItem(this, 0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
else
b = QLayoutPrivate::createSpacerItem(this, 0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
QBoxLayoutItem *it = new QBoxLayoutItem(b, stretch);
it->magic = true;
d->list.insert(index, it);
invalidate();
}
createSpacerItem,其创建了一个带膨胀的QSpacerItem
5.2.4 addSpacerItem
void QBoxLayout::addSpacerItem(QSpacerItem *spacerItem)
{
insertSpacerItem(-1, spacerItem);
}
void QBoxLayout::insertSpacerItem(int index, QSpacerItem *spacerItem)
{
Q_D(QBoxLayout);
if (index < 0) // append
index = d->list.count();
QBoxLayoutItem *it = new QBoxLayoutItem(spacerItem);
it->magic = true;
d->list.insert(index, it);
invalidate();
}
可以参考addSpacing
5.2.5 addWidget
void QBoxLayout::addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
{
insertWidget(-1, widget, stretch, alignment);
}
void QBoxLayout::insertWidget(int index, QWidget *widget, int stretch, Qt::Alignment alignment)
{
Q_D(QBoxLayout);
if (!d->checkWidget(widget))
return;
addChildWidget(widget);
if (index < 0) // append
index = d->list.count();
QWidgetItem *b = QLayoutPrivate::createWidgetItem(this, widget);
b->setAlignment(alignment);
QBoxLayoutItem *it;
QT_TRY{
it = new QBoxLayoutItem(b, stretch);
} QT_CATCH(...) {
delete b;
QT_RETHROW;
}
QT_TRY{
d->list.insert(index, it);
} QT_CATCH(...) {
delete it;
QT_RETHROW;
}
invalidate();
}
createWidgetItem
QWidgetItem *QLayoutPrivate::createWidgetItem(const QLayout *layout, QWidget *widget)
{
if (widgetItemFactoryMethod)
if (QWidgetItem *wi = (*widgetItemFactoryMethod)(layout, widget))
return wi;
return new QWidgetItemV2(widget);
}
QWidget加入Layout时,创建了QWidgetItemV2类。
5.2.6 addLayout
void QBoxLayout::addLayout(QLayout *layout, int stretch)
{
insertLayout(-1, layout, stretch);
}
void QBoxLayout::insertLayout(int index, QLayout *layout, int stretch)
{
Q_D(QBoxLayout);
if (!d->checkLayout(layout))
return;
if (!adoptLayout(layout))
return;
if (index < 0) // append
index = d->list.count();
QBoxLayoutItem *it = new QBoxLayoutItem(layout, stretch);
d->list.insert(index, it);
invalidate();
}
adoptLayout
bool QLayout::adoptLayout(QLayout *layout)
{
const bool ok = !layout->parent();
addChildLayout(layout);
return ok;
}
void QLayout::addChildLayout(QLayout *l)
{
if (Q_UNLIKELY(l->parent())) {
qWarning("QLayout::addChildLayout: layout \"%ls\" already has a parent",
qUtf16Printable(l->objectName()));
return;
}
l->setParent(this);
if (QWidget *mw = parentWidget()) {
l->d_func()->reparentChildWidgets(mw);
}
}
5.2.7 addStrut
void QBoxLayout::addStrut(int size)
{
Q_D(QBoxLayout);
QLayoutItem *b;
if (horz(d->dir))
b = QLayoutPrivate::createSpacerItem(this, 0, size, QSizePolicy::Fixed, QSizePolicy::Minimum);
else
b = QLayoutPrivate::createSpacerItem(this, size, 0, QSizePolicy::Minimum, QSizePolicy::Fixed);
QBoxLayoutItem *it = new QBoxLayoutItem(b);
it->magic = true;
d->list.append(it);
invalidate();
}
可以参考addSpacing