A first in C# and WinForms: started writing a program for changing screen resolution
Tags: dotnet, winforms, win32, cswin32
Published: 2022-02-05 18:52:59

This post mirrors:

- 2022-01-23 [A first in C# and WinForms: started writing a program for changing screen resolution](https://g.mikf.pl/gemlog/2022-01-23-winforms.gmi)
- 2022-02-05 [An update on my C#/WinForms app that changes screen resolution](https://g.mikf.pl/gemlog/2022-02-05-winforms.gmi)

# A first in C# and WinForms: started writing a program for changing screen resolution
## 2022-01-23

Since i have recently became a Windows user, some specific needs emerged for specific reasons. I needed a program to change screen resolution for me whenever I would lock my session or login/logoff, or even start my session (change on autostart, yeah maybe i will port it as a Windows Service later) with one screen resolution and then switch to another right before ending session.

I had no prior experience with WinForms and I don't recall having any meaningful experience witth C#. I had experience with IntelliJ and other JetBrains IDEs, and I dived straight into a fresh install of Visual Studio 2022 with Resharper. Today morning.

The Designer in VS feels absolutely excellent to me, my first attempt with a GUI designer that went smoothly and led to me actually creating something that works! So far I am lazy and avoid perfectionism so everything is loosely aligned with Drawing class when I look underneath but that allows for incrementalism and unhindered creativity!

Making an app have a notification icon in taskbar and minimize to it was surprisingly easy! There are problematic quirks but it is so very easy to find answers! Finding out how to get screen resolution or how to distinguish a logoff/shutdown from an Alt-F4 both didn't take long!

Ok when it now came to actually changng the screen resolution I still haven't figured it out although I'm getting closer to it. That's where be dragons.

https://github.com/mkf/Resolutioner

Mind the date of this post, consider comparing contemporary commits if you're from the future. (inb4 yes i now that future is immediate)

----------

# [An update on my C#/WinForms app that changes screen resolution](https://g.mikf.pl/gemlog/2022-02-05-winforms.gmi)
## 2022-02-05

From the most recent changes to the least recent.

It would be great if I could finish the .NET app settings config loading&saving, then I would just sit down to add autostart and the program would already be usable.

Adding Upgrade() to the function that loads config (yeah i know that could have been a handler) seemed to fix earlier issues with Debug builds not saving between executions, but on Release builds loading/saving config not longer works.

I also added a migration handler in a very-not-advised way as the advised way is handlers in a derived class. My config now has an integer for migration number, I should probably make myself somewhere a table of what migrations will correspond to which releases and commits, maybe make tags for migrations.

https://github.com/mkf/Resolutioner/commit/f584ec1f89f77d71ad71515dc612970129206a84.patch

Instead of handling all SessionSwitchReasons in one (actually two) ways I now let the user choose, introducing a rather intuitive two-column UI for which way (my program changes, on events, screen resolution to "the desired one" or "the restored one") the events work (I could make it triple-state dropdowns instead of checkboxes though, or besides checkboxes).

Wanting to continue that pattern (and make the behavior at all reasonable) with SessionSwitchReason.SessionRemoteControl as well, I ended up needing to PInvoke GetSystemMetrics, where I ended up seemingly needing the type Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX to cast to it. What I did was a bad thing as I basically added to my dependencies some random package exposing it called RawInputLight.

And that was after I learnt of this new thing Microsoft did to ease PInvoke, which is a project called win32metadata that brings it to amongst others Rust, but also to C# and in its case the so-called "language projection" is called CsWin32.

And the way I did that turned out to cause an override between CsWin32 and RawInputLight, but since in my configuration RawInputLight is winning it, I just had to add pragma to disable warning CS0436 for the line of code.

https://github.com/mkf/Resolutioner/commit/88978bf0c8e26cb6c3f776f52e3a56ae5fe5b294.patch

But I didn't yet mention how did I achieve screen resolution changing, right? Well, back then I used PInvoke without CsWin32, and I followed this guide:

https://www.codeproject.com/Articles/36664/Changing-Display-Settings-Programmatically

Written by Mohammad Elsheimy in 2009 and licensed with CPL 1.0, it proposed having the DEVMODE struct layed out in one's code, with the dmPosition struct possible to break out into two I4 fields, with [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] on top and a MarshalAs at each field. Then dll imports from User32.dll with an extern declaration all in MarshalAs for each needed function.

And to change resolution we load the original DEVMODE with EnumDisplaySettings with ENUM_CURRENT_SETTINGS (ENUM_REGISTRY_SETTINGS also available), and without getting a list of all available modes (which is doable with the guide) we just attempt to change to the same DEVMODE with dmPelsWidth and dmPelsHeight set to our other values and see which one of

 * SUCCESSFUL = 0, // Indicates that the function succeeded.
 * BADMODE = -2, // The graphics mode is not supported.
 * _FAILED = -1, // The display driver failed the specified graphics mode.
 * RESTART = 1 // The computer must be restarted for the graphics mode to work.

will our ChangeDisplaySettings result in. My function then returns it but presently the value is just ignored.

https://github.com/mkf/Resolutioner/commit/346e922a920a262dcfbf5681b15801cd2a7d209a.patch

Although now that I accidentally took a glance at

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changedisplaysettingsa

there appears that besides

 * DISP_CHANGE_SUCCESSFUL : The settings change was successful.
 * DISP_CHANGE_BADMODE : The graphics mode is not supported.
 * DISP_CHANGE_FAILED : The display driver failed the specified graphics mode.
 * DISP_CHANGE_RESTART : The computer must be restarted for the graphics mode to work.


there are also

 * DISP_CHANGE_BADDUALVIEW : The settings change was unsuccessful because the system is DualView capable.
 * DISP_CHANGE_BADFLAGS : An invalid set of flags was passed in.
 * DISP_CHANGE_BADPARAM : An invalid parameter was passed in. This can include an invalid flag or combination of flags.
 * DISP_CHANGE_NOTUPDATED : Unable to write settings to the registry.

Which I am not handling, I don't know their numeric values and worse, I am casting them into an own four-value (-2..1) enum.

Back to my configuration though, my actual configuration variables are still my input fields in my WinForms form which is just minimized. All the fields have event handlers to uncheck the "Config Saved" checkbox which can then be re-checked to save the configuration. And initially I tried writing a class deriving from System.Configuration.ConfigurationSection, only later I found out about the settings designer in Visual Studio.

And I have been asked to write another simple app, where I would use autostart too and configuration saving too. And one of the features of it are to be logging to files, so the last thing I was sitting down to in it was to have a file chooser dialog. Initially I only found out about OpenFileDialog. But it turned out there is also FileDialog but it is not to be used, only it's derivatives including there also being SaveFileDialog. But apparently these are recommended to handle the very writing of a file themselves. I am considering just creating files in a folder so maybe I will just use FolderBrowserDialog. But first, I need to learn how to save app configuration in .NET so that it loads correctly for me, especially between Release releases.