WinForms 的高DPI支援 - 4K螢幕顯示模糊、版面跑掉的原因與實務對策
· 小村 豪 · WinForms, 高DPI, Windows, .NET, C#, .NET Framework, 舊有系統維護, UI, 技術諮詢
「換了新筆電之後,商用應用程式的文字變得模糊難以閱讀」「接上4K螢幕後,按鈕和標籤重疊在一起」「只有在客戶端125%的環境下,報表預覽會跑版」。這幾年,每到換機時期,這類諮詢就會大量增加。超越Full HD的高解析度顯示器,以及125~200%的顯示縮放,如今在商用電腦上也已是標準配置,於是在96 DPI、100%顯示時代開發的 WinForms 應用程式,就無法直接維持良好的顯示效果。可以說,在舊版 WinForms 應用程式的維護諮詢中,這是目前出現頻率最高的主題。
麻煩的是,症狀看起來五花八門,有「模糊」「跑版」「只有一部分變小」等各種樣態。實際上原因可以整理成幾種類型,只要弄清楚哪種症狀對應哪種原因,就能判斷該怎麼修,也能判斷該做到什麼程度。本文以 Windows 的 DPI 縮放機制與 DPI 感知模式為基礎,從實務角度整理 WinForms 應用程式(.NET/.NET Framework 皆適用)的高DPI支援,涵蓋設定方式、常見陷阱到分階段的應對策略。
1. 先講結論
- 在高DPI環境下,舊版應用程式顯示模糊並不是錯誤,而是作業系統的補救措施(DPI 虛擬化)。Windows 讓不支援 DPI 的應用程式以為自己在96 DPI下繪製,再把結果以點陣圖放大,因此版面不會跑掉,但畫面會模糊。1
- 反過來,「畫面清晰卻跑版」是已經宣告支援 DPI,但版面配置沒能跟上的狀態。模糊與跑版的原因恰好相反,動手處理之前,請先分辨清楚是哪一種。
- 應對的第一步,是掌握自己的應用程式目前是以哪種 DPI 感知模式(Unaware/System Aware/Per-Monitor V2)在運作。請確認是透過 manifest、app.config、API 呼叫中的哪一種(或完全沒有)進行宣告。2
- 設定方式依世代而異。.NET(Core 3.1~.NET 8)可用專案檔或
Application.SetHighDpiMode;.NET Framework 4.7 以上用 app.config;4.6 以下則現實上只能靠 manifest 宣告到 System Aware 為止。34 - 設計工具一定要在100%(96 DPI)的螢幕上開啟。在150%環境下開啟表單並儲存,會改寫
AutoScaleDimensions,變成讓整個團隊的版面配置全部跑掉的經典事故。5 - 全面支援(Per-Monitor V2)需要不少工作量。以「先用 System Aware 讓主螢幕清晰」為第一階段,PMv2 完全支援為第二階段的兩階段做法,依應用程式的使用年限與使用環境投入相應的資源,比較現實。
- 若公司內部無法自行改善或時間不夠,作為權宜之計,也值得了解從 exe 的內容→相容性可由使用者端覆寫的「高DPI設定」,這能讓處理客服詢問時輕鬆不少。6
2. 為什麼會模糊 - DPI 虛擬化的原理
Windows 的顯示縮放,是以96 DPI為100%的倍率來表示。125% = 120 DPI,150% = 144 DPI,200% = 192 DPI。若在4K(3840×2160)的27吋螢幕上以100%使用,文字會太小,因此 Windows 預設會建議150%左右。也就是說,高解析度顯示器的普及,直接意味著「以非96 DPI運作的環境」的普及。
問題在於,許多舊版桌面應用程式都是在「螢幕永遠是96 DPI」的前提下寫成的。座標、字型、圖示都直接寫死成像素值,若在150%環境下直接繪製,所有東西看起來都會變成物理尺寸的2/3大小。因此,Windows 會對沒有宣告支援 DPI 的應用程式謊稱「螢幕是96 DPI」,讓應用程式以為自己在96 DPI下繪製,再把結果以點陣圖放大顯示。這就是 DPI 虛擬化(點陣圖拉伸)。1
了解這個機制後,症狀與原因就能清楚地對應起來。請用下表進行初步的判斷。
| 症狀 | 原因 | 狀態 |
|---|---|---|
| 整體均勻地模糊、失焦,版面不會跑掉 | DPI 虛擬化(作業系統在放大點陣圖) | 不支援 DPI(Unaware)。安全但不美觀 |
| 文字清晰,但控制項重疊或被裁切 | 已宣告支援 DPI,但版面配置沒能跟上 | 支援不完整。這是改善的對象 |
| 主螢幕上清晰,移到副螢幕就模糊 | System Aware(DPI 固定為啟動時主螢幕的值) | 依規格運作。是否要改用 PMv2 需要判斷 |
| 文字大小沒問題,但圖示、圖片偏小或粗糙 | 點陣圖資源仍是96 DPI用的 | 圖片資源尚未支援(第6章) |
| 只有特定畫面、控制項跑版 | 固定座標版面、自繪內容、第三方控制項 | 個別改善的對象(第6章) |
重點在於,「顯示模糊」的應用程式其實並沒有壞掉。作業系統的補救措施正在正確發揮作用。所謂的改善,就是切斷這個補救措施,宣告「我自己來處理縮放」(也就是提升 DPI 感知模式),因此宣告的那一刻起,版面配置的問題就全部變成自己的責任。「先試著改成 PMv2,結果反而變得更糟」這類諮詢,正是在這種結構下發生的。
3. 整理 DPI 感知模式
應用程式以行程(更精確地說,從 Windows 10 起是以最上層視窗)為單位,向作業系統宣告自己能處理DPI到什麼程度。模式實質上有4種。16
| 模式 | 導入版本 | 應用程式看到的 DPI | 跨螢幕移動、DPI 變更時 | 在 WinForms 中的實務定位 |
|---|---|---|---|---|
| Unaware | ── | 永遠是96 | 作業系統放大點陣圖(模糊) | 沒有任何宣告時的預設值。模糊但不會跑版 |
| System Aware | Vista | 登入時固定為主螢幕的 DPI | 主螢幕以外或變更後由作業系統放大(模糊) | 所有世代皆可使用。務實對策的主力 |
| Per-Monitor (V1) | 8.1 | 視窗所在螢幕的 DPI | 只通知最上層視窗,縮放全部要自行處理 | 缺乏框架支援,實用性低。不建議選用 |
| Per-Monitor V2 | 10 (1703) | 視窗所在螢幕的 DPI | 連子視窗都會通知,非用戶端區域由作業系統自動追蹤 | 完整支援的主力。可用於 .NET Framework 4.7+ / .NET |
Per-Monitor V1 與 V2 之間的差異,在實務上具有決定性。V1 是「只給通知,剩下全部自己處理」的原生 Win32 機制,沒有理由在 WinForms 中使用。V2 則會由作業系統自動縮放標題列、捲軸、選單等非用戶端區域,WinForms 端的框架支援(後述)也是以 V2 為前提所建構的。若要選擇 Per-Monitor,V2 是唯一的選擇。6
另外還有一種模式叫做GDI 縮放(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED,Windows 10 1809~)。應用程式本身仍維持 Unaware,但由 GDI 繪製的文字或圖形,會由作業系統以向量等級放大,是點陣圖放大的改良版本。6 因為有機會在完全不修改程式碼的情況下減輕文字模糊的程度,值得記住作為無法改善的應用程式的權宜之計(4.3節)。
另外,從 Windows 10 1607 起,也有以最上層視窗為單位混用不同模式的 Mixed-Mode,Win32 層級支援「主畫面用 PMv2,還沒修完的對話框維持 Unaware 讓作業系統放大」這種分階段遷移的做法。7 雖然這不是能從 WinForms 輕鬆使用的功能,但「不必一次修完所有畫面」的設計思路,會延伸到第7章的分階段策略。
4. 設定方法 - 依世代而定的正解
依自己的應用程式屬於哪個世代,宣告 DPI 感知模式的方式也不同。這裡如果搞錯,就會在「設定了卻沒生效」上浪費時間,因此以下依世代分別說明。
4.1 .NET(Core 3.1~.NET 8)的 WinForms
.NET 提供了 Application.SetHighDpiMode,範本產生的 ApplicationConfiguration.Initialize()(.NET 6 以上)會呼叫它。預設值是 SystemAware。3 建議把設定寫在專案檔中,Visual Studio 的設計工具也會參照這個設定。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<!-- SystemAware(預設)/ PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
</Project>
有一點要注意,專案檔中的 ApplicationHighDpiMode 與 ApplicationConfiguration.Initialize() 是.NET 6 才引入的機制。3 在 .NET Core 3.1/.NET 5 專案中,即使寫了這個屬性也不會生效,因此要像下面這樣直接在程式碼中呼叫 Application.SetHighDpiMode(不使用 ApplicationConfiguration.Initialize() 的舊式 Main 也是同樣做法)。無論哪種情況,都必須在建立任何一個視窗之前呼叫。
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
.NET 6 以後,PMv2 情境下容器控制項與 MDI 子視窗的縮放已有所改善,「從200%螢幕移到100%螢幕時控制項會跑掉」這類直到 .NET 5 都存在的問題已大幅解決。3 若要認真投入 PMv2 支援,個人的實際感受是比起 .NET Framework,用較新的 .NET 明顯更容易應付。關於是否要從 .NET Framework 遷移的參考資料,整理在「.NET Framework 遷移到 .NET 前的檢查清單」中。
4.2 .NET Framework 4.7 以上
.NET Framework 在4.7版大幅強化了高DPI支援,加入了控制項縮放改善、DPI 變更事件(DpiChanged 系列)、DeviceDpi 屬性等。不過這是選擇性開啟(opt-in)的功能,必須同時設定以下兩點才會生效。4
首先,在 manifest 中宣告 Windows 10 相容性(沒有這個宣告,4.7 的高DPI功能本身就不會啟用)。
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 相容性宣告 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
接著,在 app.config 的 System.Windows.Forms.ApplicationConfigurationSection 中宣告 DPI 感知模式。
<configuration>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
<!-- 若已有畫面自行處理過縮放,可以個別關閉這項功能 -->
<!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
同時請確認 Main 的開頭有呼叫 Application.EnableVisualStyles()。這裡有一點要注意:以前那種在 manifest 中寫 <dpiAware> / <dpiAwareness> 的方式,會覆蓋掉 app.config 的設定,因此官方建議在4.7的 WinForms 中不要同時使用兩者。4 如果過去為了改成 System Aware,曾在 manifest 中寫入 <dpiAware>true</dpiAware> 的應用程式,要升級到4.7+PMv2 時,得先從整理 manifest 端的宣告開始。「明明寫在 app.config 卻沒生效」的原因,多半就是這個。
4.3 .NET Framework 4.6 以下・VB6/MFC 混用
4.6 以下的 WinForms 沒有支援 PMv2 的框架程式碼,即使勉強宣告,也得靠自己處理所有版面跑掉的問題。這個世代的現實解法,是做到用 manifest 宣告 System Aware 為止。作業系統層級的宣告,官方建議使用 manifest;同時寫上 <dpiAware>(Vista~)與 <dpiAwareness>(Windows 10 1607~),可以讓新舊作業系統都按照預期解讀。2
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
改為 System Aware 後,WinForms 的自動縮放機制(第5章)會依啟動時主螢幕的 DPI 運作一次,讓主螢幕上的顯示保持清晰。如果表單正確設定了 AutoScaleMode.Font,光靠這個宣告,通常就能得到相當可接受的顯示效果。另外,雖然也可以用 SetProcessDpiAwareness 之類的 API 在執行期宣告,但視窗建立後就無法變更,官方仍建議透過 manifest 宣告。8 對於混用 VB6 或 MFC 程式碼的應用程式,想法也是一樣的:exe 的 manifest 決定整個行程的模式(MFC 本身沒有自動縮放的支援,因此宣告 System Aware 後,逐一檢查畫面是必要的。關於這個世代資產的處理方式,也可以參考「Windows 的 MFC 是什麼」)。
也介紹一下無法改善時使用者端的權宜之計。在 exe 上按右鍵→內容→相容性頁面→「變更高 DPI 設定」,可以覆寫該應用程式的 DPI 縮放行為。將「取代高 DPI 縮放行為」設為「系統」,會強制套用 DPI 虛擬化(=不會跑版但會模糊);設為「系統(增強)」,則會套用第3章提到的 GDI 縮放,只有 GDI 繪製的文字會變清晰。6 這並非萬能藥,但作為「客戶現場今天就想解決」時可以在電話中指導的方法,值得寫進支援作業手冊。
5. AutoScaleMode 與設計工具的陷阱
WinForms 除了 DPI 感知模式之外,表單本身還有一套自動縮放機制。如果不理解這一點,即使改成 System Aware,也無法正確縮放。
原理是這樣的:在設計階段,每個表單(ContainerControl)會把縮放基準記錄在 AutoScaleMode,並把設計時環境的基準值記錄在 AutoScaleDimensions。執行時會與目前環境的值(CurrentAutoScaleDimensions)比較,若有差異,就一併縮放所有子控制項。9 也就是說,這是用來吸收「設計時與執行時環境差異」的機制,基準值會寫進程式碼(Designer.cs)中。
AutoScaleMode 的選項實質上有2種。
| AutoScaleMode | 基準 | 特徵 |
|---|---|---|
| Font(建議・預設) | 表單字型的尺寸 | DPI 提高時系統字型的實際尺寸也會改變,因此兼具追蹤 DPI 的效果。也會追蹤使用者的字型設定變更 |
| Dpi | 螢幕的 DPI | 只與 DPI 成比例。適合以繪圖為主的畫面 |
| None | ── | 停用自動縮放。直接寫死96 DPI值的應用程式多半是這個狀態 |
若不確定,就選 Font。要注意的是,官方文件也明確指出,在基底表單與派生表單之間混用不同模式,會產生非預期的結果。9 若應用程式使用了表單繼承,第一步就是先盤點並統一所有表單的模式。
接下來就是重點陷阱了。由於 AutoScaleDimensions 是用來記錄「設計時環境的值」的機制,在150%的螢幕上開啟 Visual Studio 設計工具並儲存表單時,Designer.cs 的 AutoScaleDimensions 就會被改寫成150%的數值(在 Font 模式下,例如6F、12F會變成9F、18F)。座標與大小也會以1.5倍的值儲存。100%環境的成員建置並執行這個表單時,整體會縮小顯示,程式碼審查時也會看到所有控制項座標都變動的巨大差異。「配發了高DPI筆電的新成員動了一次表單,結果整個儲存庫的版面配置都跑掉了」,這是高DPI諮詢中的經典事故。
對策原則上只有一個:設計工具一定要在100%(96 DPI)下開啟。Visual Studio 本身是支援 DPI 的應用程式,但 WinForms 設計工具(針對 .NET Framework)並不支援 DPI,因此在高DPI螢幕上開啟時,會出現黃色資訊列,提示「以100%縮放重新啟動 Visual Studio」。5 請依照這則資訊列以不支援 DPI 的模式重新啟動,再編輯表單,完成後再切回一般模式,並將這套流程訂為團隊規範。也可以用命令列 devenv /noScale 啟動。另外,在 .NET 6 以上的專案中,若使用 Visual Studio 2022 17.8 以上,可在專案檔設定 <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>,這樣不需重新啟動整個 VS,只讓設計工具分頁以不支援 DPI 的方式運作(.NET Framework 專案無法使用)。5
若要把檢查機制化,「用 CI 或 pre-commit 擋掉 Designer.cs 的 AutoScaleDimensions 變成非預期數值的差異」是既簡單又有效的做法。比起事故發生後再修正,在提交的入口就擋下來成本更低。
6. 常見的跑版模式與修正方法
提升(或準備提升)DPI 感知模式時會跑版的地方,大致可以歸納成下面幾種模式。
| 跑版的內容 | 原因 | 修正方法 |
|---|---|---|
| 控制項重疊、被裁切 | 固定座標、固定大小的版面配置 | 改用 Anchor/Dock、TableLayoutPanel/FlowLayoutPanel。善用 AutoSize |
| 圖示、圖片偏小或粗糙 | 直接貼上96 DPI用的點陣圖 | 準備多種解析度的圖片並依 DPI 切換。可行的話從一張較大的原圖縮小 |
| 自繪內容(圖表、圖面、報表預覽) | 直接把像素值寫在 Graphics 中 | 以 DeviceDpi 為基準縮放座標、線寬、字型 |
| DataGridView 的行變得擁擠 | RowHeight 等以像素指定 | 使用 AutoSizeRowsMode,或設定依 DPI 縮放後的值 |
| ToolStrip 的圖示變得很小 | 固定為16×16 | 依 DPI 設定 ImageScalingSize |
| 只有特定的第三方/ActiveX 控制項跑版 | 控制項本身不支援 DPI | 更新為廠商支援的版本。若沒有,那裡就是支援程度的上限 |
替換版面配置是工作量的主要部分。反過來說,原本就用 TableLayoutPanel 和 Dock 建構的畫面,即使提升 DPI 感知模式,幾乎不需要額外處理。只要規定今後新畫面一開始就用版面配置面板來建構,就能減少未來的技術債。
自繪內容的縮放,基本做法是以 Control.DeviceDpi(.NET Framework 4.7+ / .NET)為基準,換算96 DPI對應的數值。
public partial class ChartPanel : Panel
{
// 將以96 DPI為基準的設計值換算成目前的 DPI
private int Scale(int value96) => value96 * DeviceDpi / 96;
protected override void OnPaint(PaintEventArgs e)
{
using var pen = new Pen(Color.Navy, Scale(2));
e.Graphics.DrawRectangle(pen,
Scale(16), Scale(16), Scale(320), Scale(120));
}
// PMv2 下跨螢幕移動時 DPI 會改變,因此觸發重新繪製
protected override void OnDpiChangedAfterParent(EventArgs e)
{
base.OnDpiChangedAfterParent(e);
Invalidate();
}
}
做到 System Aware 為止,DPI 在啟動時就固定了,只要導入 Scale 即可;但在 PMv2 下,每次跨螢幕移動 DeviceDpi 就會改變,因此需要設計成在 DpiChanged 系列事件中重新建立快取的字型、圖片、版面配置值。4 「有多少個自繪畫面」會直接影響 PMv2 支援的工作量估算。
第三方控制項與 ActiveX/OCX 是決定這次改善上限的因素。如果控制項本身不支援 DPI,無論主應用程式端怎麼努力,那個畫面都無法做到完美。請先確認廠商的支援狀況,對於不太可能更新的 ActiveX,用「維持、包裝,還是替換 ActiveX/OCX」的判斷表決定處理方式,再設定高DPI支援的目標。
7. 分階段的應對策略 - 做到什麼程度的判斷表
綜合以上內容,可以把支援程度整理成3個階段。將所有應用程式都做到 PMv2,是技術上的理想,但考量改善預算與應用程式的使用年限,很多情況下適度取捨才是正確答案。
| (1) 什麼都不做(Unaware) | (2) 改成 System Aware | (3) PMv2 完全支援 | |
|---|---|---|---|
| 顯示效果 | 所有環境都模糊(不會跑版) | 主螢幕清晰。副螢幕、DPI 變更時模糊 | 所有螢幕都清晰 |
| 主要工作 | 無 | 宣告 manifest/設定 + 檢查 AutoScaleMode + 確認所有畫面的顯示 | (2) + 全面檢查版面配置 + 自繪內容・圖片資源的 DPI 支援 + 處理 DpiChanged |
| 工作量感受 | 零 | 小~中(主要是與畫面數成比例的確認工作) | 大(主要是修正跑版;第三方控制項會決定上限) |
| 適合情況 | 幾年內即將淘汰。使用者可以接受 | 以固定桌機、單一螢幕為主的內部應用程式。作為第一階段 | 筆電+外接螢幕混用。長期使用的主力應用程式。要配發給客戶的產品 |
判斷的軸線有三個:應用程式的使用年限(還會用幾年)、使用環境(如果大家都用同樣的固定桌機,用 System Aware 實質上就足夠了;如果筆電+外接螢幕的組合較多,每次移動螢幕都會模糊的 System Aware,就會讓人不太滿意),以及改善預算。建議的做法是,先把 (2) 當作所有應用程式的標準來實施,再根據過程中發現的跑版數量與第三方控制項的限制,來決定是否要推進到 (3),或者只針對哪些畫面推進。(2) 以「宣告+檢查」為主,失敗風險小,同時也能大幅改善使用者的體感。
也稍微談一下測試。高DPI的問題,很多時候在開發機上無法重現,因此要刻意建立以下環境來確認。1
- 連接兩台縮放比例不同的螢幕(例如:150%的筆電內建螢幕+100%的外接螢幕),讓視窗在兩者之間來回移動
- 更換主螢幕之後重新登入再啟動(System DPI 會在登入時固定,若不重新登入就不會切換)
- 在應用程式執行期間變更縮放設定
- 從高DPI的用戶端透過遠端桌面連線(因為 RDP 會帶入用戶端的 DPI,「伺服器主控台正常,但用 RDP 連線就跑版」是常見的回報)
如果收到「只有在125%下才會跑版」的回報,實際重現那個縮放比例來觀察,終究是最快的方法,這是經驗法則。虛擬機也能變更縮放設定,因此事先準備好100/125/150/200%的驗證環境,可以加快問題排查的速度。
8. 總結
高DPI環境下的「模糊」是作業系統的補救措施(DPI 虛擬化),「跑版」則是支援不完整的 DPI 支援——只要掌握這個對應關係,就能從症狀反推原因與對策。設定方式依世代而定:.NET 用專案檔的 ApplicationHighDpiMode;.NET Framework 4.7以上用 app.config(不與 manifest 的 dpiAware 併用);4.6以下則靠 manifest 宣告到 System Aware 為止。再加上統一使用 AutoScaleMode.Font,以及設計工具一定要在100%下開啟的作業規範,雖然不起眼,但最能減少事故。
在此基礎上,該做到什麼程度,可以從第7章的3個階段中選擇。先透過 System Aware 讓主螢幕變清晰,若使用環境與應用程式的使用年限合適,再進到 PMv2——這樣的兩階段做法,是本公司在實際改善專案中實際推薦的進行方式。如果已經到了該重新檢視 UI 框架本身的時機(WPF 從設計之初就對 DPI 有較強的支援),也請把「如何選擇 WinForms/WPF/WinUI」納入參考。如果不確定手上的應用程式能修到哪個階段,我們可以從盤點畫面結構與控制項結構開始協助。
相關文章
- 如何選擇 WinForms/WPF/WinUI - 判斷表
- 維持、包裝,還是替換 ActiveX/OCX - 判斷表
- .NET Framework 遷移到 .NET 前的檢查清單
- Windows 的 MFC 是什麼 - 它是什麼、現在該如何處理
相關諮詢領域
合同會社小村軟體承接舊版 WinForms/MFC 應用程式的高DPI支援(現況調查、支援程度判斷、版面配置改善),以及因換機而產生的顯示異常原因調查、UI 更新諮詢等業務。
參考連結
-
Microsoft Learn, High DPI Desktop Application Development on Windows. 說明 DPI 縮放的背景、各 DPI 感知模式的行為、不支援 DPI 的應用程式如何被點陣圖放大,以及混合 DPI 環境下的測試觀點。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Setting the default DPI awareness for a process. 說明透過 manifest 的 dpiAware/dpiAwareness 元素宣告的方式、兩者的優先順序,以及為何不建議透過 API 宣告。 ↩ ↩2
-
Microsoft Learn, What’s new in Windows Forms .NET 6. 說明透過 ApplicationConfiguration.Initialize 的啟動流程、專案檔的 ApplicationHighDpiMode 設定(預設 SystemAware),以及 .NET 6 中 PerMonitorV2 縮放的改善內容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, High DPI support - Windows Forms. 說明 .NET Framework 4.7 的高DPI強化內容、app.config 的 System.Windows.Forms.ApplicationConfigurationSection(DpiAwareness=PerMonitorV2)、manifest 宣告會覆蓋 app.config 因而不建議併用的原因,以及 DpiChanged 系列事件與 DeviceDpi。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Fix DPI display issues in Windows Form Designer. 說明 WinForms 設計工具不支援 DPI、在高DPI螢幕上顯示的資訊列可用來以100%縮放重新啟動、devenv /noScale,以及針對 .NET 6+ 的 ForceDesignerDPIUnaware。 ↩ ↩2 ↩3
-
Microsoft Learn, DPI_AWARENESS_CONTEXT handle. 說明 Unaware/System Aware/Per-Monitor/Per-Monitor V2/UNAWARE_GDISCALED(GDI 縮放,Windows 10 1809~)各種 context 的定義與行為。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. 說明透過 SetThreadDpiAwarenessContext 在最上層視窗層級混用 DPI 感知模式,以及 GetDpiForWindow 等支援 DPI 的 API。 ↩
-
Microsoft Learn, SetProcessDpiAwareness function. 說明透過 API 設定行程預設 DPI 感知的方式、官方建議改用 manifest 宣告的原因,以及一旦設定後無法變更這一點。 ↩
-
Microsoft Learn, Automatic form scaling - Windows Forms. 說明 AutoScaleMode/AutoScaleDimensions/CurrentAutoScaleDimensions 的自動縮放行為,以及 Font 與 Dpi 模式混用不受支援這一點。 ↩ ↩2
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
WPF 高 DPI 對應──「不是應該對 DPI 很強嗎」卻仍模糊、暈邊的原因與對策
WPF 以 DIP(1/96 英吋)進行版面配置,一開始就是 System DPI Aware,但移到 DPI 不同的螢幕時整體會暈邊,點陣圖與細線也會模糊。本文從實務角度整理原因判斷、.NET Framework 4.6.2/.NET 上的 Per-Monitor DPI...
在 WinForms/WPF 應用程式中導入 Entra ID 驗證 ── MSAL.NET 與 WAM 代理的實務架構
從實務角度整理在 WinForms/WPF 桌面應用程式中導入 Entra ID(原 Azure AD)驗證的做法。內容涵蓋公用客戶端的概念、ROPC 廢除現況、應用程式註冊、MSAL.NET 的 AcquireTokenSilent 模式、WAM 代理、權杖快取持久化,以...
業務應用程式的日期時間與時區 ── 從 DateTime 的陷阱到 UTC 儲存原則、測試設計
伺服器搬遷後時間整整差了 9 個小時、只有海外據點的日期會變成前一天──本文從 DateTime 的 Kind 與隱式轉換整理日期時間事故的根本原因。內容涵蓋與 DateTimeOffset 的取捨、UTC 儲存與 ISO 8601 原則、TimeZoneInfo 與夏令時...
如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
整理 Windows 應用程式開發者容易混淆的「工作階段」概念。說明服務無法顯示 UI 的 Session 0 隔離原因、RDP 連線時工作階段的行為、具名物件的工作階段隔離,以及共用 PC・RDS 環境常見的設計失誤,從實務角度解說。
防止 Windows 應用程式重複啟動 ── 具名 Mutex 與重複執行時的視窗前置
整理業務用 Windows 應用程式的常見需求「不讓同一個應用程式啟動兩次」,如何用具名 Mutex 實作。涵蓋 Global\ 與 Local\ 命名空間差異在 RDP 環境中的陷阱、擁有執行緒限制與 AbandonedMutexException、考量 SetForeg...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
UI 執行緒 & 計時器
整理 WPF / WinForms UI 執行緒、非同步流程、Dispatcher 使用、計時器判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
常見問題
整理諮詢這個主題時常見的問題。
- 為什麼 WinForms 應用程式在4K螢幕上會顯示模糊?
- 這不是錯誤,而是作業系統的補救措施(DPI 虛擬化)。對於沒有宣告 DPI 感知的應用程式,Windows 會謊稱「螢幕是 96 DPI」,讓應用程式以為自己在 96 DPI 環境下繪製,再把結果以點陣圖放大顯示,因此版面不會跑掉,但畫面會模糊。反過來,「畫面清晰卻跑版」則是已經宣告 DPI 感知,但版面配置沒能跟上的狀態,原因恰好相反。動手處理之前,先分辨清楚是哪一種症狀,是第一步。
- WinForms 的高DPI支援該怎麼設定?
- 方法依世代而異。.NET 6 以上可在專案檔設定 ApplicationHighDpiMode 屬性(預設為 SystemAware);.NET Core 3.1/.NET 5 則要在建立視窗之前呼叫 Application.SetHighDpiMode。.NET Framework 4.7 以上需先在 manifest 宣告 Windows 10 相容性,再用 app.config 設定 DpiAwareness,但 manifest 裡的 dpiAware 寫法會覆蓋 app.config 的設定,官方建議不要同時使用兩者。4.6 以下則現實上只能靠 manifest 宣告到 System Aware 為止。
- System Aware 與 Per-Monitor V2 該選哪一個?
- 建議採取兩階段做法。第一階段是改成 System Aware,會依啟動時主螢幕的 DPI 縮放,在主螢幕上顯示清晰。這個階段以宣告與檢查為主,失敗風險小,若是固定桌機為主的內部應用程式,實務上做到這裡就足夠。Per-Monitor V2 可以在跨螢幕移動時也保持全畫面清晰,但需要全面檢查版面配置、讓自繪內容與圖片資源支援 DPI、處理 DpiChanged 事件,工作量較大,若第三方控制項不支援,這就會變成上限。要依應用程式的使用年限、使用環境與改善預算來判斷。
- 為什麼在設計工具中儲存表單後,版面就跑掉了?
- 在150%等高DPI螢幕上開啟 Visual Studio 的 WinForms 設計工具並儲存表單時,Designer.cs 裡的 AutoScaleDimensions 會被改寫成150%的數值,座標與大小也會以1.5倍的值儲存。100%環境的成員建置這個表單時,畫面就會整體縮小顯示。對策是永遠在100%(96 DPI)下開啟設計工具:在高DPI螢幕上會出現黃色資訊列,可以用它以100%縮放重新啟動。在.NET 6以上的專案中,也可以使用 ForceDesignerDPIUnaware 設定。用 CI 或 pre-commit 擋掉 AutoScaleDimensions 出現非預期差異的提交,也是有效的機制化做法。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。