欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
  QTableView表格控件区域选择-自绘选择区域
 
  目录
 
  一、开心一刻
 
  二、概述
 
  三、效果展示
 
  四、实现思路
 
  1、绘制区域
 
  2、绘制边框
 
  3、绘制
 
  五、相关文章
 
  原文链接:QTableView表格控件区域选择-自绘选择区域
 
  一、开心一刻
 
  陪完客户回到家,朦胧之中,看到我妈正在拖地,我掏出200块塞到我妈手里,说道:妈,给你点零花钱,别让我媳妇知道。
 
  我妈接过钱,大吼:你是不是又喝酒了?
 
  我:嘘,你怎么知道的?
 
  老妈:你看清楚了,我是你媳妇,还有。这200块钱是哪来的,说!我:啊……
 
  二、概述
 
  最近优化了一个小功能,主要是模仿excel相关的操作,觉得还挺不错的,因此在这里进行了整理,分享给有需要的朋友。今天主要是说一下区域选择这项功能,Qt自带的表格控件是具有区域选择功能的,但是他并不美观,不能支持我们自定义边框色和一些细节上的调整。
 
  今天博主就来讲解下自己是怎么自定义这个区域选择功能的。
 
  主要使用的方式还是自绘,下面先来看下效果,是不是你想要的。
 
  三、效果展示
 
  如下图所示,是一个自绘选择区域的效果展示,除此之外demo中还有一些其他的效果,但不是本篇文章所要讲述的内容。
 
  本篇文章的重点就是讲述怎么实现区域选择框绘制
 
  四、实现思路
 
  看过效果图之后,接下来开始分析怎么绘制矩形选择框。下面以问题的形式来进行分析,这样更有利于理解。
 
  那么先来思考如下几个很问题
 
  怎么确定绘制区域
 
  怎么确定绘制的边框
 
  谁去绘制更好
 
  以上三个问题搞懂了,那么今天的主要内容也就差不多了。
 
  1、绘制区域
 
  学习Qt的第一步便是看帮助文档,不得不说Qt的帮助文档那是做的相当好,非常齐全。既然如此那还等什么,直接打开Qt助手看看如下几个类都有哪些信号把。
 
  QTableView
 
  //QAbstractItemView
 
  voidactivated(constQModelIndex&index)
 
  voidclicked(constQModelIndex&index)
 
  voiddoubleClicked(constQModelIndex&index)
 
  voidentered(constQModelIndex&index)
 
  voidiconSizeChanged(constQSize&size)
 
  voidpressed(constQModelIndex&index)
 
  voidviewportEntered()
 
  QTableView是表格控件基类,我们的表格也是基于这个控件进行开发。再看这个类的包含的信号(其中都是他的父窗口信号),对于本小结开始提出的3个问题好像没有特别大的作用。那么我们继续往下看,看看他的数据存储类。
 
  QStandardItemModel
 
  voiditemChanged(QStandardItem*item)
 
  //parentQAbstractItemModel
 
  voidcolumnsAboutToBeInserted(constQModelIndex&parent,intfirst,intlast)
 
  voidcolumnsAboutToBeMoved(constQModelIndex&sourceParent,intsourceStart,intsourceEnd,constQModelIndex&destinationParent,intdestinationColumn)
 
  voidcolumnsAboutToBeRemoved(constQModelIndex&parent,intfirst,intlast)
 
  voidcolumnsInserted(constQModelIndex&parent,intfirst,intlast)
 
  voidcolumnsMoved(constQModelIndex&parent,intstart,intend,constQModelIndex&destination,intcolumn)
 
  voidcolumnsRemoved(constQModelIndex&parent,intfirst,intlast)
 
  voiddataChanged(constQModelIndex&topLeft,constQModelIndex&bottomRight,constQVector<int>&roles=QVector<int>())
 
  voidheaderDataChanged(Qt::Orientationorientation,intfirst,intlast)
 
  voidlayoutAboutToBeChanged(constQList<QPersistentModelIndex>&parents=QList<QPersistentModelIndex>(),QAbstractItemModel::LayoutChangeHinthint=QAbstractItemModel::NoLayoutChangeHint)
 
  voidlayoutChanged(constQList<QPersistentModelIndex>&parents=QList<QPersistentModelIndex>(),QAbstractItemModel::LayoutChangeHinthint=QAbstractItemModel::NoLayoutChangeHint)
 
  voidmodelAboutToBeReset()
 
  voidmodelReset()
 
  voidrowsAboutToBeInserted(constQModelIndex&parent,intstart,intend)
 
  voidrowsAboutToBeMoved(constQModelIndex&sourceParent,intsourceStart,intsourceEnd,constQModelIndex&destinationParent,intdestinationRow)
 
  voidrowsAboutToBeRemoved(constQModelIndex&parent,intfirst,intlast)
 
  voidrowsInserted(constQModelIndex&parent,intfirst,intlast)
 
  voidrowsMoved(constQModelIndex&parent,intstart,intend,constQModelIndex&destination,introw)
 
  voidrowsRemoved(constQModelIndex&parent,intfirst,intlast)
 
  QStandardItemModel便是QTableView的数据模型了,一眼扫过好像都是模型数据发生变化了的一些信号。这个时候发现M和V好像没有我们需要的东西,Qt不会真这么挫吧。答案当然是“否”,仔细翻阅Qt的帮助文档就会发现QAbstractItemView类可以返回一个selectionModel,看其名字好像是我们需要的东西。
 
  QItemSelectionModel*selectionModel()const
 
  随继续翻阅帮助文档,我们得到以下信息
 
  voidcurrentChanged(constQModelIndex&current,constQModelIndex&previous)
 
  voidcurrentColumnChanged(constQModelIndex&current,constQModelIndex&previous)
 
  voidcurrentRowChanged(constQModelIndex&current,constQModelIndex&previous)
 
  voidmodelChanged(QAbstractItemModel*model)
 
  voidselectionChanged(constQItemSelection&selected,constQItemSelection&deselected)
 
  哈哈哈,果然找到了我们需要的信号,看信号名称就知道,当前项发生变化时触发,然后我们就可以去统计哪些项被选中。
 
  到这里,我们的第一个问题就算回答了,我们可以通过selectionModel的selectionChanged信号来统计可能需要绘制border的单元格。
 
  //连接信号
 
  connect(m_pVew->selectionModel(),&QItemSelectionModel::selectionChanged,this,&ExcTableWidget::SelectionChanged);
 
  2、绘制边框
 
  信号连接上后,开始处理信号。
 
  思路大致是这样的:
 
  使用gridCell记录所有的单元格
 
  循环遍历选中的单元格
 
  判断当前单元格哪个边是需要绘制的
 
  结果存储于gridPosints结构中
 
  判断逻辑也比较简单,逻辑比较简单,可以直接看代码。这里我举一个例子,比如说是否需要绘制左border,那么就是需要看这个cell左边是否有cell,或者自己已经是第一列。
 
  gridPosints是QMap<QModelIndex,QVector>类型,键存储单元格索引,值存储4个边的状态(是否需要绘制)
 
  voidExcTableWidget::SelectionChanged(constQItemSelection&selected,constQItemSelection&deselected)
 
  {
 
  QModelIndexListindexs=m_pVew->selectionModel()->selectedIndexes();
 
  qDebug()<<indexs;
 
  introw=GetModel()->rowCount();
 
  intcolumn=GetModel()->columnCount();
 
  QVector<QVector<bool>>gridCell(row,QVector<bool>(column));
 
  foreach(constQModelIndex&indexinindexs)
 
  {
 
  gridCell[index.row()][index.column()]=true;
 
  }
 
  QMap<QModelIndex,DrawTypes>datas;
 
  QMap<QModelIndex,QVector<GridPoint>>gridPosints;
 
  foreach(constQModelIndex&indexinindexs)
 
  {
 
  DrawTypestypes;
 
  booltopLine=true,rightLine=true,bottomLine=true,leftLine=true;
 
  if(index.row()==0)
 
  {
 
  types|=TOP;
 
  }
 
  else
 
  {
 
  intaboveCell=index.row()-1;
 
  if(gridCell[aboveCell][index.column()]==false)
 
  {
 
  types|=TOP;
 
  }
 
  else
 
  {
 
  topLine=false;
 
  }
 
  }
 
  if(index.column()==GetModel()->columnCount()-1)
 
  {
 
  types|=RIGHT;
 
  }
 
  else
 
  {
 
  intrightCell=index.column()+1;
 
  if(gridCell[index.row()][rightCell]==false)
 
  {
 
  types|=RIGHT;
 
  }
 
  else
 
  {
 
  rightLine=false;
 
  }
 
  }
 
  if(index.row()==GetModel()->rowCount()-1)
 
  {
 
  types|=BOTTOM;
 
  }
 
  else
 
  {
 
  intbeloveCell=index.row()+1;
 
  if(gridCell[beloveCell][index.column()]==false)
 
  {
 
  types|=BOTTOM;
 
  }
 
  else
 
  {
 
  bottomLine=false;
 
  }
 
  }
 
  if(index.column()==0)
 
  {
 
  types|=LEFT;
 
  }
 
  else
 
  {
 
  intleftCell=index.column()-1;
 
  if(gridCell[index.row()][leftCell]==false)
 
  {
 
  types|=LEFT;
 
  }
 
  else
 
  {
 
  leftLine=false;
 
  }
 
  }
 
  datas[index]=types;
 
  gridPosints[index].push_back({TOP,topLine});
 
  gridPosints[index].push_back({RIGHT,rightLine});
 
  gridPosints[index].push_back({BOTTOM,bottomLine});
 
  gridPosints[index].push_back({LEFT,leftLine});
 
  }
 
  m_pVew->SetCellDatas(gridPosints);
 
  SelectStyle*style=m_pVew->GetDelegate();
 
  style->SetCellDatas(datas);
 
  m_pVew->update();
 
  }
 
  到这里,我们的第二个问题就算回答了,我们需要绘制边框的单元格总算是计算出来了。
 
  3、绘制
 
  数据都有了,绘制还会远吗?
 
  接下来继续往下看,Qt提供的绘制逻辑机制还是很强大滴,我们可以通过以下方式重绘
 
  1、重写QStyledItemDelegate
 
  QStyledItemDelegate是绘图代理,大多数的绘制操作最终都会在这里被执行,看参数就知道每一个cell绘制时都会来这里。
 
  virtualvoidpaint(QPainter*painter,constQStyleOptionViewItem&option,constQModelIndex&index)constoverride;
 
  但是这里有一个问题,那就是这个函数可绘制的区域问题,只能在这个cell里边绘制,如果绘制在border上将会被覆盖,不信看如下堆栈。
 
  绘图代理QStyledItemDelegate的paint函数是被QTableView的paintEvent函数进行回调。
 
  既然绘图代理中绘制cell项时不能绘制到cell外边去,那么刚好,我们可以在这里进行选择区域的填充
 
  voidSelectStyle::DrawSelected(QPainter*painter,constQRect&rect,constQModelIndex&index)const
 
  {
 
  if(m_indexs.contains(index)==false)
 
  {
 
  return;
 
  }
 
  painter->save();
 
  QPenpen=painter->pen();
 
  pen.setWidth(1);
 
  pen.setColor(m_color);
 
  painter->setPen(pen);
 
  painter->fillRect(rect,QColor(100,0,0,100));
 
  painter->restore();
 
  }
 
  填充完选择区域后,接下来便是绘制选择区域的border。
 
  2、重写paintEvent
 
  看了函数调用堆栈后,大家心里应该也比较清楚QTableView是怎么绘制的了吧。既然绘制代理不能完成需求,那么我们就只能在paintEvent这座大山中进行绘制。
 
  这里需要注意一点就是,我们需要先试用QTableView本身的paintEvent把原有的绘制走一遍,保证界面上的信息都是全的,然后在执行我们自己的定制代码。
 
  如下图所示,父类的paintEvent函数执行完毕后,我们绘制了border边线
 
  

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h56810.shtml