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进行重绘。
那么我们就要另外想办法了,经过分析,刚开始提出了两种方案:
描述 | 优点 | 缺点 | |
方案一 |
|
|
|
方案二 |
|
|
|
总结一下就是:
- 第一种方案比较简单,但是最终体验效果不太好。
- 第二种方案实现起来比较复杂,但是最终体验效果比较好。
本着成就客户与自我成长的态度,最终选择了第二种解决方案。
我们首先要做的就是创建一个继承于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
=================================分割线========================================
转载:https://blog.csdn.net/xiezhongyuan07/article/details/82857631