原文链接:Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格

一、概述

最近看到一个比较炫酷的表格效果,冻结表格列功能。经常使用excel的人应该都使用过这个功能,当我们想把一些重要的信息一直固定在界面上时,就得使用冻结行或者冻结列的功能。

之前我也做过类似的冻结列的功能,而且Qt的源码中也有类似的demo。

对Qt比较熟悉的人应该都知道,Qt的安装包里可以为我们安装很多的Qt使用事例,都非常不错,很值得学习。我个人也是经常会去学习其中的东西,建议大家没事也多看看。

Qt自带的有一个事例程序,工程名字就做frozencolumn,这个功能就演示了怎么去实现冻结列的功能,思路非常不错。于是,我也借鉴这个想法,做了好几个复杂控件,都是使用这个思路来实现的效果,后续陆续放出

像标题说的那样,本篇文章我们不仅仅是实现冻结列的功能,除此之外,冻结行、内容自适应行高,单元格合并这些我们都要需要完成。

二、效果展示

下面这张图展示了冻结行、冻结列效果。gif图有点儿长,可以花点儿时间看完,确认是自己想要的效果,再继续往下看。

三、实现思路

1、冻结行、冻结列

Qt中的demofrozencolumn是怎么干的

既然Qt中已经帮我们是想了冻结列的功能,那么久先来分析下这个demo吧。

实现列冻结,也就是说在拖动水平滚动条的时候,第一列永远显示在窗口上。 怎么做到这个效果呢?Qt给的解决办法很简单,我们只需要把两个视图叠加在一起,上层这个视图只显示第一列,下层的视图是全显示,然后拖动的时候我们只需要正常拖动下层的这个视图即可。

是不是很简单呢。Qt封装的控件,接口都很齐全,我们只需要使用connect把相关的变化绑定起来即可。

setModel(model);
frozenTableView = new QTableView(this); init(); //connect the headers and scrollbars of both tableviews together
connect(horizontalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableWidget::updateSectionWidth);
connect(verticalHeader(),&QHeaderView::sectionResized, this,
&FreezeTableWidget::updateSectionHeight); connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged,
verticalScrollBar(), &QAbstractSlider::setValue);
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);

上述代码是从Qt5.7.1_vs2013版本中复制出来的。

看到了吧,就是这么简单。

下面就是我们自己封装冻结列、冻结行的讲解,思路参考Qt的。

我们自己的高仿Excel表格

既然Qt都这么干了,我们还有什么理由不这么干呢?

话不多说,直接开干,既然要冻结列和行,那我们至少还需要在添加2个上层视图,以固定列和行。

第一个版本的结构就是这样的,多了2个上层视图。

QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;

等程序做好后,发现一个问题,上层2个用来冻结列和冻结行的视图,永远只有一个是工作正常的,2个上层视图叠加的区域总是出现问题。

要么水平滚动正常,要么垂直滚动正常

思考了很久,为什么呢?也想了很多办法去解决这个问题,最后还是决定,在添加一个视图到行和列视图重叠的区域,因为这么做最简单。

至于为什么,大家可以自己想想,这里我就不做结束了,语言不是特别好描述,感觉自己也描述不清楚,囧。。。。

最后呢,我们的上层视图列表会像下面这样,从名字应该也可以看到他们分别是干什么的。

QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;
QTableView * m_pFrozenColumnView;

然后就是构造函数了,负责同步他们之间的状态

//没有布局 因此必须把父窗口带上
m_pFrozenLeftTopView = new QTableView(this);
m_pFrozenColumnView = new QTableView(this);
m_pFrozenRowView = new QTableView(this); init(); connect(horizontalHeader(), &QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionWidth);
connect(verticalHeader(), &QHeaderView::sectionResized, this,
&FreezeTableView::updateSectionHeight); //垂直视图垂直滚动条 -> 垂直滚动条
connect(m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::valueChanged,
verticalScrollBar(), &QAbstractSlider::setValue);
//垂直滚动条 -> 垂直视图垂直滚动条
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::setValue); //水平滚动条 -> 水平视图水平滚动条
connect(horizontalScrollBar(), &QAbstractSlider::valueChanged,
m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::setValue);
connect(m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::valueChanged,
horizontalScrollBar(), &QAbstractSlider::setValue); connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenColumnView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenRowView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenLeftTopView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);

4个视图上的当前选中项维护是一个比较费劲的操作,我这里设置了每个视图都只能选中一个单元格,然后其他视图单元格被选中的时候,清空其他三个视图上的当前选中项。

当某一个视图被点击时,updateSelections槽就会被处罚。然后根据参数中被选中项,获取点击的单元格行号和列号,依次拿到被点击了的视图,接着清空其他没有被点击的视图当前选中项。

void FreezeTableView::updateSelections(const QItemSelection & selected, const QItemSelection &)
{
if (selected.isEmpty())
{
return;
}
QModelIndex index = selected.indexes().at(0);
int row = index.row();
int column = index.column();
if (row < m_iRowFrozen
&& column < m_iColumnFrozen)//左上
{
clearSelection();
m_pFrozenRowView->clearSelection();
m_pFrozenColumnView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
}
else if (row >= m_iRowFrozen
&& column < m_iColumnFrozen)//左下
{
clearSelection();
m_pFrozenRowView->clearSelection();
m_pFrozenLeftTopView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
}
else if (row >= m_iRowFrozen
&& column >= m_iColumnFrozen)//右下
{
m_pFrozenColumnView->clearSelection();
m_pFrozenRowView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
m_pFrozenLeftTopView->clearSelection();
}
else if (row < m_iRowFrozen
&& column >= m_iColumnFrozen)//右上视图点击
{
selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
m_pFrozenColumnView->clearSelection();
m_pFrozenLeftTopView->clearSelection();
}
}

大概思路就讲这么多了,其他一些细节的实现大家可以自行完成,有困难可以找我。

2、行高自适应

编辑单元格内容时,行高自适应。

直接调用我封装好的类,ResizeRowHeightEnable接口,参数传递true即可。

代码比较简单,一看应该都可以明白。是用过表格resizeRowToContents这个接口自适应行高。

void ExcTableWidget::ResizeRowHeightEnable(bool enable)
{
if (enable)
{
connect(m_pModel, &QStandardItemModel::itemChanged, m_pVew, [this](QStandardItem * item){
m_pVew->resizeRowToContents(item->row());
}, Qt::UniqueConnection);
}
else
{
m_pModel->disconnect(m_pVew);
}
}

3、蚂蚁线

蚂蚁线这个工我之前在另一篇文章中有讲过,需要重写一个绘图代理QStyledItemDelegate,然后设置给表格控件。

可以参考Qt之表格控件蚂蚁线这篇文章。

下面是这个绘图代理的头文件,其中有几个public接口,主要是用于设置蚂蚁线的样式,和是否启用蚂蚁线的。

其中比较重要的接口是paint虚函数,这个函数里边对单元格进行了绘制。

很重要:我们的绘制函数一定不要忘记调用原来的paint函数,否则单元格的其他样式都会丢失

class SelectStyle : public QStyledItemDelegate
{
Q_OBJECT public:
SelectStyle(QObject * parent = nullptr) : QStyledItemDelegate(parent), m_bAntLine(false), m_iOffset(0), m_color(0, 132, 255){}
~SelectStyle(){} public:
void GoStepAntLine(bool); void SetLineColor(const QColor & color);
void SetLineType(bool dash); protected:
virtual void paint(QPainter * painter
, const QStyleOptionViewItem & option
, const QModelIndex & index) const override; private:
void DrawBorderRect(QPainter * painter, const QRect & rect, bool firstColumn) const;
void DrawDashRect(QPainter * painter, const QRect & rect, bool firstColumn) const; protected:
bool m_bAntLine;
bool m_bDashState;
int m_iOffset;
QColor m_color;
};

四、测试代码

1、添加表格数据

QFile file(":/grades.txt");
if (file.open(QFile::ReadOnly)) {
QString line = file.readLine(200);
QStringList list = line.simplified().split(',');
tableView->SetHeaderLabels(list); QStringList lines;
while (file.canReadLine()) {
line = file.readLine(200);
lines.append(line);
}
file.close(); int i = 1;
int row = 0;
while (i-- > 0)
{
for each (const QString & line in lines)
{
if (!line.startsWith('#') && line.contains(',')) {
list = line.simplified().split(',');
for (int col = 0; col < list.length(); ++col){
tableView->SetItemData(row, col, list.at(col));
}
++row;
}
}
}
}

2、设置冻结行、列

//测试冻结列
tableView->SetFrozen(2, 2);

3、行高、列宽

//测试行高
tableView->SetRowHight(2, 100); //测试列宽
tableView->SetColumnWidth(1, 200);

4、单元格背景色

	//设置单元格文本颜色   第一行前五列字体为红色
tableView->SetItemForegroundColor(0, 0, Qt::red);
tableView->SetItemForegroundColor(0, 1, Qt::red);
tableView->SetItemForegroundColor(0, 2, Qt::red);
tableView->SetItemForegroundColor(0, 3, Qt::red);
tableView->SetItemForegroundColor(0, 4, Qt::red);

5、单元格文字

//设置单元格背景色	第二行前五列背景色为红色
tableView->SetItemBackgroundColor(1, 0, Qt::red);
tableView->SetItemBackgroundColor(1, 1, Qt::red);
tableView->SetItemBackgroundColor(1, 2, Qt::red);
tableView->SetItemBackgroundColor(1, 3, Qt::red);
tableView->SetItemBackgroundColor(1, 4, Qt::red); //设置单元格文本对齐方式 第三行前五列文字居中
tableView->SetTextAlignment(2, 0, Qt::AlignCenter);
tableView->SetTextAlignment(2, 1, Qt::AlignCenter);
tableView->SetTextAlignment(2, 2, Qt::AlignCenter);
tableView->SetTextAlignment(2, 3, Qt::AlignCenter);
tableView->SetTextAlignment(2, 4, Qt::AlignCenter); //设置单元格文本对齐方式 第四行前五列文字字体
QFont font;
font.setBold(true);//加粗
font.setPixelSize(18);//18像素
font.setItalic(true);//斜体
font.setFamily(QString("Microsoft Yahei"));
font.setUnderline(true);//是否有下划线
font.setStrikeOut(true);
tableView->SetItemFont(3, 0, font);
tableView->SetItemFont(3, 1, font);
tableView->SetItemFont(3, 2, font);
tableView->SetItemFont(3, 3, font);
tableView->SetItemFont(3, 4, font);

6、其他相关测试

//合并单元格
tableView->SetSpan(5, 5, 2, 2); //自适应第一行高度
tableView->ResizeRowHeight(0); //高度自适应 尽量放在大量数据填充完 修改数据阶段启用
tableView->ResizeRowHeightEnable(true); //选择框颜色和样式
tableView->SetFoucsLine(Qt::red, false);
tableView->SetMotionLine(true);

五、相关文章

  1. 属性浏览器控件QtTreePropertyBrowser编译成动态库(设计师插件)

  2. 超级实用的属性浏览器控件--QtTreePropertyBrowser

  3. Qt之表格控件蚂蚁线

  4. 测试程序:Qt实现高仿excel表格-可执行文件(源码不开放)


Qt自带的demo中有一个demo程序,源码工程叫做frozencolumn。

这个控件就是依赖于这个二次开发的,当然了已经被我封装成了一个控件,对外暴露的都是借口,用户不在需要关心内容的实现逻辑了。只需要调用几个接口,就可以达到这样的效果。

后续还会依次放出其他复杂控件:

  1. Qt实现表格树控件-支持多级表头
  2. Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格的更多相关文章

  1. java导出标题多行且合并单元格的EXCEL

    场景:项目中遇到有需要导出Excel的需求,并且是多行标题且有合并单元格的,参考网上的文章,加上自己的理解,封装成了可自由扩展的导出工具 先上效果,再贴代码: 调用工具类进行导出: public st ...

  2. poi的合并单元格和冻结行列

    //创建工作薄(excel) Workbook wb = new HSSFWorkbook(); //创建sheet Sheet createSheet = wb.createSheet(" ...

  3. 浏览器仿EXCEL表格插件 版本更新 - 智表ZCELL产品V1.3发布

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  4. 网页版仿Excel效果组件--handsontable拓展运用

    引言(祝看官们新年万事大吉) 前段时间项目需要实现网页版的excel表格功能,瞬间就想到了handsontable,为什么呢?理由如下:该UI组件功能齐全多样,展示效果也更贴近bootstrap风格, ...

  5. 浏览器仿EXCEL表格插件 - 智表ZCELL产品V1.4发布

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  6. 浏览器仿EXCEL表格插件 版本更新 - 智表ZCELL产品V1.3.2更新

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  7. 浏览器仿EXCEL表格插件 版本更新 - 智表ZCELL产品V1.3.1更新

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  8. C# Excel行高、列宽、合并单元格、单元格边框线、冻结

    private _Workbook _workBook = null;private Worksheet _workSheet = null;private Excel.Application _ex ...

  9. 前端Excel表格导入导出,包括合并单元格,表格自定义样式等

    表格数据导入 读取导入Excel表格数据这里采用的是 xlsx 插件 npm i xlsx 读取excel需要通过 XLSX.read(data, {type: type}) 方法来实现,返回一个叫W ...

随机推荐

  1. Flask请求和应用上下文源码分析

      flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1:本地线程,保证即使是多个线程,自己的值也是互相隔离 1 im ...

  2. windows10下Bad owner or permissions on .ssh/config的解决办法

    方法很简单,亲测有效. 1.进入如下路径C:\Users\用户名\.ssh,你会看到有config这个文件 2.右击config,属性→安全→高级→禁止继承→删除所有继承(忘了全称了,大概这个意思)→ ...

  3. springboot-项目获取resources下文件的方法

    spring项目获取resources下文件的方法   最近写读取模板文件做一些后续的处理,将文件放在了项目的resources 下,发现了一个好用的读取方法:   比如上边是你需要读取的文件:  读 ...

  4. 《快活帮》第七次作业:团队项目设计完善&编码

    项目 内容 这个作业属于哪个课程 2016计算机科学与工程学院软件工程(西北师范大学) 这个作业的要求在哪里 实验十一 团队作业7-团队项目设计完善&编码 团队名称 快活帮 作业学习目标 掌握 ...

  5. OpenCV随机颜色,用于画图调试

    static Scalar randomColor(int64 seed) { RNG rng(seed); int icolor = (unsigned)rng; return Scalar(ico ...

  6. SysML——CSE 599W: Systems for ML

    CSE 599W: Systems for ML Assignments Materials Projects Schedule Schedule The schedule is tentative ...

  7. 洛谷 U87052 一线天

    洛谷 U87052 一线天 题目传送门 题目背景 \(JDFZ\)即将举办第一届"一线天"趣味运动会...... 题目描述 "一线天"运动会在\(JLU\)南岭 ...

  8. 11/11 <Topological Sort> 207

    207. Course Schedule 我们定义二维数组 graph 来表示这个有向图,一维数组 in 来表示每个顶点的入度.我们开始先根据输入来建立这个有向图,并将入度数组也初始化好.然后我们定义 ...

  9. Salesforce 版本控制 - VS Code + GitHub + Salesforce

    使用VS Code开发Salesforce有个很好的地方是可以联接GitHub进行代码版本控制,点击查看使用VS Code开发SalesForce 第一步:安装GIthub Desktop Githu ...

  10. 第五次实验报告:使用Packet Tracer理解OSPF路由协议

    目录 1 实验目的 2 实验内容 3. 实验报告 3.1 建立网络拓扑结构 4. 配置 4.1 配置并激活串行地址和以太网地址 4.1.1 R1 4.1.2 R2 4.1.3 R3 4.1.4 PC ...