关于屏幕分辨率:如何使用C#以编程方式更改Windows 10 Display Scaling

How can I change Windows 10 Display Scaling Programmatically using C#

我正在尝试找到一种方法来使用C#以编程方式更改Windows 10中的显示比例。

我还要说的是,我并不是要创建一个自动强制用户屏幕更改分辨率/缩放比例的应用程序。它只是我可以从托盘切换秤的工具,这是我经常需要进行测试的东西。因此专门为此动作而设计。

因此,当用户通过如下所示的官方对话框手动执行此操作时,我能够跟踪设置了哪些注册表项(HKEY_CURRENT_USER \ Control Panel \ Desktop):

Windows 10 Display Scaling Dialog

但是,显然直接使用注册表意味着我需要重新启动计算机才能生效。

我知道您可以使用Pinvoke更改屏幕分辨率:
设置我的显示分辨率

我想知道是否也可以为给定的屏幕更改此"%"?也就是说,我上方的屏幕显示为150%,我希望能够以编程方式在100-500%的整个范围内进行更改。


这是我在系统设置应用程序(沉浸式控制面板)上所做的RnD的学习。 (有关我根据此学习创建的简单C ++ API的信息,请参见我的其他答案-https://stackoverflow.com/a/58066736/981766)

好的。

  • System Settings应用程序(Windows 10随附的新的沉浸式控制面板)能够做到这一点。这意味着肯定有一个API,只是Microsoft没有公开它。
  • 系统设置应用程序是UWP应用程序,但可以与调试器-WinDbg挂钩。
  • 我使用WinDbg来处理此应用程序发出的呼叫。我发现,一旦执行特定功能-user32!_imp_NtUserDisplayConfigSetDeviceInfo,新的DPI设置将在我的计算机上生效。

    好的。

    我无法在此函数上设置断点,但能够在DisplayConfigSetDeviceInfo() (bp user32!DisplayConfigSetDeviceInfo)上设置一个断点。

    好的。

    DisplayConfigSetDeviceInfo(msdn链接)是一个公共功能,但似乎设置应用程序正在向其发送未记录的参数。
    这是我在调试会话期间发现的参数。

    好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ((user32!DISPLAYCONFIG_DEVICE_INFO_HEADER *)0x55df8fba30)                 : 0x55df8fba30 [Type: DISPLAYCONFIG_DEVICE_INFO_HEADER *]
        [+0x000] type             : -4 [Type: DISPLAYCONFIG_DEVICE_INFO_TYPE]
        [+0x004] size             : 0x18 [Type: unsigned int]
        [+0x008] adapterId        [Type: _LUID]
        [+0x010] id               : 0x0 [Type: unsigned int]
    0:003> dx -r1 (*((user32!_LUID *)0x55df8fba38))
    (*((user32!_LUID *)0x55df8fba38))                 [Type: _LUID]
        [+0x000] LowPart          : 0xcbae [Type: unsigned long]
        [+0x004] HighPart         : 0 [Type: long]

    基本上,传递给DisplayConfigSetDeviceInfo()DISPLAYCONFIG_DEVICE_INFO_HEADER结构成员的值是:

    好的。

    1
    2
    3
    type : -4
    size : 0x18
    adapterId : LowPart : 0xcbae HighPart :0

    wingdi.h中定义的枚举类型为:

    好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    typedef enum
    {
          DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME                 = 1,
          DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME                 = 2,
          DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE       = 3,
          DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME                = 4,
          DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE          = 5,
          DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE            = 6,
          DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION  = 7,
          DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION  = 8,
          DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO         = 9,
          DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE        = 10,
          DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32                = 0xFFFFFFFF
    } DISPLAYCONFIG_DEVICE_INFO_TYPE;

    在设置应用尝试发送-4的类型时,我们可以看到枚举没有负值。

    好的。

    如果我们能够完全对它进行反向工程,我们将有一个有效的API来设置监视器的DPI。

    好的。

    微软为自己的应用程序提供了一些其他人无法使用的特殊API,这似乎非常不公平。

    好的。

    更新1:

    为了验证我的理论,我复制(使用WinDbg)作为参数发送到DisplayConfigSetDeviceInfo()DISPLAYCONFIG_DEVICE_INFO_HEADER结构的字节;当从"系统设置"应用更改DPI缩放比例时(尝试设置为150%DPI缩放比例)。

    好的。

    然后,我编写了一个简单的C程序,将这些字节(24字节-0x18字节)发送到DisplayConfigSetDeviceInfo()
    然后,我将DPI缩放比例更改回100%,并运行了我的代码。果然,在运行代码时DPI缩放比例确实发生了变化!!!

    好的。

    1
    2
    3
    BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00 };
    DISPLAYCONFIG_DEVICE_INFO_HEADER* packet = (DISPLAYCONFIG_DEVICE_INFO_HEADER*)buf;
        DisplayConfigSetDeviceInfo(packet);

    请注意,与LUID相同的代码可能对您不起作用,并且指向系统上的显示的id参数会有所不同(LUID通常用于GPU,id可以是源ID,目标ID或其他一些ID ,此参数取决于DISPLAYCONFIG_DEVICE_INFO_HEADER :: type)。

    好的。

    我现在必须弄清楚这24个字节的含义。

    好的。

    更新2:

    这是尝试设置175%dpi缩放比例时得到的字节数。

    好的。

    1
    BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00 };

    如果我们比较两个字节缓冲区,我们可以得出以下结论。

    好的。

  • 字节号21用于指定DPI缩放,因为所有其他字节在150%和175%之间相同。
  • 对于150%缩放,字节21的值为1,而对于175%则为2。此监视器的默认DPI缩放(推荐)为125%。
  • 在Windows看来,@ Dodge提到的Technet文章中,0对应于建议的DPI缩放值。其他整数对应于相对于此推荐值的dpi缩放。 1表示缩放比例领先一步,-1表示缩小比例。例如。如果推荐值为125%,则值为1表示150%缩放。这确实是我们所看到的。
  • 现在剩下的唯一事情就是弄清楚如何获得建议的显示器DPI缩放比例值,然后我们将能够编写以下形式的API-SetDPIScaling(monitor_LUID, DPIScale_percent)

    好的。

    更新3:

    如果我们检查@Dodge的答案中提到的注册表项,我们就会知道这些整数存储为DWORD,并且由于我的计算机是低端字节序的,因此这意味着将使用最后4个字节(21至24字节)。因此,要发送负数,我们将必须使用DWORD的2的补码,并将字节写为little endian。

    好的。

    更新4:

    我还一直在研究Windows如何尝试生成用于存储DPI缩放值的Monitor ID。
    对于任何监视器,用户选择的DPI缩放值存储在:

    好的。

    1
    2
    HKEY_CURRENT_USER\\Control Panel\\Desktop\\PerMonitorSettings\\
    *MonitorID*

    对于连接到我的计算机的Dell显示器,显示器ID为DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297
    我能够弄清监视器ID的结构。我用4种不同的显示器验证了我的理论。

    好的。

    对于Dell显示器(dpi缩放比例存储在HKEY_CURRENT_USER\\Control Panel\\Desktop\\PerMonitorSettings\\
    DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297
    中),如下所示(很抱歉,无法添加图像,无法找到一种简洁表示信息的方式)。

    好的。

    Monitor ID image windows 10 x64 17763, 18362

    好的。

    从本质上讲,从EDID到构造监视器ID所需的数据如下。

    好的。

  • 制造商编号

  • EDID的字节8、9(大字节序)。
  • 例如。对于Dell显示器,EDID为这些字节提供10AC。除位15外,请一次使用其余15位(位0至14)中的5位。 (10AC) 16 等于(0001-0000-1010-1100) 2 。从LSB开始,将该二进制文件分成5位块,得到(0-00100-00101-01100) 2 。将每个块转换为十进制(0-4-5-12) 10 ,现在'D'是第4个字母,'E'是第5个字母,'L'是第12个字母。
  • 备用广告:@@@
  • 好的。

  • 产品编号

  • EDID的字节10、11(小尾数)
  • 例如。对于Dell显示器,EDID具有BCA0。由于这是小端,因此只需将其转换为A0BC即可获得产品ID。
  • 备用广告:000
  • 好的。

  • 序列号

  • 使用DTD序列号。 EDID的基本块(前128个字节)具有4个称为DTD的数据块。它们可以用于存储时序信息或任意数据。 4个DTD块位于字节54、72、90和108。具有序列号的DTD块的前2个字节(字节0和1)为零,第2个字节也为零,第3个字节为0xFF。第四又是零。从字节5开始具有ASCII序列号。序列号最多可占用13个字节(DTD块的字节5至17)。如果序列号少于13个字符(13个字节),则它将由换行符(0x0A)终止。
  • 对于Dell显示器,它是00-00-00-FF-00-39-44-52-58-56-36-38-41-30-4C-57-4C-0A。请注意,序列号有12个字节,并以换行(0x0A)结尾。将39-44-52-58-56-36-38-41-30-4C-57-4C转换为ASCII可以得到9DRXV68A0LWL
  • 后备:EDID字节12处的序列号。 EDID可以在2个位置存储序列号,如果未找到DTD块EDID,则OS使用字节12至15(32位小端)的序列号。对于Dell显示器,它是(4C-57-4C-30) 16 ,因为是低位字节序,所以序列号是(304C574C) 16 ,即?(810309452?)。 ) 10 。操作系统将使用该值(以10为底)作为后备值(即使不存在此值),则使用0
  • 好的。

  • 制造周

  • EDID的字节16(可以有一些变化,请参阅Wikipedia文章)
  • 对于Dell显示器,为(21) 16
  • 备用广告:00
  • 好的。

  • 制造年份

  • EDID的字节17
  • 自1990年以来的制造年份。在字节17的值中加上1990。
  • 对于Dell显示器,它是(1A) 16 。 (1A) 16 +(1990) 10 =(07C6) 16
  • 备用广告:0000
  • 好的。

  • 依基块校验和

  • EDID的字节127
  • 从维基百科-校验和。所有128个字节的总和应等于0(mod 256)。
  • 没有后备。有效的EDID必须具有此值。
  • 好的。

    请注意,仅需要EDID的前128个字节。

    好的。

    关于备用的注意事项

    如果不存在构造监视器ID所需的某些数据,则OS使用回退。上面的列表中列出了我在Windows 10计算机上观察到的构造监视器ID所需的每个数据的备用。我手动编辑了DELL显示器的EDID(链接1,链接2,链接3-注意-链接3中建议的方法可能会损坏您的系统,只有在确定的情况下才可以继续操作;强烈建议使用链接1)以删除上面给出的所有6项,显示器ID为我构建的操作系统(不带MD5后缀)为@@@0000810309452_00_0000_85,当我什至删除字节12的序列号时,构建的监视器ID为@@@00000_00_0000_A4

    好的。

    更新4:

    DPI缩放是源的属性,而不是目标的属性,因此DisplayConfigGetDeviceInfo()DisplayConfigSetDeviceInfo()中使用的id参数是源ID,而不是目标ID。

    好的。

    上面建议的注册表方法在大多数情况下应该可以正常工作,但是有两个缺点。 一个是,它不能使我们与系统设置应用程序保持一致(就设置生效时间而言)。 其次,在极少数情况下(无法再复制),我已经看到OS生成的Monitor ID字符串略有不同-它具有更多组件,如上图所示。

    好的。

    我已经成功创建了一个API,我们可以使用该API以与系统设置应用程序完全相同的方式获取/设置DPI缩放比例。 将发布一个新的答案,因为这更多地是我寻找解决方案所采用的方法。

    好的。

    好。


    C++用于获取/设置DPI的API。

    我能够对系统设置应用程序进行反向工程,并提供了一个API。
    它的代码在我的github存储库https://github.com/lihas/windows-DPI-scaling-sample中。

    我已经在此问题的上一个回答(https://stackoverflow.com/a/57397039/981766)中跳过了很多关于该术语的解释。

    API的摘要

  • 类别:DpiHelper
  • 方法 :

  • GetDPIScalingInfo()和
  • SetDPIScaling()
  • 获取显示器的DPI信息


    用adapterID和sourceID调用DPIScalingInfo()

    1
    DpiHelper::DPIScalingInfo DpiHelper::GetDPIScalingInfo(LUID adapterID, UINT32 sourceID)

    设置显示器的DPI


    调用SetDPIScaling()并设置adapterID,sourceID和DPI缩放比例。例如。如果要将源的DPI缩放比例设置为175%,请在最后一个参数中传递175。

    1
    bool DpiHelper::SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet)

    存储库中的DpiHelper.h拥有这两种方法的详尽文档。

    另请阅读DpiHelper.h中的文档以及该存储库的自述文件。
    我已经在公共领域发布了回购中的所有代码,因此可以使用任何方式使用它。

    示例应用

    我还创建了一个MFC应用程序,该应用程序使用此帮助程序库来获取/设置DPI缩放比例。
    这将帮助您了解如何使用DpiHelper类。

    这是它的外观。

    MFC app to get/set DPI

    关于Windows上DPI缩放的注意事项

  • DPI缩放是源的属性,而不是目标的属性(有关这些术语,请参见ViPN)。
  • 显示器的DPI缩放取决于3个因素-分辨率,显示器的物理尺寸,预期的观看距离。 Windows用于获得推荐值的确切公式未知。
  • 在操作系统中,与建议的显示器DPI比例相比,DPI比例值具有含义。因此,尽管我们在系统设置应用程序中看到了100%,125%等,但OS无法理解百分比的缩放。而是使用高于或低于建议的缩放比例的步骤数。例如。 DPI缩放比例值为-1表示比建议的DPI缩放比例低1步。因此,如果对于监视器,建议值为150%,则-1表示125%。
  • 我使用WinDbg Preview(MS Store)和Ghidra进行逆向工程。有时候我会因为缺乏IDA Pro许可证而放弃,有人建议我Ghidra。从那以后我一直是粉丝。

    非常感谢Ghidra !!!


    在搜索完全相同的同时,我找到了您的问题并找到了可能的解决方案。

    我发现此%值的每个监视器切换位于Computer\\HKEY_CURRENT_USER\\Control Panel\\Desktop\\PerMonitorSettings\\*monitorId*\\DpiValue的注册表中。值的含义取决于屏幕(大小和dpi),有关详细信息,请参阅此reddit帖子。

    对于我的24英寸1080p屏幕,0表示100%,1表示125%。这篇Technet文章似乎有点解释了这些值。

    不幸的是,仅更改注册表值是不够的。但是您可以在写入注册表后通过更改分辨率来刷新dpi。

    下面的代码设置dpi,然后将分辨率切换为低和高以触发dpi更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;

    namespace SetDpiScale
    {
        public partial class Form1 : Form
        {
            public enum DMDO
            {
                DEFAULT = 0,
                D90 = 1,
                D180 = 2,
                D270 = 3
            }

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            struct DEVMODE
            {
                public const int DM_PELSWIDTH = 0x80000;
                public const int DM_PELSHEIGHT = 0x100000;
                private const int CCHDEVICENAME = 32;
                private const int CCHFORMNAME = 32;

                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
                public string dmDeviceName;
                public short dmSpecVersion;
                public short dmDriverVersion;
                public short dmSize;
                public short dmDriverExtra;
                public int dmFields;

                public int dmPositionX;
                public int dmPositionY;
                public DMDO dmDisplayOrientation;
                public int dmDisplayFixedOutput;

                public short dmColor;
                public short dmDuplex;
                public short dmYResolution;
                public short dmTTOption;
                public short dmCollate;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
                public string dmFormName;
                public short dmLogPixels;
                public int dmBitsPerPel;
                public int dmPelsWidth;
                public int dmPelsHeight;
                public int dmDisplayFlags;
                public int dmDisplayFrequency;
                public int dmICMMethod;
                public int dmICMIntent;
                public int dmMediaType;
                public int dmDitherType;
                public int dmReserved1;
                public int dmReserved2;
                public int dmPanningWidth;
                public int dmPanningHeight;
            }

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            static extern int ChangeDisplaySettings([In] ref DEVMODE lpDevMode, int dwFlags);

            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click(object sender, EventArgs e)
            {
                ChangeDPI(0); // 100%
            }
            private void button2_Click(object sender, EventArgs e)
            {
                ChangeDPI(1); // 125%
            }

            void ChangeDPI(int dpi)
            {
                RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel", true);

                key = key.OpenSubKey("Desktop", true);
                key = key.OpenSubKey("PerMonitorSettings", true);
                key = key.OpenSubKey("*monitor id where to change the dpi*", true); // my second monitor here

                key.SetValue("DpiValue", dpi);

                SetResolution(1920, 1080); // this sets the resolution on primary screen
                SetResolution(2560, 1440); // returning back to my primary screens default resolution
            }

            private static void SetResolution(int w, int h)
            {
                long RetVal = 0;

                DEVMODE dm = new DEVMODE();

                dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));

                dm.dmPelsWidth = w;
                dm.dmPelsHeight = h;

                dm.dmFields = DEVMODE.DM_PELSWIDTH | DEVMODE.DM_PELSHEIGHT;


                RetVal = ChangeDisplaySettings(ref dm, 0);
            }
        }
    }


    如果要更改系统范围的DPI缩放比例(系统DPI缩放比例-如果设置了多个显示器或只有一个显示器,则是主显示器的缩放比例),而不是每个显示器的DPI缩放比例,
    可以使用SystemParametersInfo()。

    该API具有一个未记录的参数,可以实现以下目的:SPI_SETLOGICALDPIOVERRIDE

    从Microsoft文档:

    1
    2
    SPI_SETLOGICALDPIOVERRIDE   Do not use.
    0x009F

    用法:

    1
    SystemParametersInfo(SPI_SETLOGICALDPIOVERRIDE, relativeIndex, (LPVOID)0, 1);

    要弄清楚上面的relativeIndex变量使用什么值,您必须了解OS如何期望指定DPI缩放值(在此进行解释)。

    简而言之,relativeIndex告诉您要在建议的DPI缩放值上方或下方执行多少步。例如。如果建议的DPI缩放比例值是125%,并且您想要将150%设置为缩放比例,则relativeIndex将为1(超过125%的一步),或者如果您想要设置100%,则relativeIndex将为-1(低于125的一步) %)。

    所有步骤的大小可能不相同。

    1
    100,125,150,175,200,225,250,300,350, 400, 450, 500

    直到250%,步长以25%为单位增加,之后以50%为单位增加。

    因此,必须首先通过SPI_GETLOGICALDPIOVERRIDE参数获得建议的DPI缩放比例的值,对于该比例,可以使用相同的API。

    1
    SystemParametersInfo(SPI_GETLOGICALDPIOVERRIDE, 0, (LPVOID)&dpi, 1);

    上面的dpi变量中返回的值也应以特殊方式理解。
    该值将为负,其大小将指示上面列表中DPI缩放百分比的指数。

    因此,如果此API返回-1,则建议的DPI缩放比例值将为125%。

    样例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #include <iostream>
    #include <Windows.h>

    using namespace std;


    static const UINT32 DpiVals[] = { 100,125,150,175,200,225,250,300,350, 400, 450, 500 };

    /*Get default DPI scaling percentage.
    The OS recommented value.
    */

    int GetRecommendedDPIScaling()
    {
        int dpi = 0;
        auto retval = SystemParametersInfo(SPI_GETLOGICALDPIOVERRIDE, 0, (LPVOID)&dpi, 1);

        if (retval != 0)
        {
            int currDPI = DpiVals[dpi * -1];
            return currDPI;
        }

        return -1;
    }

    void SetDpiScaling(int percentScaleToSet)
    {
        int recommendedDpiScale = GetRecommendedDPIScaling();

        if (recommendedDpiScale > 0)
        {
            int index = 0, recIndex = 0, setIndex = 0 ;
            for (const auto& scale : DpiVals)
            {
                if (recommendedDpiScale == scale)
                {
                    recIndex = index;
                }
                if (percentScaleToSet == scale)
                {
                    setIndex = index;
                }
                index++;
            }
           
            int relativeIndex = setIndex - recIndex;
            SystemParametersInfo(SPI_SETLOGICALDPIOVERRIDE, relativeIndex, (LPVOID)0, 1);
        }
    }

    int main()
    {
        for (;;)
        {
            int n = 0, dpiToSet = 0;
            cout << R"(
                1. Show Recommended DPI
                2. Set DPI
                Anything else to exit
    )"
    ;
            cin >> n;
            switch (n)
            {
            case 1:
                cout <<"recommened scaling:" << GetRecommendedDPIScaling() <<"%" << endl;
                break;
            case 2:
                cout <<"enter scaling to set in percentage" << endl;
                cin >> dpiToSet;
                SetDpiScaling(dpiToSet);
                break;
            default:
                exit(0);
                break;
            }
        }
        return 0;
    }

    源代码:https://github.com/lihas/windows-DPI-scaling-sample。

    这是一个示例运行。
    console app - SystemParametersInfo() sample run

    优点和缺点
    关于我以前的方法(https://stackoverflow.com/a/58066736/981766,https://stackoverflow.com/a/57397039/981766)

    优点

  • 这是一个非常简单的API。因此,当您只需要在多监视器设置中更改主监视器的DPI缩放比例时,或者如果只有一个监视器,则首选此方法。
  • 缺点

  • 无法在多监视器设置上设置非主监视器的DPI缩放比例。
  • 不返回当前应用的DPI缩放比例(尽管您可以使用其他OS API)
  • 不会为您提供最大,最小的DPI缩放比例值。虽然如果您尝试在此范围之外进行设置,则OS不允许这样做,并且将使用允许的最接近值。
  • 参考文献

  • https://social.msdn.microsoft.com/Forums/vstudio/zh-CN/3259c521-b3ed-4121-97da-70a08fb8bb19/change-setting?forum=windowsgeneraldevelopmentissues(略有错误)
  • 如何使用python代码设置Windows缩放比例和布局
  • https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-systemparametersinfoa?redirectedfrom=MSDN
  • https://github.com/lihas/windows-DPI-scaling-sample

  • 除了Sahil Singh的回答。
    MonitorID可以作为subkeys放在以下位置:HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers\\ScaleFactors


    这是我基于@Sahil Singh的代码:

    DLL项目包装了C ++ API:

    stdafx.h:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #pragma once

    #include"targetver.h"

    #define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
    // Windows Header Files
    #include <windows.h>

    // reference additional headers your program requires here
    #ifdef __cplusplus
    extern"C" {
    #endif
        extern __declspec(dllexport) void PrintDpiInfo();
        extern __declspec(dllexport) void SetDPIScaling(INT32 adapterIDHigh, UINT32 adapterIDlow, UINT32 sourceID, UINT32 dpiPercentToSet);
        extern __declspec(dllexport) void RestoreDPIScaling();
    #ifdef __cplusplus
    }
    #endif

    DpiHelper.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    // DpiHelper.cpp : Defines the exported functions for the DLL application.
    //

    #include"stdafx.h"
    #include"DpiHelper.h"
    #include <memory>
    #include <cassert>
    #include <string>
    #include <map>

    bool DpiHelper::GetPathsAndModes(std::vector<DISPLAYCONFIG_PATH_INFO>& pathsV, std::vector<DISPLAYCONFIG_MODE_INFO>& modesV, int flags)
    {
        UINT32 numPaths = 0, numModes = 0;
        auto status = GetDisplayConfigBufferSizes(flags, &numPaths, &numModes);
        if (ERROR_SUCCESS != status)
        {
            return false;
        }

        std::unique_ptr<DISPLAYCONFIG_PATH_INFO[]> paths(new DISPLAYCONFIG_PATH_INFO[numPaths]);
        std::unique_ptr<DISPLAYCONFIG_MODE_INFO[]> modes(new DISPLAYCONFIG_MODE_INFO[numModes]);
        status = QueryDisplayConfig(flags, &numPaths, paths.get(), &numModes, modes.get(), nullptr);
        if (ERROR_SUCCESS != status)
        {
            return false;
        }

        for (unsigned int i = 0; i < numPaths; i++)
        {
            pathsV.push_back(paths[i]);
        }

        for (unsigned int i = 0; i < numModes; i++)
        {
            modesV.push_back(modes[i]);
        }

        return true;
    }


    DpiHelper::DpiHelper()
    {
    }


    DpiHelper::~DpiHelper()
    {
    }


    DpiHelper::DPIScalingInfo DpiHelper::GetDPIScalingInfo(LUID adapterID, UINT32 sourceID)
    {
        DPIScalingInfo dpiInfo = {};

        DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_GET requestPacket = {};
        requestPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE;
        requestPacket.header.size = sizeof(requestPacket);
        assert(0x20 == sizeof(requestPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdated
        requestPacket.header.adapterId = adapterID;
        requestPacket.header.id = sourceID;

        auto res = ::DisplayConfigGetDeviceInfo(&requestPacket.header);
        if (ERROR_SUCCESS == res)
        {//success
            if (requestPacket.curScaleRel < requestPacket.minScaleRel)
            {
                requestPacket.curScaleRel = requestPacket.minScaleRel;
            }
            else if (requestPacket.curScaleRel > requestPacket.maxScaleRel)
            {
                requestPacket.curScaleRel = requestPacket.maxScaleRel;
            }

            std::int32_t minAbs = abs((int)requestPacket.minScaleRel);
            if (DpiHelper::CountOf(DpiVals) >= (size_t)(minAbs + requestPacket.maxScaleRel + 1))
            {//all ok
                dpiInfo.current = DpiVals[minAbs + requestPacket.curScaleRel];
                dpiInfo.recommended = DpiVals[minAbs];
                dpiInfo.maximum = DpiVals[minAbs + requestPacket.maxScaleRel];
                dpiInfo.bInitDone = true;
            }
            else
            {
                //Error! Probably DpiVals array is outdated
                return dpiInfo;
            }
        }
        else
        {
            //DisplayConfigGetDeviceInfo() failed
            return dpiInfo;
        }

        return dpiInfo;
    }

    std::wstring GetTargetName(LUID adapterLUID, UINT32 sourceId)
    {
        std::vector<DISPLAYCONFIG_PATH_INFO> pathsV;
        std::vector<DISPLAYCONFIG_MODE_INFO> modesV;
        int flags = QDC_ONLY_ACTIVE_PATHS;
        if (false == DpiHelper::GetPathsAndModes(pathsV, modesV, flags))
        {
            wprintf(L"DpiHelper::GetPathsAndModes() failed\
    \
    "
    );
        }

        for (const auto& path : pathsV)
        {

            if (adapterLUID.LowPart == path.targetInfo.adapterId.LowPart
                && adapterLUID.HighPart == path.targetInfo.adapterId.HighPart
                && sourceId == path.sourceInfo.id)
            {
                DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName;
                deviceName.header.size = sizeof(deviceName);
                deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
                deviceName.header.adapterId = adapterLUID;
                deviceName.header.id = path.targetInfo.id;
                if (ERROR_SUCCESS != DisplayConfigGetDeviceInfo(&deviceName.header))
                {
                    wprintf(L"DisplayConfigGetDeviceInfo() failed\
    \
    "
    );
                }
                else
                {

                    std::wstring nameString = deviceName.monitorFriendlyDeviceName;
                    if (DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL == deviceName.outputTechnology)
                    {
                        nameString += L"(internal display)";
                    }
                    return nameString;
                }
            }

        }
        return L"N/A";

    }



    void printOne(LUID adapterLUID, UINT32 sourceID) {
        wprintf(L"GPU=%ld.%u,Desktop_Index_In_GPU=%d,Monitor=%ls\
    \
    "

            ,adapterLUID.HighPart
            , adapterLUID.LowPart
            , sourceID
            , GetTargetName(adapterLUID, sourceID).data());
    }



    bool DpiHelper::SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet)
    {

        wprintf(L"setting dpi scale to %d:", dpiPercentToSet);
        printOne(adapterID, sourceID);
        DPIScalingInfo dPIScalingInfo = GetDPIScalingInfo(adapterID, sourceID);

        if (dpiPercentToSet == dPIScalingInfo.current)
        {
            return true;
        }

        if (dpiPercentToSet < dPIScalingInfo.mininum)
        {
            dpiPercentToSet = dPIScalingInfo.mininum;
        }
        else if (dpiPercentToSet > dPIScalingInfo.maximum)
        {
            dpiPercentToSet = dPIScalingInfo.maximum;
        }

        int idx1 = -1, idx2 = -1;

        int i = 0;
        for (const auto& val : DpiVals)
        {
            if (val == dpiPercentToSet)
            {
                idx1 = i;
            }

            if (val == dPIScalingInfo.recommended)
            {
                idx2 = i;
            }
            i++;
        }

        if ((idx1 == -1) || (idx2 == -1))
        {
            //Error cannot find dpi value
            return false;
        }

        int dpiRelativeVal = idx1 - idx2;

        DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setPacket = {};
        setPacket.header.adapterId = adapterID;
        setPacket.header.id = sourceID;
        setPacket.header.size = sizeof(setPacket);
        assert(0x18 == sizeof(setPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdated
        setPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE;
        setPacket.scaleRel = (UINT32)dpiRelativeVal;

        auto res = ::DisplayConfigSetDeviceInfo(&setPacket.header);
        if (ERROR_SUCCESS == res)
        {
            return true;
        }
        else
        {
            return false;
        }
        return true;
    }


    #define MAX_ID  10
    LUID GpuId[MAX_ID];
    UINT32 DesktopIndexInGpu[MAX_ID];
    UINT32 oldDPI[MAX_ID];


    void PrintDpiInfo() {



        std::vector<DISPLAYCONFIG_PATH_INFO> pathsV;
        std::vector<DISPLAYCONFIG_MODE_INFO> modesV;
        int flags = QDC_ONLY_ACTIVE_PATHS;
        if (false == DpiHelper::GetPathsAndModes(pathsV, modesV, flags))
        {
            wprintf(L"DpiHelper::GetPathsAndModes() failed");
        }

        int i = 0;
        for (const auto& path : pathsV)
        {
            //get display name
            auto adapterLUID = path.targetInfo.adapterId;      
            auto sourceID = path.sourceInfo.id;
            std::wstring monitor_name = GetTargetName(adapterLUID, sourceID);
            printOne(adapterLUID, sourceID);

            DpiHelper::DPIScalingInfo dpiInfo = DpiHelper::GetDPIScalingInfo(adapterLUID, sourceID);

            GpuId[i] = adapterLUID;
            DesktopIndexInGpu[i] = sourceID;
            oldDPI[i] = dpiInfo.current;


            wprintf(L"Available DPI:\
    \
    "
    );
            int curdpi = 0;
            for (const auto& dpi : DpiVals)
            {
                if ((dpi >= dpiInfo.mininum) && (dpi <= dpiInfo.maximum))
                    wprintf(L"    %d\
    \
    "
    ,dpi);
            }
            wprintf(L"    current DPI: %d\
    \
    "
    ,dpiInfo.current);

            i++;
            if (i >= MAX_ID) {
                wprintf(L"To many desktops\
    \
    "
    );
                break;
            }
        }


    }

    void SetDPIScaling(INT32 adapterIDHigh, UINT32 adapterIDlow, UINT32 sourceID, UINT32 dpiPercentToSet) {
        LUID adapterId;
        adapterId.HighPart = adapterIDHigh;
        adapterId.LowPart = adapterIDlow;  
        DpiHelper::SetDPIScaling(adapterId, sourceID, dpiPercentToSet);
    }

    void RestoreDPIScaling()
    {
        wprintf(L"Now restore DPI settings...\
    \
    "
    );
        for (int i = 0;i < MAX_ID;i++) {
            if (GpuId[i].LowPart == 0 && GpuId[i].HighPart==0) break;
            DpiHelper::SetDPIScaling(GpuId[i], DesktopIndexInGpu[i], oldDPI[i]);
        }

    }

    DpiHelper.h与引用的答案相同。在Visual Studio中创建一个C ++ Dll项目,并在上面的代码中添加/放入并在下面的C#应用??程序中使用dll。

    一个C#控制台应用程序,它根据命令行参数设置DPI并在按任意键时将其还原:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;

    namespace DispSetEx
    {
        class Program
        {



            [DllImport("DpiHelper.dll")]
            static public extern void PrintDpiInfo();

            [DllImport("DpiHelper.dll")]
            static public extern int SetDPIScaling(Int32 adapterIDHigh, UInt32 adapterIDlow, UInt32 sourceID, UInt32 dpiPercentToSet);
            [DllImport("DpiHelper.dll")]
            static public extern void RestoreDPIScaling();

            static void Main(string[] args)
            {
                if ((args.Length % 3) != 0)
                {
                    Console.WriteLine("wrong parameters");
                    return;
                }

    //print the DPI info, you need to set the command line parameters
    //according to this
                PrintDpiInfo();

        //commandline parameters should be of groups of three
        //each groups's tree paramters control a desktop's setting
        //in each group:
        //GPUIdhigh.GPUIdlow DesktopIndexInGPU DPIScalingValue
        //for example:
        //    0.1234 0 100 //set the DPI scaling to 100 for desktop 0 on GPU 0.1234
        //    0.4567 0 125 //set the DPI scaling to 125 for desktop 0 on GPU 0.5678
        //    0.4567 1 150 //set the DPI scaling to 150 for desktop 1 on GPU 0.5678
        //in most cases GPUIdhigh is 0.
        //you can use the monitor name to identify which is which easily
        //you need to set the command line parameters according to the result of PrintDpiInfo
        //e.g. you should only set the DPI scaling to a value that is supported by
        //that desktop.


                for (int i = 0; i < args.Length / 3; i++)
                {
                    string[] sa = args[i * 3].Split(new char[] { '.' });

                    Int32 adapterHigh = Int32.Parse(sa[0]);
                    UInt32 adapterLow = UInt32.Parse(sa[1]);
                    UInt32 source = UInt32.Parse(args[i * 3 + 1]);
                    UInt32 dpiscale = UInt32.Parse(args[i * 3 + 2]);

                    SetDPIScaling(adapterHigh, adapterLow, source,dpiscale);
                }

                Console.WriteLine("Press any key to resotre the settings...");
                Console.ReadKey();

                RestoreDPIScaling();  
            }
        }
    }