Qt GUI图形图像开发之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(newItemDelegate());
可是结果却是令人失望的,没有一点效果。于是反身去查找QT关于这部分的介绍,终于找到了原因:
结果就显而易见了,对于headerView,并不能使用ItemDelegate进行重绘。
那么我们就要另外想办法了,经过分析,刚开始提出了两种方案:
解决方案
- 隐藏表头
- 前两行当做表头
- 内容行从第三行开始
- 对表格设置itemDelegate,对前两行的表头进行重绘
- 当出现滚动条,表头会随着着内容表格个移动,不符合大众习惯。
- 改变了内容表格的整个原有序列,所有的行数都需要比原来大2,对所有的API进行重写工作难不高,复杂度比较高。
- 使用一个QTableWidget命名为m_frozonTableWgt作为表头。
- 使用另外一个QTableWidget作为内容显示的表格。
- m_frozonTableWgt隐藏表头、隐藏滚动条、只显示2行的内容表格、显示到内容表格上方、只占据内容表的表头高度、设置ItemDelegate进行重绘。
- 内容表格,显示表头,高度设置成m_frozonTableWgt前两行的高度。
- 需要对2个QTableWidget进行操作,比较麻烦。
- 需要对表头的QTableWidget进行锁死(固定)。
- 需要对2个QtableWidget进行联动设置
总结一下就是:
第一种方案比较简单,但是最终体验效果不太好。
第二种方案实现起来比较复杂,但是最终体验效果比较好。
本着成就客户与自我成长的态度,最终选择了第二种解决方案。
我们首先要做的就是创建一个继承于QTableWidget的一个类,命名为TDMSummaryTableWgt。
classTDMSummaryTableWgt:publicQTableWidget
然后需要在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设置的列数应该完全一致,每一列的尺寸与伸展方案也应该完全一致。
voidTDMSummaryTableWgt::initFrozenFrame() { m_frozenTableWgt=newQTableWidget(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(newItemDelegate(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(introw=2;rowrowCount();++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,newQTableWidgetItem("老师ID")); m_frozenTableWgt->setItem(0,1,newQTableWidgetItem("老师姓名")); m_frozenTableWgt->setItem(0,2,newQTableWidgetItem("老师姓名")); m_frozenTableWgt->setItem(0,3,newQTableWidgetItem("8月20日")); m_frozenTableWgt->setItem(0,7,newQTableWidgetItem("8月19日")); m_frozenTableWgt->setItem(0,9,newQTableWidgetItem("操作")); m_frozenTableWgt->setItem(1,3,newQTableWidgetItem("续报率")); m_frozenTableWgt->setItem(1,4,newQTableWidgetItem("新学员续报率")); m_frozenTableWgt->setItem(1,5,newQTableWidgetItem("续报增长人数")); m_frozenTableWgt->setItem(1,6,newQTableWidgetItem("续报增长率")); m_frozenTableWgt->setItem(1,7,newQTableWidgetItem("续报增长率")); m_frozenTableWgt->setItem(1,8,newQTableWidgetItem("新学员续报率")); //连接信号槽。用于滚动条联动 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位置。
voidTDMSummaryTableWgt::updateFrozenTableGeometry() { m_frozenTableWgt->setGeometry(frameWidth(), frameWidth(), viewport()->width(), horizontalHeader()->height()); }
我们需要重写3个上面提到问题解决方案的函数,在每个方法里都要重新执行updateFrozenTableGeometry();
protected: /** *@briefresizeEvent重载虚函数resize事件,同时更新m_frozenTableWgt的位置 *@paramevent */ virtualvoidresizeEvent(QResizeEvent*event)Q_DECL_OVERRIDE; /** *@briefmoveCursor重载虚函数鼠标移动事件 *@paramcursorAction *@parammodifiers *@return */ virtualQModelIndexmoveCursor(CursorActioncursorAction,Qt::KeyboardModifiersmodifiers)Q_DECL_OVERRIDE; /** *@briefscrollToTableWidget移动事件 *@paramindex *@paramhint */ voidscrollTo(constQModelIndex&index,ScrollHinthint=EnsureVisible)Q_DECL_OVERRIDE;
对上面这三个虚函数,我们需要特别注意的重点是moveCursor方法。这个方法里我们应该重点关注鼠标向上移动的情景:只有当鼠标向上移动,并且TDMSummaryTableWgt还未显示到第一行,并且可视区域的顶点应该小于m_frozenTableWgt的第一行,才允许继续向上移动:
QModelIndexTDMSummaryTableWgt::moveCursor(QAbstractItemView::CursorActioncursorAction,Qt::KeyboardModifiersmodifiers) { QModelIndexcurrent=QTableView::moveCursor(cursorAction,modifiers); if(cursorAction==MoveUp&¤t.row()>0 &&visualRect(current).topLeft().y()rowHeight(1)){ constintnewValue=verticalScrollBar()->value()+visualRect(current).topLeft().y() -m_frozenTableWgt->rowHeight(0)-m_frozenTableWgt->rowHeight(1); verticalScrollBar()->setValue(newValue); } returncurrent; }
做完上面这几部,基本解决了第一个问题,就是将m_frozenTableWgt的固定行(冻结)的功能。
要完成m_frozenTableWgtde的样式重绘,就是第二个要解决的问题了。
这个问题,我们要新建一个继承于QStyledItemDelegate的代理类,我们叫ItemDelegate。并且重写paint方法,在paint方法里绘制m_frozenTableWgt;
m_frozenTableWgt->setItemDelegate(newItemDelegate(0));//设置绘画代理(主要在代理中画出来header)
classItemDelegate:publicQStyledItemDelegate { Q_OBJECT public: ItemDelegate(inttype,QObject*parent=0); voidpaint(QPainter*painter, constQStyleOptionViewItem&option,constQModelIndex&index)const; private: };
在paint方法中,根据每个单元格的背景不同进行绘制背景
introwIndex=index.row();//行号 intcolIndex=index.column();//列号 if(rowIndex==0||rowIndex==1)//前两行作为header { //背景 QColorcolor; if(rowIndex==0&&(colIndex==0||//老师ID colIndex==1||//老师姓名 colIndex==2||//课程类型 colIndex==9))//操作 { color.setRgb(231,238,251); } elseif((rowIndex==0&&colIndex==3)||//8月20日 (rowIndex==1&&(colIndex==3||//续报率 colIndex==4||//新学员续报率 colIndex==5||//续报增长人数 colIndex==6)))//续报增长率 { color.setRgb(214,228,253); } elseif((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))){ intstartX=option.rect.right(); intstartY=option.rect.y()+(option.rect.height()-40)/2; intendX=startX; intendY=startY+40; QLinearGradientlinearGradient(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); } elseif(rowIndex==1&&(colIndex==3|| colIndex==4|| colIndex==5|| colIndex==7)){ intstartX=option.rect.right(); intstartY=option.rect.y()+(option.rect.height()-28)/2; intendX=startX; intendY=startY+28; QLinearGradientlinearGradient(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)); QTextOptionop; op.setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); QFontfont; font.setFamily("MicrosoftYaHei"); font.setPixelSize(14); font.setBold(true); painter->setFont(font); painter->drawText(option.rect,index.data(Qt::DisplayRole).toString(),op);
这样就解决了header里面的难题。
本文完整实例源码在这里下载
本文主要讲解了QT表格控件QTableView,QTableWidget复杂表头(多行表头)及冻结、固定特定的行的详细方法与实例,更多关于QtGUI图形图像开发知识请查看下面的相关链接
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。