C#为Windows中的每个监视器获取DPI缩放

时间:2017-01-08 09:56:17

标签: wpf windows dpi

我正在使用WPF应用程序中的代码,该应用程序需要确定Windows中每个监视器的DPI缩放大小。我能够找出主屏幕的DPI但由于某种原因我无法弄清楚如何获得其他显示器的比例 - 其他显示器都返回与主显示器相同的DPI。

有一些代码可以做到这一点,所以请耐心等待。第一组代码涉及基于HWND获取DPI。代码获取活动监视器,然后检索DPI设置,并将数字与96 DPI(通常为100%)的比率进行比较。

public static decimal GetDpiRatio(Window window)
{
    var dpi = WindowUtilities.GetDpi(window, DpiType.Effective);
    decimal ratio = 1;
    if (dpi > 96)
        ratio = (decimal)dpi / 96M;

    return ratio;
}
public static decimal GetDpiRatio(IntPtr hwnd)
{            
    var dpi = GetDpi(hwnd, DpiType.Effective);            
    decimal ratio = 1;
    if (dpi > 96)
        ratio = (decimal)dpi / 96M;

    //Debug.WriteLine($"Scale: {factor} {ratio}");
    return ratio;
}

public static uint GetDpi(IntPtr hwnd, DpiType dpiType)
{            
    var screen = Screen.FromHandle(hwnd);            
    var pnt = new Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
    var mon = MonitorFromPoint(pnt, 2 /*MONITOR_DEFAULTTONEAREST*/);

    Debug.WriteLine("monitor handle: " + mon);
    try
    {
        uint dpiX, dpiY;
        GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
        return dpiX;
    }
    catch
    {
        // fallback for Windows 7 and older - not 100% reliable
        Graphics graphics = Graphics.FromHwnd(hwnd);
        float dpiXX = graphics.DpiX;                
        return Convert.ToUInt32(dpiXX);
    }
}


public static uint GetDpi(Window window, DpiType dpiType)
{
    var hwnd = new WindowInteropHelper(window).Handle;
    return GetDpi(hwnd, dpiType);
}     

[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);

[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);        


public enum DpiType
{
    Effective = 0,
    Angular = 1,
    Raw = 2,
}

此代码用作屏幕捕获解决方案的一部分,其中应该是用户鼠标结束的窗口覆盖。我捕获鼠标位置,并根据我获得像素位置,然后我在那里创建WPF窗口。在这里,我必须应用DPI比率,以使窗口在正确的位置和大小呈现。

只要DPI相同,这一切在主监视器或多个监视器上都能正常工作。

问题是对GetDpiForMonitor()的调用总是返回主监视器DPI,即使传递给它的HMONITOR值不同。

DPI意识

这是一个WPF应用程序,因此应用程序可识别DPI,但WPF在系统DPI感知中运行,而不是在Per Monitor DPI Aware中运行。为此,我在启动时连接static App()代码以显式设置为每个监视器DPI:

    try
    {
        // for this to work make sure [assembly:dpiawareness
        PROCESS_DPI_AWARENESS awareness;
        GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awareness);
        var result = SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
        GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awareness);
}

[DllImport("SHCore.dll", SetLastError = true)]
public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);

[DllImport("SHCore.dll", SetLastError = true)]
public static extern void GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS awareness);

public enum PROCESS_DPI_AWARENESS
{
    Process_DPI_Unaware = 0,
    Process_System_DPI_Aware = 1,
    Process_Per_Monitor_DPI_Aware = 2
}

// and in assemblyinfo
[assembly: DisableDpiAwareness]

我看到DPI设置更改为Process_Per_Monitor_DPI_Aware,但这似乎也对行为没有影响。我仍然看到DPI结果与主监视器一样返回。

在一个较大的解决方案中进行测试,允许在此处玩这个: https://github.com/RickStrahl/MarkdownMonster/blob/master/Tests/ScreenCaptureAddin.Test/DpiDetectionTests.cs如果有人有兴趣查看此内容。

任何想法如何能够可靠地获得系统上所有监视器的DPI Scaling级别(以及为什么没有系统API甚至是WMI设置)?

3 个答案:

答案 0 :(得分:4)

WPF自.NET Framework 4.6.2以来一直支持DPI。 GitHub提供了更多信息和示例:http://github.com/Microsoft/WPF-Samples/tree/master/PerMonitorDPI

您可能还想查看VisualTreeHelper.GetDpi方法。

答案 1 :(得分:1)

我一直在努力解决类似的问题(辅助监视器屏幕界限似乎与主显示器上设置的缩放因子相同),我发现一些文档似乎至少可以解释这是预期的行为:< / p>

  

DPI意识模式 - 系统
  Windows版本推出 - Vista
  应用程序的DPI视图 - 所有显示都具有相同的DPI   (Windows会话时主显示的DPI   启动)
  DPI变化的行为 - 位图拉伸(模糊)

这是从High DPI desktop application development on Windows

中的第一个表格中提取的

这是我发现的第一个文档,至少明确说明当应用程序处于系统DPI意识时,代码将报告所有窗口共享相同的扩展。

答案 2 :(得分:-1)

        int GetWindowsScaling()
        {
            return (int)(100 * Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth);
        }
        int r = GetWindowsScaling();
        Console.WriteLine(r);
        Console.Read();