如何获取Windows显示设置?

87

在Windows 7中有一个“显示”设置(控制面板->显示),它允许您更改屏幕上文本和其他项目的大小。我需要获取此设置的值,以便根据设置值在我的C#应用程序中开启/关闭某些功能。这是否可行?


1
你对哪个设置感兴趣?有很多种。 - David Heffernan
如果您使用设置 - 控制面板 -> 显示 打开此页面,那么只会有一个选项可以更改屏幕上文本和其他项目的大小。选项包括“较小”,“中等”和“较大”。 - newmember
11个回答

96

在所有缩放级别下,Surface Pro 上的 graphics.DpiX 和 DeviceCap.LOGPIXELSX 均返回 96。

相反,我通过以下方法计算了缩放因子:

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
public enum DeviceCap
{
    VERTRES = 10,
    DESKTOPVERTRES = 117,

    // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html
}  


private float getScalingFactor()
{
    Graphics g = Graphics.FromHwnd(IntPtr.Zero);
    IntPtr desktop = g.GetHdc();
    int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
    int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES); 

    float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;

    return ScreenScalingFactor; // 1.25 = 125%
}

13
哇,我已经搜索了很长时间如何获取真实的缩放因子,这最终解决了我的问题! - andreas
18
就在你快要哭丧丧地放弃的时候,突然遇到这样的事情。 - The Muffin Man
7
请注意,您可能希望同时使用这个解决方案和标记为已接受的解决方案……在我的系统上,读取 g.DpiX 返回 120,但缩放因子仍为 1.0。 (注:本翻译尽量保持原文意思不变,同时使语言更加通俗易懂,并且没有添加其他内容。) - Todd Myhre
10
在一台运行Win 10 64位操作系统的Dell Precision 5510笔记本电脑上,分辨率为3840x2160,显示缩放比例设置为200%(从桌面右键单击>>显示设置),以上代码中的LogicalScreenHeight和PhysicalScreenHeight返回相同的值,均为2160。因此,在这种情况下,这段代码无法获取所需的200%显示缩放比例。 - Nerdtron
8
我注意到与@Nerdtron使用类似的硬件出现了相同的问题。不幸的是,这段代码在多个显示器间不能可靠地工作。 - OfficeAddinDev
显示剩余6条评论

59

这个设置是屏幕的 DPI,即每英寸点数。

理解方式如下:

float dpiX, dpiY;
Graphics graphics = this.CreateGraphics();
dpiX = graphics.DpiX;
dpiY = graphics.DpiY;

目前我认为X和Y值不可能不同。96代表100%字体缩放(更小),120代表125%缩放(中等),144代表150%缩放(更大)。但是,用户可以设置除这些标准值以外的值。

请注意,除非您的应用程序声明为DPI感知,否则您观察到的值可能会受到DPI虚拟化的影响。


9
我尝试使用不同的设置运行这段代码,而 graphics.DpiX 的值始终为 96。 - newmember
1
DpiX 是 90?!我注销并重新登录后,它从 96 变成了 120。我正在使用一个全新的 WinForms 应用程序。我想知道你是否在你的应用程序中做了什么特殊的事情。你是否尝试过在一个全新的 WinForms 项目中运行? - David Heffernan
1
我尝试设置中等设置 - 它有效了!DpiX为120,但对于较大的设置,它是96... - newmember
11
好的,我现在理解了。问题在于你的应用程序未标记为 DPI 感知,因此它可能看起来模糊不清。你应该在清单文件中将应用程序标记为 DPI 感知。 - David Heffernan
3
@ColonelPanic this 只是任何控件。http://msdn.microsoft.com/zh-cn/library/system.windows.forms.control.creategraphics.aspx - David Heffernan
显示剩余7条评论

19

以下是在WPF中如何进行操作。返回值以WPF的逻辑单位表示,这与1/96英寸相等。所以如果您的屏幕DPI设置为96,您将得到一个值为1。

Matrix m =
PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice;
double dx = m.M11; // notice it's divided by 96 already
double dy = m.M22; // notice it's divided by 96 already

(来源)


19

我认为最简单的方法是使用GetDeviceCaps函数。从pinvoke.net获取:

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);

public enum DeviceCap
{
  /// <summary>
  /// Logical pixels inch in X
  /// </summary>
  LOGPIXELSX = 88,
  /// <summary>
  /// Logical pixels inch in Y
  /// </summary>
  LOGPIXELSY = 90

  // Other constants may be founded on pinvoke.net
}      

用法:

Graphics g = Graphics.FromHwnd(IntPtr.Zero);            
IntPtr desktop = g.GetHdc();

int Xdpi = GetDeviceCaps(desktop, (int)DeviceCap.LOGPIXELSX);
int Ydpi = GetDeviceCaps(desktop, (int)DeviceCap.LOGPIXELSY);    
在这种方法中,您无需将应用程序标记为dpi感知。

16
不要忘记调用ReleaseHdc函数。 - Chad
1
@BitsEvolved 绝对没错。这解决了“我的”内存泄漏问题(在完全不同的上下文中)!非常感谢。 - Wolf
3
在您提供的链接中,GetDeviceCaps函数的注释中针对LOGPIXELSX和LOGPIXELSY分别写道:“在使用多个显示器的系统中,此值对于所有监视器都是相同的。”需要注意的一点是… - RenniePet

14

这里有一个在Windows 10上运作良好的解决方案。不需要DPI意识或其他任何东西。

public static int GetWindowsScaling()
{
    return (int)(100 * Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth);
}

有趣的是,有时候Screen.PrimaryScreen.Bounds.Width并不返回实际屏幕宽度,而是返回与SystemParameters.PrimaryScreenWidth相同的值。我在使用UI自动化时遇到了这个问题。因此,我不得不使用Screen.PrimaryScreen.WorkingArea.Width来解决它。 - sarh
我认为这个可以工作是因为Screen.PrimaryScreen.Bounds有物理像素,而SystemParameters.PrimaryScreenWidth是逻辑像素?很遗憾我们无法确定其他屏幕的情况。 - Garr Godfrey

13

使用Farshid T的答案作为基础,在每个缩放因子中都有效,除了125%。我测试了大约20个不同的缩放因子,DPI始终返回96,除非设置为125%,这将返回120的DPI。 120/96 = 1.25。不确定为什么会出现这种情况,但是这段代码似乎适用于任何比例设置。

    [DllImport("gdi32.dll")]
    static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
    public enum DeviceCap
    {
        VERTRES = 10,
        DESKTOPVERTRES = 117,
        LOGPIXELSY = 90,

        // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html

使用方法:

        Graphics g = Graphics.FromHwnd(IntPtr.Zero);
        IntPtr desktop = g.GetHdc();
        int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
        int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES);
        int logpixelsy = GetDeviceCaps(desktop, (int)DeviceCap.LOGPIXELSY);
        float screenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
        float dpiScalingFactor = (float)logpixelsy / (float)96;

        if (screenScalingFactor > 1 ||
            dpiScalingFactor > 1)
        {
            // do something nice for people who can't see very well...
        }

5
为了安全起见,您还应该添加以下内容: g.ReleaseHdc(desktop); - Moon

12

这是一个非常古老的问题,但自 Windows 8.1 以来,人们可以使用各种其他功能,比如GetDpiForWindow

在 C# 中:

[DllImport("user32.dll")]
static extern int GetDpiForWindow(IntPtr hWnd);

public float GetDisplayScaleFactor(IntPtr windowHandle)
{
    try
    {
        return GetDpiForWindow(windowHandle) / 96f;
    }
    catch
    {
        // Or fallback to GDI solutions above
        return 1;
    }
}

为了使其在 Windows 10 周年更新版上正常工作,您需要向 C# 项目添加一个 app.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
          manifestVersion="1.0">
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <!-- The combination of below two tags have the following effect : 
      1) Per-Monitor for >= Windows 10 Anniversary Update
      2) System < Windows 10 Anniversary Update -->
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
    </windowsSettings>
  </application>

</assembly>

你说这应该适用于Windows 8.1及更高版本,但在你引用的Microsoft文档中却写着“要求:Windows 10,版本1607”。 - RenniePet
没错,确实是个好发现。我想我把它和GetDpiForMonitor API调用混淆了。https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor - Ziriax

8

我在控制台应用程序中使用这种方式:

float dpiX, dpiY;
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
    dpiX = graphics.DpiX;
    dpiY = graphics.DpiY;
}

6
在使用WPF时,请使用以下代码片段:
PresentationSource source = PresentationSource.FromVisual(this);

        double dpiX, dpiY;
        if (source != null)
        {
            dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
            dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
        }

2

以下是Ric Gaudet答案的更完整版本:

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);

public enum DeviceCap
{
    VERTRES = 10,
    DESKTOPVERTRES = 117
}

static double GetWindowsScreenScalingFactor(bool percentage = true)
{
    //Create Graphics object from the current windows handle
    Graphics GraphicsObject = Graphics.FromHwnd(IntPtr.Zero);
    //Get Handle to the device context associated with this Graphics object
    IntPtr DeviceContextHandle = GraphicsObject.GetHdc();
    //Call GetDeviceCaps with the Handle to retrieve the Screen Height
    int LogicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.VERTRES);
    int PhysicalScreenHeight = GetDeviceCaps(DeviceContextHandle, (int)DeviceCap.DESKTOPVERTRES);
    //Divide the Screen Heights to get the scaling factor and round it to two decimals
    double ScreenScalingFactor = Math.Round((double)PhysicalScreenHeight / (double)LogicalScreenHeight, 2);
    //If requested as percentage - convert it
    if (percentage)
    {
        ScreenScalingFactor *= 100.0;
    }
    //Release the Handle and Dispose of the GraphicsObject object
    GraphicsObject.ReleaseHdc(DeviceContextHandle);
    GraphicsObject.Dispose();
    //Return the Scaling Factor
    return ScreenScalingFactor;
}

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