流式布局示例

展示如何根据不同的窗口大小排列小部件。

流式布局 实现了一种处理不同窗口大小的布局。小部件的位置会根据应用程序窗口的宽度而改变。

Screenshot of the Flow Layout example

Flowlayout 类主要使用 QLayoutQWidgetItem,而窗口使用 QWidgetQLabel

有关更多信息,请访问 布局管理 页面。

运行示例

要从 Qt Creator 运行示例,请打开 欢迎 模式,然后从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

FlowLayout 类定义

FlowLayout 类继承自 QLayout。它是一个自定义布局类,可以水平垂直排列其子小部件。

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 &rect) override;
    QSize sizeHint() const override;
    QLayoutItem *takeAt(int index) override;

private:
    int doLayout(const QRect &rect, bool testOnly) const;
    int smartSpacing(QStyle::PixelMetric pm) const;

    QList<QLayoutItem *> itemList;
    int m_hSpace;
    int m_vSpace;
};

我们重新实现了从 QLayout 继承的函数。这些函数向布局添加项目并处理它们的方向和几何形状。

我们还声明了两个私有方法,doLayout()smartSpacing()doLayout() 对布局项目进行布局,而 smartSpacing() 函数计算它们之间的间距。

FlowLayout 类实现

我们从查看构造函数开始

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);
}

在构造函数中,我们调用 setContentsMargins() 来设置左、上、右和下边距。默认情况下,QLayout 使用当前样式提供的值(见 QStyle::PixelMetric)。

FlowLayout::~FlowLayout()
{
    QLayoutItem *item;
    while ((item = takeAt(0)))
        delete item;
}

我们重实现了 addItem(),这是一个纯虚函数。当使用 addItem() 时,布局项的所有权将传递给布局,因此删除它们的责任将由布局承担。

void FlowLayout::addItem(QLayoutItem *item)
{
    itemList.append(item);
}

addItem() 被实现为向布局添加项目。

int FlowLayout::horizontalSpacing() const
{
    if (m_hSpace >= 0) {
        return m_hSpace;
    } else {
        return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
    }
}

int FlowLayout::verticalSpacing() const
{
    if (m_vSpace >= 0) {
        return m_vSpace;
    } else {
        return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
    }
}

我们实现了 horizontalSpacing()verticalSpacing() 以获取布局内部小部件之间的间距。如果值小于或等于 0,将使用此值。如果不,则调用 smartSpacing() 来计算间距。

int FlowLayout::count() const
{
    return itemList.size();
}

QLayoutItem *FlowLayout::itemAt(int index) const
{
    return itemList.value(index);
}

QLayoutItem *FlowLayout::takeAt(int index)
{
    if (index >= 0 && index < itemList.size())
        return itemList.takeAt(index);
    return nullptr;
}

然后我们实现 count() 来返回布局中的项目数量。要遍历项目列表,我们使用 itemAt() 和 takeAt() 来从列表中移除并返回项目。如果有项目被移除,剩余项目将被重新编号。这三个函数都是来自 QLayout 的纯虚函数。

Qt::Orientations FlowLayout::expandingDirections() const
{
    return { };
}

expandingDirections() 返回布局可以比其 sizeHint() 使用更多空间的方向 Qt::Orientation

bool FlowLayout::hasHeightForWidth() const
{
    return true;
}

int FlowLayout::heightForWidth(int width) const
{
    int height = doLayout(QRect(0, 0, width, 0), true);
    return height;
}

为了适应高度依赖于宽度的小部件,我们实现了heightForWidth()。函数hasHeightForWidth()用于测试这种依赖性,而heightForWidth()将宽度传递给doLayout(),然后它会将宽度用作布局矩形(即放置小部件的边界)的参数。这个矩形不包括布局边距。

void FlowLayout::setGeometry(const QRect &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->minimumSize());

    const QMargins margins = contentsMargins();
    size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
    return size;
}

setGeometry()通常用于执行实际的布局,即计算布局项的几何形状。在这个例子中,它调用了doLayout()并传递了布局矩形。

sizeHint()返回布局的首选大小,而minimumSize()返回布局的最小大小。

int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
{
    int left, top, right, bottom;
    getContentsMargins(&left, &top, &right, &bottom);
    QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
    int x = effectiveRect.x();
    int y = effectiveRect.y();
    int lineHeight = 0;

doLayout()horizontalSpacing()verticalSpacing()不返回默认值时处理布局。它使用getContentsMargins()计算布局项可用的区域。

    for (QLayoutItem *item : std::as_const(itemList)) {
        const QWidget *wid = item->widget();
        int spaceX = horizontalSpacing();
        if (spaceX == -1)
            spaceX = wid->style()->layoutSpacing(
                QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
        int spaceY = verticalSpacing();
        if (spaceY == -1)
            spaceY = wid->style()->layoutSpacing(
                QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);

然后根据当前样式为布局中的每个小部件设置适当的间距。

        int nextX = x + item->sizeHint().width() + spaceX;
        if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
            x = effectiveRect.x();
            y = y + lineHeight + spaceY;
            nextX = x + item->sizeHint().width() + spaceX;
            lineHeight = 0;
        }

        if (!testOnly)
            item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));

        x = nextX;
        lineHeight = qMax(lineHeight, item->sizeHint().height());
    }
    return y + lineHeight - rect.y() + bottom;
}

随后,通过将当前项的宽度和行高添加到初始x和y坐标来计算布局中每个项的位置。这反过来又可以让我们知道下一个项是否将放在当前行或如果必须移动到下一行。我们还根据小部件的高度计算当前行的高度。

int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
{
    QObject *parent = this->parent();
    if (!parent) {
        return -1;
    } else if (parent->isWidgetType()) {
        QWidget *pw = static_cast<QWidget *>(parent);
        return pw->style()->pixelMetric(pm, nullptr, pw);
    } else {
        return static_cast<QLayout *>(parent)->spacing();
    }
}

smartSpacing()被设计成获取顶级布局或子布局的默认间距。当父布局是QWidget时,顶级布局的默认间距将由查询样式确定。当父布局是QLayout时,子布局的默认间距将由查询父布局的间距确定。

示例项目 @ code.qt.io

© 2024 The Qt Company Ltd. 本文档中的文档贡献是各自所有者的版权。本文档根据由自由软件基金会发布的GNU自由文档许可协议版本1.3的条款进行许可。Qt及相应商标是The Qt Company Ltd.在芬兰和其他国家的商标。所有其他商标均为各自所有者的财产。