How to define a monitor as the primary display in Windows 10 using C#

How to define a monitor as the primary display in Windows 10 using C#

Recently I acquired a new monitor of 240hz, that funnily should be used only for playing competitive games. The bad situation with this is that I'm constantly coding and working with images and the color quality of the monitor is awful (for games, the smoothness is great but for office stuff, it isn't worth it). By default, my 4K Monitor is defined as primary, however, when I want to play games, as the 4k monitor is defined as primary, the games are launched in that monitor. My quickest solution is to simply change the primary display when I'm switching from working to playing, however, the way to do it is a little bit extense. Wouldn't be great if I had my own application to do this? Fortunately, it's possible to define the primary display of the system through some C# code.

In this article, I'll explain to you how to easily change the primary display of Windows 10 using C# in WinForms.

1. Include the MonitorChanger class and its helpers

In order to specify with code, the monitor that you want to use as the primary display, you will need to create the following class, the structs, and the helper classes in your project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;


class MonitorChanger
{
    /// <summary>
    /// Static method that defines the given int (identifier of the monitor) to set as primary.
    /// </summary>
    /// <param name="id"></param>
    public static void SetAsPrimaryMonitor(uint id)
    {
        DISPLAY_DEVICE device = new DISPLAY_DEVICE();
        DEVMODE deviceMode = new DEVMODE();
        device.cb = Marshal.SizeOf(device);

        NativeMethods.EnumDisplayDevices(null, id, ref device, 0);
        NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref deviceMode);
        int offsetx = deviceMode.dmPosition.x;
        int offsety = deviceMode.dmPosition.y;
        deviceMode.dmPosition.x = 0;
        deviceMode.dmPosition.y = 0;

        NativeMethods.ChangeDisplaySettingsEx(
            device.DeviceName,
            ref deviceMode,
            (IntPtr) null,
            (ChangeDisplaySettingsFlags.CDS_SET_PRIMARY | ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
            IntPtr.Zero
        );

        device = new DISPLAY_DEVICE();
        device.cb = Marshal.SizeOf(device);

        // Update remaining devices
        for (uint otherid = 0; NativeMethods.EnumDisplayDevices(null, otherid, ref device, 0); otherid++)
        {
            // Note: if you don't know if the monitors are being listed
            // you can simply uncomment the following lines to know what's being listed
            //Console.WriteLine("===================");
            //Console.WriteLine("Monitor ID: " + otherid);
            //Console.WriteLine("Device: " + device);
            //Console.WriteLine(device.DeviceName);
            //Console.WriteLine(device.DeviceID);
            //Console.WriteLine(device.DeviceString);
            //Console.WriteLine(device.DeviceKey);
            //Console.WriteLine("===================");

            if (device.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop) && otherid != id)
            {
                device.cb = Marshal.SizeOf(device);
                DEVMODE otherDeviceMode = new DEVMODE();

                NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref otherDeviceMode);

                otherDeviceMode.dmPosition.x -= offsetx;
                otherDeviceMode.dmPosition.y -= offsety;

                NativeMethods.ChangeDisplaySettingsEx(
                    device.DeviceName,
                    ref otherDeviceMode,
                    (IntPtr) null,
                    (ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
                    IntPtr.Zero
                );
            }

            device.cb = Marshal.SizeOf(device);
        }

        // Apply settings
        NativeMethods.ChangeDisplaySettingsEx(null, IntPtr.Zero, (IntPtr)null, ChangeDisplaySettingsFlags.CDS_NONE, (IntPtr)null);
    }
}

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct DEVMODE
{
    public const int CCHDEVICENAME = 32;
    public const int CCHFORMNAME = 32;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
    [System.Runtime.InteropServices.FieldOffset(0)]
    public string dmDeviceName;
    [System.Runtime.InteropServices.FieldOffset(32)]
    public Int16 dmSpecVersion;
    [System.Runtime.InteropServices.FieldOffset(34)]
    public Int16 dmDriverVersion;
    [System.Runtime.InteropServices.FieldOffset(36)]
    public Int16 dmSize;
    [System.Runtime.InteropServices.FieldOffset(38)]
    public Int16 dmDriverExtra;
    [System.Runtime.InteropServices.FieldOffset(40)]
    public UInt32 dmFields;

    [System.Runtime.InteropServices.FieldOffset(44)]
    Int16 dmOrientation;
    [System.Runtime.InteropServices.FieldOffset(46)]
    Int16 dmPaperSize;
    [System.Runtime.InteropServices.FieldOffset(48)]
    Int16 dmPaperLength;
    [System.Runtime.InteropServices.FieldOffset(50)]
    Int16 dmPaperWidth;
    [System.Runtime.InteropServices.FieldOffset(52)]
    Int16 dmScale;
    [System.Runtime.InteropServices.FieldOffset(54)]
    Int16 dmCopies;
    [System.Runtime.InteropServices.FieldOffset(56)]
    Int16 dmDefaultSource;
    [System.Runtime.InteropServices.FieldOffset(58)]
    Int16 dmPrintQuality;

    [System.Runtime.InteropServices.FieldOffset(44)]
    public POINTL dmPosition;
    [System.Runtime.InteropServices.FieldOffset(52)]
    public Int32 dmDisplayOrientation;
    [System.Runtime.InteropServices.FieldOffset(56)]
    public Int32 dmDisplayFixedOutput;

    [System.Runtime.InteropServices.FieldOffset(60)]
    public short dmColor; // See note below!
    [System.Runtime.InteropServices.FieldOffset(62)]
    public short dmDuplex; // See note below!
    [System.Runtime.InteropServices.FieldOffset(64)]
    public short dmYResolution;
    [System.Runtime.InteropServices.FieldOffset(66)]
    public short dmTTOption;
    [System.Runtime.InteropServices.FieldOffset(68)]
    public short dmCollate; // See note below!
    [System.Runtime.InteropServices.FieldOffset(72)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
    public string dmFormName;
    [System.Runtime.InteropServices.FieldOffset(102)]
    public Int16 dmLogPixels;
    [System.Runtime.InteropServices.FieldOffset(104)]
    public Int32 dmBitsPerPel;
    [System.Runtime.InteropServices.FieldOffset(108)]
    public Int32 dmPelsWidth;
    [System.Runtime.InteropServices.FieldOffset(112)]
    public Int32 dmPelsHeight;
    [System.Runtime.InteropServices.FieldOffset(116)]
    public Int32 dmDisplayFlags;
    [System.Runtime.InteropServices.FieldOffset(116)]
    public Int32 dmNup;
    [System.Runtime.InteropServices.FieldOffset(120)]
    public Int32 dmDisplayFrequency;
}

public enum DISP_CHANGE : int
{
    Successful = 0,
    Restart = 1,
    Failed = -1,
    BadMode = -2,
    NotUpdated = -3,
    BadFlags = -4,
    BadParam = -5,
    BadDualView = -6
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DISPLAY_DEVICE
{
    [MarshalAs(UnmanagedType.U4)]
    public int cb;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string DeviceName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceString;
    [MarshalAs(UnmanagedType.U4)]
    public DisplayDeviceStateFlags StateFlags;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceID;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceKey;
}

[Flags()]
public enum DisplayDeviceStateFlags : int
{
    /// <summary>The device is part of the desktop.</summary>
    AttachedToDesktop = 0x1,
    MultiDriver = 0x2,
    /// <summary>The device is part of the desktop.</summary>
    PrimaryDevice = 0x4,
    /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
    MirroringDriver = 0x8,
    /// <summary>The device is VGA compatible.</summary>
    VGACompatible = 0x10,
    /// <summary>The device is removable; it cannot be the primary display.</summary>
    Removable = 0x20,
    /// <summary>The device has more display modes than its output devices support.</summary>
    ModesPruned = 0x8000000,
    Remote = 0x4000000,
    Disconnect = 0x2000000,
}

[Flags()]
public enum ChangeDisplaySettingsFlags : uint
{
    CDS_NONE = 0,
    CDS_UPDATEREGISTRY = 0x00000001,
    CDS_TEST = 0x00000002,
    CDS_FULLSCREEN = 0x00000004,
    CDS_GLOBAL = 0x00000008,
    CDS_SET_PRIMARY = 0x00000010,
    CDS_VIDEOPARAMETERS = 0x00000020,
    CDS_ENABLE_UNSAFE_MODES = 0x00000100,
    CDS_DISABLE_UNSAFE_MODES = 0x00000200,
    CDS_RESET = 0x40000000,
    CDS_RESET_EX = 0x20000000,
    CDS_NORESET = 0x10000000
}

public class NativeMethods
{
    [DllImport("user32.dll")]
    public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);

    [DllImport("user32.dll")]
    // A signature for ChangeDisplaySettingsEx with a DEVMODE struct as the second parameter won't allow you to pass in IntPtr.Zero, so create an overload
    public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, IntPtr lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

    [DllImport("user32.dll")]
    public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
}

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

Be sure to include them in the namespace of your projects so you can use them without problems later.

2. Set a specific monitor as primary by its id

After including the previous code in your project, the way to set a specific monitor as the primary one will be quite easy, but there's something to explain before proceeding. In Windows 10, you can easily check the connected displays to the computer in the Display menu, in there you will be able to rearrange your displays, and they do have an id. In the menu, you can as well select a specific monitor and define it as primary.

For example (as you are searching how to do this, I assume you have at least 2 monitors), consider my setup that has 3 monitors in the following order:

  1. Samsung U28E590 4K (1920x1080@60Hz)
  2. Benq Zowie 27" XL2740 (1920x1080@240Hz) - Defined as primary display
  3. Samsung C24F390 (1920x1080@60Hz)

Arrange displays Windows 10

What you need to know, is that the identifiers in the code will start from 0, as with every array in programming. If you uncomment some specific lines in the provided code, where the display devices are listed, you will have a description like the following one:

===================
Monitor ID: 0
Device: Sandbox.DISPLAY_DEVICE
\\.\DISPLAY1
PCI\VEN_10DE&DEV_1F06&SUBSYS_30673842&REV_A1
NVIDIA GeForce RTX 2060 SUPER
\Registry\Machine\System\CurrentControlSet\Control\Video\{66CD29F0-C36E-11EA-B2A1-806E6F6E6963}\0000
===================
===================
Monitor ID: 1
Device: Sandbox.DISPLAY_DEVICE
\\.\DISPLAY2
PCI\VEN_10DE&DEV_1F06&SUBSYS_30673842&REV_A1
NVIDIA GeForce RTX 2060 SUPER
\Registry\Machine\System\CurrentControlSet\Control\Video\{66CD29F0-C36E-11EA-B2A1-806E6F6E6963}\0001
===================
===================
Monitor ID: 2
Device: Sandbox.DISPLAY_DEVICE
\\.\DISPLAY3
PCI\VEN_10DE&DEV_1F06&SUBSYS_30673842&REV_A1
NVIDIA GeForce RTX 2060 SUPER
\Registry\Machine\System\CurrentControlSet\Control\Video\{66CD29F0-C36E-11EA-B2A1-806E6F6E6963}\0002
===================

So in our case, if we do want to define our monitor on the left side (Monitor 1) as the primary one, the following code would do the trick:

// Set the Monitor #1 as Primary
MonitorChanger.SetAsPrimaryMonitor(0);

// Set the Monitor #2 as Primary
// MonitorChanger.SetAsPrimaryMonitor(1);

// Set the Monitor #3 as Primary
// MonitorChanger.SetAsPrimaryMonitor(2);

The static method SetAsPrimaryMonitor expects as the first argument the uint id of the desired monitor to define as primary display in the system. After running the code, you can easily verify opening the display menu of Windows, where you will find now the desired monitor defined as the primary display.

Happy coding ❤️!

This could interest you

Become a more social person