一、概述
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();
}