Creating a Screen Rotator in .NET

Introduction

I have always had a fascination with screen display settings. Well, I like to play with a computer’s display and all the options in code. It is wonderful what .NET and Windows enable you to do. If you revisit some of my older articles, you would see articles that includes the taskbar settings, desktop icons, screen savers, and display settings. Today, you will learn how to create a .NET application that enables you to rotate any one of your display screens and to reset them back.

Practical

You will create a .NET Windows Forms application in C# or in Visual Basic with four buttons. Three buttons will be used for the three different displays. The fourth button will be used to Reset the displays back to their original values. Let us jump straight to the code!

Add a class to your project, and name it APIStuff. This class will be responsible for all the Windows APIs aiding you in setting display settings. Because you will be dealing with the system API, you need to import the correct Namespaces. Do this now.

C#

using System;
using System.Runtime.InteropServices;

VB.NET

Imports System.Runtime.InteropServices

Now, add the following API methods.

C#

   [DllImport("user32.dll")]
   internal static extern DISP_CHANGE ChangeDisplaySettingsEx
      (string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd,
      DisplaySettingsFlags dwflags, IntPtr lParam);

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

   [DllImport("user32.dll", CharSet = CharSet.Ansi)]
   internal static extern int EnumDisplaySettings
      (string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);

VB.NET

   <DllImport("user32.dll")>
   Friend Shared Function ChangeDisplaySettingsEx(ByVal _
      lpszDeviceName As String, ByRef lpDevMode As DEVMODE, _
      ByVal hwnd As IntPtr, ByVal dwflags As DisplaySettingsFlags, _
      ByVal lParam As IntPtr) As DISP_CHANGE

   End Function
   <DllImport("user32.dll")>
   Friend Shared Function EnumDisplayDevices(ByVal lpDevice As _
      String, ByVal iDevNum As UInteger, ByRef lpDisplayDevice _
      As DISPLAY_DEVICE, ByVal dwFlags As UInteger) As Boolean

   End Function
   <DllImport("user32.dll", CharSet:=CharSet.Ansi)>
   Friend Shared Function EnumDisplaySettings(ByVal lpszDeviceName _
      As String, ByVal iModeNum As Integer, ByRef lpDevMode _
      As DEVMODE) As Integer

   End Function

ChangeDisplaySettingsEx changes the settings of the specified display to the specified graphics mode. EnumDisplayDevices obtains information about the display devices. EnumDisplaySettings retrieves information of the graphics modes of a display device.

Add the Constants and Structures that work with the preceding API functions.

C#

   public const int DMDO_DEFAULT = 0;
   public const int DMDO_90 = 1;
   public const int DMDO_180 = 2;
   public const int DMDO_270 = 3;

   public const int ENUM_CURRENT_SETTINGS = -1;



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

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

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

      [FieldOffset(44)]
      public POINTL dmPosition;
      [FieldOffset(52)]
      public Int32 dmDisplayOrientation;
      [FieldOffset(56)]
      public Int32 dmDisplayFixedOutput;

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

   [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
   internal 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;
   }

   [StructLayout(LayoutKind.Sequential)]
   internal struct POINTL
   {
      long x;
      long y;
   }

VB.NET

   Public Const DMDO_DEFAULT As Integer = 0
   Public Const DMDO_90 As Integer = 1
   Public Const DMDO_180 As Integer = 2
   Public Const DMDO_270 As Integer = 3
   Public Const ENUM_CURRENT_SETTINGS As Integer = -1

<StructLayout(LayoutKind.Explicit, CharSet:=CharSet.Ansi)>
Friend Structure DEVMODE

   Public Const CCHDEVICENAME As Integer = 32
   Public Const CCHFORMNAME As Integer = 32
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHDEVICENAME)>
   <FieldOffset(0)>
   Public dmDeviceName As String
   <FieldOffset(32)>
   Public dmSpecVersion As Int16
   <FieldOffset(34)>
   Public dmDriverVersion As Int16
   <FieldOffset(36)>
   Public dmSize As Int16
   <FieldOffset(38)>
   Public dmDriverExtra As Int16
   <FieldOffset(40)>
   Public dmFields As DM
   <FieldOffset(44)>
   Private dmOrientation As Int16
   <FieldOffset(46)>
   Private dmPaperSize As Int16
   <FieldOffset(48)>
   Private dmPaperLength As Int16
   <FieldOffset(50)>
   Private dmPaperWidth As Int16
   <FieldOffset(52)>
   Private dmScale As Int16
   <FieldOffset(54)>
   Private dmCopies As Int16
   <FieldOffset(56)>
   Private dmDefaultSource As Int16
   <FieldOffset(58)>
   Private dmPrintQuality As Int16
   <FieldOffset(44)>
   Public dmPosition As POINTL
   <FieldOffset(52)>
   Public dmDisplayOrientation As Int32
   <FieldOffset(56)>
   Public dmDisplayFixedOutput As Int32
   <FieldOffset(60)>
   Public dmColor As Short
   <FieldOffset(62)>
   Public dmDuplex As Short
   <FieldOffset(64)>
   Public dmYResolution As Short
   <FieldOffset(66)>
   Public dmTTOption As Short
   <FieldOffset(68)>
   Public dmCollate As Short
   <FieldOffset(72)>
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHFORMNAME)>
   Public dmFormName As String
   <FieldOffset(102)>
   Public dmLogPixels As Int16
   <FieldOffset(104)>
   Public dmBitsPerPel As Int32
   <FieldOffset(108)>
   Public dmPelsWidth As Int32
   <FieldOffset(112)>
   Public dmPelsHeight As Int32
   <FieldOffset(116)>
   Public dmDisplayFlags As Int32
   <FieldOffset(116)>
   Public dmNup As Int32
   <FieldOffset(120)>
   Public dmDisplayFrequency As Int32

End Structure


<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Friend Structure DISPLAY_DEVICE

   <MarshalAs(UnmanagedType.U4)>
   Public cb As Integer
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
   Public DeviceName As String
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
   Public DeviceString As String
   <MarshalAs(UnmanagedType.U4)>
   Public StateFlags As DisplayDeviceStateFlags
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
   Public DeviceID As String
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
   Public DeviceKey As String

End Structure

<StructLayout(LayoutKind.Sequential)>
Friend Structure POINTL

   Private x As Long
   Private y As Long

End Structure

The DEVMODE structure contains information about the environment of a display device. DISPLAY_DEVICE receives information about the given display device. POINTL simply contains the coordinates of a point. Add the next few Enumerations.

C#

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

   [Flags()]
   internal enum DisplayDeviceStateFlags : int
   {
      AttachedToDesktop = 0x1,
      MultiDriver = 0x2,
      PrimaryDevice = 0x4,
      MirroringDriver = 0x8,
      VGACompatible = 0x10,
      Removable = 0x20,
      ModesPruned = 0x8000000,
      Remote = 0x4000000,
      Disconnect = 0x2000000
   }

   [Flags()]
   internal enum DisplaySettingsFlags : int
   {
      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
   }

   [Flags()]
   internal enum DM : int
   {
      Orientation = 0x00000001,
      PaperSize = 0x00000002,
      PaperLength = 0x00000004,
      PaperWidth = 0x00000008,
      Scale = 0x00000010,
      Position = 0x00000020,
      NUP = 0x00000040,
      DisplayOrientation = 0x00000080,
      Copies = 0x00000100,
      DefaultSource = 0x00000200,
      PrintQuality = 0x00000400,
      Color = 0x00000800,
      Duplex = 0x00001000,
      YResolution = 0x00002000,
      TTOption = 0x00004000,
      Collate = 0x00008000,
      FormName = 0x00010000,
      LogPixels = 0x00020000,
      BitsPerPixel = 0x00040000,
      PelsWidth = 0x00080000,
      PelsHeight = 0x00100000,
      DisplayFlags = 0x00200000,
      DisplayFrequency = 0x00400000,
      ICMMethod = 0x00800000,
      ICMIntent = 0x01000000,
      MediaType = 0x02000000,
      DitherType = 0x04000000,
      PanningWidth = 0x08000000,
      PanningHeight = 0x10000000,
      DisplayFixedOutput = 0x20000000
   }

VB.NET

Friend Enum DISP_CHANGE

   Successful = 0
   Restart = 1
   Failed = -1
   BadMode = -2
   NotUpdated = -3
   BadFlags = -4
   BadParam = -5
   BadDualView = -6

End Enum

<Flags()>
Friend Enum DisplayDeviceStateFlags

   AttachedToDesktop = &H1
   MultiDriver = &H2
   PrimaryDevice = &H4
   MirroringDriver = &H8
   VGACompatible = &H10
   Removable = &H20
   ModesPruned = &H8000000
   Remote = &H4000000
   Disconnect = &H2000000

End Enum

<Flags()>
Friend Enum DisplaySettingsFlags

   CDS_NONE = 0
   CDS_UPDATEREGISTRY = &H1
   CDS_TEST = &H2
   CDS_FULLSCREEN = &H4
   CDS_GLOBAL = &H8
   CDS_SET_PRIMARY = &H10
   CDS_VIDEOPARAMETERS = &H20
   CDS_ENABLE_UNSAFE_MODES = &H100
   CDS_DISABLE_UNSAFE_MODES = &H200
   CDS_RESET = &H40000000
   CDS_RESET_EX = &H20000000
   CDS_NORESET = &H10000000

End Enum

<Flags()>
Friend Enum DM

   Orientation = &H1
   PaperSize = &H2
   PaperLength = &H4
   PaperWidth = &H8
   Scale = &H10
   Position = &H20
   NUP = &H40
   DisplayOrientation = &H80
   Copies = &H100
   DefaultSource = &H200
   PrintQuality = &H400
   Color = &H800
   Duplex = &H1000
   YResolution = &H2000
   TTOption = &H4000
   Collate = &H8000
   FormName = &H10000
   LogPixels = &H20000
   BitsPerPixel = &H40000
   PelsWidth = &H80000
   PelsHeight = &H100000
   DisplayFlags = &H200000
   DisplayFrequency = &H400000
   ICMMethod = &H800000
   ICMIntent = &H1000000
   MediaType = &H2000000
   DitherType = &H4000000
   PanningWidth = &H8000000
   PanningHeight = &H10000000
   DisplayFixedOutput = &H20000000

End Enum

DISP_CHANGE supplies the methods with results after the Display has been changed. These include Successful or Failed. DisplayDeviceStateFlags indicates how the device is attached to the computer. For example: Am I connected to this device through a remote connection, or is the screen connected to the current PC requesting a change of screen settings? DisplaySettingsFlags enables you to specify how the computer must store the change made to the display device. For example: If you were to supply the UPDATE_REGISTRY value, the PC will note this change in the computer’s Registry. The next time you start your PC, the changes will still be the same as you set them. Or temporary. DM specifies which screen setting you are changing. For example: Orientation.

Add a class to your project and name it clsDisplaySettings. This class will be responsible for all the Rotating and Resetting methods. Add the following Enumeration.

C#

   public enum Orientation
   {
      DEGREES_CW_270 = 1,
      DEGREES_CW_180 = 2,
      DEGREES_CW_90 = 3,
      DEGREES_CW_0 = 0
   }

VB.NET

Public Class clsDisplaySettings
   Public Enum Orientation

      DEGREES_CW_270 = 1
      DEGREES_CW_180 = 2
      DEGREES_CW_90 = 3
      DEGREES_CW_0 = 0

   End Enum

This enumeration simply indicates how many degrees the screen most rotate. Add the Rotate method.

C#

   public static bool Rotate(uint uDisplay,
      Orientation oOrientation)
   {

      bool bResult = false;
      DISPLAY_DEVICE dDevice = new DISPLAY_DEVICE();
      DEVMODE dmMode = new DEVMODE();

      dDevice.cb = Marshal.SizeOf(dDevice);

      if (!APIStuff.EnumDisplayDevices(null, uDisplay - 1,
            ref dDevice, 0))
         return false;

      if (0 != APIStuff.EnumDisplaySettings(
         dDevice.DeviceName, APIStuff.ENUM_CURRENT_SETTINGS,
            ref dmMode))
      {

         if ((dmMode.dmDisplayOrientation + (int)oOrientation)
            % 2 == 1)
         {

            int tmp = dmMode.dmPelsHeight;

            dmMode.dmPelsHeight = dmMode.dmPelsWidth;
            dmMode.dmPelsWidth = tmp;

         }

         switch (oOrientation)
         {

            case Orientation.DEGREES_CW_90:

               dmMode.dmDisplayOrientation = APIStuff.DMDO_270;
               break;

            case Orientation.DEGREES_CW_180:

               dmMode.dmDisplayOrientation = APIStuff.DMDO_180;
               break;

            case Orientation.DEGREES_CW_270:

               dmMode.dmDisplayOrientation = APIStuff.DMDO_90;
               break;

            case Orientation.DEGREES_CW_0:

               dmMode.dmDisplayOrientation = APIStuff.DMDO_DEFAULT;
               break;

            default:
               break;

         }

         DISP_CHANGE ret = APIStuff.ChangeDisplaySettingsEx
            (dDevice.DeviceName, ref dmMode, IntPtr.Zero,
            DisplaySettingsFlags.CDS_UPDATEREGISTRY, IntPtr.Zero);

         bResult = ret == 0;

      }

      return bResult;

   }

VB.NET

   Public Shared Function Rotate(ByVal uDisplay As UInteger, _
         ByVal oOrientation As Orientation) As Boolean

      Dim bResult As Boolean = False
      Dim dDevice As DISPLAY_DEVICE = New DISPLAY_DEVICE()
      Dim dmMode As DEVMODE = New DEVMODE()

      dDevice.cb = Marshal.SizeOf(dDevice)

      If Not APIStuff.EnumDisplayDevices(Nothing, uDisplay - 1, _
         dDevice, 0) Then Return False

      If 0 <> APIStuff.EnumDisplaySettings _
         (dDevice.DeviceName, APIStuff.ENUM_CURRENT_SETTINGS, _
         dmMode) Then

         If (dmMode.dmDisplayOrientation + CInt(oOrientation)) _
            Mod 2 = 1 Then

            Dim tmp As Integer = dmMode.dmPelsHeight
            dmMode.dmPelsHeight = dmMode.dmPelsWidth
            dmMode.dmPelsWidth = tmp

         End If

         Select Case oOrientation

            Case Orientation.DEGREES_CW_90
               dmMode.dmDisplayOrientation = APIStuff.DMDO_270

            Case Orientation.DEGREES_CW_180
               dmMode.dmDisplayOrientation = APIStuff.DMDO_180

            Case Orientation.DEGREES_CW_270
               dmMode.dmDisplayOrientation = APIStuff.DMDO_90

            Case Orientation.DEGREES_CW_0
               dmMode.dmDisplayOrientation = APIStuff.DMDO_DEFAULT

            Case Else

         End Select

         Dim ret As DISP_CHANGE = APIStuff.ChangeDisplaySettingsEx _
            (dDevice.DeviceName, dmMode, IntPtr.Zero, _
            DisplaySettingsFlags.CDS_UPDATEREGISTRY, IntPtr.Zero)
            bResult = ret = 0

      End If

      Return bResult

   End Function

The preceding code rotates your specified screen. Add the Reset method, which resets all screens back to their default values.

C#

   public static void Reset()
   {

      try
      {

         uint i = 0;

         while (++i <= 64)
         {

            Rotate(i, Orientation.DEGREES_CW_0);

         }

      }

      catch (ArgumentOutOfRangeException ex)
      {

      }

   }

VB.NET

   Public Shared Sub Reset()

      Try

         Dim i As Integer = 0

         While System.Threading.Interlocked.Increment(i) <= 64

            Rotate(i, Orientation.DEGREES_CW_0)

         End While

      Catch ex As ArgumentOutOfRangeException

      End Try

   End Sub

On your form, add the following code for your buttons.

C#

   private void button1_Click(object sender, EventArgs e)
   {

      clsDisplaySettings.Rotate(1,
         clsDisplaySettings.Orientation.DEGREES_CW_0);

   }

   private void button2_Click(object sender, EventArgs e)
   {

      clsDisplaySettings.Rotate(2,
         clsDisplaySettings.Orientation.DEGREES_CW_180);

   }

   private void button3_Click(object sender, EventArgs e)
   {

      clsDisplaySettings.Rotate(3,
         clsDisplaySettings.Orientation.DEGREES_CW_270);

   }

   private void button4_Click(object sender, EventArgs e)
   {

      clsDisplaySettings.Reset();

   }

VB.NET

   Private Sub button1_Click(ByVal sender As Object, ByVal e _
         As EventArgs) Handles button1.Click

      clsDisplaySettings.Rotate(1, clsDisplaySettings.Orientation _
         .DEGREES_CW_0)

   End Sub

   Private Sub button2_Click(ByVal sender As Object, ByVal e _
         As EventArgs) Handles button2.Click

      clsDisplaySettings.Rotate(2, clsDisplaySettings _
         .Orientation.DEGREES_CW_180)

   End Sub

   Private Sub button3_Click(ByVal sender As Object, ByVal e _
         As EventArgs) Handles button3.Click

      clsDisplaySettings.Rotate(3, clsDisplaySettings.Orientation _
         .DEGREES_CW_270)

   End Sub

   Private Sub button4_Click(ByVal sender As Object, ByVal e _
         As EventArgs) Handles button4.Click

      clsDisplaySettings.Reset()

   End Sub

Conclusion

It is quite easy to “play” with your desktop resolution, as you saw. I use it to prank my co-workers; I hope you put this knowledge to better use than I.

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read