一、概述
Qt的基本布局有水平布局(QHBoxLayout)、垂直布局(QVBoxLayout)、网格布局(QGridLayout)、表单布局(QFormLayout)等。QGridWidget作为一个类似网格布局的窗口容器,将加入到容器中的窗口按照网格布局方式排列,根据窗口的数量自动计算行数与列数。与网格布局的不同之处在于,网格布局中的某个单元格中的窗口暂时隐藏之后,空余出来的空间不会被同一行的其他窗口延伸利用,QGridWidget实现了某个网格暂时隐藏之后,同一行的其他窗口会重新分配横向上占用的空间,自动延伸,从而实现尽可能占用更多空间的功能。
QGridWidget与QBoxWidget的功能基本类似,主要区别有两点:
- QGridWidget的布局容器由QSplitter来实现,QBoxWidget的布局容器由水平布局(QHBoxLayout)和垂直布局(QVBoxLayout)组合实现。
- QGridWidget能够拖动调整单元格的占比,并能够自动记忆与恢复,也可以恢复统一的默认占比。
二、运行效果图
1. 所有窗口均显示的情形
2. 某些窗口暂时隐藏时的情形
3. 某个窗口最大化显示情形
4. 调整窗口在布局中的占比,下次启动时能够自动恢复退出时的占比,也可以按下“RestorePos”按钮恢复均匀分布的统一占比。
三、实现基本原理
根据容器中窗口的个数,先计算一行应该有多少列(nCol)“int nCol = 0.9999 + sqrt(m_vWidget.size());”,再计算应该有多少行nRow“int nRow = (m_vWidget.size() + nCol - 1) / nCol;”,然后创建nRow个水平分割窗口(QSplitter)容器,每个nCol水平分割窗口容器中加入最多nCol个窗口,最后将这些水平分割窗口容器加入到垂直分割窗口(QSplitter)容器中。
四、QGridWidget.h代码
#ifndef QGridWidget_H
#define QGridWidget_H
#include <QWidget>
#include <QSplitter>
#include <QVBoxLayout>
#include <vector>
class QPushButton;
class QCellWidget : public QWidget
{
Q_OBJECT
public:
explicit QCellWidget(QWidget *widget, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
enum ShowType
{
ST_HIDE,
ST_MAXIMIZED
};
protected:
virtual void resizeEvent(QResizeEvent *event);
virtual void paintEvent(QPaintEvent *event) override;
signals:
void showCell(int);
private slots :
void ShowCell();
private:
QPushButton *m_pBtnHide;
QPushButton *m_pBtnMaximize;
QVBoxLayout m_LayoutV;
};
class QGridWidget : public QWidget
{
Q_OBJECT
public:
explicit QGridWidget(QWidget *parent = nullptr, int nMaxCol = 0);
~QGridWidget();
void AddWidget(QWidget *pWidget);
void DoLayout();
int FindWidget(QWidget *pWidget);
inline const QSplitter& GetLayout() const { return m_SplitterV; };
inline const std::vector<QWidget*>& GetWidgets() const { return m_vWidget; };
inline void restoreState0() { restoreState("QGridWidget0"); };
inline void ShowAll() { MaximizeWidget2(); };
inline void MaximizeWidget(QWidget *pWidget = NULL) { MaximizeWidget2(FindWidget(pWidget)); };
inline bool HideWidget(QWidget *pWidget = NULL) { return HideWidget2(FindWidget(pWidget)); };
inline void ShowWidget(QWidget *pWidget = NULL) { ShowWidget2(FindWidget(pWidget)); };
void MaximizeWidget2(int nIndex = -1);
bool HideWidget2(int nIndex = -1);
void ShowWidget2(int nIndex = -1);
protected:
void resizeEvent(QResizeEvent *event);
private:
void saveState(const QString &strName = "QGridWidget");
void restoreState(const QString &strName = "QGridWidget");
signals:
void hideWidget(int);
void maximizeWidget(int);
public slots :
void ShowCell(int);
private:
int m_nMaxCol;
QSplitter m_SplitterV;
std::vector<QWidget*> m_vWidget;
};
#endif // QGridWidget_H
五、QGridWidget.cpp代码
#include "qgridwidget.h"
#include <math.h>
#include <QPainter>
#include <QPushButton>
#include <QMouseEvent>
#include <QSettings>
QCellWidget::QCellWidget(QWidget *widget, QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f), m_LayoutV(this)
{
widget->setParent(parentWidget());
m_LayoutV.setMargin(0);
m_LayoutV.addWidget(widget);
QPixmap pix;
pix.load("max.png");
QIcon icon(pix);
m_pBtnMaximize = new QPushButton(icon, "", this);
m_pBtnMaximize->setFlat(true);
m_pBtnMaximize->setStyleSheet("margin:0px");
m_pBtnMaximize->setFixedSize(13, 13);
m_pBtnHide = new QPushButton(QStringLiteral("×"), this);
m_pBtnHide->setFlat(true);
m_pBtnHide->setStyleSheet("margin:0px;color: rgb(160, 160, 160);");
m_pBtnHide->setFixedSize(12, 12);
connect(m_pBtnMaximize, SIGNAL(clicked()), this, SLOT(ShowCell()));
connect(m_pBtnHide, SIGNAL(clicked()), this, SLOT(ShowCell()));
}
void QCellWidget::resizeEvent(QResizeEvent *event)
{
QRect rc = rect();
m_pBtnHide->move(rc.right() - 12, 1);
m_pBtnMaximize->move(rc.right() - 28, 0);
}
void QCellWidget::paintEvent(QPaintEvent *event)
{
QRect rc = rect();
QPainter p(this);
p.setPen(QColor(224, 224, 224));
p.drawRect(rc.left(), rc.top(), rc.width() - 1, rc.height() - 1);
}
void QCellWidget::ShowCell()
{
if (sender() == m_pBtnMaximize)
{
emit showCell(ST_MAXIMIZED);
}
else
{
emit showCell(ST_HIDE);
}
}
QGridWidget::QGridWidget(QWidget *parent, int nMaxCol) : QWidget(parent), m_SplitterV(Qt::Vertical, this)//此处必须加this
{
m_nMaxCol = nMaxCol;
m_SplitterV.setHandleWidth(0);
}
QGridWidget::~QGridWidget()
{
saveState();
}
void QGridWidget::AddWidget(QWidget *pWidget)
{
if (pWidget != NULL)
{
QCellWidget *pCW = new QCellWidget(pWidget, this);
connect(pCW, SIGNAL(showCell(int)), this, SLOT(ShowCell(int)));
m_vWidget.push_back(pCW);
}
}
void QGridWidget::DoLayout()
{
int nCol = 0.9999 + sqrt(m_vWidget.size());
if (m_nMaxCol > 0 && nCol > m_nMaxCol)
{
nCol = m_nMaxCol;
}
int nRow = (m_vWidget.size() + nCol - 1) / nCol;
int i;
for (i = m_vWidget.size() - 1; i >= 0; i--)
{
m_vWidget[i]->setParent(this);
}
while (m_SplitterV.count())
{
delete m_SplitterV.widget(0);
}
for (i = 0; i < nRow; i++)
{
QSplitter *pSplitter = new QSplitter(&m_SplitterV);
for (int j = 0; j < nCol; j++)
{
int nIndex = i*nCol + j;
if (nIndex < m_vWidget.size())
{
pSplitter->addWidget(m_vWidget[nIndex]);
}
}
pSplitter->setHandleWidth(0);
}
saveState("QGridWidget0");
restoreState();
}
int QGridWidget::FindWidget(QWidget *pWidget)
{
if (pWidget != NULL)
{
for (int i = m_vWidget.size() - 1; i >= 0; i--)
{
if (m_vWidget[i] == pWidget)
{
return i;
}
}
}
return -1;
}
void QGridWidget::MaximizeWidget2(int nIndex /*= -1*/)
{
int i;
if (nIndex < 0 || nIndex >= m_vWidget.size())
{
for (i = m_vWidget.size() - 1; i >= 0; i--)
{
m_vWidget[i]->show();
}
}
else
{
for (i = m_vWidget.size() - 1; i >= 0; i--)
{
m_vWidget[i]->setVisible(i == nIndex);
}
}
m_SplitterV.update();
}
bool QGridWidget::HideWidget2(int nIndex /*= -1*/)
{
int nShow = 0;
for (int i = m_vWidget.size() - 1; i >= 0; i--)
{
if (m_vWidget[i]->isVisible())
{
nShow++;
}
}
if (nShow <= 1)
{
return false;
}
if (nIndex >= 0 && nIndex < m_vWidget.size())
{
m_vWidget[nIndex]->hide();
m_SplitterV.update();
}
return true;
}
void QGridWidget::ShowWidget2(int nIndex /*= -1*/)
{
if (nIndex >= 0 && nIndex < m_vWidget.size())
{
m_vWidget[nIndex]->show();
}
}
void QGridWidget::resizeEvent(QResizeEvent *event)
{
m_SplitterV.resize(size());
}
void QGridWidget::saveState(const QString &strName /*= "QGridWidget"*/)
{
QSettings settings("TIAF", strName);
settings.setValue("windowState", m_SplitterV.saveState());
for (int i = 0; i < m_SplitterV.count(); i++)
{
char szBuf[32];
sprintf(szBuf, "windowState%02d", i);
settings.setValue(szBuf, ((QSplitter*)m_SplitterV.widget(i))->saveState());
}
}
void QGridWidget::restoreState(const QString &strName /*= "QGridWidget"*/)
{
QSettings settings("TIAF", strName);
m_SplitterV.restoreState(settings.value("windowState").toByteArray());
for (int i = 0; i < m_SplitterV.count(); i++)
{
char szBuf[32];
sprintf(szBuf, "windowState%02d", i);
((QSplitter*)m_SplitterV.widget(i))->restoreState(settings.value(szBuf).toByteArray());
}
}
void QGridWidget::ShowCell(int nType)
{
QWidget *pWidget = (QWidget*)sender();
int nIndex = FindWidget(pWidget);
if (nType == QCellWidget::ST_MAXIMIZED)
{
MaximizeWidget(pWidget);
emit maximizeWidget(nIndex);
}
else if (nType == QCellWidget::ST_HIDE)
{
if (HideWidget(pWidget))
{
emit hideWidget(nIndex);
}
}
}