💾 Archived View for g.mikf.pl › gemlog › 2022-02-05-winforms.gmi captured on 2022-04-29 at 14:35:06. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2022-03-01)

➡️ Next capture (2023-01-29)

-=-=-=-=-=-=-

An update on my C#/WinForms app that changes screen resolution

2022-02-05

See previous post from two weeks ago: A first in C# and WinForms: started writing a program for changing screen resolution

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

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

there are also

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.