Introduction
The February 2004 edition of MSDN magazine included some example code on how
to build a custom class to save your winform settings to an *.ini file. This was
a great idea, but the code presented was extremely limited, and basically only
offered some Getval
/Setval
functions and a custom
function to Get/Set your form settings quickly. Further hampering this code was
its reliance on the Kernel32.dll *.ini file functions
(WritePrivateProfileString
, GetPrivateProfileString
).
I searched on Google for a better solution, but found nothing.
Many programmers choose to store their custom program settings in the
registry, but this defeats the XCopy deployment paradigm of .NET programs. You
should be able to copy settings along with the executable and go. Microsoft
would have you store this information in an XML file, but the fact is that many
end users (and even many programmers) still prefer the simple to open/edit
format of the venerable old *.ini file. But Microsoft left out any kind of
native support for this storage solution in .NET.
I decided to write my own class to handle this function. I'm not sure why
every reference to *.ini files begins and ends with accessing the
Kernel32.dll. I suppose people think that this would make things faster,
or perhaps more accurate or secure. But the fact is that *.ini files are
ultimately text files, and there is no reason they can't be handled as such.
Furthermore, I don't want to be limited by the Get/Set nature of the
Kernel32.dll functions. Controls designed for .NET are feature rich in
nature, and this one should be no different.
Aside from getting and setting key values, I want to able to add, remove and
edit sections. I want to be able to comment/uncomment key values, or even entire
sections with ease. I want to be able to move a key from section to section. I
want to be able to sort my sections and keys for easy reading. I want an easy
way to store my form settings. And just to top it off, let's add the ability to
dump an *.ini file out to XML should I decide to take that route in the future.
In short, it should be a fully featured, simple to use control. Oh, and it
should be free.
Using the code
Download the attached source code, which contains both the class file, and
demo project - an Ini File Editor (of course). The code for the class is heavily
commented, and should be easy to understand. There is also a detailed help file
included, produced by NDoc and the VB.DOC Visual Studio addin.
A simple example of the class file would look something like this:
Imports IniFile
Dim myIniFile As New IniFile("C:\Test.ini")
myIniFile.AddKey("MyKey","MyValue","MySection")
myIniFile.Sort()
myIniFile.ToXML("C:\Test.xml")
myIniFile.Save("C:\Test.ini")
Points of Interest
- Add, Delete, Edit, Comment and UnComment Sections
- Add, Delete, Edit, Comment, UnComment and Move Keys/Values
- Save form settings easily
- List all Sections
- Dump to XML
- Sort file
The IndexOf()
and Sort()
Conundrums
While speed is not of the essence in this class (I simply cannot imagine a
scenario where an *.ini file is being referenced thousands of times a second, or
put under any kind of heavy load situation), I did try to make things as simple
and fast as possible.
My first draft of this class was scrapped completely. It was based on a
single ArrayList - the contents of the file were read line-by-line into the
ArrayList, and then had to meticulously examined and manipulated in order to
perform the necessary functions. It worked, but the code was ugly, and every
time I tried to add even the most simple feature, I found myself re-writing huge
hunks of code, and introducing new bugs.
It was then that I remembered I was dealing with an object oriented based
language, and I should re-think my approach. I tried to think like Microsoft.
Ok, so we have an *.ini file, that's an object. That file is made up of
sections, those are objects. And each section has keys/values. Those are objects
too. Suddenly, this made a lot sense, and everything fell into place from there.
The main component, the IniFile, is an ArrayList. I chose this, because the
ArrayList class has some powerful features that make life easy, such as
Add(),
RemoveAt(),
and IndexOf(),
as well
as sorting and searching features. This made adding and manipulating sections
easy. The Section object is also based on an ArrayList
, since it
too needs to deal with child objects, namely, the keys. And keys are simple
classes, with simple name/value strings.
This posed an interesting problem for me. Consider the following example:
Dim myAL As New ArrayList()
myAL.Add("The")
myAL.Add("quick")
myAL.Add("brown")
Dim Quick_Index = myAL.IndexOf("quick")
In the above code, Quick_Index
would contain a value of 1, which
is the index of the string object called "quick". Simple enough.
But now consider this - the main ArrayList
of IniFile
(called Sections
in the source code) contains not strings,
but other ArrayLists
(Sections
). How do I use
IndexOf()
when the object I'm attempting to locate isn't a string
object? And can I still sort the ArrayList
? This same problem
continues in the Sections
Object, as it also contains custom
classes (keys), and not string objects.
The sorting problem was solved by creating a custom comparer. The custom
comparer implements IComparer
, and allows me to direct the code as
to how to compare one section to another.
Public Class SectionComparer
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer
Implements System.Collections.IComparer.Compare
Dim s1 As String = LCase(x.Name)
Dim s2 As String = LCase(y.Name)
Return s1.CompareTo(s2)
End Function
End Class
As you can see above, the custom comparer takes the x and y objects (does x =
y?) and tells the computer how to go about comparing them. Since my
Section
objects each have a name value, I instructed the Compare
function to compare those names (in lower case, just to be safe) and returned
the result of that comparison. Then in the Sort()
Function, I
specify which comparer to use:
Dim mySC As SectionComparer = New SectionComparer
Sections.Sort(mySC)
That leaves me with the IndexOf()
problem. IndexOf()
doesn't take a comparer object. So now what? Well, it turns out that
IndexOf()
uses the Equals()
method of the objects it
is comparing to determine if we have a match. When you are comparing custom
classes however, .NET simply uses the default Equals()
method,
which is to say, it compares the memory address of the object. Since two objects
will never share the same memory address, they will never be equal. The fix is
to override the Equals()
method of the class, and tell the computer
how to compare equal values of our custom class, much in the same way that we
did with the custom comparer. An excellent example of this is here.
The problem is, this still isn't working. Take a look at the
commented code in the GetKeyIndex()
function to give this a try. If
you find an answer, please email me so that I can update the code, and this
article.
A final note
This is the first release of this code. It needs lots of testing, and has
lots of room for improvement. If you find any bugs, or have any suggestions for
improvements, they are welcomed. Thanks, and I hope you find this code to be of
value to you.
History
- Initial release of source code and documentation.