关于这个问题的报告:
一个应用程序通过 Windows 虚拟化来缩放其 UI 内容,它是通过设计不支持 DPI 感知的。突然之间(尽管经过一些修改,导致了次要版本更新),似乎没有明显的原因,它变成了 DPI 感知的(系统感知的)。
该应用程序还依赖于app.manifest
<windowsSettings>
的解释,其中缺少 DPI 感知定义会默认为 DPI 无感知(向后兼容)。
没有直接引用 WPF 组件和与 DPI 相关的 API 调用。
该应用程序包括第三方组件(以及可能的外部依赖项)。
由于 DPI 感知已成为 UI 呈现的一个重要方面,考虑到可用的屏幕分辨率的多样性(以及相关的 DPI 缩放设置),大多数组件生产商已经适应了高 DPI,并且他们的产品是 DPI 感知的(在检测到 DPI 更改时进行缩放)并使用 DPI 感知程序集(通常引用 WPF 程序集,根据定义是 DPI 感知的)。
当一个 DPI 感知组件在项目中被引用(直接或间接地),一个 DPI 无感知的应用程序将变成 DPI 感知的,除非 DPI 感知已被明确禁用。
声明程序集 DPI 感知的更直接(也更推荐)的方法是在应用程序清单中明确声明它。
关于 Visual Studio 2017 之前的应用程序清单设置,请参考 Hans Passant 的答案:
如何在高 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"/>
//(...)
<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
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, version 1703
SetProcessDpiAwarenessContext()
(选择 Per-Monitor DPI-Awareness 时,请使用 Context_PerMonitorAwareV2
)
另请参见:混合模式 DPI 缩放和 DPI-aware API - MSDN
Windows 10, version 1809(2018 年 10 月)
新增了一个DPI_AWARENESS_CONTEXT
:DPI_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]
<dpiAware>False</dpiAware>
?请参考Hans的答案。DPI 意识是基于线程的。如果需要,阅读我链接的 MSDN 文档,转到SetThreadDpiAwarenessContext。这可能是由依赖项引起的(引用 WPF 组件)。使用 DPL 的 Telerik 控件也会出现此问题。 - Jimiassemblyinfo.cs
文件中设置[assembly: System.Windows.Media.DisableDpiAwareness]
。 - Jimi