原文連接:Windos高DPI系列控件(一) - 餅圖html
眨眼功夫,2020年過去一半了。回想最近一段時間的工做和生活,總以爲應該寫點兒什麼!緩存
因而,最近有空就在想啊想,想一想能夠寫點兒什麼有用的東西好呢!恰好以前寫過幾篇關於高DPI的文章,不知道什麼緣由,閱讀量不是很高,所以打算以高DPI爲索引開始引入一系列的控件使用案例,包括Qt自帶的控件、簡單圖表和一些複雜的圖表。框架
對於大衆軟件來講,友好的支持4k顯示器真的頗有必要呢。一款好的大衆桌面端軟件須要適配各類操做系統,從快要被人們遺忘的Xp到如今佔有率較高的Win10,若是想擁有一個好的用戶體驗,高DPI是必需要好好適配滴,這裏做者準備了一個系列的高DPI控件適配文章分享給你們,主要整理我工做中遇到的各類控件,適配到已經開發好的高DPI框架中,並作出演示demo,提供給有須要的同窗,除過整理已有的控件,更多的是會開發一些更有意義的新控件,好比股票中的分時圖、k線等。函數
目的:佈局
以下圖所示,適配高DPI交互效果。字體
左右兩側的顯示物理尺寸一致,也就是佔地面積同樣大,不一樣的是左側是1080p顯示器,右側是4k顯示器優化
由於是視頻錄製緣由,可能會有視覺偏差,實際看的話,左右兩個窗體給人的視覺感覺大小是同樣的。spa
《來回切換顯示器》操作系統
《餅圖支持操做》.net
高DPI的適配思路以前已經系統的分析過,詳情參看Qt之高DPI顯示器(一) - 解決方案整理和Qt之高DPI顯示器(二) - 自適配解決方案分析兩篇文章。
除過上述兩篇文章外,以前計劃中還有一篇文章要寫,後來實在是太忙了,一直沒有寫完,第三篇文章主要是想講下怎麼優化現有框架,讓DPI適配效率變的更高,後邊有機會補上。
本票文章開始算是適配高DPI實踐系列文章的開篇之做,餅圖控件很早以前就分享過,詳情參考Qt之自繪製餅圖,怎麼繪製餅圖這裏就再也不描述。本篇文章的核心主要是進行了餅圖高DPI的顯示適配,後邊會主要描述下適配的細節,後期還會陸續接入更多更豐富的組件。
下面先帶你們回顧下適配高DPI咱們都幹了哪些事情,最後在看看餅圖是怎麼適配高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的整數倍。
高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; }
自繪界面時須要咱們本身去適配高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的餅圖繪製代碼,僅供你們參考,並不能經過編譯。
值得一看的優秀文章:
很重要--轉載聲明