Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

FlowLayout doesn't take care of actual content size



  • Here my "fork" of FlowLayout, which can place the items also in vertical order:

    flowlayout.h

    #ifndef FLOWLAYOUT_H
    #define FLOWLAYOUT_H
    
    #include <QLayout>
    #include <QRect>
    #include <QStyle>
    
    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() override;
    
        void addItem(QLayoutItem *item) override;
        int horizontalSpacing() const;
        int verticalSpacing() const;
        Qt::Orientations expandingDirections() const override;
        void setOrientation(Qt::Orientations orientation) { _orientation = orientation; }
        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;
        Qt::Orientations _orientation;
        int _hSpace;
        int _vSpace;
    };
    
    #endif // FLOWLAYOUT_H
    

    flowlayout.cpp

    #include <QtWidgets>
    #include "flowlayout.h"
    
    FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : QLayout(parent), _hSpace(hSpacing), _vSpace(vSpacing)
    {
        setContentsMargins(margin, margin, margin, margin);
        _orientation = Qt::Horizontal;
    }
    
    FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : _hSpace(hSpacing), _vSpace(vSpacing)
    {
        setContentsMargins(margin, margin, margin, margin);
        _orientation = Qt::Horizontal;
    }
    
    FlowLayout::~FlowLayout()
    {
        QLayoutItem *item;
        while ((item = takeAt(0))) delete item;
    }
    
    void FlowLayout::addItem(QLayoutItem *item)
    {
        _itemList.append(item);
    }
    
    int FlowLayout::horizontalSpacing() const
    {
        if (_hSpace >= 0) return _hSpace;
        else return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
    }
    
    int FlowLayout::verticalSpacing() const
    {
        if (_vSpace >= 0) return _vSpace;
        else return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
    }
    
    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);
        else return nullptr;
    }
    
    Qt::Orientations FlowLayout::expandingDirections() const
    {
        return nullptr;
    }
    
    bool FlowLayout::hasHeightForWidth() const
    {
        return false;
    }
    
    int FlowLayout::heightForWidth(int width) const
    {
        int height = doLayout(QRect(0, 0, width, 0), true);
        return height;
    }
    
    void FlowLayout::setGeometry(const QRect &rect)
    {
        QLayout::setGeometry(rect);
        doLayout(rect, false);
    }
    
    QSize FlowLayout::sizeHint() const
    {
        return minimumSize();
    }
    
    QSize FlowLayout::minimumSize() const
    {
        QSize size;
        QLayoutItem *item;
        foreach (item, _itemList) size = size.expandedTo(item->minimumSize());
        size += QSize(2 * margin(), 2 * margin());
        return size;
    }
    
    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;
        int colWidth = 0;
    
        QLayoutItem *item;
        foreach (item, _itemList)
        {
            QWidget *widget = item->widget();
            int spaceX = horizontalSpacing();
            if (spaceX == -1) spaceX = widget->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
            int spaceY = verticalSpacing();
            if (spaceY == -1) spaceY = widget->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
    
            switch (_orientation) {
            case Qt::Horizontal: {
                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());
                break; }
    
            case Qt::Vertical: {
                int nextY = y + item->sizeHint().height() + spaceY;
                if (nextY - spaceY > effectiveRect.bottom() && colWidth > 0)
                {
                    x = x + colWidth + spaceX;
                    y = effectiveRect.y();
                    nextY = y + item->sizeHint().height() + spaceY;
                    colWidth = 0;
                }
                if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
                y = nextY;
                colWidth = qMax(colWidth, item->sizeHint().width());
                break; }
            }
    
        }
    
        if (_orientation == Qt::Horizontal) return y + lineHeight - rect.y() + bottom;
        else return x + colWidth - rect.x() + right;
    }
    
    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();
        }
    }
    

    I'm testing it mainly with vertical orientation. The problem is the horizontal scroll bar appears only when the width is less than a specific value (about 480 px) regardless the actual contents.

    I think it's related to the sizeHint() function. I don't understand how it can calculate the needed space in both direction without doing a layout(). Well, in fact it doesn't work!

    What should I change to make the horizontal scrollbar behavior reliable like the vertical one?


Log in to reply