Changing the display resolution in a multi-monitor environment…

Hi…

I use to work with my laptop in a docking station and an external monitor connected. Twice a day (at least) I have to undock and dock the machine on a projector. While plug-n-play is a nice feature, it does not always work with every projector. Well, there must be a way…

So I started to search a bit and found some code snippets (in VB.NET, C# and C). Since my first snippet was in VB.NET I have stuck to it. I found some examples talking on how to do this in a single monitor environment. But none really for multi-monitor. So here is my scribble code in which the gifted coder finds all she or he needs to build a very own solution ;-)

It is a lot of good old C-Interop thing and I think most of you will find the predefined structures helpful. I will check with www.pinvoke.net later and look if I can add some information there.

It is not an application (yet)… well, weekend lies ahead...

Enjoy.

CU

0xff

 Imports System.Runtime.InteropServices

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public 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 Integer
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)> _
    Public DeviceID As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)> _
    Public DeviceKey As String
End Structure

'DEVMODE structure, see https://msdn.microsoft.com/en-us/library/ms535771(VS.85).aspx
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Friend Structure DEVMODE

    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
    Public dmDeviceName As String
    Public dmSpecVersion As Short
    Public dmDriverVersion As Short
    Public dmSize As Short
    Public dmDriverExtra As Short
    Public dmFields As Integer
    Public dmPositionX As Integer
    Public dmPositionY As Integer
    Public dmDisplayOrientation As Integer
    Public dmDisplayFixedOutput As Integer
    Public dmColor As Short
    Public dmDuplex As Short
    Public dmYResolution As Short
    Public dmTTOption As Short
    Public dmCollate As Short
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
    Public dmFormName As String
    Public dmLogPixels As Short
    Public dmBitsPerPel As Short
    Public dmPelsWidth As Integer
    Public dmPelsHeight As Integer
    Public dmDisplayFlags As Integer
    Public dmDisplayFrequency As Integer
    Public dmICMMethod As Integer
    Public dmICMIntent As Integer
    Public dmMediaType As Integer
    Public dmDitherType As Integer
    Public dmReserved1 As Integer
    Public dmReserved2 As Integer
    Public dmPanningWidth As Integer
    Public dmPanningHeight As Integer
End Structure

Friend Class NativeMethods

    ' PInvoke declaration for EnumDisplaySettings Win32 API
    <DllImport("user32.dll", CharSet:=CharSet.Ansi)> _
    Public Shared Function EnumDisplaySettings( _
    ByVal lpszDeviceName As String, _
    ByVal iModeNum As Integer, _
    ByRef lpDevMode As DEVMODE) As Integer
    End Function

    ' PInvoke declaration for ChangeDisplaySettings Win32 API
    <DllImport("user32.dll", CharSet:=CharSet.Ansi)> _
    Public Shared Function ChangeDisplaySettings( _
    ByRef lpDevMode As DEVMODE, _
    ByVal dwFlags As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Ansi)> _
    Public Shared Function EnumDisplayDevices( _
    ByVal lpDevice As String, _
    ByVal iDevNum As Integer, _
    ByRef lpDisplayDevice As DISPLAY_DEVICE, _
    ByVal dwFlags As Integer) As Boolean
    End Function
    <DllImport("user32.dll", CharSet:=CharSet.Ansi)> _
    Public Shared Function ChangeDisplaySettingsEx( _
    ByVal lpDeviceName As String, _
    ByRef lpDevMode As DEVMODE, _
    ByVal HWND As IntPtr, _
    ByVal dwflags As Integer, _
    ByVal lParam As IntPtr) As Integer
    End Function
End Class

Public Class ResolutionChanger

    Private _DisplayDevices As New List(Of DISPLAY_DEVICE)
    Private _Monitors As New List(Of DISPLAY_DEVICE)

    Public ReadOnly Property Monitors() As List(Of DISPLAY_DEVICE)
        Get
            Return _Monitors
        End Get
    End Property

    Public ReadOnly Property DisplayDevices() As List(Of DISPLAY_DEVICE)
        Get
            Return _DisplayDevices
        End Get
    End Property

    Private Shared Function CreateDevMode() As DEVMODE
        Dim dm As New DEVMODE
        dm.dmDeviceName = New String(New Char(32) {})
        dm.dmFormName = New String(New Char(32) {})
        dm.dmSize = CShort(Marshal.SizeOf(dm))
        Return dm
    End Function

    Private Shared Function CreateDisplayDevice() As DISPLAY_DEVICE
        Dim dd As New DISPLAY_DEVICE
        dd.cb = System.Runtime.InteropServices.Marshal.SizeOf(GetType(DISPLAY_DEVICE))
        dd.DeviceName = New String(New Char(32) {})
        dd.DeviceString = New String(New Char(128) {})
        dd.DeviceID = New String(New Char(128) {})
        dd.DeviceKey = New String(New Char(128) {})
        Return dd
    End Function

    Public Enum DisplayChangeResultCode
        DISP_CHANGE_SUCCESSFUL = 0
        DISP_CHANGE_RESTART = 1
        DISP_CHANGE_FAILED = -1
        DISP_CHANGE_BADMODE = -2
        DISP_CHANGE_NOTUPDATED = -3
        DISP_CHANGE_BADFLAGS = -4
        DISP_CHANGE_BADPARAM = -5
        DISP_CHANGE_BADDUALVIEW = -6
    End Enum
#Region "DevMode Flags"
    'Flags for DevMode structure, see https://msdn.microsoft.com/en-us/library/ms535771(VS.85).aspx

    Public Const DM_ORIENTATION As Integer = &H1

    Public Const DM_PAPERSIZE As Integer = &H2

    Public Const DM_PAPERLENGTH As Integer = &H4

    Public Const DM_PAPERWIDTH As Integer = &H8

    Public Const DM_SCALE As Integer = &H10

    Public Const DM_POSITION As Integer = &H20

    Public Const DM_NUP As Integer = &H40

    Public Const DM_DISPLAYORIENTATION As Integer = &H80

    Public Const DM_COPIES As Integer = &H100

    Public Const DM_DEFAULTSOURCE As Integer = &H200

    Public Const DM_PRINTQUALITY As Integer = &H400

    Public Const DM_COLOR As Integer = &H800

    Public Const DM_DUPLEX As Integer = &H1000

    Public Const DM_YRESOLUTION As Integer = &H2000

    Public Const DM_TTOPTION As Integer = &H4000

    Public Const DM_COLLATE As Integer = &H8000

    Public Const DM_FORMNAME As Integer = &H10000

    Public Const DM_LOGPIXELS As Integer = &H20000

    Public Const DM_BITSPERPEL As Integer = &H40000

    Public Const DM_PELSWIDTH As Integer = &H80000

    Public Const DM_PELSHEIGHT As Integer = &H100000

    Public Const DM_DISPLAYFLAGS As Integer = &H200000

    Public Const DM_DISPLAYFREQUENCY As Integer = &H400000

    Public Const DM_ICMMETHOD As Integer = &H800000

    Public Const DM_ICMINTENT As Integer = &H1000000

    Public Const DM_MEDIATYPE As Integer = &H2000000

    Public Const DM_DITHERTYPE As Integer = &H4000000

    Public Const DM_PANNINGWIDTH As Integer = &H8000000

    Public Const DM_PANNINGHEIGHT As Integer = &H10000000

    Public Const DM_DISPLAYFIXEDOUTPUT As Integer = &H20000000
#End Region

    Public Const CDS_UPDATEREGISTRY As Integer = &H1
    Public Const CDS_FULLSCREEN As Integer = &H4
    Public Const CDS_NORESET As Integer = &H10000000
    Public Const CDS_SET_PRIMARY As Integer = &H10
    Public Const CDS_RESET As Integer = &H40000000

    Public Sub EnumDisplayDevices()
        Dim Device As DISPLAY_DEVICE
        Dim Device2 = CreateDisplayDevice()
        Dim Monitor1 = CreateDisplayDevice()
        Dim Monitor2 = CreateDisplayDevice()
        Dim result As Boolean
        Dim DeviceCounter As Integer

        'To enlist all devices we have to roll over the EnumDisplayDevices function until 
        'result is negative
        'This is done twice, first for the Display Devices, then for the monitors

        DeviceCounter = 0

        Do
            Device = CreateDisplayDevice()
            result = NativeMethods.EnumDisplayDevices(Nothing, DeviceCounter, Device, &H1)
            If result = True Then
                _DisplayDevices.Add(Device)
                DeviceCounter += 1
            End If
        Loop Until result = False

        For Each DispDevice As DISPLAY_DEVICE In _DisplayDevices
            DeviceCounter = 0
            Do
                Device = CreateDisplayDevice()
                result = NativeMethods.EnumDisplayDevices(DispDevice.DeviceName, DeviceCounter, Device, &H1)
                If result = True Then
                    _Monitors.Add(Device)
                    DeviceCounter += 1
                End If
            Loop Until result = False
        Next
    End Sub

    'Lots of different ways to change resolution
    'Remark: The device parameter is always the list(of DISPLAY_DEVICE) DisplayDevices

    Public Shared Sub ChangeResolution(ByRef device As DISPLAY_DEVICE, ByVal width As Integer, ByVal height As Integer)
        Const ENUM_CURRENT_SETTINGS As Integer = -1
        Dim DevM As DEVMODE = CreateDevMode()
        Dim enumResult As Integer
        Dim changeResult As DisplayChangeResultCode

        DevM.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT

        enumResult = NativeMethods.EnumDisplaySettings(device.DeviceName, ENUM_CURRENT_SETTINGS, DevM)

        DevM.dmPelsWidth = width
        DevM.dmPelsHeight = height

        DevM.dmDeviceName = device.DeviceName

        changeResult = CType(NativeMethods.ChangeDisplaySettings(DevM, 0), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If


    End Sub
    Public Shared Sub ChangeResolution(ByRef device As DISPLAY_DEVICE, ByVal width As Integer, ByVal height As Integer, ByVal freq As Integer)
        Const ENUM_CURRENT_SETTINGS As Integer = -1
        Dim DevM As DEVMODE = CreateDevMode()
        Dim enumResult As Integer
        Dim changeResult As DisplayChangeResultCode

        DevM.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or DM_DISPLAYFREQUENCY

        enumResult = NativeMethods.EnumDisplaySettings(device.DeviceName, ENUM_CURRENT_SETTINGS, DevM)

        DevM.dmPelsWidth = width
        DevM.dmPelsHeight = height
        DevM.dmDisplayFrequency = freq

        DevM.dmDeviceName = device.DeviceName

        changeResult = CType(NativeMethods.ChangeDisplaySettings(DevM, 0), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If

    End Sub
    Public Shared Sub ChangeResolution(ByVal devices As List(Of DISPLAY_DEVICE))
        Const ENUM_CURRENT_SETTINGS As Integer = -1
        Dim DevM1 As DEVMODE = CreateDevMode()
        Dim DevM2 As DEVMODE = CreateDevMode()
        Dim enumResult As Integer
        Dim changeResult As DisplayChangeResultCode

        DevM1.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or DM_DISPLAYFREQUENCY Or DM_POSITION

        enumResult = NativeMethods.EnumDisplaySettings(devices.Item(0).DeviceName, ENUM_CURRENT_SETTINGS, DevM1)

        DevM1.dmPelsWidth = 1024
        DevM1.dmPelsHeight = 768
        DevM1.dmDisplayFrequency = 50

        DevM1.dmPositionX = -1024
        DevM1.dmPositionY = 0

        DevM1.dmDeviceName = devices.Item(0).DeviceName


        DevM2.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or DM_DISPLAYFREQUENCY Or DM_POSITION

        enumResult = NativeMethods.EnumDisplaySettings(devices.Item(1).DeviceName, ENUM_CURRENT_SETTINGS, DevM2)


        DevM2.dmPelsWidth = 800
        DevM2.dmPelsHeight = 600
        DevM2.dmDisplayFrequency = 50

        DevM2.dmPositionX = 0
        DevM2.dmPositionY = 0

        DevM2.dmDeviceName = devices.Item(1).DeviceName

        changeResult = CType(NativeMethods.ChangeDisplaySettingsEx(devices.Item(0).DeviceName, DevM1, Nothing, (0), Nothing), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If

        changeResult = CType(NativeMethods.ChangeDisplaySettingsEx(devices.Item(1).DeviceName, DevM2, Nothing, (CDS_SET_PRIMARY), Nothing), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If

        changeResult = CType(NativeMethods.ChangeDisplaySettingsEx(Nothing, Nothing, Nothing, 0, Nothing), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If


    End Sub

    Public Shared Sub ChangeResolution(ByRef device As DISPLAY_DEVICE, ByVal width As Integer, ByVal height As Integer, ByVal freq As Integer, ByVal PositionX As Integer, ByVal PositionY As Integer)
        Const ENUM_CURRENT_SETTINGS As Integer = -1
        Dim DevM As DEVMODE = CreateDevMode()
        Dim enumResult As Integer
        Dim changeResult As DisplayChangeResultCode

        DevM.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or DM_DISPLAYFREQUENCY Or DM_POSITION

        enumResult = NativeMethods.EnumDisplaySettings(device.DeviceName, ENUM_CURRENT_SETTINGS, DevM)

        DevM.dmPelsWidth = width
        DevM.dmPelsHeight = height
        DevM.dmDisplayFrequency = freq

        DevM.dmPositionX = PositionX
        DevM.dmPositionY = PositionY

        DevM.dmDeviceName = device.DeviceName

        changeResult = CType(NativeMethods.ChangeDisplaySettings(DevM, 0), DisplayChangeResultCode)

        If changeResult <> DisplayChangeResultCode.DISP_CHANGE_SUCCESSFUL Then
            Throw New Exception("Failed to change resolution: " & changeResult.ToString)
        End If

    End Sub

End Class