1、概述
表格控件(QTableView)是一种使用频率较高的控件,通常情况下表格的表头只有一行。在某些业务场景下我们需要使用到多级表头,并且希望表头也能支持类似于合并单元格的操作,这时默认的QTableView已经不能满足需求。通过对QTableView源码分析,QTableView默认的表头类是QHeaderView,它继承自QAbstractItemView,与QTableView相同。也就是说理论上我们可以把QHeaderView当做一个小号的QTableView来看待。
2、实现思路
对QHeaderView设置一个QStandardItemModel,使QHeaderView能够记录多行表头信息,在对单元格设置合并信息,即合并起始索引和终止索引。在paintSection时根据合并信息重新绘制表头。
3、多表头代码
#pragma once
#include <QHeaderView>
#include <QMetaType>
#include <QPainter>
struct SpanRange
{
int startRow = -1;
int startCol = -1;
int endRow = -1;
int endCol = -1;
bool isValid()
{
if (startRow < 0 || startCol < 0 || endRow < 0 || endCol < 0)
{
return false;
}
return true;
}
};
Q_DECLARE_METATYPE(SpanRange);
class MutilHeader : public QHeaderView
{
Q_OBJECT
public:
MutilHeader(Qt::Orientation orientation, QWidget *parent = nullptr);
~MutilHeader();
void setLabels(const QList<QStringList> &headers);
void setRowHeight(int hight);
void setSpanRange(const SpanRange &range);
protected slots:
void onSectionResized(int logicalIndex, int oldSize, int newSize);
protected:
virtual void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const;
virtual QSize sectionSizeFromContents(int logicalIndex) const;
protected:
int _rowHight = 28;
};
#include "MutilHeader.h"
#include <QStandardItemModel>
MutilHeader::MutilHeader(Qt::Orientation orientation, QWidget *parent)
: QHeaderView(orientation, parent)
{
QStandardItemModel* pDefModel = new QStandardItemModel(this);
setModel(pDefModel);
connect(this, SIGNAL(sectionResized(int, int, int)), this, SLOT(onSectionResized(int, int, int)));
}
MutilHeader::~MutilHeader()
{
}
void MutilHeader::setLabels(const QList<QStringList>& headers)
{
QAbstractItemModel* pModel = model();
if (pModel == nullptr)
{
return;
}
int nRowCount = headers.size();
pModel->insertRows(0, nRowCount);
if (nRowCount > 0)
{
int nColCount = headers[0].size();
pModel->insertColumns(0, nColCount);
}
// 设置列头文字
for (int i = 0; i < nRowCount; ++i)
{
int nColCount = headers[i].size();
for (int j = 0; j < nColCount; ++j)
{
QModelIndex index = pModel->index(i, j);
pModel->setData(index, QVariant(headers[i][j]), Qt::DisplayRole);
}
}
}
void MutilHeader::setRowHeight(int hight)
{
_rowHight = hight;
}
void MutilHeader::setSpanRange(const SpanRange& range)
{
QAbstractItemModel* pModel = model();
if (pModel == nullptr)
{
return;
}
// 记录列头合并信息
QModelIndex index = pModel->index(range.startRow, range.startCol);
pModel->setData(index, QVariant::fromValue<SpanRange>(range), Qt::UserRole + 20);
for (int i = range.startRow; i <= range.endRow; ++i)
{
for (int j = range.startCol; j <= range.endCol; ++j)
{
QModelIndex index = pModel->index(i, j);
pModel->setData(index, QVariant::fromValue<SpanRange>(range), Qt::UserRole + 20);
}
}
}
void MutilHeader::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const
{
if (!rect.isValid())
return;
QStyleOptionHeader opt;
QPointF oldBO = painter->brushOrigin();
initStyleOption(&opt);
QBrush oBrushButton = opt.palette.brush(QPalette::Button);
QBrush oBrushWindow = opt.palette.brush(QPalette::Window);
initStyleOptionForIndex(&opt, logicalIndex);
// We set rect here. If it needs to be changed it can be changed by overriding this function
opt.rect = rect;
QBrush nBrushButton = opt.palette.brush(QPalette::Button);
QBrush nBrushWindow = opt.palette.brush(QPalette::Window);
// If relevant brushes are not the same as from the regular widgets we set the brush origin
if (oBrushButton != nBrushButton || oBrushWindow != nBrushWindow) {
painter->setBrushOrigin(opt.rect.topLeft());
}
QAbstractItemModel* pModel = model();
if (pModel != nullptr)
{
int top = opt.rect.top();
for (int i = 0; i < pModel->rowCount(); ++i)
{
QModelIndex index = pModel->index(i, logicalIndex);
SpanRange range = pModel->data(index, Qt::UserRole + 20).value<SpanRange>();
if (range.isValid())
{
int leftOffset = 0;
int width = 0;
for (int j = range.startCol; j <= range.endCol; ++j)
{
int curColWidth = sectionSize(j);
if (j < logicalIndex)
{
leftOffset -= curColWidth;
}
width += curColWidth;
}
int topOffset = 0;
if (i != range.startRow)
{
topOffset = (range.startRow - i) * _rowHight;
}
if (logicalIndex == range.startRow && i == range.startRow)
{
opt.text = pModel->data(index, Qt::DisplayRole).toString();
}
else
{
QModelIndex first = pModel->index(range.startRow, range.startCol);
opt.text = pModel->data(first, Qt::DisplayRole).toString();
}
opt.rect = rect;
int heightCount = range.endRow - range.startRow + 1;
opt.rect.setTop(top + _rowHight * i + topOffset);
opt.rect.setLeft(opt.rect.left() + leftOffset);
opt.rect.setBottom(opt.rect.top() + _rowHight * heightCount - 1);
opt.rect.setWidth(width);
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
}
else
{
opt.rect = rect;
opt.text = pModel->data(index, Qt::DisplayRole).toString();
opt.rect.setTop(top + _rowHight * i);
opt.rect.setBottom(top + _rowHight * (i + 1) - 1);
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
}
}
}
else
{
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
}
// draw the section.
painter->setBrushOrigin(oldBO);
}
QSize MutilHeader::sectionSizeFromContents(int logicalIndex) const
{
QAbstractItemModel* pModel = model();
if (pModel != nullptr)
{
int count = pModel->rowCount();
QSize size = QHeaderView::sectionSizeFromContents(logicalIndex);
return QSize(size.width(), _rowHight * count);
}
return QHeaderView::sectionSizeFromContents(logicalIndex);
}
void MutilHeader::onSectionResized(int logicalIndex, int oldSize, int newSize)
{
viewport()->update();
}
4、使用方式(注意查看如何使用,有细节)
void MainWindow::initTable()
{
QStandardItemModel *pModel = new QStandardItemModel(ui->tableView);
ui->tableView->setModel(pModel);
MutilHeader *pHeader = new MutilHeader(Qt::Orientation::Horizontal, ui->tableView);
ui->tableView->setHorizontalHeader(pHeader);
QList<QStringList> headers;
/*********************注意两个QStringList个数必须相同************************/
// 第一行
QStringList headerList;
headerList << "第一列" << "第二列" << "" << "" << "第五列" << "";
// 第二行
QStringList headerList2;
headerList2 << "" << "第二列" << "第三列" << "第四列" << "第五列" << "第六列";
headers << headerList << headerList2;
pHeader->setLabels(headers);
/************************************************************************/
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
ui->tableView->horizontalHeader()->setDefaultSectionSize(56);
ui->tableView->verticalHeader()->setHidden(true);
// 合并列头
pHeader->setSpanRange(SpanRange{0, 0, 1, 0});
pHeader->setSpanRange(SpanRange{0, 1, 0, 3});
pHeader->setSpanRange(SpanRange{0, 4, 0, 5});
// 插入6列,
ui->tableView->setColumnWidth(0, 100);
ui->tableView->setColumnWidth(1, 100);
ui->tableView->setColumnWidth(2, 100);
ui->tableView->setColumnWidth(3, 100);
ui->tableView->setColumnWidth(4, 100);
ui->tableView->setColumnWidth(5, 100);
pModel->insertColumns(0, 6);
pModel->insertRows(0, 5);
}
5、实例中QTableView使用的QSS
QTableView
{
background-color:#FFFFFF;
padding:4px 4px;
border:1px solid #e8e8e8;
outline:none;
}
QHeaderView::section
{
background:#FBFBFB;
border:1px solid #e3e3e3;
}
QTableView::item
{
background-color:#FFFFFF;
padding:4px 4px;
border:1px solid #e8e8e8;
}