热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

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

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

我们在开发过程中对于表格使用频率还是挺高的,使用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 关于这部分的介绍,终于找到了原因:

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

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

解决方案

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

总结一下就是:

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

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

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

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

class TDMSummaryTableWgt : public QTableWidget

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

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

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

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

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

2.重绘表头。

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

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

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

void TDMSummaryTableWgt::initFrozenFrame()
{
  m_frozenTableWgt = new QTableWidget(this);
 
  m_frozenTableWgt->horizontalHeader()->setVisible(false);//表头不可见
  m_frozenTableWgt->verticalHeader()->setVisible(false);//表头不可见
  m_frozenTableWgt->setShowGrid(false);//网格线不可见
  m_frozenTableWgt->setEditTriggers(QAbstractItemView::NoEditTriggers);//设置单元格不可编辑
  m_frozenTableWgt->horizontalHeader()->setStretchLastSection(true);//最后一个单元格扩展
  m_frozenTableWgt->setFocusPolicy(Qt::NoFocus);//解决选中虚框问题
  m_frozenTableWgt->setFrameShape(QFrame::NoFrame);//去除边框 尴尬
  m_frozenTableWgt->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//隐藏滚动条
  m_frozenTableWgt->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//
  m_frozenTableWgt->setHorizontalScrollMode(ScrollPerPixel);
 
  m_frozenTableWgt->setItemDelegate(new ItemDelegate(0));//设置绘画代理(主要在代理中画出来header)
 
  viewport()->stackUnder(m_frozenTableWgt);//设置窗口层次
 
  m_frozenTableWgt->setColumnCount(10);//header10列
  m_frozenTableWgt->setRowCount(2);//header2行
 
  m_frozenTableWgt->setRowHeight(0, 42);//第一行设置高度42px
  m_frozenTableWgt->setRowHeight(1, 42);//第二行设置高度42px
 
  for (int row = 2; row rowCount(); ++row)//隐藏2行后的行
     m_frozenTableWgt->setRowHidden(row, true);
 
  //===================设置header内容=================//
  //合并单元格
  m_frozenTableWgt->setSpan(0, 0, 2, 1);//老师ID
  m_frozenTableWgt->setSpan(0, 1, 2, 1);//老师姓名
  m_frozenTableWgt->setSpan(0, 2, 2, 1);//老师姓名
  m_frozenTableWgt->setSpan(0, 3, 1, 4);//最新日期(8月20)
  m_frozenTableWgt->setSpan(0, 7, 1, 2);//前一日(8月19)
  m_frozenTableWgt->setSpan(0, 9, 2, 1);//操作
 
  m_frozenTableWgt->setItem(0, 0, new QTableWidgetItem("老师ID"));
  m_frozenTableWgt->setItem(0, 1, new QTableWidgetItem("老师姓名"));
  m_frozenTableWgt->setItem(0, 2, new QTableWidgetItem("老师姓名"));
  m_frozenTableWgt->setItem(0, 3, new QTableWidgetItem("8月20日"));
  m_frozenTableWgt->setItem(0, 7, new QTableWidgetItem("8月19日"));
  m_frozenTableWgt->setItem(0, 9, new QTableWidgetItem("操作"));
  m_frozenTableWgt->setItem(1, 3, new QTableWidgetItem("续报率"));
  m_frozenTableWgt->setItem(1, 4, new QTableWidgetItem("新学员续报率"));
  m_frozenTableWgt->setItem(1, 5, new QTableWidgetItem("续报增长人数"));
  m_frozenTableWgt->setItem(1, 6, new QTableWidgetItem("续报增长率"));
  m_frozenTableWgt->setItem(1, 7, new QTableWidgetItem("续报增长率"));
  m_frozenTableWgt->setItem(1, 8, new QTableWidgetItem("新学员续报率"));
 
  //连接信号槽。用于滚动条联动
  connect(m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::valueChanged,
      verticalScrollBar(), &QAbstractSlider::setValue);
  connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
      m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::setValue);
 
  updateFrozenTableGeometry();//更新位置
  m_frozenTableWgt->show();
}

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

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

void TDMSummaryTableWgt::updateFrozenTableGeometry()
{
  m_frozenTableWgt->setGeometry(frameWidth(),
                 frameWidth(),
                 viewport()->width(),
                 horizontalHeader()->height());
 
}

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

protected:
  /**
   * @brief resizeEvent        重载虚函数 resize事件,同时更新m_frozenTableWgt的位置
   * @param event
   */
  virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
 
  /**
   * @brief moveCursor        重载虚函数 鼠标移动事件
   * @param cursorAction
   * @param modifiers
   * @return
   */
  virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE;
 
  /**
   * @brief scrollTo         TableWidget移动事件
   * @param index
   * @param hint
   */
  void scrollTo (const QModelIndex & index, ScrollHint hint = EnsureVisible) Q_DECL_OVERRIDE;

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

QModelIndex TDMSummaryTableWgt::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
  QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);
 
  if (cursorAction == MoveUp && current.row() > 0
      && visualRect(current).topLeft().y() rowHeight(1) ){
     const int newValue = verticalScrollBar()->value() + visualRect(current).topLeft().y()
                - m_frozenTableWgt->rowHeight(0) - m_frozenTableWgt->rowHeight(1);
     verticalScrollBar()->setValue(newValue);
  }
  return current;
}

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

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

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

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

 

class ItemDelegate : public QStyledItemDelegate
{
  Q_OBJECT
public:
  ItemDelegate(int type, QObject *parent=0);
 
  void paint(QPainter *painter,
        const QStyleOptionViewItem &option, const QModelIndex &index) const;
 
private:
  
};

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

  int rowIndex = index.row();//行号
  int colIndex = index.column();//列号
  if (rowIndex == 0 || rowIndex == 1)//前两行作为header
  {
    //背景
    QColor color;
 
    if (rowIndex == 0 && (colIndex == 0 || //老师ID
               colIndex == 1 || //老师姓名
               colIndex == 2 || //课程类型
               colIndex == 9)) //操作
    {
      color.setRgb(231, 238, 251);
    }
    else if ((rowIndex == 0 && colIndex == 3) || //8月20日
         (rowIndex == 1 && (colIndex == 3 || //续报率
                  colIndex == 4 || //新学员续报率
                  colIndex == 5 || //续报增长人数
                  colIndex == 6))) //续报增长率
    {
      color.setRgb(214, 228, 253);
    }
    else if ((rowIndex == 0 && colIndex == 7) || //8月19日
         (rowIndex == 1 && (colIndex == 7 || //续报率
                  colIndex == 8))) //新学员续报率
    {
      color.setRgb(203, 221, 255);
    }
 
    //绘制背景
    painter->setPen(color);
    painter->setBrush(QBrush(color));
    painter->drawRect(option.rect);

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

    //右侧spacer
    if ((rowIndex == 0 && (colIndex == 0 || colIndex == 1) )) {
      int startX = option.rect.right();
      int startY = option.rect.y() + (option.rect.height() - 40) / 2;
      int endX = startX;
      int endY = startY + 40;
      QLinearGradient linearGradient(startX, startY, endX, endY);
      linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
      linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
      linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
      painter->setBrush(linearGradient);
      painter->drawRect(option.rect.right()- 2, startY, 2, 40);
 
    }
    else if (rowIndex == 1 && (colIndex == 3 ||
                  colIndex == 4 ||
                  colIndex == 5 ||
                  colIndex == 7 )) {
 
      int startX = option.rect.right();
      int startY = option.rect.y() + (option.rect.height() - 28) / 2;
      int endX = startX;
      int endY = startY + 28;
      QLinearGradient linearGradient(startX, startY, endX, endY);
      linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
      linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
      linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
      painter->setBrush(linearGradient);
      painter->drawRect(option.rect.right()- 2, startY, 2, 28);    
    }

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

    //字体
    painter->setPen(QColor(51, 51, 51));
    QTextOption op;
    op.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
 
    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPixelSize(14);
    font.setBold(true);
    painter->setFont(font);
    painter->drawText(option.rect, index.data(Qt::DisplayRole).toString(), op);

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

本文完整实例源码在这里下载

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


推荐阅读
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
author-avatar
拍友2602890695
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有