Menu

Why I use INI Files, and a FormPositionSaver class

January 12, 2012 - .NET, C#, General Computing, Programming, Windows

In some of my recent posts, I’ve covered the topic of accessing and parsing an INI file for configuration data in a C# Application.

Some may wonder why. After all; the “norm” for C# and .NET applications is to use XML files for configuration information, isn’t it? Well, yes. But to be honest, XML files are a fucking pain in the ass. They aren’t human readable to your average person the same way an INI file is, and getting/setting values is tedious. Primarily, the reason I use INI files is that they are:

  1. Human Readable: Anybody can understand the basic structure of the sections and Name=Value syntax.
  2. Accessible: You don’t need a special editor
  3. Portable: since the entire thing is interpreted using Managed code, it will act the same on any platform (Mono or the MS CLR).

Mostly, I feel that XML, and in many ways other configuration options, are more or less driven by fad. Another option for configuration settings on Windows is the Registry, which is in fact often the recommended method; but this is anything but accessible to the user. Would you rather guide a user to edit a INI file or to fiddle with registry settings?

With that said, INI Files do have their own issues. For example, their data is typically typeless; or, more precisely, the Values are all strings. Whereas using a .NET XML Serializer, for example, you could easily(relatively speaking) serialize and deserialize a special configuration class to and from an XML file and preserve it’s format, with my INI file class there will typically be some work to parse the values.

It was with the idea of turning my string-only INIFile configuration settings into something that can be used for nearly any type that I created the INItemValueExtensions class, which is nothing more than a static class that provides some extension methods for the INIDataItem class. I covered this in my previous post.

The prototypes for the two static functions are:

How would one use these extension methods? Well, here’s an Example:

Woah, hold the phone! What’s going on here? We’re loading DateTime values directly from the INI File? How does that work?

All the “magic” happens in the getValue generic extension method. The first thing the routine does is check to see if the Type Parameter has a static TryParse() method; if it implements ISerializable and have a TryParse method, than the routine will read the string from the INI file, decode it via Base64, and throw it in a MemoryStream, and then try to deserialize the Object Graph for a Type T using that stream.

If it does implement a TryParse() routine, (like, for example, DateTime) it doesn’t try quite as hard. It takes the string from the INI file and hands it to the Type’s TryParse() routine, and then returns what that gives back. Naturally, the inverse function (setValue) does something somewhat opposite; it checks the Base64 logic, and if so it sets the value of the item to the Base64 encoded value of the serialized object. Otherwise, it just uses toString().

This typically works, particularly with DateTime, because usually ToString() is the inverse of TryParse(). In the case of DateTime, this has a few edge cases with regards to locale, but usually it works quite well. And more importantly, the introduction of allowing any object that implements ISerializable to simply be thrown as an INI value via a Base64 encoded string is useful too, although with large objects it’s probably not a good idea for obvious reasons.

But… I still want to access other settings!

Of Course, an INIFile is only one of any number of ways to store/retrieve configuration settings. And while they don’t typically lend themselves to the same syntax provided by the INIFile class, it would be useful to have some sort of common denominator that can handle it all. That was the original intent of the relatively unassuming ISettingsStorage interface:

This uses a concept known as a “category” which is pretty much the same idea as an INI File section. What makes it different is that, for implementors that use other storage mechanisms, it could have additional meaning; for example, a fictitious XML implementation of ISettingsStorage could use the “Category” string as an XPath to an element; and the Value could be stored/retrieved as a Attribute. a Registry implementation might use it as a Registry path, and so on.

The problem is, even though the INIFile class implements this interface, it’s too basic, and doesn’t provide nearly the syntactic cleanliness that just using the INIFile does. Stemming from that, and because I wanted to try to get a way to store settings directly in a DB, I introduced two events to the INIFile class; one that fires when a Value is retrieved, and one when a value is saved. This way, the event could be hooked and the value saved elsewhere, If desired. Now, to be fair, this is mostly a shortcoming of my interface definition; as you can see above, there is no way to, for example, inspect category or Value names. I toyed with the idea of adding a “psuedo” category/value combination that would return a delimited string of category names, but that felt extremely silly. The creation of a generic interface- or abstract class- that provides all the conveniences I currently enjoy using my INIFile class but allowing me to also use XML, Registry, or nearly any other persistent storage for settings will be a long term goal. For now, I’m content with accessing INI files and having a unclean event to hack in my own behaviour.

My first test of the above feature- whereby it allows values to be TryParse’d and ToString’d back and forth from a given type on the fly- was the creation of a FormPositionSaver class.

The proper way to save and restore a window’s position on Windows is using the GetWindowPlacement() and SetWindowPlacement() API Functions. These use a structure, named, quite aptly, “WINDOWPLACEMENT” to retrieve and set the window position and various attributes. Therefore, our first task is to create the proper P/Invoke’s for these functions:

I also include OffsetRect(), but I’ll get to that in a bit. Now the “big one” is the definition of the WINDOWPLACEMENT structure and it’s various aggregate structures. Why? well, in the interest of leveraging the INIFile’s static extensions, Why not define a static TryParse() and a toString() method on the structure that can set and retrieve the member values:

WHEW! that’s quite a bit of code for a structure definition, but we’ll make up for it with the brevity of the actual FormPositionSaver class itself. First, my design goal with this class was to make it basically do all the heavy lifting; it hooks both the Load and Unload event, and saves to and from a given INIFile Object in those events. Since the application I was working on at the time didn’t actually get a Valid INI object until during it’s main form’s Load event, and since there is no way to say “Invoke this event first no matter what” I also added a way for it to be told that hooking the load event would be pointless since it already occured, at which point it will not hook the event and instead set the form position immediately. Values are stored

Alright, so maybe I lied a bit. It’s not super short. Although a lot of it is comments. Some might note that I only sporadically add doc comments, even though I ought to be adding them everywhere. Well, sue me. I just add them when I feel like it. When I’m concentrating on function, I’m not one to give creedence to form.

This is where I explain OffsetRect(). Basically, if your application is run twice, and you load the form position twice, the second form will open over the first one, and the screen will look pretty much the same. So we detect previous instances and offset by an amount to make it’s position different from any previous instances as necessary. That’s pretty much the only purpose of OffsetRect.

I have packaged the current versions of cINIFile.cs and the new FormPositionSaver.cs in a zip file, it can be downloaded from here.

Have something to say about this post? Comment!