Windows高DPI系列控件(一) - 餅圖

原文鏈接:Windos高DPI系列控件(一) - 餅圖

一、醉一醉

眨眼功夫,2020年過去一半了。回想最近一段時間的工作和生活,總覺得應該寫點兒什麼!

於是,最近有空就在想啊想,想想可以寫點兒什麼有用的東西好呢!剛好之前寫過幾篇關於高DPI的文章,不知道什麼原因,閱讀量不是很高,因此打算以高DPI爲索引開始引入一系列的控件使用案例,包括Qt自帶的控件、簡單圖表和一些複雜的圖表。

  1. Qt自帶的控件就不說了,高DPI框架幾乎可以完美適配
  2. 簡單的圖表這裏主要會引入柱狀圖、折線圖、餅圖等
  3. 複雜圖表主要是定製一些股票看盤相關圖表,例如分時圖、k線圖等

對於大衆軟件來說,友好的支持4k顯示器真的很有必要呢。一款好的大衆桌面端軟件需要適配各種操作系統,從快要被人們遺忘的Xp到現在佔有率較高的Win10,如果想擁有一個好的用戶體驗,高DPI是必須要好好適配滴,這裏作者準備了一個系列的高DPI控件適配文章分享給大家,主要整理我工作中遇到的各種控件,適配到已經開發好的高DPI框架中,並做出演示demo,提供給有需要的同學,除過整理已有的控件,更多的是會開發一些更有意義的新控件,比如股票中的分時圖、k線等。

目的:

  1. 推廣我自己適配高DPI的方案,供大家討論,是否有更好的優化空間
  2. 整理股票相關的控件,適配到我的高DPI框架中,提供給有需要的同學參考
  3. 階段性整理我自己的知識庫,讓零散的知識點匯聚起來

二、效果展示

如下圖所示,適配高DPI交互效果。

左右兩側的顯示物理尺寸一致,也就是佔地面積一樣大,不同的是左側是1080p顯示器,右側是4k顯示器

因爲是視頻錄製原因,可能會有視覺誤差,實際看的話,左右兩個窗體給人的視覺感受大小是一樣的。

《來回切換顯示器》


《餅圖支持操作》

三、高DPI適配

高DPI的適配思路之前已經系統的分析過,詳情參看Qt之高DPI顯示器(一) - 解決方案整理Qt之高DPI顯示器(二) - 自適配解決方案分析兩篇文章。

除過上述兩篇文章外,之前計劃中還有一篇文章要寫,後來實在是太忙了,一直沒有寫完,第三篇文章主要是想講下怎麼優化現有框架,讓DPI適配效率變的更高,後邊有機會補上。

本票文章開始算是適配高DPI實踐系列文章的開篇之作,餅圖控件很早之前就分享過,詳情參考Qt之自繪製餅圖,怎麼繪製餅圖這裏就不再描述。本篇文章的核心主要是進行了餅圖高DPI的顯示適配,後邊會主要描述下適配的細節,後期還會陸續接入更多更豐富的組件。

下面先帶大家回顧下適配高DPI我們都幹了哪些事情,最後在看看餅圖是怎麼適配高DPI的。

1、高DPI框架運作

看過前兩篇文章的同學應該知道,我們適配高DPI主要從兩個方面進行的,分別是窗體的物理尺寸和字體

物理尺寸

物理尺寸從字面上看就是軟件大小,不過我們這裏的物理尺寸也包含子窗口的大小,那也就是說子窗口之間的間隙也在物理尺寸這裏進行適配。

爲了讓開發同學無感知的使用,我們新增了同樣數量的可能會使用到的界面類,作爲我們自己的基礎類,並且重寫了界面類中跟尺寸相關的函數,讓大家使用界面類的時候只換類名稱,接口用法還是跟以前一樣。

重寫了尺寸函數,如果我們也知道當前顯示器的DPI,縮放界面是不是就很簡單了!當前開發設置的尺寸乘以需要縮放的係數就是最終需要顯示的尺寸,框架只需要把最後需要顯示的尺寸設置給Qt的接口,也就是當前類的父類接口,這樣我們的軟件界面就實現了放大。做到這裏算不算完呢?仔細想一想,開發同學使用這些接口的時候都是設置了96DPI下的尺寸,如果窗口移動到另一臺不同DPI的顯示器上,難道我們要把所有set接口重新調用一遍?如果數量少了還行,但一個複雜的軟件這樣的set接口會多的令你髮指,如果你讓開發同學都調用一遍,我估計他們會打死你。除過需要調用以外,調用順序也至關重要,試想這樣一種場景,如果需要縮放的窗體很多,你會希望窗體局部突然變大,沒有匯率的跳動嗎?答案當然是否。

爲了讓窗體有規律的縮放,那我們就需要控制縮放的順序了,這裏就需要額外的繼續重寫一些關鍵方法,之前我們重寫類的時候重寫了尺寸相關函數,這一次我們還需要把佈局相關的函數也進行重寫,爲的就是在界面佈局的時候我們把他們的關係記錄下來,後續在出現DPI變化時,我們根據之前維護的佈局關係,按層調用每一個需要縮放的界面。

界面佈局

這裏普及一個知識點,平時我們所看到的軟件界面是平面的,是一個二維的概念,但是軟件界面在開發的過程中,界面的佈局關係其實是一棵樹,比如像下圖這樣的界面,界面A包括了界面B和界面C,而界面B又包括了兩個界面D,這樣當我們構造兩個界面A時,其實所有的界面都被構造了兩份,並且界面D被構造了四份。

字體大小

我們適配的高DPI框架,除過自繪文字以外,其他情況是不需要關注字體怎麼變動的,這些字體適配都在我們的高DPI適配框架中完成了,這裏簡單描述下字體適配高DPI的方式。

高DPI框架運行過程中,主動維護了1x、2x和3x下的qss文件,如果檢測到程序所需要的Qss文件不在這三個配置中,那麼框架會動態的根據一個最接近當前縮放比的qss文件生成一個臨時qss文件,比如當前縮放比是1.8x,那麼程序將會根據2xqss文件,生成一個適合1.8x縮放比的qss文件,首先就是圖片進行壓縮顯示,字號會乘以1.8然後除以2轉換成1.8x縮放比下的字號,可能會有舍入,但是對於大家常用的0.5整數倍縮放比基本都是沒有問題的,因爲字號一般都是2的整數倍。

2、適配高DPI

高DPI框架設計之初就是想讓開發同學儘量少的去操心DPI的事情,但是實際情況是還有少部分情況是需要自己去適配的,比方說自毀界面,這個時候我們就需要自己去獲取高DPI相關信息合理繪製

繪製文字

繪製文字時,我們需要獲取當前的縮放係數,在合適的實際去縮放繪製相關參數,比方說,當我們繪製12px字體時,如果是在4k顯示器下,我們的dpi可能爲192,那麼縮放係數就是2x,繪製文字時就需要繪製24px字體

縮放係數 = 當前顯示器DPI / 96.0

有時候繪製文字時可能會附帶限制文字所在區域,96DPI下我們不需要操心區域是否會出現問題,但是如果顯示器DPI大於96時,文字繪製的區域我們也就需要相應的適配下,否則可能會出現你不希望的結果。比方說,我們需要在座標爲10,10這個位置,以邊框100px正方形框內繪製一段文本,96DPI下可能剛剛好能繪製完這段文字,如果192DPI下,我們把字號放大了一倍,如果繪製區域還是100px的正方形,那麼文字很可能連一半都繪製不完。問題出在哪裏呢?很顯然,字體變大了,我們的繪製區域肯定也需要進行相應的放大,不妨試試200px的正方形是不是可以呢!答案是Yes。

繪製圖片

自繪界面時往往少不了繪製圖片,下面具體分析下怎麼在任意DPI下繪製圖片!

首先是獲取1x縮放比下的圖片路徑,然後我們通過一個轉換函數轉換成我們當前顯示器下需要的圖片路徑,並縮放圖片,以達到最好的顯示效果。

如下代碼所示,是一個封裝好的函數,主要完成了根據1x圖片路徑獲取我們將要繪製的圖片,並且給我們返回的是內存地址。這裏需要額外補充下,Qt中的QPixmap是有做緩存機制的,當我們第二次獲取同一張圖片時,Qt會直接從內存中獲取到上一次圖片的內存直接返回給我們,因此這裏不需要擔心效率問題。

QPixmap TIGERQTCOMM_EXPORT ImagePath::GetStretchPixmap(const std::string & path, float scale)
{
	std::string tpath = ImagePath::GetPixmapPath(path, (int)(scale + 0.5001));
	float factor = ImagePath::GetStretchFactor(scale);

	QPixmap pixmap(tpath.c_str());
	if (factor != 1.0)
	{
		pixmap = pixmap.scaled(pixmap.size() * factor);
	}

	return pixmap;
}

3、適配餅圖

自繪界面時需要我們自己去適配高DPI,主要是繪製所需要的的幾何大小需要調整,說的直白一點兒就是看下圖,左側1080P顯示器,右側4K顯示器,並且兩個顯示器尺寸是一樣大的。看左右兩側的矩形區域座標很清楚的展示出來了,右側看着一樣大的舉行是左側舉行的兩倍大,並且左上角的座標也是兩倍。

左側矩形幾何大小是(83, 104, 168, 211),右側矩形幾何大小是(166, 208, 336, 422) ,按照我們高DPI適配的叫法左側顯示器的縮放比就是1x,右側是2X

《1080P vs 4K》

前邊小節說過了,自繪界面時適配高DPI主要是針對繪製的幾何大小,餅圖也不例外,這裏我貼一個餅圖各模塊幾何大小計算的函數,已經適配過高DPI,方法也很簡單

適配過程主要是用宏來完成的,宏定義如下:#define SCALE_NUMBER(n) ((n) * dpi_scale),dpi_scale爲每個高DPI框架下類的成員變量,該變量由框架維護,表示當前窗口需要縮放的係數

廢話不多說,如下代碼是適配過高DPI後的函數,主要是對一些影響幾何位置計算的參數進行了縮放。

void CPieChart::ConstructCornerLayout(const QSize & size)
{
	int currentR = SCALE_NUMBER(d_ptr->m_MinDiameter);
	int diameter;
	int horiWidth = size.width();
	if (d_ptr->m_bLegendVisible)
	{
		horiWidth -= SCALE_NUMBER(d_ptr->m_LegendWidth * 2);
	}
	int PieHeight;
	if (d_ptr->m_MutiDay.size() >= 1)
	{
		PieHeight = size.height() - SCALE_NUMBER(d_ptr->m_BarHeight) * d_ptr->m_MutiDay.size()
			- SCALE_NUMBER(d_ptr->m_BottomMargin + d_ptr->m_Space + d_ptr->m_BarSpace * (d_ptr->m_MutiDay.size() - 1))
			- SCALE_NUMBER(d_ptr->m_LabelHeight * 2);
	}
	else
	{
		PieHeight = size.height();
	}
	if (horiWidth > PieHeight)
	{
		diameter = PieHeight;
	}
	else
	{
		diameter = horiWidth;
	}

	int x, y;
	int r = diameter - SCALE_NUMBER(d_ptr->m_Minx * 2);
	currentR = r > currentR ? r : currentR;
	if (d_ptr->m_bLegendVisible)
	{
		d_ptr->m_Items.resize(4);

		x = width() / 2 - currentR / 2;
		y = (PieHeight - currentR) / 2;

		d_ptr->m_Items[1].m_LegendRect = QRect(SCALE_NUMBER(d_ptr->m_Minx), SCALE_NUMBER(d_ptr->m_Miny)
			, SCALE_NUMBER(d_ptr->m_LegendWidth), SCALE_NUMBER(30));

		d_ptr->m_Items[0].m_LegendRect = QRect(size.width() - Margin - SCALE_NUMBER(d_ptr->m_LegendWidth)
			, SCALE_NUMBER(d_ptr->m_Miny)
			, SCALE_NUMBER(d_ptr->m_LegendWidth), SCALE_NUMBER(30));

		d_ptr->m_Items[3].m_LegendRect = QRect(size.width() - Margin - SCALE_NUMBER(d_ptr->m_LegendWidth)
			, PieHeight - SCALE_NUMBER(d_ptr->m_Miny + 30)
			, SCALE_NUMBER(d_ptr->m_LegendWidth), SCALE_NUMBER(30));

		d_ptr->m_Items[2].m_LegendRect = QRect(SCALE_NUMBER(d_ptr->m_Minx)
			, PieHeight - SCALE_NUMBER(d_ptr->m_Miny + 30)
			, SCALE_NUMBER(d_ptr->m_LegendWidth), SCALE_NUMBER(30));

		d_ptr->m_Items[0].m_bAlign = false;
		d_ptr->m_Items[3].m_bAlign = false;
	}
	else
	{
		x = SCALE_NUMBER(d_ptr->m_Minx);
		y = SCALE_NUMBER(d_ptr->m_Miny);
	}

	d_ptr->m_PieRect = QRect(x, y, currentR, currentR);
	d_ptr->m_BarsRect = QRect(SCALE_NUMBER(20), 2 * y + currentR + SCALE_NUMBER(d_ptr->m_Space)
		, width() - SCALE_NUMBER(50)
		, size.height() - PieHeight);
}

到目前爲止,本篇文章要分享的內容算基本完成了。餅圖控件的其他的代碼邏輯,包括繪製邏輯適配高DPI方式都合上述函數類似,大家自行腦補即可。感興趣的朋友可以到餅圖-高DPI下載,CSDN鏈接中的資源只包含適配過高DPI的餅圖繪製代碼,僅供大家參考,並不能通過編譯。

四、相關文章

  1. Qt之高DPI顯示器(一) - 解決方案整理
  2. Qt之高DPI顯示器(二) - 自適配解決方案分析
  3. Qt之自繪製餅圖

值得一看的優秀文章:

  1. 財聯社-產品展示
  2. 廣聯達-產品展示
  3. Qt定製控件列表
  4. 牛逼哄哄的Qt庫

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




很重要--轉載聲明

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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章