服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

2021-08-24 14:53漫步繁华街 C/C++

这篇文章主要介绍了Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例,需要的朋友可以参考下

我们在开发过程中对于表格使用频率还是挺高的,使用QT框架开发时候我们使用QTableView或者QTableWidget创建表格。

其中表格分为 表格头与表格体:

Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

对于简单地表格,我们可以设置表头来满足我们的要求(当然也可以隐藏表头),不过对于定制化的表头,我们能做的不是特别多。特别是对于复杂的表头,使用自带的表头,无论怎么设置都不太可能达到需求。例如我最近接到的一个项目,需求是:

Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

我们分析一下这个表格有什么特点:

1.表头不是简单的一行,而是两行。

2.表头有单元格的合并。

3.部分表头中间有使用渐变的分隔线且分割线不是上下充满表格的。

如果能解决上面三个问题,我们基本都可以把这个表格做出来了。这个表头明显是一个比较复杂的表头。对于只对QT提供的API或者CSS上面三个问题,没有一个能够解决的。

这时候可能会有老师提出解决办法:给header 设置itemDelegate,自己在itemDelegate中重写paintEvent,自己画表头。 因为我们都知道,自己画单元格,要更灵活,满足更多需求。但是我们平时都是对单元格进行重绘,并不是对header的单元格进行重绘。于是就去搜索QT的帮助文档,惊喜的发现居然有设置itemdelegate的API,心里觉得有戏于是创建 ItemDelegate类,对header进行设置如下

tableWidget->horizontalHorizon()->setItemDelegate(new ItemDelegate());

可是结果却是令人失望的,没有一点效果。于是反身去查找QT 关于这部分的介绍,终于找到了原因:

Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

结果就显而易见了,对于headerView,并不能使用ItemDelegate进行重绘。

那么我们就要另外想办法了,经过分析,刚开始提出了两种方案:

解决方案

  描述 优点 缺点
方案一
  • 隐藏表头
  • 前两行当做表头
  • 内容行从第三行开始
  • 对表格设置itemDelegate,对前两行的表头进行重绘
一个QTableWidget,实现起来方便一些。
  • 当出现滚动条,表头会随着着内容表格个移动,不符合大众习惯。
  • 改变了内容表格的整个原有序列,所有的行数都需要比原来大2,对所有的API进行重写工作难不高,复杂度比较高。
方案二
  • 使用一个QTableWidget命名为m_frozonTableWgt作为表头。
  • 使用另外一个QTableWidget作为内容显示的表格。
  • m_frozonTableWgt隐藏表头、隐藏滚动条、只显示2行的内容表格、显示到内容表格上方、只占据内容表的表头高度、设置ItemDelegate进行重绘。
  • 内容表格,显示表头,高度设置成m_frozonTableWgt前两行的高度。
最终效果更好,体验更好。
  • 需要对2个QTableWidget进行操作,比较麻烦。
  • 需要对表头的QTableWidget进行锁死(固定)。
  • 需要对2个QtableWidget进行联动设置

总结一下就是:

第一种方案比较简单,但是最终体验效果不太好。

第二种方案实现起来比较复杂,但是最终体验效果比较好。

本着成就客户与自我成长的态度,最终选择了第二种解决方案。

我们首先要做的就是创建一个继承于QTableWidget的一个类,命名为TDMSummaryTableWgt。

  1. class TDMSummaryTableWgt : public QTableWidget

然后需要在TDMSummaryTableWgt类中,声明另外一个用于header的QTableWidget,命名为 m_frozenTableWgt;

  1. private:
  2. QTableWidget *m_frozenTableWgt;// 使用TableWidget 作为header,并冻结

这个m_frozenTableWgt,就是作为表头,并且固定位置,不随着滚动条移动位置。

这个时候我们只需要解决两个问题,就可以搞定表头了:

1.表头位置锁定(固定、锁死)。

2.重绘表头。

对于第一个问题,表头位置的固定。我们应该从哪些方面考虑来解决?

1.从界面初始化开始,我们应当让表头m_frozenTableWgt具备: 不显示表头,不显示滚动条、设置rowcount为2行并隐藏2行后所有的元素、设置窗口层次在TDMSummaryTableWgt之前、对单元格进行合并等要素。

这里要特别注意的是,m_frozenTableWgt与TDMSummaryTableWgt设置的列数应该完全一致,每一列的尺寸与伸展方案也应该完全一致。

  1. void TDMSummaryTableWgt::initFrozenFrame()
  2. {
  3. m_frozenTableWgt = new QTableWidget(this);
  4.  
  5. m_frozenTableWgt->horizontalHeader()->setVisible(false);//表头不可见
  6. m_frozenTableWgt->verticalHeader()->setVisible(false);//表头不可见
  7. m_frozenTableWgt->setShowGrid(false);//网格线不可见
  8. m_frozenTableWgt->setEditTriggers(QAbstractItemView::NoEditTriggers);//设置单元格不可编辑
  9. m_frozenTableWgt->horizontalHeader()->setStretchLastSection(true);//最后一个单元格扩展
  10. m_frozenTableWgt->setFocusPolicy(Qt::NoFocus);//解决选中虚框问题
  11. m_frozenTableWgt->setFrameShape(QFrame::NoFrame);//去除边框 尴尬
  12. m_frozenTableWgt->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//隐藏滚动条
  13. m_frozenTableWgt->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//
  14. m_frozenTableWgt->setHorizontalScrollMode(ScrollPerPixel);
  15.  
  16. m_frozenTableWgt->setItemDelegate(new ItemDelegate(0));//设置绘画代理(主要在代理中画出来header)
  17.  
  18. viewport()->stackUnder(m_frozenTableWgt);//设置窗口层次
  19.  
  20. m_frozenTableWgt->setColumnCount(10);//header10列
  21. m_frozenTableWgt->setRowCount(2);//header2行
  22.  
  23. m_frozenTableWgt->setRowHeight(0, 42);//第一行设置高度42px
  24. m_frozenTableWgt->setRowHeight(1, 42);//第二行设置高度42px
  25.  
  26. for (int row = 2; row < m_frozenTableWgt->rowCount(); ++row)//隐藏2行后的行
  27. m_frozenTableWgt->setRowHidden(row, true);
  28.  
  29. //===================设置header内容=================//
  30. //合并单元格
  31. m_frozenTableWgt->setSpan(0, 0, 2, 1);//老师ID
  32. m_frozenTableWgt->setSpan(0, 1, 2, 1);//老师姓名
  33. m_frozenTableWgt->setSpan(0, 2, 2, 1);//老师姓名
  34. m_frozenTableWgt->setSpan(0, 3, 1, 4);//最新日期(8月20)
  35. m_frozenTableWgt->setSpan(0, 7, 1, 2);//前一日(8月19)
  36. m_frozenTableWgt->setSpan(0, 9, 2, 1);//操作
  37.  
  38. m_frozenTableWgt->setItem(0, 0, new QTableWidgetItem("老师ID"));
  39. m_frozenTableWgt->setItem(0, 1, new QTableWidgetItem("老师姓名"));
  40. m_frozenTableWgt->setItem(0, 2, new QTableWidgetItem("老师姓名"));
  41. m_frozenTableWgt->setItem(0, 3, new QTableWidgetItem("8月20日"));
  42. m_frozenTableWgt->setItem(0, 7, new QTableWidgetItem("8月19日"));
  43. m_frozenTableWgt->setItem(0, 9, new QTableWidgetItem("操作"));
  44. m_frozenTableWgt->setItem(1, 3, new QTableWidgetItem("续报率"));
  45. m_frozenTableWgt->setItem(1, 4, new QTableWidgetItem("新学员续报率"));
  46. m_frozenTableWgt->setItem(1, 5, new QTableWidgetItem("续报增长人数"));
  47. m_frozenTableWgt->setItem(1, 6, new QTableWidgetItem("续报增长率"));
  48. m_frozenTableWgt->setItem(1, 7, new QTableWidgetItem("续报增长率"));
  49. m_frozenTableWgt->setItem(1, 8, new QTableWidgetItem("新学员续报率"));
  50.  
  51. //连接信号槽。用于滚动条联动
  52. connect(m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::valueChanged,
  53. verticalScrollBar(), &QAbstractSlider::setValue);
  54. connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
  55. m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::setValue);
  56.  
  57. updateFrozenTableGeometry();//更新位置
  58. m_frozenTableWgt->show();
  59. }

2.除了上面的考虑之外,我们就需要考虑m_frozenTableWgt与TDMSummaryTableWgt之间的联动问题了,主要包括表格的尺寸变化、滚动条移动、界面平移等问题。

我们首先要写一个方法,来确定m_frozenTableWgt与TDMSummaryTableWgt位置。

  1. void TDMSummaryTableWgt::updateFrozenTableGeometry()
  2. {
  3. m_frozenTableWgt->setGeometry(frameWidth(),
  4. frameWidth(),
  5. viewport()->width(),
  6. horizontalHeader()->height());
  7.  
  8. }

我们需要重写3个上面提到问题解决方案的函数,在每个方法里都要重新执行updateFrozenTableGeometry();

  1. protected:
  2. /**
  3. * @brief resizeEvent 重载虚函数 resize事件,同时更新m_frozenTableWgt的位置
  4. * @param event
  5. */
  6. virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
  7.  
  8. /**
  9. * @brief moveCursor 重载虚函数 鼠标移动事件
  10. * @param cursorAction
  11. * @param modifiers
  12. * @return
  13. */
  14. virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE;
  15.  
  16. /**
  17. * @brief scrollTo TableWidget移动事件
  18. * @param index
  19. * @param hint
  20. */
  21. void scrollTo (const QModelIndex & index, ScrollHint hint = EnsureVisible) Q_DECL_OVERRIDE;

对上面这三个虚函数,我们需要特别注意的重点是moveCursor方法。这个方法里我们应该重点关注鼠标向上移动的情景:只有当鼠标向上移动,并且TDMSummaryTableWgt还未显示到第一行,并且可视区域的顶点应该小于m_frozenTableWgt的第一行,才允许继续向上移动:

  1. QModelIndex TDMSummaryTableWgt::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
  2. {
  3. QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);
  4.  
  5. if (cursorAction == MoveUp && current.row() > 0
  6. && visualRect(current).topLeft().y() < m_frozenTableWgt->rowHeight(1) ){
  7. const int newValue = verticalScrollBar()->value() + visualRect(current).topLeft().y()
  8. - m_frozenTableWgt->rowHeight(0) - m_frozenTableWgt->rowHeight(1);
  9. verticalScrollBar()->setValue(newValue);
  10. }
  11. return current;
  12. }

做完上面这几部,基本解决了第一个问题,就是将m_frozenTableWgt的固定行(冻结)的功能。

要完成m_frozenTableWgtde 的样式重绘,就是第二个要解决的问题了。

这个问题,我们要新建一个继承于QStyledItemDelegate的代理类,我们叫ItemDelegate。并且重写paint方法,在paint方法里绘制m_frozenTableWgt;

  1. m_frozenTableWgt->setItemDelegate(new ItemDelegate(0));//设置绘画代理(主要在代理中画出来header)

 

  1. class ItemDelegate : public QStyledItemDelegate
  2. {
  3. Q_OBJECT
  4. public:
  5. ItemDelegate(int type, QObject *parent=0);
  6.  
  7. void paint(QPainter *painter,
  8. const QStyleOptionViewItem &option, const QModelIndex &index) const;
  9.  
  10. private:
  11.  
  12. };

在paint方法中,根据每个单元格的背景不同进行绘制背景

  1. int rowIndex = index.row();//行号
  2. int colIndex = index.column();//列号
  3. if (rowIndex == 0 || rowIndex == 1)//前两行作为header
  4. {
  5. //背景
  6. QColor color;
  7.  
  8. if (rowIndex == 0 && (colIndex == 0 || //老师ID
  9. colIndex == 1 || //老师姓名
  10. colIndex == 2 || //课程类型
  11. colIndex == 9)) //操作
  12. {
  13. color.setRgb(231, 238, 251);
  14. }
  15. else if ((rowIndex == 0 && colIndex == 3) || //8月20日
  16. (rowIndex == 1 && (colIndex == 3 || //续报率
  17. colIndex == 4 || //新学员续报率
  18. colIndex == 5 || //续报增长人数
  19. colIndex == 6))) //续报增长率
  20. {
  21. color.setRgb(214, 228, 253);
  22. }
  23. else if ((rowIndex == 0 && colIndex == 7) || //8月19日
  24. (rowIndex == 1 && (colIndex == 7 || //续报率
  25. colIndex == 8))) //新学员续报率
  26. {
  27. color.setRgb(203, 221, 255);
  28. }
  29.  
  30. //绘制背景
  31. painter->setPen(color);
  32. painter->setBrush(QBrush(color));
  33. painter->drawRect(option.rect);

根据每个单元格要求绘画是否需要右侧的渐变的分隔线。

  1. //右侧spacer
  2. if ((rowIndex == 0 && (colIndex == 0 || colIndex == 1) )) {
  3. int startX = option.rect.right();
  4. int startY = option.rect.y() + (option.rect.height() - 40) / 2;
  5. int endX = startX;
  6. int endY = startY + 40;
  7. QLinearGradient linearGradient(startX, startY, endX, endY);
  8. linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
  9. linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
  10. linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
  11. painter->setBrush(linearGradient);
  12. painter->drawRect(option.rect.right()- 2, startY, 2, 40);
  13.  
  14. }
  15. else if (rowIndex == 1 && (colIndex == 3 ||
  16. colIndex == 4 ||
  17. colIndex == 5 ||
  18. colIndex == 7 )) {
  19.  
  20. int startX = option.rect.right();
  21. int startY = option.rect.y() + (option.rect.height() - 28) / 2;
  22. int endX = startX;
  23. int endY = startY + 28;
  24. QLinearGradient linearGradient(startX, startY, endX, endY);
  25. linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
  26. linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
  27. linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
  28. painter->setBrush(linearGradient);
  29. painter->drawRect(option.rect.right()- 2, startY, 2, 28);
  30. }

最后将每个单元格的字体画出来

  1. //字体
  2. painter->setPen(QColor(51, 51, 51));
  3. QTextOption op;
  4. op.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
  5.  
  6. QFont font;
  7. font.setFamily("Microsoft YaHei");
  8. font.setPixelSize(14);
  9. font.setBold(true);
  10. painter->setFont(font);
  11. painter->drawText(option.rect, index.data(Qt::DisplayRole).toString(), op);

这样就解决了header里面的难题。

Qt GUI图形图像开发之QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例

本文主要讲解了QT表格控件QTableView,QTableWidget复杂表头(多行表头) 及冻结、固定特定的行的详细方法与实例,更多关于Qt GUI图形图像开发知识请查看下面的相关链接

原文链接:https://blog.csdn.net/xiezhongyuan07/article/details/82857631

延伸 · 阅读

精彩推荐
  • C/C++c/c++实现获取域名的IP地址

    c/c++实现获取域名的IP地址

    本文给大家汇总介绍了使用c/c++实现获取域名的IP地址的几种方法以及这些方法的核心函数gethostbyname的详细用法,非常的实用,有需要的小伙伴可以参考下...

    C++教程网10262021-03-16
  • C/C++OpenCV实现拼接图像的简单方法

    OpenCV实现拼接图像的简单方法

    这篇文章主要为大家详细介绍了OpenCV实现拼接图像的简单方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    iteye_183805102021-07-29
  • C/C++深入C++拷贝构造函数的总结详解

    深入C++拷贝构造函数的总结详解

    本篇文章是对C++中拷贝构造函数进行了总结与介绍。需要的朋友参考下...

    C++教程网5182020-11-30
  • C/C++c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。...

    jihite5172022-02-22
  • C/C++关于C语言中E-R图的详解

    关于C语言中E-R图的详解

    今天小编就为大家分享一篇关于关于C语言中E-R图的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看...

    Struggler095962021-07-12
  • C/C++C语言实现双人五子棋游戏

    C语言实现双人五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    两片空白7312021-11-12
  • C/C++使用C++制作简单的web服务器(续)

    使用C++制作简单的web服务器(续)

    本文承接上文《使用C++制作简单的web服务器》,把web服务器做的功能稍微强大些,主要增加的功能是从文件中读取网页并返回给客户端,而不是把网页代码...

    C++教程网5492021-02-22
  • C/C++C语言main函数的三种形式实例详解

    C语言main函数的三种形式实例详解

    这篇文章主要介绍了 C语言main函数的三种形式实例详解的相关资料,需要的朋友可以参考下...

    ieearth6912021-05-16