devxlogo

How to Make Your .NET Windows Forms Configuration Files Dynamic

How to Make Your .NET Windows Forms Configuration Files Dynamic

he .NET framework supports configuration files to supply settings and static data to both ASP.NET and Windows Forms applications. Configuration files are XML-formatted files loaded at startup time and cached in memory so that you have fast access to data. Essentially, configuration files are intended to replace both the Win16 INI file format and application-specific registry entries for managed code.

As delivered, the .NET framework supports a number of different configuration sections and tags within configuration files, including an tag, which can contain a list of child elements that hold data specific to your application. There’s one problem though; the native .NET classes can read the configuration data, but can’t alter it. Why would you want to alter the data? Here are two common examples:

  • You want your application to “remember” its window size and position between sessions.
  • You want to create user-configurable options, such as toolbar buttons, color themes, or favorites lists, and store them with the application.

You can’t know these values in advance?you need to store and change them at runtime. But if you can’t use the framework classes to write to the files, you need to create some other way to alter them. Fortunately, as the files are well-formed XML, you’re perfectly free to create new classes or to use classes in the System.Xml namespace to alter the contents of your configuration files.

There are some significant differences between the way Windows Forms and ASP.NET applications interact with configuration files. Although both read the files at startup and cache the contents, if you change a Windows Forms configuration file, the changes don’t take effect until the next time you start the application. In contrast, when you change an ASP.NET applications configuration file, the framework recognizes the change immediately.

Author’s Note: This article applies only to Windows Forms configuration files?the sample code does not work with ASP.NET web.config files.

Configuration File Name and Location
Whenever you store data outside of your application, there’s always a danger that the data might not be available, and you also face the problem of finding the file. You can’t simply hard-code a path, because you may not know exactly where a user installed your application. The framework simplifies the problem by restricting the name and location of configuration files using two rules:

  1. Windows Forms configuration files must be named exactly the same as the executable file, including the .exe extension, followed by .config. For example, if your executable file is myApp.exe, the configuration file must be named myApp.exe.config.
  2. The configuration file must reside in the same folder as the executable file.

These restrictions make finding the configuration file for a Windows Form application a one-line operation. Here’s how to obtain the configuration file name for a Windows Forms application:

   Dim configFile As String = _   Application.ExecutablePath & ".config"

The Application.ExecutablePath property returns the full path to the exe file for the running application. Appending .config gives you the configuration file name that matches the configuration file name and location rules. Just because you have the proper file name doesn’t mean that the file exists. Use the File or FileInfo classes’ Exists method before attempting to read or modify the file.

Modifying Configuration Values
After obtaining the file location, you can add, remove, or modify (or any other section’s) values using straightforward XML DOM methods. For example, suppose you have a configuration file containing the following three key/value pairs.

                                        

You can create additional settings by reading the file, obtaining an XmlElement object reference for the element, creating a new child element, setting its attributes and values, and then appending the new child element to the element’s ChildNodes?collection.

The following code shows the entire process to create a new element containing a key and value pair to the element for the configuration file shown earlier.

         Dim s As String = ""      s = Me.getConfigValue("v4")      If s Is Nothing Then         Console.WriteLine( _            "Adding new configuration value.")            ' Get the configuration file name         Dim configFile As String =             Application.ExecutablePath + ".config"            ' create a new XmlDocument object         Dim Xml As XmlDocument = New XmlDocument()            ' load the configuration file         Xml.Load(configFile)            ' get a reference to the  section         Dim appSettingsElement As _            XmlNode = Xml.SelectSingleNode( _            "//configuration/appSettings")               ' create a new  element            Dim newElement As XmlElement = _               Xml.CreateElement("add")               ' set its attributes            newElement.SetAttribute("key", "v4")            newElement.SetAttribute( _               "value", "Value 4")               ' append it to the  section            appSettingsElement.AppendChild(newElement)            Xml.Save(configFile)               ' changes are not live until             ' the app is restarted--the following            ' line prints nothing            Console.WriteLine(Me.getConfigValue("v4"))         Else            Console.WriteLine("Configuration value " & _               """v4"" already exists." & _               "  Value: " & s)         End If         Private Function getConfigValue( _         ByVal key As String) As String         Return System.Configuration. _            ConfigurationSettings.AppSettings.Get(key)      End Function

The sample code (download from the link in the left column) contains a form where you can test the preceding code. Click the “Simple Example” button to see the configuration file before and after running the code (see Figure 1).

Figure 1
Figure 1: Clicking the “Simple Example” button on the sample form adds an entry to the configuration file using the System.Xml classes and shows the configuration file before and after clicking the button.

The first time you run the application, clicking the Simple Example button creates a new “v4” entry for each click, even though the code checks to see if the “v4” key already exists! However, if you close the application and then restart it, clicking the Simple Example button doesn’t do anything except display a message box that the setting already exists. That seems odd. Why doesn’t the check at the beginning of the code find the entry the second time you click the button? The answer is that because the call to getConfigValue() uses the System.Configuration.Configuration.AppSettings class to retrieve the value?and that class doesn’t recognize changes made to the configuration file while the application is running. The second time you run the application though, the framework reads the altered configuration file, the getConfigValue() call returns the “v4” setting value, and the class properly displays the message box.

But My Changes Aren’t Live!
As you can see by running the code in the preceding section, you can easily alter the contents of a configuration file programmatically, but the built-in configuration classes don’t recognize any changes you make to the configuration file until the next time you launch the application. That’s no good.

What you need is to be able to both modify the file and use the modifications within a single application instance. To do that, you have to compromise a bit. The key is to avoid using the built-in configuration classes to access your data; instead, create a class to manage them for you. The sample SettingsManager project contains a class named AppSettingsManager that both reads and writes values dynamically. You use the AppSettingsManager class to read and modify items rather than the built-in classes. When the class terminates, it automatically saves any changes you’ve made to the configuration file.

Building the AppSettingsManager Class
Ideally, you want full control over the section of your configuration file at runtime, including the ability to create, delete, or modify any value within that section. That means you have to compromise and accept the fact that you’ll have two separate copies of the configuration file in memory?one read-only version that reflects the state of the configuration file at application startup time, and one “live” version managed by an AppSettingsManager class.

Except for the slight memory resource increase, having two versions in memory shouldn’t matter, because the framework version is read-only, and after startup, it never needs to access the (possibly altered) configuration file. However you should be careful. As you’ve seen, the framework doesn’t recognize changes to the configuration file after startup, so if you delete or change a setting via the AppSettingsManager, and then check the setting with the System.Configuration.AppSettings methods, you’ll get the setting value as of application startup, not the current value. However, as long as you keep that in mind, the AppSettingsManager shouldn’t interfere with any existing code that reads application settings, because it doesn’t replace the System.Configuration.AppSettings functionality, it complements it.

The AppSettingsManager class fulfills the requirements. It reads the application’s configuration file when you instantiate it, lets you modify the contents of the section, and saves any changes you make during class finalization.

The Singleton AppSettingsManager Class
To avoid threading problems, you only want one instance of an AppSettingsManager for any given application; otherwise, you might inadvertently create a situation where two different AppSettingsManager instances contained different data. Therefore, the AppSettingsManager constructor is private, and calling code must obtain a reference to an AppSettingsManager via the Shared GetManager method.

   Public Class AppSettingsManager      Private Shared asm As AppSettingsManager      Private xml As XmlDocument      Private configFileName As String = String.Empty      Private appSettingsElement As XmlElement      Private dirty As Boolean = False         Public Shared Function GetManager()         ' Allow only one instance         If asm Is Nothing Then            asm = New AppSettingsManager()            Return asm         Else            Return asm         End If      End Function         ' more class methods here...   End Class

Here’s how the calling code looks:

      Dim settingsManager As _         SettingsManager.AppSettingsManager = _         AppSettingsManager.GetManager()

When the preceding statement runs, the class runs the private AppSettingsManager constructor, sets the Private Shared field asm to the resulting AppSettingsManager instance, and returns that. If the calling code executes the GetManager method again the class returns the existing asm instance. In other words, no matter how many times you call the GetManager method, all outside references to an AppSettingsManager object all point to the same instance of the class.

The AppSettingsManager Constructor
The private constructor reads the configuration file, if one exists, and readies the class to retrieve and alter the section data.

      Private Sub New()         configFileName = _         Application.ExecutablePath & ".config"         If File.Exists(configFileName) Then            Try               ' Create a new XmlDocument object               xml = New XmlDocument()                  ' load the configuration file               xml.Load(configFileName)                  ' get the  element               appSettingsElement = _               xml.SelectSingleNode _               ("//configuration/appSettings")                  ' if there's no  section,               ' create one.               If appSettingsElement Is Nothing Then                  appSettingsElement = _                  xml.CreateElement("appSettings")                  xml.DocumentElement.AppendChild _                  (appSettingsElement)               End If               Catch ex As Exception               ' handle Load errors               Throw New ApplicationException _               ("Unable to load the configuration " & _                "file for this application.", ex)            End Try         Else            ' no configuration file exists            ' you might want to alter this method to             ' create one            Throw New ApplicationException _            ("This application (" & _              Application.ExecutablePath & _              " has no configuration file.")         End If      End Sub

The constructor sets the String variable configFileName to the expected path of the configuration file, creates a new XmlDocument instance, and tries to load the file, trapping any errors that occur. The only error that should be able to occur is the absence of a configuration file; the framework will catch malformed configuration files during application load. The sample AppSettingsManager simply catches the error, but you may want to alter the code to create a configuration file if it doesn’t already exist.

Next, the constructor obtains a reference to the element. If there’s no element in the configuration file, it creates one.

After obtaining a reference to an AppSettingsManager object, you can modify the settings. Whenever you make a change to the contents, the object sets the dirty field to True, which forces the class to save the changes either when you explicitly call the Save() method, or when the overridden Finalize() method fires, whichever comes first.

Creating, Altering, and Deleting Settings
The AppSettingsManager class has a number of methods for altering the settings in the section. These are listed in Table 1. Although many of the methods accept an Object or Object array as parameters rather than simple strings, the types passed or requested must be convertible to or from string representations, because the values must be stored in string form in the XML-based configuration file. If the AppSettingsManager cannot convert the value to or from a String successfully, it throws an InvalidCastException.

Table 1.

Method SignatureDescription

Public SubAbandonChanges()

Abandons any changes made to the configuration file since thelast call to Save(), or if Save() has not beencalled, since the start of the application.

PublicSub AddValue( _
??? ByVal aKey As String, _
???ByVal newValue As Object)
Adds a new value to thespecified key. If the key exists, it appends the new value to the currentvalue as an item in a comma-separated list. If the key does not exist, itcreates it and sets the value to the new value. If the new value alreadyexists in the specified key, the method leaves it alone.
PublicSub AddValues( _
???ByVal aKey As String _
???ByVal newValues() as object)
Adds the new values to thespecified key. If the key exists, it appends the new values to the currentvalue as items in a comma-separated list. If the key does not exist, itcreates it, and sets the value to a comma-separated list of the new values.If a value already exists in the specified key, the method leaves it alone.
PublicFunction GetManager() _
???As AppSettingsManager
Returns theAppSettingsManager object.
PublicFunction GetValue( _
???ByVal aKey As String) _
???As String
Returns a stringrepresentation of the value associated with the specified key, or Nothing if the key doesnot exist.
PublicFunction GetValue( _
???ByVal aKey As String, _
???ByVal aType As _
???System.Type) As Object
Returns an Object cast tothe specified type and associated with the specified key, or Nothing if thekey does not exist.
PublicFunction GetValues( _
???ByVal aKey As String, _
???ByVal aType As _
???System.Type) As Object()
Returns an array of Objectscast to the specified type and associated with the specified key, or Nothing, if the keydoes not exist.
PublicReadOnly Property _
???HasPendingChanges() _
???As Boolean
Returns a Boolean valueindicating whether the values have changed since the last call to Save()or, if Save() has notbeen called, since the start of the application.
PublicReadOnly Property _
???HasSettingChanged(ByVal _
???aKey As String) As Boolean
Returns a Boolean valueindicating whether the value of the setting at the specified key has changedsince the start of the application.
PublicReadOnly Property _
???KeyExists(ByVal aKey _
???As String) As Boolean
Returns a Boolean valueindicating whether the specified key exists.
PublicSub Remove( _
???ByVal aKey As String)
Removes the specified keyfrom the application settings. If the key does not exist, theAppSettingsManager ignores the call.
PublicSub RemoveValue( _
???ByVal aKey As String, _
???ByVal aValue As Object)
Removes the specified objectfrom the specified key.
PublicSub Replace( _
???ByVal aKey As String, _
???ByVal oldValue As Object, _
???ByVal newValue As Object)
Replaces the specifiedexisting value in the specified key with the new value. If either thespecified key or the oldValueparameter does not exist, the AppSettingsManager throws an exception.
PublicSub Save()Saves the current settingsto the application’s configuration file. After calling Save(), any call to HasPendingChangesreturns False until youmake further changes to the settings values.
PublicReadOnly Property _
???ValueExists( _
???ByVal aKey As String, _
???ByVal aValue As Object) _
???As Boolean
Returns a Boolean valueindicating whether the specified value exists in the specified key.
PublicFunction _
???GetAppSettingsElement() _
???As XmlElement
Returns an XmlElement thatrepresents the tag, including any unsaved modifications. Thismethod was included for demonstration purposes only, and should be removedfrom a production version of the class.

Using the AppSettingsManager
To use the AppSettingsManger, create an instance, and then call its methods and properties freely. If you want to save the settings you’ve created, you don’t need to do anything?the class automatically saves settings alterations when it’s finalized. If you want to save the settings earlier than that, call the Save() method to force an immediate save. If you want to abandon changes that you’ve made, call the AbandonChanges() method.

The code is somewhat complicated by the fact that configuration settings can be either single-valued or multi-valued. All settings exist in tags that look like this:

   

Single-valued settings contain a single value for the “value” attribute, whereas multi-valued settings contain a comma-delimited list of values, so that you can store multiple values associated with a single key. For example, you might wish your application to open its main window at the same size it was when the application last closed. Rather than have two keys, one for the Width and one for the Height, it’s probably more convenient to have a single Size key, such as:

   

To retrieve a single-valued setting, use the GetValue() method, passing the key associated with the value you want to retrieve. To retrieve a multi-valued setting, use the GetValues() method, which returns an array of Objects containing the settings. If you use the GetValue() method to retrieve a multi-valued setting, the method returns all the values as a single concatenated comma-delimited string.

Similarly, to create a single-valued setting, use the AddValue() method; to create a multi-valued setting, use the AddValues() method, passing an Object array for the newValues() parameter. You can also create a multi-valued setting by passing a comma-separated array of strings to the AddValue() method.

Remember that XML is case-sensitive, so if the configuration file contains a key named “someKey“, calling GetValue(“somekey”) does not return the correct result. Because the class can throw exceptions due to conversion problems, you should use Try/Catch when you’re not sure that the data you pass or request can be converted to or from the specified type. However, I’ve minimized other types of exceptions; the class throws an exception only if the configuration file doesn’t exist when the application first loads, or during the Replace() method, if the key or the value you’re attempting to replace doesn’t exist.

There’s not enough room in this article to show examples of every call to the AppSettingsManager class, but the downloadable sample code contains a ConfigurationAlterationsVB project that lets you test the AppSettingsManager methods and properties, and see the changes made to the section of the configuration file.

Creating Custom Configuration Elements
Windows Forms configuration files don’t load properly if you misspell the or the tags, or if you add unrecognized tags to those sections. However, you’re perfectly free to add whatever you like as child nodes of the child tags?in other words, the sky’s the limit.

For example, the following XML causes an error when you try to obtain a value using the System.Configuration.ConfigurationSettings.AppSettings.Get(key) method.

                                test        

In the configuration file shown above, the unrecognized tag causes an error when you attempt to read a configuration setting. However, by placing custom tags inside a (recognized) ?tag, you can create any XML you like. The following configuration file does not cause an error:

                                         test                      

Despite the child tag, the framework happily reads the second version and simply ignores the extra child tags. If you use the framework classes to query the value of the “vx” key, it returns “nothing,” and no error occurs.

So, you can see that although the sample code for this article deals only with modifying the standard child tags of the element, you can create your own version of the AppSettingsManager class to read and write any XML markup that meets your needs.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist