Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using User32.ChangeDisplaySettings to set resolution fails only on max resolution ('badmode' error)

Tags:

c#

So what I am trying to do is set a screen resolution on the primary display in C# using ChangeDisplaySettings. I have tested this across multiple windows 7 computers that I own, and the result is always the same: any* valid resolution (those that are listed in the "screen resolution" menu upon right-clicking the desktop) will work just fine, except for the largest resolution that can be selected in that menu, which will cause User32.ChangeDisplaySettings to return -2, which is DISP_CHANGE_BADMODE, meaning that the requested display mode was invalid.

*On some of the computers with larger primary displays, I didn't bother to test every resolution, and just picked some arbitrary smaller ones & the maximum, as there were too many to test on every single one. I feel confident enough in my testing to say that the maximum resolution ALWAYS fails, while smaller resolutions usually/always succeed (I never had any of them fail during my tests anyways).

Documentation on ChangeDisplaySettings: http://msdn.microsoft.com/en-us/library/dd183411%28VS.85%29.aspx

Documentation on the DEVMODE struct it uses: http://msdn.microsoft.com/en-us/library/dd183565%28v=vs.85%29.aspx

So for an example, lets say I'm running on a 1920x1080 display.

I set my resolution manually (or programmatically) to something else, doesn't matter what it is or how it was changed, and then run the following code:

DEVMODE dm = new DEVMODE();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
if (User32.EnumDisplaySettings(null, User32.ENUM_CURRENT_SETTINGS, ref dm) != 0)
{
     dm.dmPelsWidth = 1920;
     dm.dmPelsHeight = 1080;
     Console.WriteLine("" + User32.ChangeDisplaySettings(ref dm, User32.CDS_UPDATEREGISTRY) + "\n");
}

*Note that this is not actually the code from the program. I just made this simplified version to cut it down to the bare necessities to illustrate the point.

The program would print out:

-2

Which, as mentioned previously, is the value of DISP_CHANGE_BADMODE, and the resolution would fail to change.

Now, if I changed the values of 1080 and 1920 to 900 and 1600 respectively, another supported resolution on this monitor, and then set the resolution to something other than 1600x900, and then ran this program, it would in fact change the resolution to 1600x900, and return DISP_CHANGE_SUCCESSFUL.

Note that using other flags, such as CDS_RESET (0x40000000 or 1073741824) in place of CDS_UPDATEREGISTRY also result in the same error.

This is a tutorial I found that helped me get started:

www.codeproject.com/Articles/6810/Dynamic-Screen-Resolution

[I removed the hyperlink because of the apparent spam prevention system. Sort of silly given that the first to were msdn.microsoft links, and this is a codeproject one, but, w/e]

Note that in the comments section, it seems there is someone who used the provided source files directly, and is experiencing a similar problem. To quote them:

hello , i'm using Resolution.cs on my c# application and it doesn't work with high resolutions like " 1366*768 " & " 1280*720 " can any one help ???

However, despite how commonly ChangeDisplaySettings seems to be recommended by tutorials, I can't find any information on resolving this issue (which could very well be operating system specific, but I currently lack any non-Windows 7 computers to test on, and even if I did, it would not solve the issue that it doesn't work on Windows 7 computers)

like image 576
Avan Avatar asked Mar 18 '12 06:03

Avan


2 Answers

As it turns out, the tutorial I was using was making the assumption that nothing would change any of the display mode parameters to something invalid during the time that it was at a lower resolution (Such as increasing the refresh rate, which is capped on the monitor's side. And since I was using the same two monitors for the testing, and the max resolution had a lower max refresh rate than any of the other resolutions, I would encounter this issue.)

A safer method than what is illustrated in the tutorial is to work with the indexes of the display modes or work only with EnumDisplayModes and never touch the data inside the DEVMODE struct.

The following is an excerpt of an example program I whipped up that changes the resolution to the specified parameters, and then back again.

            int selB, selG, selF, selH, selW;
            ... //these values get defined, etc.
            DEVMODE OSpecs = new DEVMODE();
            getCurrentRes(ref OSpecs);
            int Ondx = getDMbySpecs(OSpecs.dmPelsHeight, OSpecs.dmPelsWidth, OSpecs.dmDisplayFrequency, OSpecs.dmDisplayFlags, OSpecs.dmBitsPerPel, ref OSpecs);
            Screen Srn = Screen.PrimaryScreen;
            Console.WriteLine("Current res is " + OSpecs.dmPelsHeight + " by " + OSpecs.dmPelsWidth + "\n");
            DEVMODE NSpecs = new DEVMODE();
            int Nndx = getDMbySpecs(selH, selW, selF, selG, selB, ref NSpecs);
                //Note that this function sets both the DEVMODE to the selected display mode and returns the index value of this display mode. It returns -1 if it fails (-1 is the value of ENUM_CURRENT_SETTINGS), and sets the DEVMODE to the current display mode.
            if (Nndx == -1)
            {
                 Console.WriteLine("Could not find specified mode");
            }
            else if (setDisplayMode(ref NSpecs) || setDisplayMode(Nndx)) //This is just to illustrate both ways of doing it. One or the other may be more convenient (ie, the latter if you are getting this from a file, the former if you already have the DEVMODE in your program, etc.)
            {
                //reset display mode to original after waiting long enough to see it changed
                Console.WriteLine("Successful change. Waiting 4 seconds.");
                Thread.Sleep(4000);
                if (setDisplayMode(ref OSpecs) || setDisplayMode(Ondx))
                {
                    //success!
                    Console.WriteLine("Mode reversion succeeded.");
                }
                else
                {
                    Console.WriteLine("Mode reversion failed. Manual reset required.");
                }
            }
            else
            {
                //return
                Console.WriteLine("Resolution change failed. Aborting");
            }

where the functions used here are as follows:

    static bool setDisplayMode(int i)
    {
        DEVMODE DM = new DEVMODE();
        DM.dmSize = (short)Marshal.SizeOf(DM);
        User32.EnumDisplaySettings(null, i, ref DM);
        if (User32.ChangeDisplaySettings(ref DM, User32.CDS_TEST) == 0 && User32.ChangeDisplaySettings(ref DM, User32.CDS_UPDATEREGISTRY) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    static bool setDisplayMode(ref DEVMODE DM)
    {
        if (User32.ChangeDisplaySettings(ref DM, User32.CDS_TEST) == 0 && User32.ChangeDisplaySettings(ref DM, User32.CDS_UPDATEREGISTRY) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    static int getDMbySpecs(int H, int W, int F, int G, int B, ref DEVMODE DM)
    {
        DM.dmSize = (short)Marshal.SizeOf(DM);
        DEVMODE SelDM = new DEVMODE();
        SelDM.dmSize = (short)Marshal.SizeOf(SelDM);
        int iOMI = 0;
        for (iOMI = 0; User32.EnumDisplaySettings(null, iOMI, ref SelDM) != 0; iOMI++)
        {
            if (( B == -1 || B == SelDM.dmBitsPerPel) && ( H == -1 || H == SelDM.dmPelsHeight) && ( W == -1 || W == SelDM.dmPelsWidth) && ( G == -1 || G == SelDM.dmDisplayFlags) && ( F == -1 || F == SelDM.dmDisplayFrequency))

                break;
        }
        if (User32.EnumDisplaySettings(null, iOMI, ref DM) == 0)
        {
            iOMI = -1;
            getCurrentRes(ref DM);
        }
        return iOMI;
    }
    static void getCurrentRes(ref DEVMODE dm)
    {
        dm = new DEVMODE();
        dm.dmSize = (short)Marshal.SizeOf(dm);
        User32.EnumDisplaySettings(null, User32.ENUM_CURRENT_SETTINGS, ref dm);
        return;
    }
like image 97
Avan Avatar answered Oct 05 '22 09:10

Avan


Apart from the answer that @Avan has given, I found that this problem also happens when you pass wrong parameter to the dmDisplayFrequency of DevMode structure

To solve this, you can first get all the available screen resolutions from Graphics device and then update the frequency before sending it to the ChangeDisplaySettings function.

To get display settings list use EnumDisplaySettings

like image 36
virusrocks Avatar answered Oct 05 '22 11:10

virusrocks