Click here to Skip to main content
Click here to Skip to main content

Quick and Dirty Settings Persistence with XML

, 9 Sep 2006
Rate this:
Please Sign up or sign in to vote.
A quick and dirty use of an XML file to save program settings between application execution sessions.

The demo project is just a form that remembers its Top and Left values. I'm sure you've seen a gray form with no controls before, so I've spared you the graphic.

Introduction

Yes, I'm afraid it's yet another class to save application settings in an XML file. Why, when there are so many similar articles here? Well, most of them use classes that are big, and some are huge! Great when you need them, but do we really need to use a DLL built from tens of files and thousands of lines of code just to save a few simple settings? In many cases, no.

Why XML and not INI or Registry?

I shall not get into this as it's well covered elsewhere - but basically, you should be saving your settings in XML these days. (Ideally, this will let you move an entire .NET application, including its settings, to another PC just by copying the application folder.)

The Class

This class is quick and dirty. It has no customised trapping of unexpected errors, is not optimized to the last decimal point, and will probably offend the 'best practice' gurus. But it's small, simple, easy to use, and it works.

This is the entire code:

using System;
using System.Windows.Forms;
using System.Xml;

namespace QuickNDirtyXML
{
  public class Settings
  {
    XmlDocument xmlDocument = new XmlDocument();
    string documentPath = Application.StartupPath + "//settings.xml";

    public  Settings()
    { try {xmlDocument.Load(documentPath);}
      catch {xmlDocument.LoadXml("<settings></settings>");}
    }

    public int GetSetting(string xPath, int defaultValue)
    { return Convert.ToInt16(GetSetting(xPath, Convert.ToString(defaultValue))); }

    public void PutSetting(string xPath, int value)
    { PutSetting(xPath, Convert.ToString(value)); }

    public string GetSetting(string xPath,  string defaultValue)
    { XmlNode xmlNode = xmlDocument.SelectSingleNode("settings/" + xPath );
      if (xmlNode != null) {return xmlNode.InnerText;}
      else { return defaultValue;}
    }

    public void PutSetting(string xPath,  string value)
    { XmlNode xmlNode = xmlDocument.SelectSingleNode("settings/" + xPath);
      if (xmlNode == null) { xmlNode = createMissingNode("settings/" + xPath); }
      xmlNode.InnerText = value;
      xmlDocument.Save(documentPath);
    }

    private XmlNode createMissingNode(string xPath)
    { string[] xPathSections = xPath.Split('/');
      string currentXPath = "";
      XmlNode testNode = null;
      XmlNode currentNode = xmlDocument.SelectSingleNode("settings");
      foreach (string xPathSection in xPathSections)
      { currentXPath += xPathSection;
        testNode = xmlDocument.SelectSingleNode(currentXPath);
        if (testNode == null)
        {
            currentNode.InnerXml += "<" + 
                        xPathSection + "></" + 
                        xPathSection + ">";
        }
        currentNode = xmlDocument.SelectSingleNode(currentXPath);
        currentXPath += "/";
      }
      return currentNode;
    }
  }
}

To use it, just drop the settings.cs file into your project (changing the namespace, if you like). Provide a path to the setting and a default value:

private void Form1_Load(object sender, EventArgs e)
{
  this.Top = settings.GetSetting("Form1/Top", this.Top);
  this.Left = settings.GetSetting("Form1/Left", this.Left);
}
private void Form1_FormClosing(object sender, 
                   FormClosingEventArgs e)
{
  settings.PutSetting("Form1/Top", this.Top);
  settings.PutSetting("Form1/Left", this.Left);
}

This generates an XML file like this:

<settings>
  <Form1>
    <Top>187</Top>
    <Left>256</Left>
  </Form1>
</settings>

Why pass a path rather than a pair of strings? Well, you can go down as many nodes as you like (within reason) and are not limited to just a group and a key, so you can do handy things like this:

<settings>
  <Items>
    <Forms>
      <Form1>
        <Top>187</Top>
        <Left>256</Left>
        <Buttons>
          <Button1>
            <Top>10</Top>
            <Left>40</Left>
          </Button1>
          <Button2>
            <Top>10</Top>
            <Left>80</Left>
          </Button2>
        </Buttons>
      </Form1>
      <Form2>
        <Top>187</Top>
        <Left>256</Left>
      </Form2>
    </Forms>
  </Items>
</settings>

Overloads

The Type of the default value determines which overload is called and consequently the Type of the returned value. The class handles strings natively, and there is an overload for integers:

public int GetSetting(string xPath, int defaultValue)
{
    return Convert.ToInt16(GetSetting(xPath, 
           Convert.ToString(defaultValue)));
}
public void PutSetting(string xPath, int value)
{
    PutSetting(xPath, Convert.ToString(value));
}

You can add other overloads if you need them, following the same structure. (Don't forget to specify the culture for converting dates etc., or you can get inconsistent results if the user changes the ambient culture between reading and writing.)

Let the flaming begin Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

circumpunct

Saudi Arabia Saudi Arabia
Circumpunct is a Brianist

Comments and Discussions

 
GeneralImplementation with Typeconverter and Application Relative filenames Pinmemberphelmers12-Jul-07 14:03 
I liked this article, and extended it with a generic Get\PutSetting as inspired by the TypeConverters topic below. In addition, I added a special Get\PutFilenameSetting which supports files relative top the application directory. The code is shown below. Hope this helps!
 
<code>
            public T GetSetting<T>(string xPath, T defaultvalue )
            {
                  string sValue = GetSetting(xPath, defaultvalue.ToString() );
                  T result= defaultvalue;
                  if( ConvertFromStringToT<T>(sValue, ref result ))
                        return result;
                  return defaultvalue;
            }
 
            public void PutSetting<T>(string xPath, T value)
            {
                  string s="";
                  if (ConvertFromTToString<T>(out s, value))
                        PutSetting(xPath, s);
            }
 
     // From: http://www.sellsbrothers.com/news/showTopic.aspx?ixTopic=1898
     // Generics Q: Best way to convert a string to a T?
     // Comment by Chris Sells, Saturday, November 05, 2005 8:26 AM
            static private bool ConvertFromStringToT<T>(string s, ref T value)
            {
                  TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
                  if (!converter.CanConvertFrom(typeof(string)))
                  {
                        return false;
                  }
                  value = (T)converter.ConvertFrom(s);
                  return true;
            }
 
            // Adapted from ConvertFromStringToT above
            static private bool ConvertFromTToString<T>(out string s, T value)
            {
                  TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
                  if (!converter.CanConvertTo(typeof(string)))
                  {
                        s = "";
                        return false;
                  }
                  s = (string)converter.ConvertToString(value);
                  return true;
            }
 

            public string GetFilenameSetting(string xPath, string defaultValue)
            {
                  // handles application relative paths; returns absolute paths
                  string sNode = GetSetting(xPath, defaultValue);
                  sNode = AbsolutePath(sNode, Application.StartupPath);
                  return sNode;
            }
 
            public void PutFilenameSetting(string xPath, string value)
            {
                  string sPath = RelativePath(Application.StartupPath, value);
          // tries to make value into a path relative to our application; unchanged if can't
                  PutSetting(xPath, sPath);
            }
 
            /// <summary>
            /// Creates an absolute path from a possible relative path and and anchor its relative to
            /// </summary>
            /// <param name="sPath">An absolute or relative path; an absolute path is returned unchanged</param>
            /// <param name="sAnchor">The path that the sPath parameter is possibly relative to</param>
            /// <returns>The absolute path</returns>
            static private string AbsolutePath(string sPath, string sAnchor)
            {
                  // handles paths relative to sAnchor
                  string sCurDir = Directory.GetCurrentDirectory();
                  if (sCurDir.ToLower() == sAnchor.ToLower())
                        sCurDir = "";
                  else
                        Directory.SetCurrentDirectory(sAnchor);
 
                  sPath = Path.GetFullPath(sPath);
                  if (sCurDir != string.Empty)
                        Directory.SetCurrentDirectory(sCurDir);
                  return sPath;
            }
 
            /// <summary>
            /// convert an absolute path to a relative path based on a specified base path.
            ///         c:\a\b\c -> c:\a\b\c\d\file.txt = d\file.txt
            ///         c:\a\b\c -> c:\a\file.txt = ..\..\file.txt
            ///         c:\a\b\c -> c:\a\x\file.txt = ..\..\x\file.txt
            /// </summary>
            /// <remarks> From Peter Morris blog at
            /// http://mrpmorris.blogspot.com/2007/05/convert-absolute-path-to-relative-path.html</remarks>
            /// <param name="absolutePath">An absolute path that we will try to generate a filename relative to</param>
            /// <param name="relativeTo">Path to modify into a relative path, if possible</param>
            /// <returns>Relative path if they have common roots, else the orig value of relativeTo</returns>
            static private string RelativePath(string absolutePath, string relativeTo)
            {
                  string[] absoluteDirectories = absolutePath.Split('\\');
                  string[] relativeDirectories = relativeTo.Split('\\');
 
                  //Get the shortest of the two paths
                  int length = absoluteDirectories.Length < relativeDirectories.Length ? absoluteDirectories.Length : relativeDirectories.Length;
 
                  //Use to determine where in the loop we exited
                  int lastCommonRoot = -1;
                  int index;
 
                  //Find common root
                  for (index = 0; index < length; index++)
                        if (absoluteDirectories[index] == relativeDirectories[index])
                              lastCommonRoot = index;
                        else
                              break;
 
                  //If we didn't find a common prefix then throw
                  if (lastCommonRoot == -1)
                  {
                        return relativeTo;            // no common base
                        //throw new ArgumentException("Paths do not have a common base");
                  }
                  //Build up the relative path
                  StringBuilder relativePath = new StringBuilder();
 
                  //Add on the ..
                  for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
                        if (absoluteDirectories[index].Length > 0)
                              relativePath.Append("..\\");
 
                  //Add on the folders
                  for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
                        relativePath.Append(relativeDirectories[index] + "\\");
                  relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);
 
                  return relativePath.ToString();
            }
</code>
 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 9 Sep 2006
Article Copyright 2006 by circumpunct
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid