以编程方式更改显示设置






3.76/5 (27投票s)
了解如何通过 API 和 .NET Framework 以编程方式更改显示设置。
引言
今天,我们将讨论如何通过 API 更改显示设置。我们将使用 C# 和 .NET Framework 通过 API 更改屏幕分辨率(边界)、颜色系统(位数)、旋转(方向)和刷新率(频率)。
目录
- 引言
- 目录
- 概述
- EnumDisplaySettings() 函数
- DEVMODE 结构
- ChangeDisplaySettings() 函数
- 检索当前显示模式
- 枚举支持的显示模式
- 更改显示模式
- 示例应用
- 参考文献
概述
本文首先讨论所需的函数和结构。然后,它重点介绍如何检索当前显示设置。之后,它讨论如何获取显示器支持的所有模式。如您所知,模式是许多显示设置的组合,包括边界、位数、方向和频率;因此,我们将显示设置称为显示模式。
最后,本文讨论如何更改当前显示设置。在讨论过程中,您将学习其他技术,例如如何 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
对象。DEVMODE
的dmSize
成员用作输入,表示结构大小,而其他成员用作输出。
正如您可能期望的,要检索当前显示设备的当前模式(设置),您需要将 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。
最后但并非最不重要的一点是,第二个结构联合在内部定义了两个成员:dmDisplayFlags
和 dmNup
。为简单起见,我们将移除联合及其一个成员,并定义另一个。因为两个成员都是 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
属性必须应用于数组。
在所有这些成员中,我们只对少数几个感兴趣
dmPelsWidth
和dmPelsHeight
:表示显示器的边界(宽度和高度)。这些值可用于确定显示器方向是纵向还是横向。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 结构
DEVMODE
的 dmPosition
成员表示显示设备相对于桌面区域的位置。它始终位于 (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
成员;您可以将它们称为 dmPositionX
和 dmPositionY
,并且这将正常工作,因为您知道结构的大小和布局(成员位置)非常重要。这样做不会破坏此规则。
ChangeDisplaySettings() 函数
此函数位于 user32.dll 中。它用于将显示设置更改为指定的模式,但仅当模式有效时。
此函数的定义如下
LONG ChangeDisplaySettings(
LPDEVMODE lpDevMode, // graphics mode
DWORD dwflags // graphics mode options
);
此函数只接受两个参数
lpDevMode
:一个类型为DEVMODE
的引用(In/Out)参数,表示将应用于显示设备的新设置(模式)。使用EnumDisplaySettings()
函数检索当前设置后,您可以更改DEVMODE
对象的所需成员,并使用此函数将显示设备更改为新设置。再次,此参数是一个 In/Out 参数,这意味着它用于输入和输出。DEVMODE
的dmSize
成员用于输入,其他成员用于输出。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
// ...
}
示例应用
代码示例是一个简单的应用程序,用于更改显示设置和旋转屏幕。可以通过本文顶部的链接下载。
参考文献
- EnumDisplaySettings() 函数
- EnumDisplayDevices() 函数
- ChangeDisplaySettings() 函数
- ChangeDisplaySettingsEx() 函数
- DEVMODE 结构
- POINTL 结构
很高兴收到您的反馈和评论。