1. 程式人生 > >QFlowLayout——一個Qt Example中實用的佈局

QFlowLayout——一個Qt Example中實用的佈局

一、概述

emmm,其實這篇部落格跟外面的很多一樣,只是簡單講一下Qt Example中flowlayout這個例子。因為在實際的專案中如果想在介面上顯示很多個同類型資料總覽情況的話,最直觀的就是將相同例項的某幾個重要資料提取出來,在介面中顯示,當用戶需要了解詳細資料時只需要點選那一個點就行了,比如:

使用者可以通過拖動介面的邊框,讓整個程式顯示的每行每列的裝置數量動態的進行變化。

 

二、實現

接下來將佈局檔案的程式碼複製出來(粘程式碼就是這麼爽。。。):

#ifndef QFLOWLAYOUT_H
#define QFLOWLAYOUT_H

#include <QLayout>
#include <QRect>
#include <QStyle>


/**
 * @brief   To make your own layout manager, implement the functions addItem(),
 *        sizeHint(), setGeometry(), itemAt() and takeAt().
 *
 *          You should also implement minimumSize() to ensure your layout isn't
 *        resized to zero size if there is too little space. To support children
 *        whose heights depend on their widths, implement hasHeightForWidth() and heightForWidth().
 *
 */
class QFlowLayout : public QLayout
{
public:
    explicit QFlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
    explicit QFlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
    ~QFlowLayout() override;

    Qt::Orientations expandingDirections(void) const override;
    bool hasHeightForWidth(void) const override;
    int heightForWidth(int) const override;
    int count(void) const override;
    QSize minimumSize(void) const override;
    QSize sizeHint(void) const override;
    void addItem(QLayoutItem *item) override;
    QLayoutItem *itemAt(int index) const override;
    QLayoutItem *takeAt(int index) override;
    void setGeometry(const QRect &rect) override;

    int horizontalSpacing(void) const;
    int verticalSpacing(void) const;

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

private:
    QList<QLayoutItem *> m_itemList;
    int m_hSpace;
    int m_vSpace;
};

#endif // QFLOWLAYOUT_H

自定義的QFlowLayout繼承於QLayout,需要我們實現addItem(), sizeHint(), setGeometry(), itemAt() 和takeAt()。除了這幾個函式以外還為防止控制元件大小太小而實現minimumSize(),為了flowlayout的高寬度自動調控需要實現hasHeightForWidth()和 heightForWidth()。

然後裡面的幾個類成員變數,一個是用於存放佈局中item的容器,另外是佈局中各個控制元件之間的垂直和水平的距離。

至於實現檔案我裡面寫了少許的英文註釋,大家看懂看不懂都湊合著看吧,畢竟英語沒過六級。。。

#include <QtWidgets>

#include "qflowlayout.h"

/**
 * @brief Set margins in constructor.
 *
 * @param parent  : pointer of parent widget.
 * @param margin  : margin of this layout.
 * @param hSpacing: horizontal spacing of each controls in this layout.
 * @param vSpacing: vertical spacing of each controls in this layout.
 *
 */
QFlowLayout::QFlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) :
    QLayout(parent),
    m_hSpace(hSpacing),
    m_vSpace(vSpacing)
{
    this->setContentsMargins(margin, margin, margin, margin);
}

QFlowLayout::QFlowLayout(int margin, int hSpacing, int vSpacing) :
    m_hSpace(hSpacing),
    m_vSpace(vSpacing)
{
    this->setContentsMargins(margin, margin, margin, margin);
}

/**
 * @brief Destroy items that add by addWidget() in destructor.
 *
 */
QFlowLayout::~QFlowLayout(void)
{
    QLayoutItem *item;
    while ((item = this->takeAt(0))) {
        delete item;
    }
}

/**
 * @brief Return horizontal spacing of each controls in this layout.
 *        Call smartSpacing() to set spacing while spacing width not set.
 *
 */
int QFlowLayout::horizontalSpacing(void) const
{
    return m_hSpace >= 0 ? m_hSpace : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}

/**
 * @brief Return vertical spacing of each controls in this layout.
 *
 */
int QFlowLayout::verticalSpacing(void) const
{
    return m_vSpace >= 0 ? m_vSpace : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}

/**
 * @brief Number of layout item in this layout.
 *
 */
int QFlowLayout::count(void) const
{
    return m_itemList.size();
}

/**
 * @brief Add layout item to this layout.
 *
 * @param item: Object of item to add.
 *
 */
void QFlowLayout::addItem(QLayoutItem *item)
{
    m_itemList.append(item);
}

/**
 * @brief Return layout item of index in this layout.
 *
 * @param index: index of item in this layout.
 *
 */
QLayoutItem *QFlowLayout::itemAt(int index) const
{
    return m_itemList.value(index);
}

/**
 * @brief Take layout item in this layout, if index not exist, return null.
 *
 * @param index: index of item to take.
 *
 */
QLayoutItem *QFlowLayout::takeAt(int index)
{
    return (index >= 0) && (index < m_itemList.size()) ? m_itemList.takeAt(index) :  Q_NULLPTR;
}

/**
 * @brief Return the Qt::Orientations in which the layout
 *          can make use of more space than its sizeHint().
 *
 */
Qt::Orientations QFlowLayout::expandingDirections(void) const
{
    return Q_NULLPTR;
}

/**
 * @brief Layout's height depends on width.The default implementation returns false.
 *
 */
bool QFlowLayout::hasHeightForWidth(void) const
{
    return true;
}

/**
 * @brief To adjust to widgets of which height is dependent on width.The default implementation
 *        returns -1, indicating that the preferred height is independent of the width of the item.
 *
 * @param width: width to set.
 *
 */
int QFlowLayout::heightForWidth(int width) const
{
    return doLayout(QRect(0, 0, width, 0), true);
}

/**
 * @brief Set this layout's geometry in rect.
 *
 * @param rect: rect to set.
 *
 */
void QFlowLayout::setGeometry(const QRect &rect)
{
    /* do the actual layout */
    QLayout::setGeometry(rect);
    /* do item's layout in this layout */
    doLayout(rect, false);
}

/**
 * @brief Return the preferred size of the layout.
 *
 */
QSize QFlowLayout::sizeHint(void) const
{
    return this->minimumSize();
}

/**
 * @brief Return the minimum size of the layout.
 *
 */
QSize QFlowLayout::minimumSize(void) const
{
    QSize size(0, 0);

    for (auto item : m_itemList) {
        size = size.expandedTo(item->minimumSize());
    }

    size += QSize(2 * margin(), 2 * margin());
    return size;
}

/**
 * @brief Core function realizes the flow layout.
 *
 *
 *
 * @param rect    :
 * @param testOnly:
 *
 */
int QFlowLayout::doLayout(const QRect &rect, bool testOnly) const
{
    int left, top, right, bottom;

    /* get the area available to the layout items */
    this->getContentsMargins(&left, &top, &right, &bottom);

    QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
    int x = effectiveRect.x();
    int y = effectiveRect.y();
    int lineHeight = 0;

    for (auto item : m_itemList) {
        /**
         * Set the proper amount of spacing for each widget
         * in the layout, based on the current style.
         */
        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);
        }

        /**
         * 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 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;
        }

        /* set item's geometry while not in test */
        if (!testOnly) {
            item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
        }

        x = nextX;
        lineHeight = qMax(lineHeight, item->sizeHint().height());
    }

    return y + lineHeight - rect.y() + bottom;
}

/**
 * @brief 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.
 *
 * @param pm: Type of style.
 *
 */
int QFlowLayout::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, Q_NULLPTR, pw);
    } else {
        return static_cast<QLayout *>(parent)->spacing();
    }
}

當然重點的地方還是要講一下,直接翻到最後的同學就比較聰明啦。這個類中除了應該實現的那些用於管理item的方法,最重要的就是介面長寬自動調節,通過doLayout()函式進行的介面自動佈局:

1.通過getContentsMargins()獲取到整個介面可用於放置控制元件的區域;

2.通過horizontalSpacing()和verticalSpacing()獲取到當前控制元件的水平和垂直的間距,如果例項化時未設定這兩個引數的話通過smartSpacing()返回當前控制元件或者layout父控制元件的間隔距離。

3.通過nextX - spaceX > effectiveRect.right()是否控制元件超出了右邊界,lineHeight為0是否為row的第一個控制元件,判斷控制元件的擺放位置,如果當前row無法擺放的話,將控制放置於下一行x = effectiveRect.x();控制元件將放置的x座標,y = y + lineHeight + spaceY;控制元件將放置的y座標。

4.通過setGeometry()設定控制元件的位置。

因為介面的高寬是有關聯的所以在實現的hasHeightForWidth()中直接返回true,而heightForWidth()提前計算出接下來y的擺放點,即doLayout的返回值,而真正介面長寬需要變化的時候需要在setGeometry()中實現。

還有就是在實現的widget中最好搭配QScrollArea,否則這個高度會突破天際。。。。。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QScrollArea scrollArea;

    scrollArea.resize(800, 600);

    QWidget w;
    w.setStyleSheet("background: rgb(232, 241, 252);");
    QFlowLayout *layout = new QFlowLayout(&w);

    for (int i = 0; i < 100; i++) {
        layout->addWidget(new MyWidget(i + 1, &w));
    }

    w.setLayout(layout);

    scrollArea.setWidget(&w);
    scrollArea.setWidgetResizable(true);

    scrollArea.show();

    return app.exec();
}