原文連接: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); } }
很重要--轉載聲明