c# - DPI意識-在一個版本中不知道,在其他版本中意識到系統



.net winforms (1)

關於本課題報告的問題
一個應用程序,DPI不知道設計,依靠Windows虛擬化來擴展其UI內容,突然(雖然經過一些修改,導致次要版本更新) - 並且顯然沒有可觀察到的原因 - 成為DPI-Aware(系統意識) )。

  • 該應用程序還依賴於 app.manifest <windowsSettings> 的解釋,其中缺少DPI感知定義,默認(對於向後兼容性)DPI-Unaware。

  • 沒有對WPF程序集的直接引用,也沒有與DPI相關的API調用。

  • 該應用程序包括第三方組件(可能還包括外部依賴項)。

由於DPI-Awareness已經成為UI呈現的一個相關方面,考慮到可用屏幕分辨率的多樣性(以及相關的DPI縮放設置),大多數組件生產商已經適應了高DPI,他們的產品是DPI-Aware(DPI更改時的擴展)檢測到並使用DPI-Aware程序集(通常根據定義引用WPF程序集,DPI-Aware)。

當在項目中(直接或間接)重新引用其中一個DPI-Aware組件時,如果未明確禁用DPI-Awareness,則DPI-Unaware應用程序將成為DPI-Aware。

聲明程序集DPI-Awareness的更直接(和推薦)方法是在應用程序清單中明確聲明它。

有關Visual Studio 2017之前的應用程序清單設置,請參閱 Hans Passant 答案:
如何配置應用程序以在具有高DPI設置的計算機上運行

在Visual Studio 2015-Upd.1和Visual Studio 2017 app.manifest ,此設置已存在,只需取消註釋即可。 設置部分: <dpiAware>false</dpiAware>

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

  //(...)

  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiAware>
    </windowsSettings>
  </application>

//(...)

</assembly>

有關更多信息,請參閱這些MSDN文章:
Windows上的高DPI桌面應用程序開發
設置進程的默認DPI感知

另一種方法是使用以下Windows API函數設置進程上下文DPI-Awareness:

Windows 7的
SetProcessDPIAware

[DllImport("user32.dll", SetLastError=true)]
static extern bool SetProcessDPIAware();

Windows 8.1
SetProcessDpiAwareness

[DllImport("shcore.dll")]
static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);

enum ProcessDPIAwareness
{
    DPI_Unaware = 0,
    System_DPI_Aware = 1,
    Per_Monitor_DPI_Aware = 2
}

Windows 10,版本1703
SetProcessDpiAwarenessContext()
(當選擇Per-Monitor DPI-Awareness時,請使用 Context_PerMonitorAwareV2

另請參閱: 混合模式DPI擴展和DPI感知API - MSDN

Windows 10,版本1809 (2018 10月)
添加了新的 DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED

DPI沒有意識到基於GDI的內容質量的提高。 此模式的行為與DPI_AWARENESS_CONTEXT_UNAWARE類似,但也可以使系統在高DPI監視器上顯示窗口時自動提高文本和其他基於GDI的基元的渲染質量。

使用 GetWindowDpiAwarenessContext() 函數檢索Window的 DPI_AWARENESS_CONTEXT 句柄和當前線程的 DPI_AWARENESS_CONTEXT 句柄的 GetThreadDpiAwarenessContext() 。 然後 GetAwarenessFromDpiAwarenessContext() DPI_AWARENESS_CONTEXT 結構中檢索 DPI_AWARENESS 值。

[DllImport("user32.dll", SetLastError=true)]
static extern IntPtr GetWindowDpiAwarenessContext(IntPtr hWnd);

[DllImport("user32.dll", SetLastError=true)]
static extern IntPtr GetThreadDpiAwarenessContext();

[DllImport("user32.dll", SetLastError=true)]
static extern int GetAwarenessFromDpiAwarenessContext(InPtr DPI_AWARENESS_CONTEXT);


[DllImport("user32.dll", SetLastError=true)]
static extern int SetProcessDpiAwarenessContext(ContextDPIAwareness value);

// Virtual enumeration: DPI_AWARENESS_CONTEXT is *contextual*. 
// This value is returned by GetWindowDpiAwarenessContext() or GetThreadDpiAwarenessContext()
// and finalized by GetAwarenessFromDpiAwarenessContext(). See the Docs.
enum ContextDPIAwareness
{
    Context_Unaware = ((DPI_AWARENESS_CONTEXT)(-1)),
    Context_SystemAware = ((DPI_AWARENESS_CONTEXT)(-2)),
    Context_PerMonitorAware = ((DPI_AWARENESS_CONTEXT)(-3)),
    Context_PerMonitorAwareV2 = ((DPI_AWARENESS_CONTEXT)(-4)),
    Context_UnawareGdiScaled = ((DPI_AWARENESS_CONTEXT)(-5))
}

由於DPI-Awareness是基於線程的,因此這些設置可以應用於特定線程。 在重新設計用戶界面以實現DPI-Awareness時,這可能很有用,讓系統在關注更重要的功能時縮放不太重要的組件。

SetThreadDpiAwarenessContext
(與 SetProcessDpiAwarenessContext() 相同的參數)

Assemblyinfo.cs
如果引用WPF程序集的第三方/外部組件重新定義了應用程序的DPI-Awareness狀態,則可以禁用此自動行為,在Project Assemblyinfo.cs 插入參數:

[assembly: System.Windows.Media.DisableDpiAwareness]

所以我們有這個奇怪的問題。 我們的應用程序是一個C#/ WinForms應用程序。 在我們的6.0版本中,我們的應用程序不支持DPI。 在我們的6.1版本中,它突然變成了DPI意識。
在6.0版本中,如果您在高DPI中運行它,它使用Windows位圖縮放,這很好,因為這不會影響屏幕佈局。 在6.1版本中,由於它因某種原因而成為DPI識別,因此用戶界面受到嚴重破壞。
我們現在無法解決這個問題。 我們有數百個屏幕,因此在DPI識別模式下使它們全部正常工作將花費大量時間。

我們已使用SysInternals Process Explorer確認了這一點。 在我們的6.0版本中,它顯示了 Unaware ,但在我們的6.1版本中,它最初顯示 Unaware ,但隨後更改為 System Aware
後者發生在代碼從EXE進入包含所有用戶界面代碼的程序集DLL中時(我們的EXE基本上是一個非常薄的shell;它實際上只是在我們的表示層程序集上調用一個Controller類。)

我們已確認以下內容:

  • 兩個版本都使用VSS 2017在發布模式下構建。
  • 兩個版本都針對相同的.NET Framework(4.5)
  • 兩個版本都使用相同的DevExpress版本。
  • 兩個版本都具有相同的應用程序清單,該清單 啟用DPI感知設置。
  • 這兩個版本都沒有任何與DPI相關的Windows API的調用。
  • 使用Sys Internals和一些消息框,我們確定了6.1版本在什麼時候發現(進入Presentation程序集的入口點)以及那時加載了什麼DLL(我們的,DevExpress,其他依賴項),然後我們構建了一個小的虛擬應用程序,引用相同的DLL,並確認它們已加載。 該虛擬應用程序不會成為DPI意識。
  • 我們比較了兩個版本之間的主要csproj文件,沒有任何有意義的差異。
    • 這兩個版本都沒有引用WPF的任何內容。

我們不明白為什麼我們的6.1版本突然變成DPI意識。 我們無知還有什麼可以看,我們需要一個修復程序,將此版本恢復到DPI unaware模式。 它正在阻止我們的發布。 非常感謝任何指針。 我們願意在這一點上嘗試任何事情。





dpi-aware