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

这个问题在这里已有答案:

Windows Forms window changes its size when I create a WPF window 3回答

所以我们有这个奇怪的问题。我们的应用程序是一个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模式。它正在阻止我们的发布。非常感谢任何指针。我们愿意在这一点上尝试任何事情。

9
投票

关于本课题报告的问题: 一个应用程序,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答案: How to configure an app to run on a machine with a high DPI setting

在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文章: 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() (当选择Per-Monitor DPI-Awareness时,请使用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结构中恢复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(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状态,则可以禁用此自动行为,在项目Assemblyinfo.cs中插入参数:

[assembly: System.Windows.Media.DisableDpiAwareness]