Click here to Skip to main content
15,894,410 members
Articles / Programming Languages / C#

Saving and Restoring Application Settings

Rate me:
Please Sign up or sign in to vote.
4.78/5 (38 votes)
15 Mar 20034 min read 128.4K   3.4K   86  
The Savior class makes it simple to save and restore application settings using the registry or a binary file.
using System;
// =====================================================================
//
// Savior - Simplify Saving and Restoring Application Settings
//
// by Jim Hollenhorst, jim@ultrapico.com
// Copyright Ultrapico, March 2003
// http://www.ultrapico.com
//
// =====================================================================
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Drawing;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.IO;

namespace SaviorClass
{
	/// This namespace is used for saving data to the registry. A very simple class
	/// is customized for each application by storing all the variables
	/// that are to be saved as public fields, with default values specified.
	/// The work is done by the "Savior" class. It has static methods to
	/// Save and Read the data from the registry or a file.
	/// 
	/// The following data types are supported:
	/// 
	/// string, bool, decimal, int, float, double, Color, Point, Size, Font, 
	/// DateTime, TimeSpan, int[], byte[], string[], bool[], float[], double[],
	/// as well as any enum class whose definition is visible to the class.
	///
 
	/// The method calls are all static and given as follows:
	///
  /// void Save(Settings)                  --> Saves the settings to the default registry key
  /// void Save(Settings,string)           --> Saves the settings to a specified key name in HKCU
  /// void Save(Settings,RegistryKey)      --> Saves the settings to a specified registry key
  /// void SaveToFile(Settings,string)     --> Saves the settings to a specified file
  ///
	/// void Read(Settings)                  --> Reads the settings from the default registry key
	/// void Read(Settings,string)           --> Reads the settings from a specified key name in HKCU
	/// void Read(Settings,RegistryKey)      --> Reads the settings from a specified registry key
	/// object ReadFromFile(string)          --> Reads the settings from a specified file
	/// 
	/// string ToString()                    --> Returns information about the settings
	/// 
	/// Note that the ReadFromFile method works differently from the Read methods. It returns the
	/// settings as on object, which must then be cast to the proper type.
	/// 
	/// A proper call of this method would look like this:
	/// 
	/// settings = (Settings)Savior.ReadFromFile(FileName);
	/// 
	
	/// <summary>
	/// Savior is the class that is used to save and restore data using the registry
	/// or by binary serialization in a file. It works in conjuction with another class
	/// (with the recommended name "Settings") that contains all the data to be
	/// stored. 
	/// </summary>
	public class Savior
	{		

		/// <summary>
		/// Returns a list of all the fields and field types for the specified class
		/// Useful for debugging.
		/// </summary>
		public static string ToString(object settings)
		{
			string msg="";
			foreach(FieldInfo fi in settings.GetType().GetFields(
				BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
			{ 
				string TheValue="";
				if(fi.GetValue(settings)!=null)TheValue=fi.GetValue(settings).ToString();
				msg+="Name: "+fi.Name+" = "+TheValue+
					"\n    FieldType: "+fi.FieldType+"\n\n";
			}
			return msg;
		}		
				
		/// <summary>
		/// Save all the information in a class to the registry. This method iterates through
		/// all members of the specified class and saves them to the registry.
		/// </summary>
		/// <param name="settings">An object that holds all the desired settings</param>
		/// <param name="Key">The registry key in which to save data</param>
		public static void Save(object settings, RegistryKey Key)
		{
			RegistryKey subkey;

			// Iterate through each Field of the specified class using "Reflection".
			// The name of each Field is used to generate the name of the registry
			// value, sometimes with appended characters to specify elements of more
			// complex Field types such as a Font or a Point. Arrays are stored by
			// creating a new subkey with the same name as the Field. The subkey holds
			// values with names that are just the integers 0,1,2,... giving the index
			// of each value.
			//

			foreach(FieldInfo fi in settings.GetType().GetFields(
				BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
			{ 
				// If this field is an Enum it needs to be handled separately. The text
				// name for the enum is written to the registry.
				if(fi.FieldType.IsEnum)
				{
					Key.SetValue(fi.Name,Enum.GetName(fi.FieldType,fi.GetValue(settings)));
				}

				// Otherwise each different field type is handled case by case using
				// a large switch statement
				else 
				{
					// Test the name of the Field type, converted to lower case
					switch (fi.FieldType.Name.ToLower())
					{
						case "string":
							Key.SetValue(fi.Name,(string)fi.GetValue(settings));
							break;

						case "boolean":
							Key.SetValue(fi.Name,(bool)fi.GetValue(settings));
							break;

						case "int32":
							Key.SetValue(fi.Name,(int)fi.GetValue(settings));
							break;

						case "decimal":
							decimal TheDecimal=(decimal)fi.GetValue(settings);
							Key.SetValue(fi.Name,(int)TheDecimal);
							break;

						case "single":
							Key.SetValue(fi.Name,(float)fi.GetValue(settings));
							break;

						case "double":
							Key.SetValue(fi.Name,(double)fi.GetValue(settings));
							break;

						// Store a Point as two separate integers
						case "point": 
							Point point=(Point)fi.GetValue(settings);
							Key.SetValue(fi.Name+".X",point.X);
							Key.SetValue(fi.Name+".Y",point.Y);
							break;

						// Store a Size as two separate integers
						case "size":
							Size size=(Size)fi.GetValue(settings);
							Key.SetValue(fi.Name+".Height",size.Height);
							Key.SetValue(fi.Name+".Width",size.Width);
							break;

						// byte arrays are a native registry type and thus easy to handle
						case "byte[]":
							byte[] bytes = (byte[])fi.GetValue(settings);
							if(bytes==null)break;
							Key.SetValue(fi.Name,bytes);
							break;

						// string arrays are a native registry type and thus easy to handle
						case "string[]":
							string[] strings = (string[])fi.GetValue(settings);
							if(strings==null)break;
							Key.SetValue(fi.Name,strings);
							break;

						// For arrays that aren't native registry types, create a subkey and then
						// create values that are numbered sequentially. First delete the subkey
						// if it already exists then refill it with all the values of the array.
						case "int32[]":
							int[] numbers = (int[])fi.GetValue(settings);
							if(numbers==null)break;
							Key.DeleteSubKey(fi.Name,false);   // first delete all the old values
							subkey=Key.CreateSubKey(fi.Name);
							for(int i=0;i<numbers.Length;i++)
							{
								subkey.SetValue(i.ToString(),numbers[i]);
							}
							break;
						
						case "boolean[]":
							bool[] bools = (bool[])fi.GetValue(settings);
							if(bools==null)break;
							Key.DeleteSubKey(fi.Name,false);   // first delete all the old values
							subkey=Key.CreateSubKey(fi.Name);
							for(int i=0;i<bools.Length;i++)
							{
								subkey.SetValue(i.ToString(),bools[i]);
							}
							break;

						case "single[]":
							float[] floats = (float[])fi.GetValue(settings);
							if(floats==null)break;
							Key.DeleteSubKey(fi.Name,false);   // first delete all the old values
							subkey=Key.CreateSubKey(fi.Name);
							for(int i=0;i<floats.Length;i++)
							{
								subkey.SetValue(i.ToString(),floats[i]);
							}
							break;

						case "double[]":
							double[] doubles = (double[])fi.GetValue(settings);
							if(doubles==null)break;
							Key.DeleteSubKey(fi.Name,false);   // first delete all the old values
							subkey=Key.CreateSubKey(fi.Name);
							for(int i=0;i<doubles.Length;i++)
							{
								subkey.SetValue(i.ToString(),doubles[i]);
							}
							break;

						// Saving colors is easy, unlike reading them, which is trickier. We just use the Color's
						// Name property. If there is no known name, the Argb value will be written in hexadecimal.
						case "color":
							Key.SetValue(fi.Name,((Color)fi.GetValue(settings)).Name);
							break;

						// Save a TimeSpan using it's ToString() method
						case "timespan":
							Key.SetValue(fi.Name,((TimeSpan)fi.GetValue(settings)).ToString());
							break;

						// Save a DateTime using it's ToString() method
						case "datetime":
							Key.SetValue(fi.Name,((DateTime)fi.GetValue(settings)).ToString());
							break;

						// Save a Font by separately storing Name, Size, and Style
						case "font":
							Key.SetValue(fi.Name+".Name",((Font)fi.GetValue(settings)).Name);
							Key.SetValue(fi.Name+".Size",((Font)fi.GetValue(settings)).Size);
							Key.SetValue(fi.Name+".Style",((Font)fi.GetValue(settings)).Style);
							break;

						default:
							MessageBox.Show("This Field type cannot be saved by the Savior class: "+fi.FieldType);
							break;
					}
				}
			}
		}

		// Here are several overloads for the Save routine, specifying the RegistryKey in
		// several different ways

		/// <summary>
		/// Save to the registry using the specified key
		/// </summary>
		/// <param name="key">A string giving the registry key path relative to HKCU</param>
		public static void Save(object settings, string key)
		{
			RegistryKey Key = Registry.CurrentUser.CreateSubKey(key);
			Save(settings,Key);
		}

		/// <summary>
		/// Save to the registry using the default key, the standard user application
		/// data registry key. To use this effectively, be sure to specify the
		/// appropriate information in the AssemblyInfo file. 
		/// </summary>
		public static void Save(object settings)
		{
			RegistryKey Key=Application.UserAppDataRegistry;
			Save(settings,Key);
		}

		
		/// <summary>
		/// Read all the information in a class to the registry. This method iterates through
		/// all members of the specified class and reads them to the registry.
		/// </summary>
		/// <param name="settings">An object that holds all the desired settings</param>
		/// <param name="Key">The registry key in which to save data</param>
		public static void Read(object settings, RegistryKey Key)
		{
			// Iterate through each Field of the specified class using "Reflection".
			// The name of each Field is used to generate the name of the registry
			// value, sometimes with appended characters to specify elements of more
			// complex Field types such as a Font or a Point. Arrays are read from
			// a subkey with the same name as the Field. The subkey holds
			// values with names that are just the integers 0,1,2,... giving the index
			// of each value.
			//
			foreach(FieldInfo fi in settings.GetType().GetFields(
				BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
			{ 
				object obj;
				int X,Y,Height,Width;
				string name;
				float emSize;
				FontStyle style;
				RegistryKey subkey;

				// If this field is an Enum it needs to be handled separately. The text
				// name for the enum is read from the registry.
				if(fi.FieldType.IsEnum)
				{
					if((obj=Key.GetValue(fi.Name))!=null)
						fi.SetValue(settings,Enum.Parse(fi.FieldType,(string)obj));
				}

				// Otherwise each different field type is handled case by case using
				// a large switch statement that tests the lower case name of the Field type
				else
				{
					switch (fi.FieldType.Name.ToLower())
					{
						case "string":
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,(string)obj);
							break;

						case "boolean":
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,bool.Parse((string)obj));
							break;

						case "int32":
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,(int)obj);
							break;

						case "decimal":
							if((obj=Key.GetValue(fi.Name))!=null)
							{
								int TheInt=(int)obj;
								fi.SetValue(settings,(decimal)TheInt);
							}
							break;

						case "single":
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,float.Parse((string)obj));
							break;

						case "double":
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,double.Parse((string)obj));
							break;

						case "point":
							if((obj=Key.GetValue(fi.Name+".X"))!=null)
							{
								X=(int)obj;
								if((obj=Key.GetValue(fi.Name+".Y"))!=null)
								{
									Y=(int)obj;
									fi.SetValue(settings,new Point(X,Y));
									//MessageBox.Show("X:"+point.X+" Y:"+point.Y);
								}
							}
							break;

						case "size":
							if((obj=Key.GetValue(fi.Name+".Height"))!=null)
							{
								Height=(int)obj;
								if((obj=Key.GetValue(fi.Name+".Width"))!=null)
								{
									Width=(int)obj;
									fi.SetValue(settings,new Size(Width,Height));
								}
							}
							break;

						// string arrays are a native registry type and thus easy to handle
						case "string[]":  // Get an array of strings
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,(string[])obj);
							break;

						// byte arrays are a native registry type and thus, easy to handle
						case "byte[]":  // Get an array of bytes
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,(byte[])obj);
							break;

						case "int32[]":  // Get an array of ints
							if((subkey=Key.OpenSubKey(fi.Name))!=null)
							{
								int i=0;
								int N=subkey.ValueCount;
								int[] integers = new int[N];
								while((obj=subkey.GetValue(i.ToString()))!=null)
									integers[i++]=(int)obj;
								fi.SetValue(settings,integers);
							}
							break;

						case "single[]":  // Get an array of floats
							if((subkey=Key.OpenSubKey(fi.Name))!=null)
							{
								int i=0;
								int N=subkey.ValueCount;
								float[] floats = new float[N];
								while((obj=subkey.GetValue(i.ToString()))!=null)
								{
									floats[i++]=float.Parse((string)obj);
								}
								fi.SetValue(settings,floats);
							}
							break;

						case "double[]":  // Get an array of doubles
							if((subkey=Key.OpenSubKey(fi.Name))!=null)
							{
								int i=0;
								int N=subkey.ValueCount;
								double[] doubles = new double[N];
								while((obj=subkey.GetValue(i.ToString()))!=null)
								{
									doubles[i++]=double.Parse((string)obj);
								}
								fi.SetValue(settings,doubles);
							}
							break;

						case "boolean[]":  // Get an array of booleans
							if((subkey=Key.OpenSubKey(fi.Name))!=null)
							{
								int i=0;
								int N=subkey.ValueCount;
								bool[] bools = new bool[N];
								while((obj=subkey.GetValue(i.ToString()))!=null)
								{
									bools[i]=bool.Parse((string)obj);
									i++;
								}
								fi.SetValue(settings,bools);
							}
							break;

						// Colors are tricky. If it is a known named color it is easy, we just
						// use Color.FromName. If it is not a named color, than we have to decode
						// the Argb values from the hexadecimal number.
						// So we check to see if the string is a hexadecimal number and, if so
						// decode it as an Argb value, otherwise we reconstruct from the name
						// If the name is invalid, we should just get a default color value
						case "color":  // Get a Color
							if((obj=Key.GetValue(fi.Name))!=null)
							{								
								string TheColorName = (string)obj;
								Color TheColor;
								if(IsHexadecimal(TheColorName))
									TheColor = Color.FromArgb(Int32.Parse(TheColorName,NumberStyles.HexNumber));
								else
									TheColor = Color.FromName(TheColorName);
								fi.SetValue(settings,TheColor);
							}
							break;

						case "timespan":  // Get a TimeSpan
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,TimeSpan.Parse((string)obj));
							break;

						case "datetime":  // Get a DateTime
							if((obj=Key.GetValue(fi.Name))!=null)
								fi.SetValue(settings,DateTime.Parse((string)obj));
							break;

						case "font":
							if((obj=Key.GetValue(fi.Name+".Name"))!=null)
							{
								name=(string)obj;
								if((obj=Key.GetValue(fi.Name+".Size"))!=null)
								{
									emSize=float.Parse((string)obj);
									if((obj=Key.GetValue(fi.Name+".Style"))!=null)
									{
										style=(FontStyle)Enum.Parse(typeof(FontStyle),(string)obj);
										fi.SetValue(settings,new Font(name,emSize,style));
									}
								}
							}
							break;

						default:
							MessageBox.Show("This type has not been implemented: "+fi.FieldType);
							break;
					}
				}
			}
		}

		// Here are several overloads for the Read routine, specifying the RegistryKey in
		// several different ways

		/// <summary>
		/// Read from the registry using the specified key
		/// </summary>
		/// <param name="key">A string giving the registry key path relative to HKCU</param>
		public static void Read(object settings, string key)
		{
			RegistryKey Key = Registry.CurrentUser.CreateSubKey(key);
			Read(settings,Key);
		}

		/// <summary>
		/// Read from the registry using the default key, the standard user application
		/// data registry key. To use this effectively, be sure to specify the
		/// appropriate information in the AssemblyInfo file. 
		/// </summary>
		public static void Read(object settings)
		{
			RegistryKey Key=Application.UserAppDataRegistry;
			Read(settings,Key);
		}

		
		/// <summary>
		/// Save settings to a file using binary serialization
		/// </summary>
		/// <param name="settings">This is the object that we want to serialize</param>
		/// <param name="FileName">The name of the file in which to store settings</param>
		public static bool SaveToFile(object settings, string FileName)
		{
			try
			{
				IFormatter formatter = new BinaryFormatter();
				Stream stream = new FileStream(FileName, FileMode.Create,
					FileAccess.Write, FileShare.None);
				formatter.Serialize(stream, settings);
				stream.Close();
				return true;
			}
			catch
			{
				MessageBox.Show("Error attempting to save the settings to a file\n\n"+FileName,
					"Expresso Error",
					MessageBoxButtons.OK,MessageBoxIcon.Error);
				return false;
			}
		}

		/// <summary>
		/// Read from a file using binary serialization. Normally a call to this method would
		/// cast the return value to the correct type as in this example:
		/// 
		/// settings = (Settings)Savior.ReadFromFile(FileName);
		/// 
		/// </summary>
		/// <param name="FileName">The name of the file from which to read settings</param>
		/// <returns>An object is returned containing the settings</returns>
		public static object ReadFromFile(string FileName)
		{
			try
			{
				// First try to read the version information				
				IFormatter formatter = new BinaryFormatter();
				Stream stream = new FileStream(FileName, FileMode.Open,
					FileAccess.Read, FileShare.Read);
				object NewSettings = (object)formatter.Deserialize(stream);
				stream.Close();
				return NewSettings;
			}
			catch
			{
				MessageBox.Show("Error attempting to read the settings from a file\n\n"+FileName,
					"Expresso Error",
					MessageBoxButtons.OK,MessageBoxIcon.Error);
				return null;   // If there is an error return null
			}
		}

		/// <summary>
		/// This returns true if the input is a string of valid hexadecimal digits 0-9, A-F, or a-f
		/// </summary>
		/// <param name="input">The input string</param>
		/// <returns>true if the input is a valid hex number</returns>
		public static bool IsHexadecimal(string input)
		{
			foreach ( char c in input)
			{
				if(!Uri.IsHexDigit(c))return false;
			}
			return true;
		}
	}	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Researcher
United States United States
Ultrapico Website: http://www.ultrapico.com

Download Expresso 3.0, the latest version of the award-winning regular expression development tool.

Comments and Discussions