目录html
原文连接:Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格浏览器
最近看到一个比较炫酷的表格效果,冻结表格列功能。常用excel的人应该都使用过这个功能,当咱们想把一些重要的信息一直固定在界面上时,就得使用冻结行或者冻结列的功能。app
以前我也作过相似的冻结列的功能,并且Qt的源码中也有相似的demo。ide
对Qt比较熟悉的人应该都知道,Qt的安装包里能够为咱们安装不少的Qt使用事例,都很是不错,很值得学习。我我的也是常常会去学习其中的东西,建议你们没事也多看看。函数
Qt自带的有一个事例程序,工程名字就作frozencolumn,这个功能就演示了怎么去实现冻结列的功能,思路很是不错。因而,我也借鉴这个想法,作了好几个复杂控件,都是使用这个思路来实现的效果,后续陆续放出布局
像标题说的那样,本篇文章咱们不只仅是实现冻结列的功能,除此以外,冻结行、内容自适应行高,单元格合并这些咱们都要须要完成。学习
下面这张图展现了冻结行、冻结列效果。gif图有点儿长,能够花点儿时间看完,确认是本身想要的效果,再继续往下看。测试
Qt中的demofrozencolumn
是怎么干的字体
既然Qt中已经帮咱们是想了冻结列的功能,那么久先来分析下这个demo吧。this
实现列冻结,也就是说在拖动水平滚动条的时候,第一列永远显示在窗口上。 怎么作到这个效果呢?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(); } }
大概思路就讲这么多了,其余一些细节的实现你们能够自行完成,有困难能够找我。
编辑单元格内容时,行高自适应。
直接调用我封装好的类,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); } }
蚂蚁线这个工我以前在另外一篇文章中有讲过,须要重写一个绘图代理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; };
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; } } } }
//测试冻结列 tableView->SetFrozen(2, 2);
//测试行高 tableView->SetRowHight(2, 100); //测试列宽 tableView->SetColumnWidth(1, 200);
//设置单元格文本颜色 第一行前五列字体为红色 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);
//设置单元格背景色 第二行前五列背景色为红色 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);
//合并单元格 tableView->SetSpan(5, 5, 2, 2); //自适应第一行高度 tableView->ResizeRowHeight(0); //高度自适应 尽可能放在大量数据填充完 修改数据阶段启用 tableView->ResizeRowHeightEnable(true); //选择框颜色和样式 tableView->SetFoucsLine(Qt::red, false); tableView->SetMotionLine(true);
Qt自带的demo中有一个demo程序,源码工程叫作frozencolumn。
这个控件就是依赖于这个二次开发的,固然了已经被我封装成了一个控件,对外暴露的都是借口,用户不在须要关心内容的实现逻辑了。只须要调用几个接口,就能够达到这样的效果。
后续还会依次放出其余复杂控件:
![]() |
![]() |
很重要--转载声明