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 settings—a set of value pairs—from an XML file to an in-memory collection. You can modify any of those values by key—the name of the setting—and 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 direction—reading the settings from disk to memory—in 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].

comments powered by Disqus

Featured

Subscribe on YouTube