Click here to Skip to main content
Licence 
First Posted 2 Nov 2004
Views 37,047
Bookmarked 28 times

User options in XML

By | 2 Nov 2004 | Article
Serialization of user options.

Introduction

Most applications need to maintain options specific to the user. In this article, I develop a single class, UserOptions, to maintain and persist user options. The options are stored in an XML file in a sub-directory of the ApplicationData special folder.

The ApplicationData folder serves as a common repository for application-specific data for the current roaming user. A roaming user works on more than one computer on a network. A roaming user's profile is kept on a server on the network and is loaded onto a system when the user logs on.

The Design

While designing the UserOptions class, I kept the following concepts in mind:

  1. Lightweight - This should be simple and should not require much code. The more code we write, the more faults we introduce into an application!
  2. Extensible - It should be very simple to add new options. As the application feature set grows, so will the number of user options.
  3. Polymorphic - User options can be of any type (scalar, array, string, even another object).

The above three concepts scream "OO". So, why not use a class whose properties are the options to maintain? So, we can do something like this to store where the application should start on the desktop:

public class UserOptions
{
  private Point startLocation;

  public Point StartLocation
  {
    get {return startLocation;}
    set {startLocation = value;}
  }
}

Serialization

We can use the XmlSerializer class to save and load the UserOptions.

Loading the options presents a problem in that the XmlSerializer always returns a new object when it deserializes. My solution is to require developers to use the static property Current to access the options, as in UserOptions.Current.StartLocation.

The Current property can then determine if an instance of UserOptions needs to be created. This solution also allows code anywhere in the application to access the UserOptions without the need of the application maintaining a global variable.

public static UserOptions Current
{
   get
   {
      if (current == null)
      {
         lock (typeof(UserOptions))
         {
            if (current == null)
            {
               current = Load();
            }
         }
      }
      return current;
   }
}

Note that I lock the type to prevent any race conditions in a multi-threaded application.

And now for the Load() and Save() methods:

private static UserOptions Load()
{
   if (!File.Exists(OptionsPath))
      return new UserOptions();

   XmlSerializer serializer = new XmlSerializer(typeof(UserOptions));
   using (FileStream stream = new FileStream(OptionsPath, FileMode.Open))
   {
      XmlReader reader = new XmlTextReader(stream);
      return (UserOptions) serializer.Deserialize(reader);
   }
}

public void Save()
{
   XmlSerializer serializer = new XmlSerializer(this.GetType());
   using (StreamWriter writer = new StreamWriter(OptionsPath))
   {
     serializer.Serialize(writer, this);
   }
}

Sample Usage

The following snippet is from a Form that wants to remember its window location on desktop. When it is closed, the current location is saved, and when it is created, the saved location is used.

public class MyForm : Form
{
  public MyForm()
  {
     // Do standard stuff

     // Check our options
     if (!UserOptions.Current.StartLocation.IsEmpty)
     {
       this.StartPosition = FormStartPosition.Manual;
       this.Location = UserOptions.Current.StartLocation;
     }
  }

  protected override void OnClosed(EventArgs e)
  {
    UserOptions.Current.StartLocation = this.Location;
    UserOptions.Current.Save();

    base.OnClosed (e);
  }
}

UserOptions Source

The following is a comment stripped version of the UserOptions class. See the download for all the gory details.

using System;
using System.Drawing;
using System.IO;
using System.Globalization;
using System.Security;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;

namespace BlackHen.Samples
{
   public class UserOptions
   {
      private Point startLocation;

      private static UserOptions current;

      public UserOptions()
      {
         startLocation = Point.Empty;
      }

      public static UserOptions Current
      {
         get
         {
            if (current == null)
            {
               lock (typeof(UserOptions))
               {
                  if (current == null)
                  {
                     current = Load();
                  }
               }
            }
            return current;
         }
      }

      private static UserOptions Load()
      {
         if (!File.Exists(OptionsPath))
            return new UserOptions();

         XmlSerializer serializer = new XmlSerializer(typeof(UserOptions));
         using (FileStream stream = new FileStream(OptionsPath, FileMode.Open))
         {
            XmlReader reader = new XmlTextReader(stream);
            return (UserOptions) serializer.Deserialize(reader);
         }
      }

      public void Save()
      {
         XmlSerializer serializer = new XmlSerializer(this.GetType());
         using (StreamWriter writer = new StreamWriter(OptionsPath))
         {
            serializer.Serialize(writer, this);
         }
      }

      private static string OptionsPath
      {
         get
         {
            // Build the directory.
            StringBuilder path = new StringBuilder();
            path.Append
               (
               Environment.GetFolderPath
                  (
                  Environment.SpecialFolder.ApplicationData
                  )
               );
            path.Append(Path.DirectorySeparatorChar);
            path.Append(Application.CompanyName);
            path.Append(Path.DirectorySeparatorChar);
            path.Append(Application.ProductName);
            lock (typeof(UserOptions))
            {
               string dir = path.ToString();
               if (!Directory.Exists(dir))
                  Directory.CreateDirectory(dir);
            }

            // Add the file name.
            path.Append(Path.DirectorySeparatorChar);
            path.Append(Path.GetFileName(Application.ExecutablePath));
            path.Append(@".options.xml");

            return path.ToString();
         }
      }
   }
}

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

About the Author

Richard Schneider

Web Developer

New Zealand New Zealand

Member

I have been involved with computer engineering (both hardware and software) since 1975. During these almost 30 years, I have mainly been associated with start-up companies, except for a 3-year stint at Digital Equipment Corp. and 2 years at Telecom New Zealand Ltd. My positions have included Analyst, Software Engineer, R&D Manager and Director of Research and Development.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralWell Done PinmemberThisIsMyUserName211:18 16 Apr '06  
Generallock for preventing race conditions PinmemberRoyPardee7:05 15 Nov '04  
GeneralRe: lock for preventing race conditions PinmemberRichard Schneider11:27 16 Nov '04  
GeneralRe: lock for preventing race conditions PinmemberRoyPardee5:14 17 Nov '04  
GeneralThanks PinmemberSteven Campbell8:00 3 Nov '04  
GeneralRe: Thanks Pinmemberzjian19:57 3 Nov '04  
GeneralRe: Thanks PinmemberSteven Campbell2:51 5 Nov '04  
GeneralRe: Thanks Pinmemberzjian20:03 6 Nov '04  

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.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 3 Nov 2004
Article Copyright 2004 by Richard Schneider
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid