如何获取所有屏幕的DPI比例?

18
我需要获取每个连接到计算机的屏幕的 DPI 缩放比例,即使这些屏幕没有打开 WPF 窗口也可以。我已经看到了一些获取 DPI 的方法(例如,http://dzimchuk.net/post/Best-way-to-get-DPI-value-in-WPF),但这些似乎都依赖于 Graphics.FromHwnd(IntPtr.Zero) 或者 PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice
是否有一种方法可以获取每个单独屏幕的 DPI 设置?
背景 - 我正在创建一个布局配置编辑器,以便用户在启动之前设置其配置。为此,我将每个屏幕相对于彼此绘制。对于一种配置,我们使用具有较大 DPI 缩放比例的 4K 显示器。它显示出来比其他屏幕更小,因为它报告与其他屏幕相同的分辨率。

你能否在每个屏幕上创建一个虚拟窗口,然后通过这种方式获取信息? - Gabriel Negut
你可以使用之前的评论,否则您将需要枚举显示设备并获取DPI。 - Kcvin
Windows现在支持每个屏幕的DPI,从8.1开始。您可能会在设置中遇到它,其中一个是昂贵的“视网膜”显示器,另一个是普通的投影仪。WPF的背景信息在此处。 - Hans Passant
@GabrielNegut - 是的,我相信那也可以。然而,我正在寻找一种更加程序化的解决方案,这样我就可以构建一个数据绑定转换器,而不是创建窗口并等待它们加载后再开始绘制。 - Sarah
@HansPassant - 是的,这就是我在这里试图解决的问题。就像我的例子一样,屏幕具有不同的DPI会导致它们相对于彼此绘制不正确,因为Screen.Bounds表示DPI调整后的像素大小。 - Sarah
1个回答

31

我发现了一种使用WinAPI获取dpi的方法。首先需要引用System.DrawingSystem.Windows.Forms,可以使用WinAPI从显示区域上的某个点获取监视器句柄 - Screen类可以给我们这些点。然后GetDpiForMonitor函数返回指定监视器的dpi。

public static class ScreenExtensions
{
    public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
    {
        var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
        var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
        GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
    }

    //https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
    [DllImport("User32.dll")]
    private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);

    //https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
    [DllImport("Shcore.dll")]
    private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
}

//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511(v=vs.85).aspx
public enum DpiType
{
    Effective = 0,
    Angular = 1,
    Raw = 2,
}

有三种缩放类型,您可以在MSDN中找到详细介绍

我用一个新的WPF应用程序快速测试了一下:

private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
    var sb = new StringBuilder();
    sb.Append("Angular\n");
    sb.Append(string.Join("\n", Display(DpiType.Angular)));
    sb.Append("\nEffective\n");
    sb.Append(string.Join("\n", Display(DpiType.Effective)));
    sb.Append("\nRaw\n");
    sb.Append(string.Join("\n", Display(DpiType.Raw)));

    this.Content = new TextBox() { Text = sb.ToString() };
}

private IEnumerable<string> Display(DpiType type)
{
    foreach (var screen in System.Windows.Forms.Screen.AllScreens)
    {
        uint x, y;
        screen.GetDpi(type, out x, out y);
        yield return screen.DeviceName + " - dpiX=" + x + ", dpiY=" + y;
    }
}

希望这可以帮到你!


我不得不将 MonitorFromPoint(Point, short) 更改为 MonitorFromPoint(Point, uint),但除此之外它运作良好。谢谢! - Sarah
2
一个快速的提示,这仅适用于Windows 8及以上版本。 - xandermonkey
2
我发现在Windows 10上,使用多台DPI不同的监视器时, Angular是正确的DpiType以获取屏幕元素的适当比例。正常DPI是96,而我175%的屏幕是55。96/1.75 = 54.857,四舍五入为55。 - Ryan Lundy
2
在一个做奇怪DPI事情的Visual Studio 2019进程中,从Point(screen.Bounds.Left + 1,screen.Bounds.Top + 1)检索监视器的启发式方法没有起作用。我不得不使用https://dev59.com/Z2445IYBdhLWcg3wIG19#5020623 EnumDisplayMonitors来获取所有监视器句柄IntPtr +我必须使用DpiType.Effective来获取正确的DPI值(96 = 100%144 = 150%192 = 200%等等...) - Patrick from NDepend team
GetDpiForMonitor 在更改主监视器时会显示错误的监视器 DPI。只有重新启动应用程序才能解决问题。 - steam3d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接