DPI 感知 - 在一个版本中不知道,在另一个版本中系统感知



所以我们有这个非常奇怪的问题。我们的应用程序是一个C#/WinForms应用程序。在我们的 6.0 版本中,我们的应用程序无法识别 DPI。在我们的 6.1 版本中,它突然变得可以感知 DPI。
在 6.0 版本中,如果在高 DPI 下运行它,它会使用 Windows 位图缩放,这很好,因为这不会影响屏幕布局。在 6.1 版本中,由于它由于某种原因变得能够识别 DPI,因此用户界面被破坏了。
我们现在无法解决这个问题。我们有数百个屏幕,因此使它们在DPI感知模式下正常工作将花费大量时间。

我们已经使用 SysInternals Process Explorer 确认了这一点。在我们的 6.0 版本中,它显示">不知道",但在 6.1 版本中,它最初显示">不知道",但后来更改为"系统感知"。
当代码从 EXE 进入包含所有用户界面代码的程序集 DLL 时,就会发生后者(我们的 EXE 基本上是一个非常薄的外壳;它真正做的只是在我们的表示层程序集上调用控制器类。

我们已确认以下内容:

  • 这两个版本都是使用 VSS 2017 在发布模式下构建的。
  • 两个版本都面向相同的 .NET Framework (4.5)
  • 两个版本使用相同的 DevExpress 版本。
  • 两个版本具有相同的应用程序清单,其中启用 DPI 感知设置。
  • 这两个版本都没有调用任何与DPI相关的Windows API。
  • 使用 Sys Internals 和一些消息框,我们确定了 6.1 版本在什么时候知道(演示文稿程序集的入口点)以及在该点加载了哪些 DLL(我们的、DevExpress、其他依赖项),然后我们构建了一个引用相同 DLL 的小型虚拟应用程序,并确认这些已加载。该虚拟应用程序无法识别 DPI。
  • 我们已经比较了两个版本之间的主要csproj文件,没有有意义的差异。
    • 这两个版本都没有引用 WPF 中的任何内容。

我们不明白为什么我们的 6.1 版本突然变得可以感知 DPI。我们不知道还有什么要看的,我们需要一个修复程序,将此版本恢复为 DPI 无意识模式。它阻碍了我们的发布。真的很感激任何指示。在这一点上,我们愿意尝试任何事情。

关于本问题中报告的问题
一个应用程序,它在设计上是 DPI 无法感知的,依靠 Windows 虚拟化来扩展其 UI 内容,突然(尽管经过一些修改,导致次要版本更新) - 显然没有可观察的原因 - 变得 DPI 感知(系统感知)。

  • 该应用程序还依赖于对app.manifest<windowsSettings>的解释,其中缺少DPI感知定义,默认(为了向后兼容)DPI-Unaware。

  • 没有对 WPF 程序集的直接引用,也没有与 DPI 相关的 API 调用。

  • 该应用程序包括第三方组件(可能还有外部依赖项)。


由于 DPI 感知已成为 UI 演示的一个相关方面,鉴于可用的屏幕分辨率(以及相关的 DPI 缩放设置)的多样性,大多数组件生产商已经适应了高 DPI,他们的产品是 DPI 感知(检测到 DPI 更改时缩放)并使用 DPI 感知程序集(通常引用 WPF 程序集,根据定义是 DPI 感知)。

当在项目中(直接或间接)引用这些 DPI 感知组件之一时,当尚未显式禁用 DPI 感知时,DPI 非感知应用程序将变为 DPI 感知。

声明程序集 DPI 感知的更直接(也是推荐)的方法是在应用程序清单中显式声明它。

请参阅 Hans Passant 对 Visual Studio 2017 之前的应用程序清单设置的回答:
如何将应用配置为在具有高 DPI 设置的计算机上运行

从Visual Studio 2015-Upd.1开始,此设置已经存在于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
DPI. 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 感知:

Windows 7
SetProcessDPIAware

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

Windows 8.1<</strong>br/>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<</strong>br/>SetProcessDpiAwarenessContext()
(选择每监视器 DPI 感知时,请使用Context_PerMonitorAwareV2)

另请参阅:混合模式 DPI 缩放和 DPI 感知 API - MSDN

Windows 10版本 1809(2018 年 10月)
添加了一个新的DPI_AWARENESS_CONTEXTDPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED

DPI 不知道基于 GDI 的内容质量有所提高。此模式 行为类似于DPI_AWARENESS_CONTEXT_UNAWARE,但也使 系统自动提高文本的渲染质量和 窗口显示在高 DPI 上时的其他基于 GDI 的基元 监控。

使用GetWindowDpiAwarenessContext()函数检索窗口的DPI_AWARENESS_CONTEXT句柄,并GetThreadDpiAwarenessContext()当前线程的DPI_AWARENESS_CONTEXT句柄。然后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(IntPtr DPI_AWARENESS_CONTEXT);

[DllImport("user32.dll", SetLastError=true)]
static extern int SetProcessDpiAwarenessContext(DpiAwarenessContext value);
// Virtual enumeration: DPI_AWARENESS_CONTEXT is *contextual*. 
// This value is returned by GetWindowDpiAwarenessContext() or GetThreadDpiAwarenessContext()
// and finalized by GetAwarenessFromDpiAwarenessContext(). See the Docs.
enum DpiAwarenessContext
{
Context_Undefined = 0,
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 感知是基于线程的,因此这些设置可以应用于特定线程。这在重新设计用户界面以实现 DPI 感知时非常有用,可以让系统扩展不太重要的组件,同时专注于更重要的功能。

SetThreadDpiAwarenessContext
(与SetProcessDpiAwarenessContext()参数相同)

Assemblyinfo.cs
如果引用 WPF 程序集的第三方/外部组件重新定义了应用程序的 DPI 感知状态,则可以禁用此自动行为,并在项目的Assemblyinfo.cs中插入参数:

[assembly: System.Windows.Media.DisableDpiAwareness]

最新更新