Win8.1/10引入了多显示器DPI特性,允许对不同显示器指定不同DPI,窗口拖到不同DPI的显示器时,会通知程序动态进行缩放。但是目前为止,WPF的稳定版是不支持Win8.1/10的这种特性的。为了支持这个特性,必须安装最新版.NET框架4.6.2 Preview。
对于低于4.6.2版本的编译,默认是不响应DPI变化的,开启DPI响应需要修改app.config,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<appcontextswitchoverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false">
</appcontextswitchoverrides></runtime>
<startup>
<supportedruntime version="v4.0.30319">
</supportedruntime></startup>
</configuration>
以上只是开启了WPF内部的DPI响应支持。在目前的4.6.2 Preview版本中,默认DPI级别还是等于1(支持系统级DPI),需要手动设置DPI级别为2(支持多显示器DPI)。暂时还没有找到用一般的方法如何改,在这里提供了一个用C#代码调用系统函数修改的方法。
首先在AssemblyInfo.cs中加入这一句,不让WPF自己设置DPI缩放级别:
[assembly: System.Windows.Media.DisableDpiAwareness()]
然后在App.cs中改成如下所示:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Runtime.InteropServices; // DllImport
using System.Diagnostics; // Debug
namespace WpfApplication8
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string libname);
[DllImport("kernel32.dll")]
private static extern void FreeLibrary(IntPtr hlib);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hlib, string procname);
private delegate int ProcSetProcessDpiAwareness(int level);
private delegate void ProcSetProcessDPIAware();
static void Win7SetDPIAware()
{
IntPtr hUser32 = LoadLibrary("user32.dll");
if (hUser32 != IntPtr.Zero)
{
IntPtr addrSetProcessDPIAware = GetProcAddress(hUser32, "SetProcessDPIAware");
if (addrSetProcessDPIAware != IntPtr.Zero)
{
ProcSetProcessDPIAware SetProcessDPIAware =
(ProcSetProcessDPIAware)
Marshal.GetDelegateForFunctionPointer(addrSetProcessDPIAware,
typeof(ProcSetProcessDPIAware));
SetProcessDPIAware();
}
FreeLibrary(hUser32);
}
}
// 需要在AssemblyInfo.cs里面设置
// [assembly: System.Windows.Media.DisableDpiAwareness()]
static bool Win81SetDPIAware()
{
IntPtr hShcore = LoadLibrary("shcore.dll");
if (hShcore != IntPtr.Zero)
{
IntPtr addrSetProcessDpiAwareness = GetProcAddress(hShcore, "SetProcessDpiAwareness");
if (addrSetProcessDpiAwareness != IntPtr.Zero)
{
// Win8.1以上版本,支持多显示器DPI
ProcSetProcessDpiAwareness SetProcessDpiAwareness =
(ProcSetProcessDpiAwareness)
Marshal.GetDelegateForFunctionPointer(addrSetProcessDpiAwareness,
typeof(ProcSetProcessDpiAwareness));
Type t = typeof(Window);
var evt = t.GetEvent("DpiChanged");
if (evt != null)
{
// 4.6.2新版WPF,支持DPI响应
// 需在app.config里面设置
// <runtime>
// <appcontextswitchoverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false">
// </appcontextswitchoverrides></runtime>
if (SetProcessDpiAwareness(2) < 0)
{
Debug.WriteLine("SetProcessDpiAwareness(2) failed!");
}
else
{
Debug.WriteLine("SetProcessDpiAwareness(2) succeeded!");
}
}
else
{
// 旧版WPF,不支持DPI响应
SetProcessDpiAwareness(1);
}
FreeLibrary(hShcore);
return true;
}
else // 找不到SetProcessDpiAwareness
{
FreeLibrary(hShcore);
return false;
}
}
else // 找不到shcore.dll
{
return false;
}
}
static App()
{
if (!Win81SetDPIAware())
{
Win7SetDPIAware();
}
}
}
}