【Qt6】支持多级表头的QTableView

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;
}
  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QTableViewQt框架中用于显示和编辑表格数据的控件,它默认只支持单级表头。如果需要实现多级表头,可以通过自定义代理来实现。 首先,需要创建一个自定义的代理类,继承QStyledItemDelegate。在该代理类中重写paint()方法,实现绘制多级表头的功能。 在paint()方法中,可以通过QPainter的绘图函数来绘制表头的内容。根据需要,可以设置表头的背景色、边框等样式。 在QTableView中使用这个自定义代理类,可以通过setViewportMargins()方法来修改视图的边距,以留出足够的空间绘制多级表头。 接下来,需要使用QHeaderView来实现多级表头的功能。通过setSectionsMovable()和setSectionResizeMode()方法可以设置表头的可移动性和调整模式。 首先要创建一个QHeaderView对象,并将其设置为表格控件的水平表头(horizontalHeader)。然后,通过调用QHeaderView的setModel()方法,将模型与表头关联,从而显示表头内容。 如果需要设置多级表头,可以在QHeaderView中添加子表头,即使每个单独子表头也是一个QHeaderView,可以设置其大小、样式等属性。 最后,通过调用QTableView的setHorizontalHeader()方法,将创建好的多级表头设置到表格视图中。 总结来说,实现QTableView多级表头,需要自定义代理类以绘制表头的内容,并使用QHeaderView来实现多级表头的功能。通过设置QHeaderView的子表头和调整模式,最后将多级表头set到QTableView中即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值