65.9K
CodeProject is changing. Read more.
Home

Change Resolution Before Starting Application

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (7 votes)

Dec 27, 2013

CPOL

2 min read

viewsIcon

21060

downloadIcon

1035

How to change resolution before starting an application

Introduction

In December 2013, it was time for me to buy a new laptop. I looked around and ended up with one of the best laptops I could find... a DELL XPS 15 (2013 model). I really like the laptop but it has one disadvantage. The desktop resolution is insanely high. It is a whopping 3200 x 1800. Most programs look good on this resolution when you have at least Windows 8.1. But games that are played in a lower resolution all had black borders around them. For some reason, the NVIDIA 750 graphics drivers don't seem to scale the games right. So, since I'm a developer, I decided to fix this. After testing, I found out that the games did work without black borders when I first switched back the desktop resolution to a lower setting. Since I didn't want to do that every time, I decided to write a simple console program for it.

The program had to do a few things for me:
  • Change the resolution to a lower setting
  • Start a program (game)
  • Wait until the program (game) is closed
  • Change the resolution back to the old settings

Using the Code

I decided to call the program ResChanger (resolution changer). First, I created a new C# console app in Visual Studio 2013 as you can see in the code below:

using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ResChanger
{
    internal class Program
    {
        #region DllImport
        [DllImport("kernel32.dll")]
        static extern bool FreeConsole();
        #endregion

        private static void Main(string[] args)
        {
            FreeConsole();

            if (args.Length != 2)
            {
                ShowMessage("Not enough command line arguments given.");
                return;
            }

            var newResolution = args[0].Split('x');
            int newWidth;
            int newHeight;

            if (newResolution.Length != 2)
            {
                ShowMessage("Invalid resolution format given.");
                return;
            }

            if (!int.TryParse(newResolution[0], out newWidth) || !int.TryParse(newResolution[1], out newHeight))
            {
                ShowMessage("Invalid resolution given, could not parse to integer.");
                return;
            }

            var resolution = new Resolution();
            string supportedModes;

            if (!resolution.IsDisplayModeSupported(newWidth, newHeight, out supportedModes))
            {
                ShowMessage("The mode '" + args[0] + "' is not supported, 
                supported modes are: " + Environment.NewLine + supportedModes +
                            Environment.NewLine);
                return;
            }

            if (!File.Exists(args[1]))
            {
                ShowMessage("The file '" + args[1] + "' does not exists.");
                return;
            }

            if (!resolution.ChangeDisplaySettings(newWidth, newHeight))
                return;

            var runningProgram = Process.Start(args[1]);
            if (runningProgram != null) runningProgram.WaitForExit();

            resolution.RestoreDisplaySettings();
        }

        #region ShowMessage
        private static void ShowMessage(string errorMessage)
        {
            MessageBox.Show("Invalid command line arguments given:" + 
            Environment.NewLine + Environment.NewLine +
                "Error: " + errorMessage + Environment.NewLine + Environment.NewLine +
                "Expected \"<resolution>\" \"<program to start, 
                with full path\">" + Environment.NewLine +
                Environment.NewLine +
                "For example: " + Path.GetFileName(Environment.GetCommandLineArgs()[0]) +
                " \"1920x1080\"  
                \"C:\\Program Files (x86)\\SQUARE ENIX\\Tombraider\\TombRaider.exe\"" +
                Environment.NewLine + Environment.NewLine +
                "This will first switch the resolution to 1920x1080 and then start Tombraider." +
                Environment.NewLine +
                "After you exit the game the resolution will be switched back to the old configuration.");
        }
        #endregion
    }
} 

I also needed a class to change the resolution itself. Since it is not possible to do this with standard C# .NET, I did have to switch to DLLImports. I found some nice articles on CodeProject that I could use for this. I ended up with the following class called Resolution:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ResChanger
{
    #region Struct Pointl
    [StructLayout(LayoutKind.Sequential)]
    public struct Pointl
    {
        [MarshalAs(UnmanagedType.I4)]
        public int x;
        [MarshalAs(UnmanagedType.I4)]
        public int y;
    }
    #endregion

    #region Struct Devmode
    [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;
    }
    #endregion

    internal class Resolution
    {
        #region Fields
        private static Devmode _oldDevmode;
        #endregion

        #region Consts
        private const int EnumCurrentSettings = -1;     // Retrieves the current display mode.
        private const int DispChangeSuccessful = 0;     // Indicates that the function succeeded.
        private const int DispChangeBadmode = -2;       // The graphics mode is not supported.
        private const int DispChangeRestart = 1;        // The computer must be restarted 
        				// for the graphics mode to work.
        #endregion

        #region DllImport
        [DllImport("User32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern Boolean EnumDisplaySettings(
            [param: MarshalAs(UnmanagedType.LPTStr)] string lpszDeviceName,
            [param: MarshalAs(UnmanagedType.U4)] int iModeNum,
            [In, Out] ref Devmode lpDevMode);

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

        #region IsDisplayModeSupported
        /// <summary>
        /// Checks if the given display mode is supported
        /// </summary>
        /// <param name="width" />
        /// <param name="height" />
        /// <param name="supportedModes" />
        /// <returns>
        public bool IsDisplayModeSupported(int width, int height, out string supportedModes)
        {
            var mode = new Devmode();
            mode.dmSize = (ushort) Marshal.SizeOf(mode);

            var modeIndex = 0; // 0 = The first mode
            supportedModes = string.Empty;
            var previousSupportedMode = string.Empty;

            while (EnumDisplaySettings(null,
                modeIndex,
                ref mode)) // Mode found
            {
                if (mode.dmPelsWidth == (uint) width && mode.dmPelsHeight == (uint) height)
                    return true;

                var newSupportedMode = mode.dmPelsWidth + "x" + mode.dmPelsHeight;
                if (newSupportedMode != previousSupportedMode)
                {
                    if (supportedModes == string.Empty)
                        supportedModes += newSupportedMode;
                    else
                        supportedModes += ", " + newSupportedMode;
                    
                    previousSupportedMode = newSupportedMode;
                }

                modeIndex++; // The next mode
            }

            return false;
        }
        #endregion

        #region ChangeDisplaySettings
        /// <summary>
        /// Changes the display settings
        /// </summary>
        /// <param name="width" />
        /// <param name="height" />
        public bool ChangeDisplaySettings(int width, int height)
        {
            _oldDevmode = new Devmode();
            _oldDevmode.dmSize = (ushort) Marshal.SizeOf(_oldDevmode);

            // Retrieving current settings
            // to edit them
            EnumDisplaySettings(null,
                EnumCurrentSettings,
                ref _oldDevmode);

            // Making a copy of the current settings
            // to allow reseting to the original mode
            var newMode = _oldDevmode;

            // Changing the settings
            newMode.dmPelsWidth = (uint)width;
            newMode.dmPelsHeight = (uint)height;

            // Capturing the operation result, 1 = update registry
            var result =
                ChangeDisplaySettings(ref newMode, 1);

            switch (result)
            {
                case DispChangeSuccessful:
                    return true;

                case DispChangeBadmode:
                    MessageBox.Show("Mode not supported.");
                    return false;

                case DispChangeRestart:
                    MessageBox.Show("Restart required.");
                    return false;

                default:
                    MessageBox.Show("Failed. Error code = " + result);
                    return false;
            }
        }
        #endregion

        #region RestoreDisplaySettings
        /// <summary>
        /// Restores the old display settings
        /// </summary>
        public void RestoreDisplaySettings()
        {
            ChangeDisplaySettings(ref _oldDevmode, 1);
        }
        #endregion
    }
}

After putting everything together, we end up with a very simple console program that can be called like this: Reschanger.exe "resolution" "<program to start, including path" for example Reschanger.exe "1920x1080" "c:\Program Files\totalcmd\totalcmd.exe"

This will switch the desktop resolution to 1920 x 1080 and then start totalcmd.exe. You can make a shortcut on the Windows desktop to make everything easier.

And that's it... a very simple solution to an annoying problem.

History

  • 2013-12-27 - First version
  • 2014-01-03 - Version 1.1, cleaned up the code somewhat and added EXE file to ZIP file