c# - DPI 意识 - 在一个版本中没有意识到,在另一个版本中有系统意识

标签 c# .net winforms highdpi dpi-aware

<分区>

所以我们遇到了这个非常奇怪的问题。我们的应用程序是 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 在 Release模式下构建的。
  • 两个版本都针对相同的 .NET Framework (4.5)
  • 两个版本使用相同的 DevExpress 版本。
  • 两个版本都有相同的应用程序 list ,启用 DPI 感知设置。
  • 这两个版本都没有调用任何与 DPI 相关的 Windows API。
  • 使用 Sys Internals 和一些消息框,我们确定了 6.1 版本在什么时候开始意识到(Presentation 程序集的入口点)以及在那个时候加载了哪些 DLL(我们的、DevExpress、其他依赖项),然后我们构建了一个小的引用相同 DLL 的虚拟应用程序,并确认这些已加载。该虚拟应用不会感知 DPI。
  • 我们比较了两个版本之间的主要 csproj 文件,没有明显差异。
    • 两个版本均未引用 WPF 中的任何内容。

我们不明白为什么我们的 6.1 版本突然变得支持 DPI。我们不知道还有什么要看,我们需要一个修复程序,将此版本恢复到 DPI 无意识模式。它阻碍了我们的释放。非常感谢任何指点。我们愿意在这一点上尝试任何事情。

最佳答案

关于本Question中报告的问题:
一个应用程序,它在设计上是 DPI 不感知的,依赖于 Windows 虚拟化来扩展它的 UI 内容,突然(虽然经过一些修改,导致一个小的版本更新) - 并且显然没有可观察的原因 - 变成 DPI 感知(系统感知).

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

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

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


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

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

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

请参阅 Hans Passant 对 Visual Studio 2017 之前的应用程序 list 设置的回答:
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 Windows
Setting the default DPI awareness for a process

另一种方法是使用这些 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()
(选择每显示器 DPI 感知时,请使用 Context_PerMonitorAwareV2 )

另见:Mixed-Mode DPI Scaling and DPI-aware APIs - MSDN

Windows 10,版本 1809(2018 年 10 月)
一个新的 DPI_AWARENESS_CONTEXT 已添加:DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED

DPI unaware with improved quality of GDI-based content. This mode behaves similarly to DPI_AWARENESS_CONTEXT_UNAWARE, but also enables the system to automatically improve the rendering quality of text and other GDI-based primitives when the window is displayed on a high-DPI monitor.

使用 GetWindowDpiAwarenessContext() 检索 DPI_AWARENESS_CONTEXT 的函数窗口句柄和 GetThreadDpiAwarenessContext() 对于 DPI_AWARENESS_CONTEXT当前线程的句柄。那么 GetAwarenessFromDpiAwarenessContext() 检索 DPI_AWARENESS来自 DPI_AWARENESS_CONTEXT 的值结构。

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

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

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

[assembly: System.Windows.Media.DisableDpiAwareness]

关于c# - DPI 意识 - 在一个版本中没有意识到,在另一个版本中有系统意识,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50239138/

相关文章:

c# - 在 Windows Forms 窗体上动态删除/添加控件

c# - 使用 AutoPoco 填充列表属性

.net - 文件的安全流更新

Winforms ClickOnce 发布因 app.config 转换而失败

c# - 哪个更利于可读性?

c# - 继承上下文时避免从基本 EF Core 3.0 模型重新建模对象

c# - 如何更改 WCF 的默认配置?

c# - LINQ 无法在特定列中按日期排序?

winforms - 限制 OxyPlot 中的放大/缩小比例变化

c# - 如何在c#中实现没有更多实例的登录表单和主表单