获取屏幕的比例

本文关键字:屏幕 获取 | 更新日期: 2023-09-27 18:13:03

我们可以通过Screen类获得屏幕的分辨率和内容。所以,我们应该使用Screen.WorkingArea.WidthScreen.WorkingArea.Height,如果我们想在屏幕中央放置一些东西。

但是,在Windows 8.1(不确定其他操作系统)有可能缩放屏幕上的项目,如果他们看起来太小了。您可以通过调用桌面的上下文菜单,选择"屏幕分辨率",然后选择"放大或缩小文本和其他项目"来检查。

问题是,它似乎确实提高了屏幕分辨率!所以,Screen.WorkingArea.WidthScreen.WorkingArea.Height将给你缩放值,Screen.WorkingArea.Width/2Screen.WorkingArea.Height/2点将不再在实际屏幕的中心(缩放200%,这个点将在屏幕的右下角)。这可能会破坏许多UI项的位置。

那么,有可能得到缩放值吗?

获取屏幕的比例

大多数检索DPI的方法依赖于现有的控件,这有时可能不方便。但是DPI总是可以从注册表中检索到。在c#中:

using Microsoft.Win32;
//...
var currentDPI = (int)Registry.GetValue("HKEY_CURRENT_USER''Control Panel''Desktop", "LogPixels", 96);

刻度将为

var scale = 96/(float)currentDPI;
 var currentDPI = (int)Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER''Control Panel''Desktop''WindowMetrics", "AppliedDPI", 0);
 SetClientSizeCore((int)((float)Width * (float)currentDPI/96.0f), (int)((float)Height * (float)currentDPI/96.0f));

这是一个在Windows 10上获取DPI信息的工作方法:

int DPI = Int32.Parse((string)Registry.GetValue(
   @"HKEY_CURRENT_USER'Software'Microsoft'Windows'CurrentVersion'ThemeManager",
   "LastLoadedDPI", "96"));

正如评论中提到的,接受的答案是基于注册表的,在Windows 11中不起作用。经过所有的研究,我找到了一个在Windows 11上运行良好的解决方案,并为每个显示器显示正确的刻度。关键思想包括以下几个步骤:

  1. 检查操作系统版本是否支持DPI每监视器使用此结构
  2. 如果它不支持:尝试通过Control内置函数获得DPI: DeviceDpi
  3. 如果它支持:
  4. 按点获取监视器(您可以使用Screen.Bounds.Left+1): MonitorFromPoint;
  5. 使用:GetDpiForMonitor获取该监视器的DPI最终标度值为DPI/100 * 96。

完整脚本如下:

public static class DPIUtil
{
    /// <summary>
    /// Min OS version build that supports DPI per monitor
    /// </summary>
    private const int MinOSVersionBuild = 14393;
    /// <summary>
    /// Min OS version major build that support DPI per monitor
    /// </summary>
    private const int MinOSVersionMajor = 10;
    /// <summary>
    /// Flag, if OS supports DPI per monitor
    /// </summary>
    private static bool _isSupportingDpiPerMonitor;
    /// <summary>
    /// Flag, if OS version checked
    /// </summary>
    private static bool _isOSVersionChecked;
    /// <summary>
    /// Flag, if OS supports DPI per monitor
    /// </summary>
    internal static bool IsSupportingDpiPerMonitor
    {
        get
        {
            if (_isOSVersionChecked)
            {
                return _isSupportingDpiPerMonitor;
            }
            _isOSVersionChecked = true;
            var osVersionInfo = new OSVERSIONINFOEXW
            {
                dwOSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEXW))
            };
            if (RtlGetVersion(ref osVersionInfo) != 0)
            {
                _isSupportingDpiPerMonitor = Environment.OSVersion.Version.Major >= MinOSVersionMajor && Environment.OSVersion.Version.Build >= MinOSVersionBuild;
                return _isSupportingDpiPerMonitor;
            }
            _isSupportingDpiPerMonitor = osVersionInfo.dwMajorVersion >= MinOSVersionMajor && osVersionInfo.dwBuildNumber >= MinOSVersionBuild;
            return _isSupportingDpiPerMonitor;
        }
    }
    /// <summary>
    /// Get scale factor for an each monitor
    /// </summary>
    /// <param name="control"> Any control for OS who doesn't support DPI per monitor </param>
    /// <param name="monitorPoint"> Monitor point (Screen.Bounds) </param>
    /// <returns> Scale factor </returns>
    public static double ScaleFactor(Control control, Point monitorPoint)
    {
        var dpi = GetDpi(control, monitorPoint);
        return dpi * 100 / 96.0;
    }
    /// <summary>
    /// Get DPI for a monitor
    /// </summary>
    /// <param name="control"> Any control for OS who doesn't support DPI per monitor </param>
    /// <param name="monitorPoint"> Monitor point (Screen.Bounds) </param>
    /// <returns> DPI </returns>
    public static uint GetDpi(Control control, Point monitorPoint)
    {
        uint dpiX;
        if (IsSupportingDpiPerMonitor)
        {
            var monitorFromPoint = MonitorFromPoint(monitorPoint, 2);
            GetDpiForMonitor(monitorFromPoint, DpiType.Effective, out dpiX, out _);
        }
        else
        {
            // If using with System.Windows.Forms - can be used Control.DeviceDpi
            dpiX = control == null ? 96 : (uint)control.DeviceDpi;
        }
        return dpiX;
    }
    /// <summary>
    /// Retrieves a handle to the display monitor that contains a specified point.
    /// </summary>
    /// <param name="pt"> Specifies the point of interest in virtual-screen coordinates. </param>
    /// <param name="dwFlags"> Determines the function's return value if the point is not contained within any display monitor. </param>
    /// <returns> If the point is contained by a display monitor, the return value is an HMONITOR handle to that display monitor. </returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint"/>
    /// </remarks>
    [DllImport("User32.dll")]
    internal static extern IntPtr MonitorFromPoint([In] Point pt, [In] uint dwFlags);
    /// <summary>
    /// Queries the dots per inch (dpi) of a display.
    /// </summary>
    /// <param name="hmonitor"> Handle of the monitor being queried. </param>
    /// <param name="dpiType"> The type of DPI being queried. </param>
    /// <param name="dpiX"> The value of the DPI along the X axis. </param>
    /// <param name="dpiY"> The value of the DPI along the Y axis. </param>
    /// <returns> Status success </returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor"/>
    /// </remarks>
    [DllImport("Shcore.dll")]
    private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);
    /// <summary>
    /// The RtlGetVersion routine returns version information about the currently running operating system.
    /// </summary>
    /// <param name="versionInfo"> Operating system version information </param>
    /// <returns> Status success</returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion"/>
    /// </remarks>
    [SecurityCritical]
    [DllImport("ntdll.dll", SetLastError = true)]
    private static extern int RtlGetVersion(ref OSVERSIONINFOEXW versionInfo);
    /// <summary>
    /// Contains operating system version information.
    /// </summary>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexw"/>
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    private struct OSVERSIONINFOEXW
    {
        /// <summary>
        /// The size of this data structure, in bytes
        /// </summary>
        internal int dwOSVersionInfoSize;
        /// <summary>
        /// The major version number of the operating system.
        /// </summary>
        internal int dwMajorVersion;
        /// <summary>
        /// The minor version number of the operating system.
        /// </summary>
        internal int dwMinorVersion;
        /// <summary>
        /// The build number of the operating system.
        /// </summary>
        internal int dwBuildNumber;
        /// <summary>
        /// The operating system platform.
        /// </summary>
        internal int dwPlatformId;
        /// <summary>
        /// A null-terminated string, such as "Service Pack 3", that indicates the latest Service Pack installed on the system.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        internal string szCSDVersion;
        /// <summary>
        /// The major version number of the latest Service Pack installed on the system. 
        /// </summary>
        internal ushort wServicePackMajor;
        /// <summary>
        /// The minor version number of the latest Service Pack installed on the system.
        /// </summary>
        internal ushort wServicePackMinor;
        /// <summary>
        /// A bit mask that identifies the product suites available on the system. 
        /// </summary>
        internal short wSuiteMask;
        /// <summary>
        /// Any additional information about the system.
        /// </summary>
        internal byte wProductType;
        /// <summary>
        /// Reserved for future use.
        /// </summary>
        internal byte wReserved;
    }
    /// <summary>
    /// DPI type
    /// </summary>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-monitor_dpi_type"/>
    /// </remarks>
    private enum DpiType
    {
        /// <summary>
        /// The effective DPI. This value should be used when determining the correct scale factor for scaling UI elements.
        /// </summary>
        Effective = 0,
        /// <summary>
        /// The angular DPI. This DPI ensures rendering at a compliant angular resolution on the screen.
        /// </summary>
        Angular = 1,
        /// <summary>
        /// The raw DPI. This value is the linear DPI of the screen as measured on the screen itself. Use this value when you want to read the pixel density and not the recommended scaling setting.
        /// </summary>
        Raw = 2,
    }
}