Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Changing Display Settings Programmatically

3.76/5 (27 votes)
18 Jun 2009CPL10 min read 218.9K   11.4K  
Learn how to change display settings programmatically via an API and the .NET Framework.

Display Settings Sample Snashot

Introduction

Today, we are talking about how to change display settings via an API. We will change the screen resolution (bounds,) color system (bit count), rotation (orientation), and refresh rate (frequency) via an API with C# and the .NET Framework.

Table of Contents

Overview

This article firstly discusses the required functions and structures. Then, it focuses on how to retrieve the current display settings. After that, it discusses how to get all the modes supported by your display. As you already know, a mode is a combination of many display settings including bounds, bit count, orientation, and frequency; therefore, we will refer to display settings as display modes.

Finally, this article discusses how to change the current display settings. Along with the discussion, you will learn additional techniques like how to PInvoke Win32 API functions and to marshal unmanaged data types.

This article also comes with a sample application used for changing the display settings.

Now, we are going to discuss the required functions and structures and how to use them. After that, we will focus on the implementation code. Get ready.

EnumDisplaySettings() Function

This function resides in user32.dll. It is used to retrieve one of the modes supported by a graphics device.

The definition of this function is as follows:

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

This function accepts only three parameters:

  • lpszDeviceName: Specifies the display device name that will be used to retrieve its modes. This parameter can be either NULL to indicate the default device, or the name of the display device. You can enumerate display devices using the EnumDisplayDevices() function.
  • iModeNum: Specifies the type of information to retrieve. It could be either a mode index or one of these values:
    • ENUM_CURRENT_SETTINGS = -1: Retrieves the current display mode.
    • ENUM_REGISTRY_SETTINGS = -2: Retrieves the current display mode stored in the Registry.
  • lpDevMode: A reference (In/Out) parameter representing the DEVMODE object that encapsulates the retrieved display mode information. The DEVMODE's dmSize member is used for input to represent the structure size, while other members are used for output.

As you might expect, to retrieve the current mode (settings) of the current display device, you will need to pass a NULL value as the lpszDeviceName parameter to indicate the current display, and the value -1 to the iModeNum parameter to indicate the current mode.

Unfortunately, EnumDisplaySettings() can return only one mode per call, and that mode is encapsulated into the DEVMODE object. To retrieve all modes (or a few) supported by a display device, you need to call EnumDisplaySettings() many times specifying iModeNum as the mode index. Mode indexes start from zero. Therefore, to retrieve all modes, you will need to call the EnumDisplaySettings() function many times, specifying 0 for iModeNum on the first time, and increment that index in every call until EnumDisplaySettings() returns FALSE, which indicates that the previous mode was the last mode supported.

If you want to retrieve a mode (or all modes) supported by other display devices, you will need to change lpszDeviceName to the name of that device. You can get a list of all devices connected using the EnumDisplayDevices() function.

Now, it is the time for the PInvoke method. We can PInvoke this function in C# as follows:

C#
[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);

What is Platform Invocation (PInvoke)? You already know, there is no compatibility between managed code (.NET) and unmanaged code (Win32 API, in our case.) Therefore, they cannot call each other directly. Rather, you make use of the PInvoke service to call unmanaged functions from the managed environment.

What is Marshaling? Marshaling is another service of the CLR. Again, there is no compatibility between managed code and unmanaged code. Therefore, they cannot communicate directly. To send data between a managed client and an unmanaged server, and vice versa, you will need to use marshaling to allow sending and receiving the data. Marshaling converts managed data types to unmanaged data and vice versa.

Now, we will talk about the DEVMODE structure.

DEVMODE Structure

This structure encapsulates information about a printer or a display device. This is a fairly complex structure, but we will try to break it down to be simple and easier to marshal. The definition of this structure is as follows:

C++
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 */
}

Really complex, isn't it? Yeah, DEVMODE is one of the largest and most complex structures.

You might have noticed the two unions defined inside the structure. In addition, a structure is defined inside the first union - note that this structure is only available if it is a printer device. Plus, the union defined in the structure is for printer devices only. Therefore, for display devices, you can omit the structure and define other members of the union sequentially, no additional work is required.

In addition, the last eight members are not supported by Windows NT, while the last two members are not supported by Windows ME and its ascendants. To solve this dilemma and support all versions, you can define three versions of the structure: one for Windows ME and its ascendants, one for Windows NT, and the last for Windows 2000 and higher versions. In addition, you will need to create three overloads of the function for the three structures. For simplicity, we will marshal the whole structure for Windows 2000 and higher versions.

Notice that there are arrays that are defined with the length CCHFORMNAME which equals 32.

Last but not least, the second union of structures defines two members inside: dmDisplayFlags and dmNup. For simplicity, we will take away the union and one of its members and define the other. Because both members are 4-bytes wide, we can omit anyone of them.

We can marshal that structure in C# as follows:

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;
}

We will cover the PONTL structure soon.

Actually, these dozens of MarshalAsAttribute attributes are not all required. Honestly, it is not required for marshaling DWORD into UInt32 because they are counterparts. On the other hand, the MarshalAsAttribute attribute must be applied to arrays.

From all of those members, we are interested only in a few:

  • dmPelsWidth and dmPelsHeight: Represent the bounds (width and height) of the display. These values can be used to determine whether the display orientation is portrait or landscape.
  • dmBitsPerPel: Represents the bit count (color system) of the display.
  • dmDisplayOrientation: Represents the orientation (rotation) of the display. This member can be one of these values:
    • DMDO_DEFAULT = 0: The display is in the natural orientation. It is the default.
    • DMDO_90 = 1: The display is rotated 90 degrees (measured clockwise) from DMDO_DEFAULT.
    • DMDO_180 = 2: The display is rotated 180 degrees (measured clockwise) from DMDO_DEFAULT.
    • DMDO_270 = 3: The display is rotated 270 degrees (measured clockwise) from DMDO_DEFAULT.
  • dmDisplayFrequency: Represents the frequency (refresh rate) of the display.

POINTL Structure

The DEVMODE's dmPosition member represents the location of the display device in reference to the desktop area. It is always located at (0, 0). This member is of the structure POINTL which represents the coordinates (x and y) of a point. As you might expect, this structure is very simple. It is defined as follows:

C++
typedef struct POINTL {
  LONG x;
  LONG y;
}

We can marshal this structure easily, as follows:

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

However, for code clarity, we have a workaround. You can omit the POINTL structure and replace the dmPosition member with two members; you can call them dmPositionX and dmPositionY, and that will work fine because you know that the size and layout (position of members) of structures is very important. And, doing that will not break this rule.

ChangeDisplaySettings() Function

This function resides in user32.dll. It is used to change the display settings to the mode specified, but only if the mode is valid.

The definition of this function is as follows:

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

This function accepts only two arguments:

  • lpDevMode: A reference (In/Out) argument of the type DEVMODE that represents the new settings (mode) that will be applied to the display device. After retrieving the current settings using the EnumDisplaySettings() function, you can change the desired members of the DEVMODE object and use this function to change the display device to the new settings. Again, this argument is an In/Out argument, which means that it is used for input and output. DEVMODE's dmSize member is used for input, and other members are used for output.
  • dwflags: Indicates how the mode should be changed. Actually, in this example, we are not interested in this argument, so we will set it to zero. If you want more help on this argument, consult the MSDN documentation.

The return value of this function varies based on the success or failure of the settings change. This function can return one of several values including:

  • DISP_CHANGE_SUCCESSFUL = 0: Indicates that the function succeeded.
  • DISP_CHANGE_BADMODE = -2: The graphics mode is not supported.
  • DISP_CHANGE_FAILED = -1: The display driver failed the specified graphics mode.
  • DISP_CHANGE_RESTART = 1: The computer must be restarted for the graphics mode to work.

Consult the MSDN documentation to know more about the ChangeDisplaySettings() return value. The last section of this article is devoted for this.

Another point of interest is that ChangeDisplaySettings() only changes the default display. If you want to change another display device, you can use the ChangeDisplaySettingsEx() function. It is very similar to ChangeDisplaySettings(). Consult the MSDN documentation for more help.

Now, it is time for creating the PInvoke method for the ChangeDisplaySettings() function.

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

Now, we are going to mix things together and talk about the implementation code. Get ready.

Retrieving the Current Display Mode

The code that obtains the current display settings is very easy. We use the EnumDisplaySettings() function, passing it ENUM_CURRENT_SETTINGS (-1) in the iModeNum parameter to get the current settings, and NULL in the lpszDeviceName parameter to indicate the current display device.

Here is the code (code abbreviated for clarity):

C#
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);
    }
}

Enumerating the Supported Display Modes

As a refresher, to get the current mode or even another supported mode of the display device, you make use of the EnumDisplaySettings() function. If you pass ENUM_CURRENT_SETTINGS (-1) in the iModeNum parameter, you get the current mode. On the other hand, you can enumerate through the list of supported modes by passing the mode index in this parameter. We start by 0, which indicates the first mode, and increment it in every call to enumerate through the list of supported modes. If the function returns FALSE, that means that the mode with the index specified is not found. Therefore, the previous mode was the last one. Here is the code.

C#
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
    }
}

Changing the Display Mode

Now, we are going to change the current display settings. This can be done through the ChangeDispalySettings() function.

Changing the Screen Resolution and Bit Count

The following code example loads the current settings and changes only the resolution and the bit count. Actually, you are free to change all the settings or a few of them; that is up to you. However, for the sake of simplicity, we are going to change the screen resolution and bit count in this section, and the orientation in the next section.

C#
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);
}

Changing the Screen Orientation

Now, we are going to change the screen orientation clockwise and anti-clockwise.

C#
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
    // ...
}

Sample Application

The code sample is a simple application used to change the display settings and to rotate the screen. It is available for download using the link at the top of this article.

References

It is a pleasure receiving your feedback and comments.

License

This article, along with any associated source code and files, is licensed under The Common Public License Version 1.0 (CPL)