Windows 上的高DPI 桌面應用程式開發- Win32 apps

文章推薦指數: 80 %
投票人數:10人

多重監視器的安裝,其中每個顯示器都有不同的縮放比例,而應用程式會從一個顯示器移至另一個(例如4K 和1080p 顯示器); 使用低DPI 的外部顯示器來停駐 ... 跳到主要內容 已不再支援此瀏覽器。

請升級至MicrosoftEdge,以利用最新功能、安全性更新和技術支援。

下載MicrosoftEdge 其他資訊 目錄 結束焦點模式 儲存 編輯 共用 Twitter LinkedIn Facebook 電子郵件 WeChat 目錄 Windows上的高DPI桌面應用程式開發 發行項 11/03/2021 m o 此頁面有所助益嗎? 請為您的體驗評分 Yes No 還有其他意見反應嗎? 系統會將意見反應傳送給Microsoft:按下[提交]按鈕,您的意見反應將用來改善Microsoft產品和服務。

隱私權原則。

送出 謝謝。

本文內容 此內容的目標物件是想要更新傳統型應用程式的開發人員,以處理顯示比例因素(每英寸的點或DPI)變更,讓其應用程式在轉譯的任何顯示器上都有清晰的外觀。

首先,如果您要從頭開始建立新的Windows應用程式,強烈建議您建立通用Windows平臺(UWP)應用程式。

UWP應用程式會自動—動態—調整其執行所在的每個顯示器。

使用舊版Windows程式設計技術的桌面應用程式(原始Win32程式設計、WindowsForms、WindowsPresentationFramework(WPF)等)。

)無法自動處理DPI調整,而不需要額外的開發人員工作。

如果沒有這樣的工作,應用程式會在許多常見的使用案例中呈現模糊或大小不正確。

本檔提供有關更新桌面應用程式以正確轉譯的相關內容和資訊。

顯示縮放比例&DPI 隨著顯示器技術的進展,顯示器面板製造商已將遞增的圖元數封裝到其面板上的每個實體空間單位中。

這會導致新式顯示器面板的每英寸(DPI)比以往更高。

在過去,大部分顯示器的實體空間每個線性英寸都有96圖元(96DPI);在2017中,可以立即取得幾乎300DPI或更高版本的顯示器。

大部分舊版桌面UI架構都有內建的假設,在進程存留期間,顯示DPI不會變更。

此假設不再成立,因為在應用程式進程的存留期內,顯示Dpi經常變更數次。

顯示比例因數/DPI變更的一些常見案例如下: 多重監視器的安裝,其中每個顯示器都有不同的縮放比例,而應用程式會從一個顯示器移至另一個(例如4K和1080p顯示器) 使用低DPI的外部顯示器來停駐和移除高DPI的膝上型電腦(或反之亦然) 從高DPI膝上型電腦/平板電腦透過遠端桌面連線至低DPI裝置(或反之亦然) 在應用程式執行時變更顯示比例因數設定 在這些情況下,UWP應用程式會自動為新的DPI重繪其本身。

在預設情況下,如果沒有其他開發人員工作,桌面應用程式就不會執行。

未執行此額外工作來回應DPI變更的桌面應用程式,可能會對使用者顯示模糊或大小不正確。

DPI感知模式 桌面應用程式必須告訴Windows是否支援DPI縮放比例。

根據預設,系統會將桌面應用程式的DPI感知和點陣圖延伸到其視窗。

藉由設定下列其中一種可用的DPI感知模式,應用程式可以明確地分辨Windows其要如何處理DPI調整: 不知道DPI DPI感知應用程式會以固定的DPI值96(100%)轉譯。

當這些應用程式在顯示比例大於96DPI的螢幕上執行時,Windows會將應用程式點陣圖延展到預期的實體大小。

這會導致應用程式出現模糊。

系統DPI感知 以系統DPI感知的桌面應用程式,通常會在使用者登入的時間內,收到主要連線監視器的DPI。

在初始化期間,它們會適當地配置UI,(調整大小控制項、選擇字型大小、載入資產等)使用該系統的DPI值。

如此一來,系統DPI感知的應用程式不會縮放(點陣圖,Windows在以該單一DPI呈現的顯示器轉譯)。

當應用程式移至具有不同縮放比例的顯示器時,或如果顯示比例因數變更時,Windows會以點陣圖調整應用程式的視窗,使其看起來模糊不清。

實際上,系統DPI感知的桌面應用程式只會在單一顯示器縮放比例轉譯crisply,每次DPI變更時變得模糊。

Per-Monitor和Per-Monitor(V2)DPI感知 建議您更新傳統型應用程式,以使用個別監視器DPI感知模式,讓它們可以在每次DPI變更時立即正確轉譯。

當應用程式向Windows報告想要在此模式中執行時,Windows不會在DPI變更時以點陣圖延展應用程式,而是將WM_DPICHANGED傳送至應用程式視窗。

然後,應用程式必須負責處理新DPI的大小調整。

大部分的桌面應用程式所使用的UI架構(Windows的通用控制項(comctl32.dll)、WindowsForms、WindowsPresentationFramework等等。

)不支援自動DPI縮放,因此需要開發人員調整大小並重新調整其視窗的內容。

有兩個版本的Per-Monitor認知,應用程式可以將自己註冊為:第1版和第2版(PMv2)。

註冊在PMv2感知模式中執行的進程會導致: 當DPI變更(最上層和子系Hwnd時,所要通知的應用程式) 應用程式查看每個顯示的原始圖元 應用程式永遠不會依Windows縮放點陣圖 自動的非工作區(視窗標題、捲軸等)DPI縮放比例Windows Win32對話方塊(從CreateDialog)自動以Windows調整的DPI 主題-在通用控制項中繪製點陣圖資產(核取方塊、按鈕背景等)會自動以適當的DPI縮放比例呈現 在Per-Monitorv2感知模式中執行時,會在應用程式的DPI變更時通知應用程式。

如果應用程式不會為新的DPI調整本身的大小,則應用程式UI會出現太小或太大的(,視先前和新的DPI值)的差異而定。

注意 Per-MonitorV1(PMv1)感知的功能非常有限。

建議應用程式使用PMv2。

下表顯示應用程式如何在不同的情況下呈現: DPI感知模式 Windows引進的版本 應用程式的DPI視圖 DPI變更的行為 知道 N/A 所有顯示器都是96DPI 點陣圖延展(模糊) 系統 Vista 所有顯示器都有相同的DPI(在目前的使用者會話開始時主顯示器的DPI) 點陣圖延展(模糊) Per-Monitor 8.1 應用程式視窗主要所在的顯示器DPI 最上層HWND會收到DPI變更的通知沒有任何UI元素的DPI縮放比例。

Per-MonitorV2 Windows10CreatorsUpdate(1703) 應用程式視窗主要所在的顯示器DPI 最上層和子系hwnd會收到DPI變更的通知自動調整DPI規模:非工作區主題-(comctl32.dllV6)的通用控制項中繪製的點陣圖對話方塊(CreateDialog) 每台監視器(V1)DPI感知 Per-MonitorV1DPI感知模式(PMv1)是Windows8.1引進的。

此DPI感知模式相當有限,僅提供下列功能。

建議桌面應用程式使用Windows101703或更新版本上支援的Per-Monitorv2感知模式。

針對個別監視器感知的初始支援僅提供下列應用程式: 最上層的Hwnd會收到DPI變更的通知,並提供新的建議大小 Windows不會以點陣圖延展應用程式UI 應用程式會看到所有顯示的實體圖元(請參閱虛擬化) 在Windows101607或更新版本中,PMv1應用程式也可能會在WMNCCREATE期間呼叫EnableNonClientDpiScaling,_要求Windows正確地調整視窗的非工作區。

UI架構/技術的每個監視器DPI調整支援 下表顯示從Windows101703的各種WindowsUI架構所提供的每個監視器DPI感知支援層級: 架構/技術 支援 作業系統版本 處理的DPI縮放比例 深入閱讀 通用Windows平台(UWP) 完整 1607 UI架構 通用Windows平台(UWP) 原始Win32/通用控制項V6(comctl32.dll) 傳送至所有Hwnd的DPI變更通知訊息主題-繪製的資產會在通用控制項中正確呈現自動調整對話方塊的DPI 1703 應用程式 GitHub樣品 WindowsForms 某些控制項的每個監視器的自動DPI縮放比例有限 1703 UI架構 WindowsForms中的高DPI支援 WindowsPresentationFramework(WPF) 原生WPF應用程式將會以DPI比例調整裝載于其他架構中的WPF,而WPF中裝載的其他架構則不會自動調整 1607 UI架構 GitHub樣品 GDI 無 N/A 應用程式 查看GDI高DPI縮放比例 GDI+ 無 N/A 應用程式 查看GDI高DPI縮放比例 MFC 無 N/A 應用程式 N/A 更新現有的應用程式 若要更新現有的桌面應用程式,以適當地處理DPI調整,必須更新其UI的重要部分,以回應DPI變更。

大部分的桌面應用程式都是以系統DPI感知模式來執行。

系統DPI感知的應用程式通常會調整為主顯示器的DPI,(系統匣在Windows會話啟動)時的顯示器。

當DPI變更時,Windows會以點陣圖延展這些應用程式的UI,這通常會導致它們模糊。

當更新系統DPI感知應用程式,使其變成個別監視器DPI感知時,處理UI配置的程式碼需要更新,如此一來,它不僅會在應用程式初始化期間執行,還會在收到Win32)的情況下(WM_DPICHANGED的DPI變更通知時執行。

這通常需要重新驗證程式代碼中的任何假設,UI只需要調整一次。

此外,在Win32程式設計的情況下,許多Win32Api都沒有任何DPI或顯示內容,因此它們只會傳回相對於系統DPI的值。

在您的程式碼中尋找某些Api,並以DPI感知的變異數取代這些Api,可能會很有用。

有些具有DPI感知變異的常見Api如下: 單一DPI版本 Per-Monitor版本 GetSystemMetrics GetSystemMetricsForDpi AdjustWindowRectEx AdjustWindowRectExForDpi SystemParametersInfo SystemParametersInfoForDpi GetDpiForMonitor GetDpiForWindow 在您的程式碼基底中搜尋採用固定DPI的硬式編碼大小也是個不錯的主意,以正確的DPI縮放比例的程式碼取代它們。

以下是包含所有這些建議的範例: 範例: 下列範例顯示簡化的Win32案例,以建立子系HWND。

對CreateWindow的呼叫會假設應用程式是以96DPI執行,而且在較高的Dpi中,按鈕的大小或位置都不正確: caseWM_CREATE: { //Addabutton HWNDhWndChild=CreateWindow(L"BUTTON",L"ClickMe", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 50, 50, 100, 50, hWnd,(HMENU)NULL,NULL,NULL); } 下列更新的程式碼顯示: 視窗建立程式碼DPI針對其父視窗的DPI調整子HWND的位置和大小 藉由重新置放並調整子系HWND的大小來回應DPI變更 以回應DPI變更的程式碼移除和取代的硬式編碼大小 #defineINITIALX_96DPI50 #defineINITIALY_96DPI50 #defineINITIALWIDTH_96DPI100 #defineINITIALHEIGHT_96DPI50 //DPIscalethepositionandsizeofthebuttoncontrol voidUpdateButtonLayoutForDpi(HWNDhWnd) { intiDpi=GetDpiForWindow(hWnd); intdpiScaledX=MulDiv(INITIALX_96DPI,iDpi,96); intdpiScaledY=MulDiv(INITIALY_96DPI,iDpi,96); intdpiScaledWidth=MulDiv(INITIALWIDTH_96DPI,iDpi,96); intdpiScaledHeight=MulDiv(INITIALHEIGHT_96DPI,iDpi,96); SetWindowPos(hWnd,hWnd,dpiScaledX,dpiScaledY,dpiScaledWidth,dpiScaledHeight,SWP_NOZORDER|SWP_NOACTIVATE); } ... caseWM_CREATE: { //Addabutton HWNDhWndChild=CreateWindow(L"BUTTON",L"ClickMe", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 0, 0, 0, 0, hWnd,(HMENU)NULL,NULL,NULL); if(hWndChild!=NULL) { UpdateButtonLayoutForDpi(hWndChild); } } break; caseWM_DPICHANGED: { //Findthebuttonandresizeit HWNDhWndButton=FindWindowEx(hWnd,NULL,NULL,NULL); if(hWndButton!=NULL) { UpdateButtonLayoutForDpi(hWndButton); } } break; 更新系統DPI感知應用程式時,需要遵循的一些常見步驟如下: 使用應用程式資訊清單(或其他方法,將處理常式標示為個別監視器DPI感知(V2),取決於)使用)的UI架構(。

讓UI版面配置邏輯可重複使用,並將其移出應用程式初始化程式碼,以便_在Windows(Win32)程式設計)的情況下,于發生DPI變更(WMDPICHANGED時重複使用。

將假設DPI機密資料(DPI/字型/大小/等等的任何程式碼失效。

)永遠不需要更新。

在處理常式初始化時快取字型大小和DPI值是非常常見的作法。

更新應用程式以使其成為個別監視器DPI感知時,必須在每次遇到新的DPI時重新評估DPI敏感的資料。

發生DPI變更時,請重載新DPI的(或重新圖像)任何點陣圖資產,或選擇性地將目前載入的資產延展到正確的大小。

不Per-MonitorDPI感知的Api的Grep,並將其取代為適用)Per-MonitorDPI感知Api(。

範例:將GetSystemMetrics取代為GetSystemMetricsForDpi。

在多重顯示/多DPI系統上測試您的應用程式。

如果您的應用程式中有任何最上層視窗無法更新為適當的DPI縮放比例,請使用下列所述的混合模式DPI縮放(),以允許系統將這些最上層視窗的點陣圖延展。

Mixed-ModeDPI調整(子進程的DPI縮放比例) 更新應用程式以支援個別監視器DPI感知時,有時可能會使應用程式中的每個視窗不穩定或無法更新一次。

這可能只是因為更新和測試所有UI所需的時間和精力,或是因為您的應用程式可能會載入協力廠商UI),而不是您需要執行的所有UI程式碼(。

在這些情況下,Windows提供一種方式,讓您能夠輕鬆進入每個監視器的認知,方法是讓您只在其原始的DPI感知模式中執行一些應用程式視窗(最上層),同時將您的時間和精力集中在更新UI的重要部分。

以下是這種情況的圖例:您可以將主要的應用程式UI更新為圖例中的「主視窗」(),以使用個別監視器DPI感知來執行,同時在現有的模式中執行其他視窗(「次要視窗」)。

在Windows10周年更新(1607)之前,進程的DPI感知模式是整個進程的屬性。

從Windows10周年更新開始,您現在可以針對每個最上層視窗設定這個屬性。

(子視窗必須繼續符合其父系的縮放大小。

)最上層視窗會定義為沒有父系的視窗。

這通常是具有最小化、最大化和關閉按鈕的「一般」視窗。

子進程DPI感知的目的是要讓次要ui隨Windows(點陣圖調整),同時將您的時間和資源集中在更新主要UI上。

若要啟用子進程DPI感知,請在任何視窗建立呼叫之前和之後呼叫SetThreadDpiAwarenessCoNtext。

所建立的視窗會與您透過SetThreadDpiAwarenessCoNtext設定的DPI感知相關聯。

使用第二個呼叫來還原目前線程的DPI感知。

雖然使用子進程DPI調整可讓您依賴Windows來為您的應用程式進行某些DPI調整,但它可能會增加應用程式的複雜度。

請務必瞭解這種方法的缺點,以及它所引進之複雜性的本質。

如需子進程DPI感知的詳細資訊,請參閱混合模式Dpi縮放比例和DPI感知api。

測試您的變更 在您更新應用程式以使其成為個別監視器DPI感知之後,請務必驗證應用程式是否正確地回應混合DPI環境中的DPI變更。

測試的一些細節包括: 在不同DPI值的顯示之間來回移動應用程式視窗 在顯示不同DPI值的情況上啟動應用程式 當應用程式正在執行時,變更監視的縮放比例 變更您用來作為主顯示器的顯示器、登出Windows,然後在重新登入之後重新測試您的應用程式。

這在尋找使用硬式編碼大小/維度的程式碼時特別有用。

Win32)(常見的陷阱 不使用在WMDPICHANGED中提供的建議矩形_ 當Windows傳送您的應用程式視窗至WM_DPICHANGED訊息時,此訊息會包含建議的矩形,您應該使用該矩形來調整視窗的大小。

您的應用程式必須使用這個矩形來調整本身的大小,因為這樣會: 確定在顯示器之間拖曳時,滑鼠游標會在視窗上保持相同的相對位置 防止應用程式視窗進入遞迴的DPI變更迴圈,其中一個DPI變更會觸發後續的DPI變更,而這會觸發另一個DPI變更。

如果您有應用程式特定的需求,而無法使用Windows在WMDPICHANGED訊息中提供的建議矩形_,請參閱wm_GETDPISCALEDSIZE。

這則訊息可用來提供Windows在發生DPI變更時所要使用的所需大小,同時仍能避免上述問題。

缺乏虛擬化的相關檔 當HWND或進程以DPI感知或系統DPI感知的形式執行時,可以Windows來延展點陣圖。

發生這種情況時,Windows會將某些api的DPI機密資訊調整並轉換成呼叫執行緒的座標空間。

例如,如果不知道DPI的執行緒在高DPI顯示器上執行時查詢螢幕大小,Windows將會虛擬化指定給應用程式的答案,就像螢幕是96DPI單位一樣。

或者,當目前使用者的會話開始時,當系統DPI感知執行緒與不同DPI上的顯示器互動時,Windows會將某些API呼叫調整成以其原始的DPI縮放比例執行時,將會使用此HWND所使用的座標空間。

當您將傳統型應用程式更新為正確調整時,可能很難知道哪些API呼叫可以根據執行緒內容傳回虛擬化值;這項資訊目前不是由Microsoft所充分記載。

請注意,如果您從不感知DPI或系統DPI感知的執行緒內容呼叫任何系統API,則傳回值可能會虛擬化。

因此,請確定您的執行緒是在與螢幕或個別視窗互動時所預期的DPI內容中執行。

使用SetThreadDpiAwarenessCoNtext暫時變更執行緒的DPI內容時,請務必在您完成時還原舊的內容,以避免在應用程式中的其他位置造成不正確的行為。

許多Windowsapi都沒有DPI內容 許多舊版Windowsapi都不會在其介面中包含DPI或HWND內容。

因此,開發人員通常必須執行額外的工作來處理任何DPI機密資訊的調整,例如大小、點或圖示。

例如,使用LoadIcon的開發人員必須使用點陣圖延展已載入的圖示,或使用替代的api來載入適當DPI大小的正確圖示,例如LoadImage。

強制重設整個進程的DPI感知 一般而言,進程初始化之後,就無法變更您進程的DPI感知模式。

但是,如果您嘗試中斷視窗樹狀結構中的所有hwnd都有相同的DPI感知模式,Windows可以強制變更您進程的DPI感知模式。

在所有版本的Windows(從Windows101703),在不同的DPI感知模式中,hwnd樹狀結構中不能有不同的hwnd。

如果您嘗試建立中斷此規則的父子式關聯性,可以重設整個進程的DPI感知。

這可以透過下列方式觸發: 一個CreateWindow呼叫,其中傳入的父視窗是與呼叫執行緒不同的DPI感知模式。

這兩個視窗與不同DPI感知模式相關聯的SetParent呼叫。

下表顯示當您嘗試違反此規則時所發生的情況: 作業 Windows8.1 Windows10(1607及更早版本) Windows10(1703和更新版本) 在進程內)的CreateWindow( N/A 子系繼承(混合模式) 子系繼承(混合模式) (跨進程)的CreateWindow 呼叫端進程的強制重設() 子系繼承(混合模式) 呼叫端進程的強制重設() SetParent(同進程) N/A 目前進程的強制重設() 失敗(錯誤_的_狀態無效) SetParent(跨進程) 子視窗進程的強制重設() 子視窗進程的強制重設() 子視窗進程的強制重設() 相關主題 高DPIAPI參考 混合模式DPI縮放比例和DPI感知Api。

本文內容



請為這篇文章評分?