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¤t,constQModelIndex&previous)
voidcurrentColumnChanged(constQModelIndex¤t,constQModelIndex&previous)
voidcurrentRowChanged(constQModelIndex¤t,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