原文连接:QRowTable表格控件(二)-红涨绿跌浏览器
一天,五娃和六娃去跟蛇精决斗,决斗前有这样一段对话。app
五娃:“妖精!今天我俩就要消灭你!今天就是你的死期!”less
蛇精:“呵呵呵,真是好笑。大家本身个儿都是从树上长出来的,凭什么叫我妖精?!”ide
五娃:“你也说了,咱们是从树上长出来的,是葫芦变的,天然不是妖精。”函数
蛇精:“大家不是妖精,难道仍是神仙了,再难不成你把本身当人了?”字体
五娃和六娃异口同声道:“哈哈哈哈哈哈!你说对了,我就是人,是植!物!人!”优化
最近工做比较忙,不过仍是抽时间把这个表格组件继续完善下去。ui
Qt的自带的表格控件很是强大,支持各类方便操做,上一篇文章QRowTable表格控件-支持hover整行、checked整行、指定列排序等介绍了一个简单的demo,主要是作一个股票表格控件,而他自然的就是支持hover和checked行特性,对Qt比较熟悉的同窗可能都知道Qt其实有两个接口,能够设置交互行为为选择行。this
接口就是这两个货了。
setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QTableView::SingleSelection);//不能多选
既然Qt已经有接口了,为何咱们还要本身写这个控件呢!
尝试过用Qt的接口设置相关行为的同窗我相信最后都会发现是什么缘由,这里我直接把缘由放出来。
view_option.palette.setColor(QPalette::HighlightedText, index.data(Qt::ForegroundRole).value<QColor>()); view_option.palette.setColor(QPalette::Highlight, index.data(Qt::BackgroundRole).value<QColor>());
如下是红涨绿跌效果图,实现功能同样。
UI展示形式不同,可是都实现了红涨绿跌
纯白版
腹黑版
看过效果图以后,有没有发现咱们这里的表格控件和Qt自带的控件有什么区别呢?其实这里咱们要达到gif展现的那种效果,仍是使用了不少实现技巧。
控件都包含哪些功能呢?
指定列排序,这个版本的代码通过了优化,比QRowTable表格控件-支持hover整行、checked整行、指定列排序等这篇文章中将的版本要更优雅一些。
纯白版使用的是Qt排序图标
腹黑版排序图标使咱们本身去绘制的,而且绘制水平表头时文本有特殊处理
看到这里的同窗不放本身也能够先思考下,看这3种需求的实现方式,有了大体思路后在继续往下看。
这样带着思路看,理解起来应该更加的容易。
还记得上一篇文章QRowTable表格控件-支持hover整行、checked整行、指定列排序等中怎么禁用指定列排序吗?忘记的同窗能够到这篇文章中去熟悉下,我记着应该是发现排序列不容许被排序时,直接禁用排序功能。
本篇文章咱们使用了一种更加优雅的方式来阻止指定列排序。
首先咱们重写了表头组件,而且重写了mouseReleaseEvent这个函数,由于这个函数中才触发了排序,所以这个函数中足以过滤掉不让排序的列。
class QRowHeader : public QHeaderView { Q_OBJECT public: QRowHeader(Qt::Orientation orientation, QWidget * parent = nullptr); public: //设置是否支持排序 void SetSortEnable(int logicalIndex, bool enable); signals: void MouseMove(); protected: virtual void mouseMoveEvent(QMouseEvent * event) override; virtual void mouseReleaseEvent(QMouseEvent *e) override; virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override; private: QMap<int, bool> m_Indicator; };
而后代码是这样处理的,代码量不大,就是判断鼠标点击的位置若是是禁止排序的列,咱们直接把事件循环中断啦!!!
是否是很坏,哈哈哈
void QRowHeader::mouseReleaseEvent(QMouseEvent * event) { int column = logicalIndexAt(event->pos().x()); if (m_Indicator.contains(column) && m_Indicator[column] == false) { return; } QHeaderView::mouseReleaseEvent(event); }
上边讲完了怎么去阻止排序,重写了QHeaderView。
virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
里边有paintSection这样的函数,他就是绘制表头一个单元格所产生的回调。
在这个函数里边首先调用父窗口绘制表头,而后若是发现本身是排序列时,顺道去绘制一个排序图标.
代码很容易理解,绘制朝上仍是朝下的图标取决于咱们当前的排序方式
void QRowHeader::paintSection(QPainter * painter, const QRect & rect, int logicalIndex) const { ... //绘制三角形 if (sortIndicatorSection() == logicalIndex) { QRect indicator = rect; indicator.setLeft(indicator.right() - 6); indicator.setHeight(10); indicator.moveTop((rect.height() - indicator.height()) / 2); indicator.moveLeft(indicator.left() - 10);//距离左边界10像素 if (sortIndicatorOrder() == Qt::AscendingOrder) { painter->drawPixmap(indicator, QPixmap(":/QRowTable/down_arrow.png.png")); } else if (sortIndicatorOrder() == Qt::DescendingOrder) { painter->drawPixmap(indicator, QPixmap(":/QRowTable/up_arrow.png.png")); } } }
绘制列头中的项时,若是当前列是排序列,那么这里就须要绘制排序图标。若是恰好这一列的文字是右对齐呢,正常状况下咱们的图标还有文字多是没有问题的。
若是该列很窄,那么排序图标和列cell中的文字可能会重叠
既然有几率重叠,这里咱们就须要处理这个异常,当出现上述这种状况时,咱们须要改变原有的文字绘制区域,不让他在图标的绘制区域绘制文本。
仍是重写paintSection方法,这个是绘制列头一个单元格的方法。
看以下代码,首先咱们填充了这个cell,为何呢?不着急回答这个问题,接着往下看,咱们把原有的rect向左便宜了16个像素,而后绘制列头cell。
void QRowHeader::paintSection(QPainter * painter, const QRect & rect, int logicalIndex) const { painter->fillRect(rect.adjusted(-1, 0, -1, -1), QColor("#212121")); painter->save(); QRect r = rect; if (sortIndicatorSection() == logicalIndex) { r.adjust(0, 0, -16, 0); } QHeaderView::paintSection(painter, r, logicalIndex); painter->restore(); painter->setPen(QColor("#2B2B2B")); painter->drawRect(rect.adjusted(-1, 0, -1, -1)); ...
这样会有什么问题?仔细想一想,文字绘制没问题,其实有问题的是背景色绘制会发现错误
所以咱们在函数开头先把列头cell背景色进行了填充,这个背景色其实就是列头cell的背景色
setStyleSheet("QTableView{background:#333333;}" "QHeaderView{background:#212121;color:gray;}" "QHeaderView:section{padding:6px;border:0;background:#212121;color:gray;}");
上边是这个组件的qss样式,表头cell的背景色其实就是#2212121,所以填充区域咱们也使用了这个色值,最终达到咱们预期的效果。
这里插一句:绘制时必定要注意绘制的顺序,不然咱们自定义的绘制可能会和Qt本身的绘制有冲突。这里尽可能考虑清楚,省得产生冲突。
当列表头被点击时,QRowHeader对象会发出列cell被点击信号sectionClicked,这个信号也是从鼠标弹起函数mouseReleaseEvent中触发。
connect(m_pHHeader, &QRowHeader::sectionClicked, m_pFilter, &QFilterModel::setFilterKeyColumn); connect(m_pHHeader, &QRowHeader::sectionClicked, this, [this](int column){ if (column == 0 || column == 1 || column == 2) { GetFilterModel()->SetCompareType(QFilterModel::CT_INT); } else { GetFilterModel()->SetCompareType(QFilterModel::CT_STRING); } });
sectionClicked信号触发后,这里干了两件事:设置当前的排序列和当前的排序方式。
SetCompareType
接口用于设置当前排序方式。
目前这个组件支持3中排序方式,数字、百分比和字符串
bool QFilterModel::lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const { if (m_eType == CT_INT) { double l = source_left.data().toDouble(); double r = source_right.data().toDouble(); return l < r; } else if (m_eType == CT_PERCENT) { double l = source_left.data(Qt::UserRole + 2).toString().remove("%").toDouble(); double r = source_right.data(Qt::UserRole + 2).toString().remove("%").toDouble(); return l < r; } else { QString l = source_left.data().toString(); QString r = source_right.data().toString(); return l < r; } }
这里须要说明一下,百分比为何要单独拿出来分一类,设计之初,百分比和数字是放在一类中去比较的,可是后来发现百分比没有办法存储在double中,所以添加了百分比分类。这里也不强制非要添加一个新类,其余能实现比较需求的办法都可。
Qt的文字对其方式有以下这么多,而后咱们这个组件支持比较主流的集中排序方式,分别是:水平居左、水平居中、水平居右
水平居右时,若是当前列时排序列,咱们文字绘制的位置区域不能包含排序图标的大小,不然文字可能和图标重叠
enum AlignmentFlag { AlignLeft = 0x0001, AlignLeading = AlignLeft, AlignRight = 0x0002, AlignTrailing = AlignRight, AlignHCenter = 0x0004, AlignJustify = 0x0008, AlignAbsolute = 0x0010, AlignHorizontal_Mask = AlignLeft | AlignRight | AlignHCenter | AlignJustify | AlignAbsolute, AlignTop = 0x0020, AlignBottom = 0x0040, AlignVCenter = 0x0080, AlignBaseline = 0x0100, // Note that 0x100 will clash with Qt::TextSingleLine = 0x100 due to what the comment above // this enum declaration states. However, since Qt::AlignBaseline is only used by layouts, // it doesn't make sense to pass Qt::AlignBaseline to QPainter::drawText(), so there // shouldn't really be any ambiguity between the two overlapping enum values. AlignVertical_Mask = AlignTop | AlignBottom | AlignVCenter | AlignBaseline, AlignCenter = AlignVCenter | AlignHCenter };
控件以外,咱们经过SetAlignment接口设置了该列的排序方式,排序方式对列的内容和列头都起做用。也就是说列头和列内容排序方式是一致的。
model->SetAlignment(0, Qt::AlignRight | Qt::AlignVCenter); model->SetAlignment(1, Qt::AlignRight | Qt::AlignVCenter); model->SetAlignment(2, Qt::AlignRight | Qt::AlignVCenter); model->SetAlignment(3, Qt::AlignLeft | Qt::AlignVCenter); model->SetAlignment(4, Qt::AlignLeft | Qt::AlignVCenter); model->SetAlignment(5, Qt::AlignLeft | Qt::AlignVCenter);
列头绘制时,会经过headerData接口拿文字对其方式,而后这里只须要返回以前使用SetAlignment接口设置的对其方式便可。
QVariant QRowModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const { if (Qt::TextAlignmentRole == role) { //Q::AlignLeft | Qt::AlignVCenter auto iter = m_AlignmentList.find(section); if (iter != m_AlignmentList.end()) { return iter->second; } return Qt::AlignCenter; //return m_AlignmentList.at(section); } return QStandardItemModel::headerData(section, orientation, role); }
补充
从新换了一种方式实现第五节的mouseReleaseEvent函数。
以前的方法在开启了列拖拽以后会出现问题
void QRowHeader::mouseReleaseEvent(QMouseEvent * event) { int column = logicalIndexAt(event->pos().x()); if (m_Indicator.contains(column) && m_Indicator[column] == false) { setSectionsClickable(false); QHeaderView::mouseReleaseEvent(event); setSectionsClickable(true); } else { QHeaderView::mouseReleaseEvent(event); } }
![]() |
![]() |
很重要--转载声明