demo下载地址在最后
================================分割线======================================
对于所有前端开发人员会留意到,我们在开发过程中对于表格使用频率还是挺高的,使用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,对前两行的表头进行重绘
-
当出现滚动条,表头会随着着内容表格个移动,不符合大众习惯。
-
改变了内容表格的整个原有序列,所有的行数都需要比原来大2,对所有的API进行重写工作难不高,复杂度比较高。
方案二
-
使用一个QTableWidget命名为m_frozonTableWgt作为表头。
-
使用另外一个QTableWidget作为内容显示的表格。
-
m_frozonTableWgt隐藏表头、隐藏滚动条、只显示2行的内容表格、显示到内容表格上方、只占据内容表的表头高度、设置ItemDelegate进行重绘。
-
内容表格,显示表头,高度设置成m_frozonTableWgt前两行的高度。
-
需要对2个QTableWidget进行操作,比较麻烦。
-
需要对表头的QTableWidget进行锁死(固定)。
-
3.需要对2个QtableWidget进行联动设置
总结一下就是:
-
第一种方案比较简单,但是最终体验效果不太好。
-
第二种方案实现起来比较复杂,但是最终体验效果比较好。
本着成就客户与自我成长的态度,
最终选择了第二种解决方案。
我们首先要做的就是创建一个继承于QTableWidget的一个类,命名为TDMSummaryTableWgt。
class TDMSummaryTableWgt : public QTableWidget
然后需要在TDMSummaryTableWgt类中,声明另外一个用于header的QTableWidget,命名为 m_frozenTableWgt;
private:
QTableWidget *m_frozenTableWgt;// 使用TableWidget 作为header,并冻结
这个m_frozenTableWgt,就是作为表头,并且固定位置,不随着滚动条移动位置。
这个时候我们只需要解决两个问题,就可以搞定表头了:
一.表头位置锁定(固定、锁死)。
二.重绘表头。
对于第一个问题,表头位置的固定。我们应该从哪些方面考虑来解决?
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 < m_frozenTableWgt->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() < m_frozenTableWgt->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里面的难题。
=================================分割线========================================
demo下载地址:https://download.csdn.net/download/xiezhongyuan07/10694125 (没有积分的小伙伴评论上写邮箱,我发给你们)
没有积分支持的小伙伴,也可以访问、下载github代码,欢迎加星,会持续更新:https://github.com/xiezhongyuan/ComplexMeterTable
=================================分割线========================================
demo下载地址在最后================================分割线======================================对于所有前端开发人员会留意到,我们在开发过程中对于表格使用频率还是挺高的,使用QT框架开发时候我们使用QTableView或者QTableWidget创建表格。其中表格分为 表格头与表格体:对于简单地表格,...
//设置该单元格为屏蔽状态,不能编辑和选中。
ui->tableWidget->item(0,0)->setFlags(
Qt::ItemIsEditable);//取消
表头的在选中单元格时的高亮状态。
ui->tableWidget->horizontalHeader()->setHighlightSections(false);//取消
表头的在选中单元格时的高亮状态。
A.详情见readme.txt
1.自定义了一个表格View(TcTableView),支持继承重载相关虚函数,
2.TcTabelView支持设置多行横向表头(默认2行),
3.可以添加多张表格,每个表格是独立的,它们都有属于自己的自定义表头。
4.表头的右键操作我是自己写的,也可以用原例的方式,不过要复杂一点。
5.每一张表,一个tab,tab可以鼠标左击关闭
QTableWidget *table_header = new
QTableWidget(this);
QTableWidget *table_content = new
QTableWidget(this);
table_header->setObjectName("table_header");
table_...
文章目录多行表头效果逻辑使用资源
多行表头
Qt不提供默认的多行表头控件, 网上大多也是用2个QTableWidget组合来实现,最近在网上看到一份资源。他的思路是使用Model/View,通过使用重载来实现,我觉得很有学习借鉴的价值。所以在这分享。
RbTableHeaderItem:它负责保存每个项目的信息,主要是除了标签之外的行数和列宽。
RbTableHeaderModel...
二、基本思想
由于无法直接操作表格的 header,所以只能采用一个 QTableWidget 和 QTableView 组合来实现一个表格, QTableWidget 用来实现表头,QTableView 用来加载数据。
(一)自定义TableView
CustomTableView.h
#ifndef CUSTOMTABLEVIEW_H
#define CUSTOMTABLEVIEW_H
#include <QTableWidget>
#include <QMap>
model提供数据 view提供视图 view用来显示model的数据 必须将model绑定到某个view中才能显示
QStandardItemModel* model = new QStandardItemModel();
QStringList labels = QObject::trUtf8("频率,功率,误差")....