1. 程式人生 > >Windows高DPI系列控制元件(一) - 餅圖

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

[TOC] **原文連結:**[Windos高DPI系列控制元件(一) - 餅圖](https://www.cnblogs.com/swarmbees/p/13223795.html) ## 一、醉一醉 眨眼功夫,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顯示器(一) - 解決方案整理](https://www.cnblogs.com/swarmbees/p/12004594.html)和[Qt之高DPI顯示器(二) - 自適配解決方案分析](https://www.cnblogs.com/swarmbees/p/12006601.html)兩篇文章。 除過上述兩篇文章外,之前計劃中還有一篇文章要寫,後來實在是太忙了,一直沒有寫完,第三篇文章主要是想講下怎麼優化現有框架,讓DPI適配效率變的更高,後邊有機會補上。 本票文章開始算是適配高DPI實踐系列文章的開篇之作,餅圖控制元件很早之前就分享過,詳情參考[Qt之自繪製餅圖](https://www.cnblogs.com/swarmbees/p/6033217.html),怎麼繪製餅圖這裡就不再描述。本篇文章的核心主要是進行了餅圖高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](https://download.csdn.net/download/qq_30392343/12569680)下載,CSDN連結中的資源只包含適配過高DPI的餅圖繪製程式碼,僅供大家參考,並不能通過編譯。** ## 四、相關文章 1. [Qt之高DPI顯示器(一) - 解決方案整理](https://www.cnblogs.com/swarmbees/p/12004594.html) 2. [Qt之高DPI顯示器(二) - 自適配解決方案分析](https://www.cnblogs.com/swarmbees/p/12006601.html) 3. [Qt之自繪製餅圖](https://www.cnblogs.com/swarmbees/p/6033217.html) *** **值得一看的優秀文章:** 1. **[財聯社-產品展示](https://www.cnblogs.com/swarmbees/p/6707798.html)** 2. **[廣聯達-產品展示](https://www.cnblogs.com/swarmbees/p/10836505.html)** 3. **[Qt定製控制元件列表](https://blog.csdn.net/qq_30392343/article/details/95527107)** 4. **[牛逼哄哄的Qt庫](https://blog.csdn.net/qq_30392343/article/details/95526527)**
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!


* * * **很重要--轉載宣告** 1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:[朝十晚八](https://www.cnblogs.com/swarmbees/) or [Twowords](https://www.jianshu.com/u/7673f8cfb4e6) 2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的