QRowTable表格控件(二)-紅漲綠跌


原文鏈接:QRowTable表格控件(二)-紅漲綠跌

一、開心一刻

一天,五娃和六娃去跟蛇精決斗,決斗前有這樣一段對話。

五娃:“妖精!今天我倆就要消滅你!今天就是你的死期!”

蛇精:“呵呵呵,真是可笑。你們自己個兒都是從樹上長出來的,憑什么叫我妖精?!”

五娃:“你也說了,我們是從樹上長出來的,是葫蘆變的,自然不是妖精。”

蛇精:“你們不是妖精,難道還是神仙了,再難不成你把自己當人了?”

五娃和六娃異口同聲道:“哈哈哈哈哈哈!你說對了,我就是人,是植!物!人!”

二、概述

最近工作比較忙,不過還是抽時間把這個表格組件繼續完善下去。

Qt的自帶的表格控件非常強大,支持各種方便操作,上一篇文章QRowTable表格控件-支持hover整行、checked整行、指定列排序等介紹了一個簡單的demo,主要是做一個股票表格控件,而他天然的就是支持hover和checked行特性,對Qt比較熟悉的同學可能都知道Qt其實有兩個接口,可以設置交互行為為選擇行。

接口就是這兩個貨了。

setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QTableView::SingleSelection);//不能多選

既然Qt已經有接口了,為什么我們還要自己寫這個控件呢!

嘗試過用Qt的接口設置相關行為的同學我相信最后都會發現是什么原因,這里我直接把原因放出來。

  1. 首先我們的需求是每一行的文字顏色是不一樣的,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>());
  1. 最重要的時候我們自己還是實現了一堆的小需求,下一小節我們來一個個分析

三、效果展示

以下是紅漲綠跌效果圖,實現功能一樣。

UI展現形式不一樣,但是都實現了紅漲綠跌

  1. 背景色不同
  2. 排序效果不同

純白版

腹黑版

四、任務需求

看過效果圖之后,有沒有發現我們這里的表格控件和Qt自帶的控件有什么區別呢?其實這里我們要達到gif展示的那種效果,還是使用了很多實現技巧。

控件都包含哪些功能呢?

  1. 指定列排序

指定列排序,這個版本的代碼經過了優化,比QRowTable表格控件-支持hover整行、checked整行、指定列排序等這篇文章中將的版本要更優雅一些。

  1. 排序圖標

純白版使用的是Qt排序圖標

腹黑版排序圖標使我們自己去繪制的,並且繪制水平表頭時文本有特殊處理

  1. 列內容對其方式

看到這里的同學不放自己也可以先思考下,看這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);
	}
}

八、相關文章

  1. Qt實現表格控件-支持多級列表頭、多級行表頭、單元格合並、字體設置等

  2. Qt高仿Excel表格組件-支持凍結列、凍結行、內容自適應和合並單元格

  3. 屬性瀏覽器控件QtTreePropertyBrowser編譯成動態庫(設計師插件)

  4. 超級實用的屬性瀏覽器控件--QtTreePropertyBrowser

  5. Qt之表格控件螞蟻線

  6. QRowTable表格控件-支持hover整行、checked整行、指定列排序等


如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!




很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM