Flow Layout Example▲
Flow Layout implements a layout that handles different window sizes. The widget placement changes depending on the width of the application window.
The Flowlayout class mainly uses QLayout and QWidgetItem, while the Window uses QWidget and QLabel.
For more information, visit the Layout Management page.
Running the Example▲
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
FlowLayout Class Definition▲
The FlowLayout class inherits QLayout. It is a custom layout class that arranges its child widgets horizontally and vertically.
class
FlowLayout : public
QLayout
{
public
:
explicit
FlowLayout(QWidget *
parent, int
margin =
-
1
, int
hSpacing =
-
1
, int
vSpacing =
-
1
);
explicit
FlowLayout(int
margin =
-
1
, int
hSpacing =
-
1
, int
vSpacing =
-
1
);
~
FlowLayout();
void
addItem(QLayoutItem *
item) override
;
int
horizontalSpacing() const
;
int
verticalSpacing() const
;
Qt::
Orientations expandingDirections() const
override
;
bool
hasHeightForWidth() const
override
;
int
heightForWidth(int
) const
override
;
int
count() const
override
;
QLayoutItem *
itemAt(int
index) const
override
;
QSize minimumSize() const
override
;
void
setGeometry(const
QRect &
amp;rect) override
;
QSize sizeHint() const
override
;
QLayoutItem *
takeAt(int
index) override
;
private
:
int
doLayout(const
QRect &
amp;rect, bool
testOnly) const
;
int
smartSpacing(QStyle::
PixelMetric pm) const
;
QList&
lt;QLayoutItem *&
gt; itemList;
int
m_hSpace;
int
m_vSpace;
}
;
We reimplement functions inherited from QLayout. These functions add items to the layout and handle their orientation and geometry.
We also declare two private methods, doLayout() and smartSpacing(). doLayout() lays out the layout items, while the smartSpacing() function calculates the spacing between them.
FlowLayout Class Implementation▲
We start off by looking at the constructor:
FlowLayout::
FlowLayout(QWidget *
parent, int
margin, int
hSpacing, int
vSpacing)
:
QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::
FlowLayout(int
margin, int
hSpacing, int
vSpacing)
:
m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
In the constructor we call setContentsMargins() to set the left, top, right and bottom margin. By default, QLayout uses values provided by the current style (see QStyle::PixelMetric).
FlowLayout::
~
FlowLayout()
{
QLayoutItem *
item;
while
((item =
takeAt(0
)))
delete
item;
}
In this example we reimplement addItem(), which is a pure virtual function. When using addItem() the ownership of the layout items is transferred to the layout, and it is therefore the layout's responsibility to delete them.
void
FlowLayout::
addItem(QLayoutItem *
item)
{
itemList.append(item);
}
addItem() is implemented to add items to the layout.
int
FlowLayout::
horizontalSpacing() const
{
if
(m_hSpace &
gt;=
0
) {
return
m_hSpace;
}
else
{
return
smartSpacing(QStyle::
PM_LayoutHorizontalSpacing);
}
}
int
FlowLayout::
verticalSpacing() const
{
if
(m_vSpace &
gt;=
0
) {
return
m_vSpace;
}
else
{
return
smartSpacing(QStyle::
PM_LayoutVerticalSpacing);
}
}
We implement horizontalSpacing() and verticalSpacing() to get hold of the spacing between the widgets inside the layout. If the value is less than or equal to 0, this value will be used. If not, smartSpacing() will be called to calculate the spacing.
int
FlowLayout::
count() const
{
return
itemList.size();
}
QLayoutItem *
FlowLayout::
itemAt(int
index) const
{
return
itemList.value(index);
}
QLayoutItem *
FlowLayout::
takeAt(int
index)
{
if
(index &
gt;=
0
&
amp;&
amp; index &
lt; itemList.size())
return
itemList.takeAt(index);
return
nullptr
;
}
We then implement count() to return the number of items in the layout. To navigate the list of items we use itemAt() and takeAt() to remove and return items from the list. If an item is removed, the remaining items will be renumbered. All three functions are pure virtual functions from QLayout.
Qt::
Orientations FlowLayout::
expandingDirections() const
{
return
{
}
;
}
expandingDirections() returns the Qt::Orientations in which the layout can make use of more space than its sizeHint().
bool
FlowLayout::
hasHeightForWidth() const
{
return
true
;
}
int
FlowLayout::
heightForWidth(int
width) const
{
int
height =
doLayout(QRect(0
, 0
, width, 0
), true
);
return
height;
}
To adjust to widgets of which height is dependent on width, we implement heightForWidth(). The function hasHeightForWidth() is used to test for this dependency, and heightForWidth() passes the width on to doLayout() which in turn uses the width as an argument for the layout rect, i.e., the bounds in which the items are laid out. This rect does not include the layout margin().
void
FlowLayout::
setGeometry(const
QRect &
amp;rect)
{
QLayout::
setGeometry(rect);
doLayout(rect, false
);
}
QSize FlowLayout::
sizeHint() const
{
return
minimumSize();
}
QSize FlowLayout::
minimumSize() const
{
QSize size;
for
(const
QLayoutItem *
item : std::
as_const(itemList))
size =
size.expandedTo(item-&
gt;minimumSize());
const
QMargins margins =
contentsMargins();
size +=
QSize(margins.left() +
margins.right(), margins.top() +
margins.bottom());
return
size;
}
setGeometry() is normally used to do the actual layout, i.e., calculate the geometry of the layout's items. In this example, it calls doLayout() and passes the layout rect.
sizeHint() returns the preferred size of the layout and minimumSize() returns the minimum size of the layout.
int
FlowLayout::
doLayout(const
QRect &
amp;rect, bool
testOnly) const
{
int
left, top, right, bottom;
getContentsMargins(&
amp;left, &
amp;top, &
amp;right, &
amp;bottom);
QRect effectiveRect =
rect.adjusted(+
left, +
top, -
right, -
bottom);
int
x =
effectiveRect.x();
int
y =
effectiveRect.y();
int
lineHeight =
0
;
doLayout() handles the layout if horizontalSpacing() or verticalSpacing() don't return the default value. It uses getContentsMargins() to calculate the area available to the layout items.
for
(QLayoutItem *
item : std::
as_const(itemList)) {
const
QWidget *
wid =
item-&
gt;widget();
int
spaceX =
horizontalSpacing();
if
(spaceX ==
-
1
)
spaceX =
wid-&
gt;style()-&
gt;layoutSpacing(
QSizePolicy::
PushButton, QSizePolicy::
PushButton, Qt::
Horizontal);
int
spaceY =
verticalSpacing();
if
(spaceY ==
-
1
)
spaceY =
wid-&
gt;style()-&
gt;layoutSpacing(
QSizePolicy::
PushButton, QSizePolicy::
PushButton, Qt::
Vertical);
It then sets the proper amount of spacing for each widget in the layout, based on the current style.
int
nextX =
x +
item-&
gt;sizeHint().width() +
spaceX;
if
(nextX -
spaceX &
gt; effectiveRect.right() &
amp;&
amp; lineHeight &
gt; 0
) {
x =
effectiveRect.x();
y =
y +
lineHeight +
spaceY;
nextX =
x +
item-&
gt;sizeHint().width() +
spaceX;
lineHeight =
0
;
}
if
(!
testOnly)
item-&
gt;setGeometry(QRect(QPoint(x, y), item-&
gt;sizeHint()));
x =
nextX;
lineHeight =
qMax(lineHeight, item-&
gt;sizeHint().height());
}
return
y +
lineHeight -
rect.y() +
bottom;
}
The position of each item in the layout is then calculated by adding the items width and the line height to the initial x and y coordinates. This in turn lets us find out whether the next item will fit on the current line or if it must be moved down to the next. We also find the height of the current line based on the widgets height.
int
FlowLayout::
smartSpacing(QStyle::
PixelMetric pm) const
{
QObject *
parent =
this
-&
gt;parent();
if
(!
parent) {
return
-
1
;
}
else
if
(parent-&
gt;isWidgetType()) {
QWidget *
pw =
static_cast
&
lt;QWidget *&
gt;(parent);
return
pw-&
gt;style()-&
gt;pixelMetric(pm, nullptr
, pw);
}
else
{
return
static_cast
&
lt;QLayout *&
gt;(parent)-&
gt;spacing();
}
}
smartSpacing() is designed to get the default spacing for either the top-level layouts or the sublayouts. The default spacing for top-level layouts, when the parent is a QWidget, will be determined by querying the style. The default spacing for sublayouts, when the parent is a QLayout, will be determined by querying the spacing of the parent layout.