DPI 意识 - 在一个版本中不知道,在另一个版本中知道系统 [重复]

Posted

技术标签:

【中文标题】DPI 意识 - 在一个版本中不知道,在另一个版本中知道系统 [重复]【英文标题】:DPI Awareness - Unaware in one Release, System Aware in the Other [duplicate] 【发布时间】:2018-10-18 17:33:32 【问题描述】:

所以我们遇到了这个非常奇怪的问题。我们的应用程序是一个 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 基本上是一个非常薄的外壳;它真正所做的只是在我们的表示层程序集上调用一个 Controller 类。)

我们已确认以下内容:

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

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

【问题讨论】:

您是否引用了来自 WPF 的内容(“演示文稿”)?从 6.0 到 6.1 发生了一些变化。你提到了没有改变的地方。另外,请参阅SetProcessDpiAwareness 强制它。 更新了原始帖子以表明没有引用 WPF 中的任何内容。我们不希望应用程序能够识别 DPI。我们希望它使用 Window 的自动位图缩放。因为让应用程序在 DPI 感知模式下工作意味着更新大量用户界面以在 DPI 感知模式下正常运行。 清单没有 DPI 感知。但它是否明确禁用它:<dpiAware>False</dpiAware>?请参阅Hans answer for this。 DPI Awareness 是基于线程的。阅读我链接的 MSDN 文档,如有必要,请移至 SetThreadDpiAwarenessContext。依赖项可能会导致此问题(引用 WPF 程序集)。使用 DPL 的 Telerik 控件可以。 ... 如果是这种情况(而您不知道),请尝试在您的 assemblyinfo.cs 文件 [assembly: System.Windows.Media.DisableDpiAwareness] 中进行设置。 另一个忙碌的人将一个问题标记为重复的,但不是。我永远不会搜索所谓的重复问题的主题。这个问题是相关的,并且很好地提出了。 【参考方案1】:

关于此问题中报告的问题: 一个设计上不支持 DPI 的应用程序,依靠 Windows 虚拟化来扩展其 UI 内容,突然(尽管经过一些修改,导致一个小的版本更新) - 显然没有明显的原因 - 变为 DPI-Aware(系统感知)。

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

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

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


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

当项目中(直接或间接)引用这些 DPI-Aware 组件之一时,如果 DPI-Awareness 尚未明确禁用,则 DPI-Unaware 应用程序将变为 DPI-Aware。

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

有关 Visual Studio 2017 之前的应用程序清单设置,请参阅 Hans Passant 的回答:How to configure an app to run on a machine with a high DPI setting

自 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 文章:High DPI desktop application development on WindowsSetting the default DPI awareness for a process

另一种方法是使用这些 Windows API 函数设置进程上下文 DPI-Awareness:

Windows 7SetProcessDPIAware

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

Windows 8.1SetProcessDpiAwareness

[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,版本 1703SetProcessDpiAwarenessContext() (When opting for a Per-Monitor DPI-Awareness, use Context_PerMonitorAwareV2)

另见:Mixed-Mode DPI Scaling and DPI-aware APIs - 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 = -1,
    Context_SystemAware = -2,
    Context_PerMonitorAware = -3,
    Context_PerMonitorAwareV2 = -4,
    Context_UnawareGdiScaled = -5

由于 DPI-Awareness 是基于线程的,因此这些设置可以应用于特定线程。这在重新设计用户界面以实现 DPI 感知时非常有用,可以让系统扩展不太重要的组件,同时专注于更重要的功能。

SetThreadDpiAwarenessContext (与SetProcessDpiAwarenessContext()相同的参数)

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

[assembly: System.Windows.Media.DisableDpiAwareness]

【讨论】:

你的 ContextDPIAwareness 枚举值与这些不同:***.com/a/43537991/1248295 你能确认哪些是正确的值吗? @ElektroStudios 更新了更多信息。现在应该更清楚了。 @ElektroStudios DPI_AWARENESS_CONTEXT(参见文档和我写的内容)不是常量。它是一个不透明结构的句柄,由两个描述的函数返回:GetWindowDpiAwarenessContextGetThreadDpiAwarenessContext。然后使用GetAwarenessFromDpiAwarenessContext 检索实际值,这将返回一个converted DPI_AWARENESS_CONTEXT,表示该值。您需要减去 ContextDPIAwareness 枚举器值并将该值传递给 SetProcessDpiAwarenessContextSetThreadDpiAwarenessContext 以设置 DPIAwareness 上下文。 @ElektroStudios 硬编码某些任意值可能有效或无效,具体取决于 Windows 版本和更新。实际值可能会发生变化,这就是它以这种 contorted :) 方式提供的方式。顺便说一句,你并没有以任何方式打扰我。你的问题很中肯,我想补充一下,对于我过去所见的情况非常有趣。 @ElektroStudios 嗯,是的,DPI_AWARENESS_CONTEXT 不能在设计时设置,因为它在那个阶段不存在。您需要派生它(它可能会从进程感知变为线程感知)。枚举器与GetAwarenessFromDpiAwarenessContext 返回的转换值 结合使用。看,当我手头有 Visual Studio 时,我会为此发布一个示例代码。既然你是对的,那就不是那么清楚了;我假设有人在阅读本文时会去 Docs 并自动将其正确处理。但是一个 C# 实现实际上也在 PInvoke.net 中。

以上是关于DPI 意识 - 在一个版本中不知道,在另一个版本中知道系统 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

不同版本(16、15、14)的 TFS API dll 在单个项目中不起作用

Windows 8之前的GetProcessDpiAwareness()

如何为python安装不同版本的模块

在另一个版本中更改 NSManagedObject 父实体

OpenCV Android 应用程序(基于 Qt)在一个版本上运行,但在另一个版本上失败

我可以在一个版本的 Windows 上编译 C 程序,然后在另一个版本的 Windows 上运行它吗?