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

User options in XML

, 2 Nov 2004
Rate this:
Please Sign up or sign in to vote.
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

Share

About the Author

Richard Schneider
Web Developer
New Zealand New Zealand
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.

Comments and Discussions

 
GeneralWell Done PinmemberThisIsMyUserName216-Apr-06 12:18 
Generallock for preventing race conditions PinmemberRoyPardee15-Nov-04 8:05 
GeneralRe: lock for preventing race conditions PinmemberRichard Schneider16-Nov-04 12:27 
GeneralRe: lock for preventing race conditions PinmemberRoyPardee17-Nov-04 6:14 

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
Web02 | 2.8.141223.1 | Last Updated 3 Nov 2004
Article Copyright 2004 by Richard Schneider
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid