一、開心一刻
一天,五娃和六娃去跟蛇精決鬥,決鬥前有這樣一段對話。
五娃:“妖精!今天我倆就要消滅你!今天就是你的死期!”
蛇精:“呵呵呵,真是可笑。你們自己個兒都是從樹上長出來的,憑什麼叫我妖精?!”
五娃:“你也說了,我們是從樹上長出來的,是葫蘆變的,自然不是妖精。”
蛇精:“你們不是妖精,難道還是神仙了,再難不成你把自己當人了?”
五娃和六娃異口同聲道:“哈哈哈哈哈哈!你說對了,我就是人,是植!物!人!”
二、概述
最近工作比較忙,不過還是抽時間把這個表格元件繼續完善下去。
Qt的自帶的表格控制元件非常強大,支援各種方便操作,上一篇文章QRowTable表格控制元件-支援hover整行、checked整行、指定列排序等介紹了一個簡單的demo,主要是做一個股票表格控制元件,而他天然的就是支援hover和checked行特性,對Qt比較熟悉的同學可能都知道Qt其實有兩個介面,可以設定互動行為為選擇行。
介面就是這兩個貨了。
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QTableView::SingleSelection);//不能多選
既然Qt已經有介面了,為什麼我們還要自己寫這個控制元件呢!
嘗試過用Qt的介面設定相關行為的同學我相信最後都會發現是什麼原因,這裡我直接把原因放出來。
- 首先我們的需求是每一行的文字顏色是不一樣的,Qt表格預設的行為是:表格cell如果被選中,前景色和背景色則是從高亮role中拿到的色值,以下程式碼是一個示例程式碼,其中的QPalette::HighlightedText和QPalette::Highlight就是儲存單元格被選中時,繪製的顏色。
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);
}
八、相關文章
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。