Aprende a configurar dinámicamente un monitor específico como la pantalla principal en Windows 10 usando C # en WinForms.

Cómo definir un monitor como la pantalla principal en Windows 10 usando C#

Recientemente adquirí un nuevo monitor de 240 Hz, que curiosamente debería usarse solo para jugar juegos competitivos. La mala situación con esto es que estoy constantemente programando y trabajando con imágenes y la calidad del color del monitor es terrible (para los juegos, la suavidad es excelente, pero para las cosas de oficina, no vale la pena). De forma predeterminada, mi monitor 4K se define como principal, sin embargo, cuando quiero jugar juegos, como el monitor 4k se define como principal, los juegos se inician en ese monitor. Mi solución más rápida es simplemente cambiar la pantalla principal cuando cambio de trabajar a jugar, sin embargo, la forma de hacerlo es un poco extensa. ¿No sería genial si tuviera mi propia aplicación para hacer esto? Afortunadamente, es posible definir la pantalla principal del sistema a través de algo de código de C#.

En este artículo, te explicaré cómo cambiar fácilmente la pantalla principal de Windows 10 usando C # en WinForms.

1. Incluye la clase MonitorChanger y sus ayudantes

Para especificar con código, el monitor que desea usar como pantalla principal, deberás crear la siguiente clase, las estructuras y las clases auxiliares en tu proyecto:

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


class MonitorChanger
{
    /// <summary>
    /// Método estático que define el int dado (identificador del monitor) para establecerlo como primario.
    /// </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);

        // Actualizar los dispositivos restantes
        for (uint otherid = 0; NativeMethods.EnumDisplayDevices(null, otherid, ref device, 0); otherid++)
        {
            // Nota: si no sabe si los monitores están en la lista
            // simplemente puedes descomentar las siguientes líneas para saber qué se enumera
            //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);
        }

        // Aplicar configuraciones
        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;
}

Asegúrate de incluirlos en el espacio de nombres de tu proyecto para que puedas usarlos sin problemas más adelante.

2. Establecer un monitor específico como principal con su id

Después de incluir el código anterior en su proyecto, la forma de configurar un monitor específico como principal será bastante fácil, pero hay algo que explicar antes de continuar. En Windows 10, puede verificar fácilmente las pantallas conectadas a la computadora en el menú Pantalla, allí podrás reorganizar tus pantallas y tienen una identificación. En el menú, también puedes seleccionar un monitor específico y definirlo como principal.

Por ejemplo (mientras buscas cómo hacer esto, supongo que tienes al menos 2 monitores), considera mi configuración que tiene 3 monitores en el siguiente orden:

  1. Samsung U28E590 4K (1920 x 1080 a 60 Hz)
  2. Benq Zowie 27 "XL2740 (1920 x 1080 a 240 Hz): definido como pantalla principal 
  3. Samsung C24F390 (1920 x 1080 a 60 Hz)

Arrange displays Windows 10

Lo que necesita saber es que los identificadores en el código comenzarán desde 0, como con cada matriz en programación. Si quitas el comentario de algunas líneas específicas en el código proporcionado, donde se enumeran los dispositivos de visualización, tendrás una descripción como la siguiente:

===================
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
===================

Entonces, en nuestro caso, si queremos definir nuestro monitor en el lado izquierdo (Monitor 1) como el principal, el siguiente código funcionaría:

// Configura el monitor n. ° 1 como principal
MonitorChanger.SetAsPrimaryMonitor(0);

// Configura el monitor n. ° 2 como principal
// MonitorChanger.SetAsPrimaryMonitor(1);

// Configura el monitor n. ° 3 como principal
// MonitorChanger.SetAsPrimaryMonitor(2);

El método estático SetAsPrimaryMonitor espera como primer argumento el ID de uint del monitor deseado para definir como pantalla principal en el sistema. Después de ejecutar el código, puedes verificar fácilmente abriendo el menú de pantalla de Windows, donde encontrarás ahora el monitor deseado definido como la pantalla principal.

Que te diviertas ❤️!


Ingeniero de Software Senior en EPAM Anywhere. Interesado en la programación desde los 14 años, Carlos es un programador autodidacta, fundador y autor de la mayoría de los artículos de Our Code World.

Conviertete en un programador más sociable

Patrocinadores