65.9K
CodeProject 正在变化。 阅读更多。
Home

以编程方式更改显示设置

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.76/5 (27投票s)

2009年5月22日

CPL

10分钟阅读

viewsIcon

224809

downloadIcon

11425

了解如何通过 API 和 .NET Framework 以编程方式更改显示设置。

Display Settings Sample Snashot

引言

今天,我们将讨论如何通过 API 更改显示设置。我们将使用 C# 和 .NET Framework 通过 API 更改屏幕分辨率(边界)、颜色系统(位数)、旋转(方向)和刷新率(频率)。

目录

概述

本文首先讨论所需的函数和结构。然后,它重点介绍如何检索当前显示设置。之后,它讨论如何获取显示器支持的所有模式。如您所知,模式是许多显示设置的组合,包括边界、位数、方向和频率;因此,我们将显示设置称为显示模式。

最后,本文讨论如何更改当前显示设置。在讨论过程中,您将学习其他技术,例如如何 PInvoke Win32 API 函数以及如何封送非托管数据类型。

本文还附带了一个用于更改显示设置的示例应用程序。

现在,我们将讨论所需的函数和结构以及如何使用它们。之后,我们将重点介绍实现代码。准备好了。

EnumDisplaySettings() 函数

此函数位于 user32.dll 中。它用于检索图形设备支持的其中一种模式。

此函数的定义如下

BOOL EnumDisplaySettings(
  LPCTSTR lpszDeviceName,  // display device
  DWORD iModeNum,          // graphics mode
  [In, Out] LPDEVMODE lpDevMode      // graphics mode settings
);

此函数只接受三个参数

  • lpszDeviceName:指定将用于检索其模式的显示设备名称。此参数可以为 NULL 以指示默认设备,或为显示设备的名称。您可以使用 EnumDisplayDevices() 函数枚举显示设备。
  • iModeNum:指定要检索的信息类型。它可以是模式索引或以下值之一
    • ENUM_CURRENT_SETTINGS = -1:检索当前显示模式。
    • ENUM_REGISTRY_SETTINGS = -2:检索注册表中存储的当前显示模式。
  • lpDevMode:一个引用(In/Out)参数,表示封装检索到的显示模式信息的 DEVMODE 对象。DEVMODEdmSize 成员用作输入,表示结构大小,而其他成员用作输出。

正如您可能期望的,要检索当前显示设备的当前模式(设置),您需要将 NULL 值作为 lpszDeviceName 参数传递以指示当前显示器,并将值 -1 传递给 iModeNum 参数以指示当前模式。

不幸的是,EnumDisplaySettings() 每次调用只能返回一种模式,并且该模式封装在 DEVMODE 对象中。要检索显示设备支持的所有模式(或部分模式),您需要多次调用 EnumDisplaySettings(),并将 iModeNum 指定为模式索引。模式索引从零开始。因此,要检索所有模式,您需要多次调用 EnumDisplaySettings() 函数,第一次将 iModeNum 指定为 0,然后在每次调用中递增该索引,直到 EnumDisplaySettings() 返回 FALSE,这表示前一种模式是支持的最后一种模式。

如果您想检索其他显示设备支持的一种模式(或所有模式),您需要将 lpszDeviceName 更改为该设备的名称。您可以使用 EnumDisplayDevices() 函数获取所有已连接设备的列表。

现在,是时候使用 PInvoke 方法了。我们可以在 C# 中如下 PInvoke 此函数

[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean EnumDisplaySettings(
    [param: MarshalAs(UnmanagedType.LPTStr)]
    string lpszDeviceName,
    [param: MarshalAs(UnmanagedType.U4)]
    int iModeNum,
    [In, Out]
    ref DEVMODE lpDevMode);

什么是平台调用 (PInvoke)?您已经知道,托管代码 (.NET) 和非托管代码 (在本例中为 Win32 API) 之间没有兼容性。因此,它们无法直接相互调用。相反,您可以使用 PInvoke 服务从托管环境调用非托管函数。

什么是封送处理?封送处理是 CLR 的另一项服务。同样,托管代码和非托管代码之间没有兼容性。因此,它们无法直接通信。要在托管客户端和非托管服务器之间发送数据,反之亦然,您需要使用封送处理来允许发送和接收数据。封送处理将托管数据类型转换为非托管数据,反之亦然。

现在,我们将讨论 DEVMODE 结构。

DEVMODE 结构

此结构封装有关打印机或显示设备的信息。这是一个相当复杂的结构,但我们将尝试将其分解为简单且易于封送。此结构的定义如下

typedef struct DEVMODE {
  BCHAR  dmDeviceName[CCHDEVICENAME];
  WORD   dmSpecVersion;
  WORD   dmDriverVersion;
  WORD   dmSize;
  WORD   dmDriverExtra;
  DWORD  dmFields;
  union {
    struct {
      short dmOrientation;
      short dmPaperSize;
      short dmPaperLength;
      short dmPaperWidth;
      short dmScale;
      short dmCopies;
      short dmDefaultSource;
      short dmPrintQuality;
    };
    POINTL dmPosition;
    DWORD  dmDisplayOrientation;
    DWORD  dmDisplayFixedOutput;
  };

  short  dmColor;
  short  dmDuplex;
  short  dmYResolution;
  short  dmTTOption;
  short  dmCollate;
  BYTE  dmFormName[CCHFORMNAME];
  WORD  dmLogPixels;
  DWORD  dmBitsPerPel;
  DWORD  dmPelsWidth;
  DWORD  dmPelsHeight;
  union {
    DWORD  dmDisplayFlags;
    DWORD  dmNup;
  }
  DWORD  dmDisplayFrequency;
#if(WINVER >= 0x0400)
  DWORD  dmICMMethod;
  DWORD  dmICMIntent;
  DWORD  dmMediaType;
  DWORD  dmDitherType;
  DWORD  dmReserved1;
  DWORD  dmReserved2;
#if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400)
  DWORD  dmPanningWidth;
  DWORD  dmPanningHeight;
#endif
#endif /* WINVER >= 0x0400 */
}

真的很复杂,不是吗?是的,DEVMODE 是最大和最复杂的结构之一。

您可能已经注意到结构中定义的两个联合。此外,在第一个联合中定义了一个结构 - 请注意,此结构仅在它是打印机设备时才可用。此外,结构中定义的联合仅用于打印机设备。因此,对于显示设备,您可以省略该结构并按顺序定义联合的其他成员,无需额外工作。

此外,Windows NT 不支持最后八个成员,而 Windows ME 及其后代不支持最后两个成员。为了解决这个难题并支持所有版本,您可以定义三个版本的结构:一个用于 Windows ME 及其后代,一个用于 Windows NT,最后一个用于 Windows 2000 及更高版本。此外,您还需要为这三个结构创建三个函数重载。为简单起见,我们将为 Windows 2000 及更高版本封送整个结构。

请注意,有些数组的长度定义为 CCHFORMNAME,等于 32。

最后但并非最不重要的一点是,第二个结构联合在内部定义了两个成员:dmDisplayFlagsdmNup。为简单起见,我们将移除联合及其一个成员,并定义另一个。因为两个成员都是 4 字节宽,我们可以省略其中任何一个。

我们可以在 C# 中如下封送该结构

[StructLayout(LayoutKind.Sequential,
    CharSet = CharSet.Ansi)]
public struct DEVMODE
{
    // You can define the following constant
    // but OUTSIDE the structure because you know
    // that size and layout of the structure
    // is very important
    // CCHDEVICENAME = 32 = 0x50
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmDeviceName;
    // In addition you can define the last character array
    // as following:
    //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    //public Char[] dmDeviceName;

    // After the 32-bytes array
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dmSpecVersion;

    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dmDriverVersion;

    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dmSize;

    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dmDriverExtra;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmFields;

    public POINTL dmPosition;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmDisplayOrientation;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmDisplayFixedOutput;

    [MarshalAs(UnmanagedType.I2)]
    public Int16 dmColor;

    [MarshalAs(UnmanagedType.I2)]
    public Int16 dmDuplex;

    [MarshalAs(UnmanagedType.I2)]
    public Int16 dmYResolution;

    [MarshalAs(UnmanagedType.I2)]
    public Int16 dmTTOption;

    [MarshalAs(UnmanagedType.I2)]
    public Int16 dmCollate;

    // CCHDEVICENAME = 32 = 0x50
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmFormName;
    // Also can be defined as
    //[MarshalAs(UnmanagedType.ByValArray,
    //    SizeConst = 32, ArraySubType = UnmanagedType.U1)]
    //public Byte[] dmFormName;

    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dmLogPixels;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmBitsPerPel;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmPelsWidth;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmPelsHeight;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmDisplayFlags;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmDisplayFrequency;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmICMMethod;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmICMIntent;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmMediaType;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmDitherType;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmReserved1;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmReserved2;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmPanningWidth;

    [MarshalAs(UnmanagedType.U4)]
    public UInt32 dmPanningHeight;
}

我们很快将介绍 PONTL 结构。

实际上,这些几十个 MarshalAsAttribute 属性并非都是必需的。老实说,对于将 DWORD 封送为 UInt32 并不需要,因为它们是对应项。另一方面,MarshalAsAttribute 属性必须应用于数组。

在所有这些成员中,我们只对少数几个感兴趣

  • dmPelsWidthdmPelsHeight:表示显示器的边界(宽度和高度)。这些值可用于确定显示器方向是纵向还是横向。
  • dmBitsPerPel:表示显示器的位数(颜色系统)。
  • dmDisplayOrientation:表示显示器的方向(旋转)。此成员可以是以下值之一
    • DMDO_DEFAULT = 0:显示器处于自然方向。这是默认值。
    • DMDO_90 = 1:显示器从 DMDO_DEFAULT 顺时针旋转 90 度。
    • DMDO_180 = 2:显示器从 DMDO_DEFAULT 顺时针旋转 180 度。
    • DMDO_270 = 3:显示器从 DMDO_DEFAULT 顺时针旋转 270 度。
  • dmDisplayFrequency:表示显示器的频率(刷新率)。

POINTL 结构

DEVMODEdmPosition 成员表示显示设备相对于桌面区域的位置。它始终位于 (0, 0)。此成员是 POINTL 结构,表示点的坐标 (x 和 y)。正如您可能期望的,此结构非常简单。它的定义如下

typedef struct POINTL {
  LONG x;
  LONG y;
}

我们可以轻松地封送此结构,如下所示

[StructLayout(LayoutKind.Sequential)]
public struct POINTL
{
    [MarshalAs(UnmanagedType.I4)]
    public int x;
    [MarshalAs(UnmanagedType.I4)]
    public int y;
}

然而,为了代码清晰,我们有一个变通方法。您可以省略 POINTL 结构,并用两个成员替换 dmPosition 成员;您可以将它们称为 dmPositionXdmPositionY,并且这将正常工作,因为您知道结构的大小和布局(成员位置)非常重要。这样做不会破坏此规则。

ChangeDisplaySettings() 函数

此函数位于 user32.dll 中。它用于将显示设置更改为指定的模式,但仅当模式有效时。

此函数的定义如下

LONG ChangeDisplaySettings(
  LPDEVMODE lpDevMode,  // graphics mode
  DWORD dwflags         // graphics mode options
);

此函数只接受两个参数

  • lpDevMode:一个类型为 DEVMODE 的引用(In/Out)参数,表示将应用于显示设备的新设置(模式)。使用 EnumDisplaySettings() 函数检索当前设置后,您可以更改 DEVMODE 对象的所需成员,并使用此函数将显示设备更改为新设置。再次,此参数是一个 In/Out 参数,这意味着它用于输入和输出。DEVMODEdmSize 成员用于输入,其他成员用于输出。
  • dwflags:指示应如何更改模式。实际上,在此示例中,我们对此参数不感兴趣,因此将其设置为零。如果您想获得有关此参数的更多帮助,请查阅 MSDN 文档。

此函数的返回值根据设置更改的成功或失败而有所不同。此函数可以返回多个值之一,包括

  • DISP_CHANGE_SUCCESSFUL = 0:表示函数成功。
  • DISP_CHANGE_BADMODE = -2:不支持图形模式。
  • DISP_CHANGE_FAILED = -1:显示驱动程序未能更改指定的图形模式。
  • DISP_CHANGE_RESTART = 1:需要重新启动计算机才能使图形模式生效。

请查阅 MSDN 文档以了解有关 ChangeDisplaySettings() 返回值的更多信息。本文的最后一节专门讨论此内容。

另一个值得注意的地方是 ChangeDisplaySettings() 只更改默认显示器。如果您想更改其他显示设备,可以使用 ChangeDisplaySettingsEx() 函数。它与 ChangeDisplaySettings() 非常相似。请查阅 MSDN 文档以获取更多帮助。

现在,是时候为 ChangeDisplaySettings() 函数创建 PInvoke 方法了。

[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.I4)]
public static extern int ChangeDisplaySettings(
    [In, Out]
    ref DEVMODE lpDevMode,
    [param: MarshalAs(UnmanagedType.U4)]
    uint dwflags);

现在,我们将把各种内容整合在一起,并讨论实现代码。准备好了。

检索当前显示模式

获取当前显示设置的代码非常简单。我们使用 EnumDisplaySettings() 函数,将 ENUM_CURRENT_SETTINGS (-1) 传递给 iModeNum 参数以获取当前设置,并将 NULL 传递给 lpszDeviceName 参数以指示当前显示设备。

以下是代码(为清晰起见,代码已缩写)

public static void GetCurrentSettings()
{
    DEVMODE mode = new DEVMODE();
    mode.dmSize = (ushort)Marshal.SizeOf(mode);

    if (EnumDisplaySettings(null,
        ENUM_CURRENT_SETTINGS,
        ref mode) == true) // Succeeded
    {
        Console.WriteLine("Current Mode:\n\t" +
            "{0} by {1}, " +
            "{2} bit, " +
            "{3} degrees, " +
            "{4} hertz",
            mode.dmPelsWidth,
            mode.dmPelsHeight,
            mode.dmBitsPerPel,
            mode.dmDisplayOrientation * 90,
            mode.dmDisplayFrequency);
    }
}

枚举支持的显示模式

提醒一下,要获取显示设备的当前模式或甚至其他支持的模式,您可以使用 EnumDisplaySettings() 函数。如果您在 iModeNum 参数中传递 ENUM_CURRENT_SETTINGS (-1),您将获得当前模式。另一方面,您可以通过在此参数中传递模式索引来枚举支持的模式列表。我们从 0 开始,表示第一个模式,并在每次调用中递增它以枚举支持的模式列表。如果函数返回 FALSE,则表示未找到指定索引的模式。因此,前一个模式是最后一个模式。以下是代码。

public static void EnumerateSupportedModes()
{
    DEVMODE mode = new DEVMODE();
    mode.dmSize = (ushort)Marshal.SizeOf(mode);

    int modeIndex = 0; // 0 = The first mode

    Console.WriteLine("Supported Modes:");

    while (EnumDisplaySettings(null,
        modeIndex,
        ref mode) == true) // Mode found
    {
        Console.WriteLine("\t" +
            "{0} by {1}, " +
            "{2} bit, " +
            "{3} degrees, " +
            "{4} hertz",
            mode.dmPelsWidth,
            mode.dmPelsHeight,
            mode.dmBitsPerPel,
            mode.dmDisplayOrientation * 90,
            mode.dmDisplayFrequency);

        modeIndex++; // The next mode
    }
}

更改显示模式

现在,我们将更改当前显示设置。这可以通过 ChangeDispalySettings() 函数完成。

更改屏幕分辨率和位数

以下代码示例加载当前设置并仅更改分辨率和位数。实际上,您可以自由更改所有设置或其中一部分;这取决于您。但是,为简单起见,我们将在本节中更改屏幕分辨率和位数,并在下一节中更改方向。

static void Main()
{
    // Changing the display resolution
    // to 800 by 600
    // and the color system (bit count)
    // to 16-bit
    ChangeDisplaySettings(800, 600, 16);
}

public static void ChangeDisplaySettings
    (int width, int height, int bitCount)
{
    DEVMODE originalMode = new DEVMODE();
    originalMode.dmSize =
        (ushort)Marshal.SizeOf(originalMode);

    // Retrieving current settings
    // to edit them
    EnumDisplaySettings(null,
        ENUM_CURRENT_SETTINGS,
        ref originalMode);

    // Making a copy of the current settings
    // to allow reseting to the original mode
    DEVMODE newMode = originalMode;

    // Changing the settings
    newMode.dmPelsWidth = (uint)width;
    newMode.dmPelsHeight = (uint)height;
    newMode.dmBitsPerPel = (uint)bitCount;

    // Capturing the operation result
    int result =
        ChangeDisplaySettings(ref newMode, 0);

    if (result == DISP_CHANGE_SUCCESSFUL)
    {
        Console.WriteLine("Succeeded.\n");

        // Inspecting the new mode
        GetCurrentSettings();

        Console.WriteLine();

        // Waiting for seeing the results
        Console.ReadKey(true);

        ChangeDisplaySettings(ref originalMode, 0);
    }
    else if (result == DISP_CHANGE_BADMODE)
        Console.WriteLine("Mode not supported.");
    else if (result == DISP_CHANGE_RESTART)
        Console.WriteLine("Restart required.");
    else
        Console.WriteLine("Failed. Error code = {0}", result);
}

更改屏幕方向

现在,我们将顺时针和逆时针更改屏幕方向。

static void Main()
{
    // 0 degrees ( DMDO_DEFAULT = 0 )

    Console.WriteLine
        ("Press any key to rotate the screen . . .");
    Console.ReadKey(true);
    Console.WriteLine();

    RotateScreen(true); // 90 degrees ( DMDO_90 = 1 )
    Console.WriteLine
        ("Press any key to rotate the screen . . .");
    Console.ReadKey(true);
    Console.WriteLine();

    RotateScreen(true); // 180 degrees ( DMDO_180 = 2 )
    Console.WriteLine
        ("Press any key to rotate the screen . . .");
    Console.ReadKey(true);
    Console.WriteLine();

    RotateScreen(true); // 270 degrees ( DMDO_270 = 3 )
    Console.WriteLine
        ("Press any key to rotate the screen . . .");
    Console.ReadKey(true);
    Console.WriteLine();

    RotateScreen(true); // 0 degrees ( DMDO_DEFAULT = 0 )
}

public static void RotateScreen(bool clockwise)
{
    // Retrieving current settings
    // ...

    // Rotating the screen
    if (clockwise)
        if (newMode.dmDisplayOrientation < DMDO_270)
            newMode.dmDisplayOrientation++;
        else
            newMode.dmDisplayOrientation = DMDO_DEFAULT;
    else
        if (newMode.dmDisplayOrientation > DMDO_DEFAULT)
            newMode.dmDisplayOrientation--;
        else
            newMode.dmDisplayOrientation = DMDO_270;

    // Swapping width and height;
    uint temp = newMode.dmPelsWidth;
    newMode.dmPelsWidth = newMode.dmPelsHeight;
    newMode.dmPelsHeight = temp;

    // Capturing the operation result
    // ...
}

示例应用

代码示例是一个简单的应用程序,用于更改显示设置和旋转屏幕。可以通过本文顶部的链接下载。

参考文献

很高兴收到您的反馈和评论。

© . All rights reserved.