【问题标题】:How to get DPI scale for all screens?如何获得所有屏幕的 DPI 比例?
【发布时间】:2015-04-03 19:25:27
【问题描述】:

我需要为连接到计算机的每个屏幕(即使是那些没有打开 WPF 窗口的屏幕)设置从控制面板 > 显示中设置的 DPI 比例。我见过许多获取 DPI 的方法(例如,http://dzimchuk.net/post/Best-way-to-get-DPI-value-in-WPF),但这些方法似乎依赖于Graphics.FromHwnd(IntPtr.Zero) 或PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice。

有没有办法为每个单独的屏幕获取 DPI 设置?

背景 - 我正在创建一个布局配置编辑器,以便用户可以在启动之前设置他们的配置。为此,我相对于彼此绘制每个屏幕。对于一种配置,我们使用的 4K 显示器具有大于默认 DPI 比例集。它的绘制比它相对于其他屏幕的实际显示要小得多,因为它报告的分辨率与其他屏幕相同。

【问题讨论】:

  • 你可以在每个屏幕上创建一个虚拟窗口并以这种方式获取信息吗?
  • 您可以使用之前的评论,否则您将不得不枚举显示设备并以这种方式获取 DPI。
  • Windows 现在支持每屏 DPI,从 8.1 开始。您可能会在一个安装了昂贵的“视网膜”显示器和另一个普通的显示器(通常是投影仪)的设置中遇到它。 WPF 的后台程序is here.
  • @GabrielNegut - 是的,我相信这也可以。但是,我一直在寻找一种更加程序化的解决方案,这样我就可以构建一个数据绑定转换器,而不是创建窗口并等待它们加载,然后再开始绘图。
  • @HansPassant - 是的,这就是我要解决的问题。在我的示例中,具有不同 DPI 的屏幕导致它们相互之间绘制不正确,因为 Screen.Bounds 表示 DPI 调整后的像素大小。

标签: c# wpf windows


【解决方案1】:

我找到了一种使用 WinAPI 获取 dpi 的方法。 首先需要参考System.Drawing 和System.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,
}

缩放有3种,可以找description in the 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),但除此之外它效果很好。谢谢!
  • 快速说明,这仅适用于 Windows 8 及更高版本。
  • 我发现在 Windows 10 上使用具有不同 DPI 比例的多台显示器,Angular 是正确的 DpiType 以获得屏幕元素的正确比例。常规 DPI 为 96,而我 175% 屏幕上的角度为 55。96 / 1.75 = 54.857,四舍五入为 55。
  • 在执行奇怪 DPI 操作的 Visual Studio 2019 进程中,从 Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1) 检索监视器的启发式方法不起作用.我必须使用stackoverflow.com/a/5020623/27194 EnumDisplayMonitors 来获取所有监视器句柄 IntPtr + 我必须使用 DpiType.Effective 来获取正确的 DPI 值(96 = 100% 144 = 150% 192 = 200% 等等......)跨度>
  • GetDpiForMonitor 将在您更改主显示器时显示错误的显示器 DPI。只有重启应用才有帮助。
猜你喜欢
  • 2014-03-27
  • 1970-01-01
  • 1970-01-01
  • 2015-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多