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 以上)會呼叫它。預設值是 SystemAware3 建議把設定寫在專案檔中,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>

有一點要注意,專案檔中的 ApplicationHighDpiModeApplicationConfiguration.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/MFC 應用程式的高DPI支援(現況調查、支援程度判斷、版面配置改善),以及因換機而產生的顯示異常原因調查、UI 更新諮詢等業務。

參考連結

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. 說明 DPI 縮放的背景、各 DPI 感知模式的行為、不支援 DPI 的應用程式如何被點陣圖放大,以及混合 DPI 環境下的測試觀點。  2 3 4

  2. Microsoft Learn, Setting the default DPI awareness for a process. 說明透過 manifest 的 dpiAware/dpiAwareness 元素宣告的方式、兩者的優先順序,以及為何不建議透過 API 宣告。  2

  3. Microsoft Learn, What’s new in Windows Forms .NET 6. 說明透過 ApplicationConfiguration.Initialize 的啟動流程、專案檔的 ApplicationHighDpiMode 設定(預設 SystemAware),以及 .NET 6 中 PerMonitorV2 縮放的改善內容。  2 3 4

  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

  5. Microsoft Learn, Fix DPI display issues in Windows Form Designer. 說明 WinForms 設計工具不支援 DPI、在高DPI螢幕上顯示的資訊列可用來以100%縮放重新啟動、devenv /noScale,以及針對 .NET 6+ 的 ForceDesignerDPIUnaware。  2 3

  6. 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

  7. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. 說明透過 SetThreadDpiAwarenessContext 在最上層視窗層級混用 DPI 感知模式,以及 GetDpiForWindow 等支援 DPI 的 API。 

  8. Microsoft Learn, SetProcessDpiAwareness function. 說明透過 API 設定行程預設 DPI 感知的方式、官方建議改用 manifest 宣告的原因,以及一旦設定後無法變更這一點。 

  9. Microsoft Learn, Automatic form scaling - Windows Forms. 說明 AutoScaleMode/AutoScaleDimensions/CurrentAutoScaleDimensions 的自動縮放行為,以及 Font 與 Dpi 模式混用不受支援這一點。  2

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

為什麼 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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽