Getting Started
Build Your Own App Settings Class
Learn how to create your own application settings class by leveraging the FileInfo, HashTable, XMLReader, and XMLWriter classes.
Technology Toolbox: VB.NET, XML
VB.NET's AppSettings class lets you save settings easily. They go into an XML file named YourAppName.config, and you're all set. That is, unless you're working on a solution with multiple executables or DLLs but still would like to use a common settings file. You could solve this problem with a third-party DLL such as OptionsLib.dll, but you'll have problems if the DLL isn't signed and you wish to strong-name sign your application.
You can remedy this situation by using some .NET Framework classes to roll your own application settings class. This class gives you an easy way to read application settingsa set of value pairsfrom an XML file to an in-memory collection. You can modify any of those values by keythe name of the settingand persist those settings to disk again. I'll show you how to design such a class based on some XML classes, the HashTable class, and a FileInfo class. You can extend this class later to meet your particular needs.
Your application settings class needs to meet several requirements to make it practical and versatile. It should accept an XML file path in its constructor, have a simple interface, use XML for persistence, read from and write to the specified XML file, and allow default return values (as classic VB's GetSetting function did).
You need quick access to a particular setting by key (the name of the setting), so give your class a private member variable of type System.Collections.HashTable. You might prefer to inherit from the DictionaryBase class, but let's use an internal HashTable member variable for the purpose of this article.
You also need a member variable that maintains the file path of the XML file for your settings. Use a variable of type System.IO.FileInfo. Employ ReadSettings and WriteSettings methods to get your settings from disk to memory and back, along with a constructor to receive a file path argument (see Figure 1). Finally, you need some means of accessing the appropriate property to read or set its value. Download the demo program to see this class in use, albeit trivially.
You could choose to use two separate methods, such as ReadSettings and WriteSettings, but I chose to use a default property called Item for simplicity's sake. The Item property's Get method gets passed the desired setting's name (key) and optionally a default value to return if the item wasn't found. Get then returns either the corresponding value, or the default value if it finds no matches for the key. The default is Nothing if you don't supply a default value. The Item property's Set method also gets passed the key; don't pass it a default value.
Make your declarations simpler by placing three Imports statements at the top of your class:
Imports System.Collections
Imports System.IO
Imports System.Xml
Then give your class its "skeleton":
Public Class clsAppSettings
Private _Settings As New Hashtable
Private _fiSettingsFile As FileInfo
Default Public Property Item(ByVal _
strKey As String, Optional ByVal _
DefaultVal As Object = Nothing)
Public Sub New()
Public Sub ReadSettings()
Public Sub WriteSettings()
Bring on the Constructor
A constructor is a special method that provides a means of initializing the object being created from your class. The constructor is always called New. You can overload the constructor, as you can the other methods in your class. This means that you may choose to offer several versions of the New method, each with a slightly different set of parameters. Offer only one for now.
Earlier I said that your constructor should accept a file path to an existing XML file adhering to your XML specifications. I recommend adding an optional boolean parameter so you can direct the class to load the settings into memory automatically upon creation:
Public Sub New(ByVal strFileName As _
String, Optional ByVal _
bReadSettings As Boolean = True)
_fiSettingsFile = New FileInfo( _
strFileName)
If bReadSettings Then ReadSettings()
End Sub
By default, bReadSettings is true when your class is instantiated, and you call the ReadSettings method automatically to fill the _Settings HashTable with the contents of the XML file.
Now bring in the Item property:
Dim Settings as New clsAppSettings( _
".\MYAPP.XML")
'reading a value
If Settings.Item("ShowTips",True) Then
Msgbox("Should show startup tips")
End If
'writing a value
Settings.Item("LastRan")=Now.ToString
'ensure that it gets saved to disk now
Settings.WriteSettings
You might prefer to instantiate Settings when your application starts, then call WriteSettings as the application ends. You can do this by making the Settings variable global and placing Settings.WriteSettings inside the Closing event of your main form.
Remember that the constructor has an optional parameter, bReadSettings, and its default value is true, so you don't have to call ReadSettings to load your settings into memory. Now code the Item property:
Default Public Property Item( _
ByVal strKey As String, Optional _
ByVal DefaultVal As Object _
= Nothing)
Get
If Not _Settings.Contains( _
strKey.ToLower) Then
Return DefaultVal
Else
Return _Settings.Item( _
strKey.ToLower)
End If
End Get
Set(ByVal Value As Object)
_Settings.Item(strKey.ToLower) _
= Value
End Set
End Property
Your Item property checks the internal HashTable to see if the key is found. The key is converted to lowercase when using the Get or Set methods on Item, which helps avoid any case-sensitivity issues with your keys. There might be a few C++/C# purists who'd want "WindowPos" and "windowpos" to be two entirely different keys, and thus settings, but most developers would probably rather avoid such headaches. Besides, users may edit this file to change settings, and case sensitivity isn't a great feature for the typical user.
The class might look complete at this point, but it still needs a means of getting the settings from disk to memory and back. You do this with two crucial methods: ReadSettings and WriteSettings.
Save Your Settings
You can make classes persist themselves using XML, and you can use the XMLTextWriter class to write the XML just the way you want it. Your XML file contents might look like this:
<Settings>
<Setting key="LastRan"
value="6/22/2004" />
<Setting key="ShowTips"
value="False"/>
</Settings>
The XML format is simple. You have a <Settings> tag within which one or more specific <Setting> tags are nested. Of course, you might decide to do something more advanced than this demo.
The basic idea of the WriteSettings method is to use the XMLWriter class to open the XML file, loop through all items in the HashTable, write them out as <Property> tag elements, and then close the XMLWriter streams. Start out by opening the XML file:
Public Sub WriteSettings()
Dim XWriter As XmlTextWriter
Try
XWriter = New XmlTextWriter( _
fiSettingsFile.ToString, _
System.Text.Encoding.Default)
Now instantiate the XmlTextWriter class. Specify your XML file path and the desired text encoding in the constructor. Then make your XML more human-readable by telling XWriter to indent and to use four spaces for indentions:
With XWriter
.Indentation = 4
.Formatting = _
Xml.Formatting.Indented
The XMLTextWriter class has a WriteStartElement method that writes a beginning tag such as <Settting>. It has a WriteAttributeString method that lets you put attributes inside the property tag:
<Setting LastRan="12-6-2004">
It also has a WriteEndElement method to write the matching end tag for an element. You use all three in the next part of the WriteSettings method:
'write <Settings> begin tag
.WriteStartElement("Settings")
Dim de As DictionaryEntry
'loop & write out each item
For Each de In _Settings
'write <Setting> tag
.WriteStartElement("Setting")
'write attributes
.WriteAttributeString("key", de.Key)
.WriteAttributeString("value", de.Value)
.WriteEndElement()
Next
Write a <Setting> tag with two attributes: key and value. Then close the tag by calling WriteEndElement, which adds a "/>" to close the element. Finally, write the </Settings> closing tag and close the XML stream:
.WriteEndElement()
'close streams
.Close()
End With
Catch ex As Exception
MsgBox(ex.Message, _
MsgBoxStyle.Exclamation, _
"There was an error saving application settings")
Finally
If Not IsNothing(XWriter) Then
XWriter.Close()
End if
End Try
End Sub
You code the other directionreading the settings from disk to memoryin similar fashion (download the sample app for specifics). The basic differences are that you use XmlTextReader, you must check whether the file exists using _fiSettingsFile.Exists, and you must clear the HashTable before adding items (see Listing 1).
Also, the fact that you're going in the other direction causes you to make several changes to your procedure: You declare an XmlTextReader object and open it; you see whether the XML file exists and exit if it doesn't; you find the appropriate tags, read their attributes, and add them to the HashTable; and finally, you close the streams.
Reading is a little trickier than writing because you must locate the appropriate element first, using the Read and ReadStartElement methods, and then read its attributes. You read each element while its Name property equals "Setting" once you're past the <Settings> tag, then quit reading.
You could extend this class in a variety of ways, but even as it stands, it offers usefulness and simplicity. You could make your AppSettings class more robust by offering various levels of settings (group settings) as does the Windows Registry. You might also decide to add encryption to the class in case you don't want anyone changing your settings from outside your application. You can shape and grow this class to meet your own evolving needs.
About the Author
Shane Story is an independent consultant and software developer with Story ITC. He has a bachelor''s degree in computer science and has been programming for more than 22 years. His current endeavors include developing two VB.NET commercial applications and a large ASP.NET/SQL Server Web site. He specializes in Microsoft technologies. Reach him at [email protected].