Click here to Skip to main content
15,884,298 members
Articles / Programming Languages / C#

Class Library to Automatically Maintain Persistent Variables, Properties and Data Between Sessions

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
11 Jan 2008CPOL15 min read 44K   591   43  
A class library that maintains persistent variables, properties and data between sessions in XML files. It supports default values for new variables and events when the values in the XML file are changed
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Xml;

// Part of RJConfig V1.3

// Classes for the config variable
namespace RJConfig
{
	/// <summary>
	/// Delegate signature for the ConfigFileVariableChanged event. This event is raised when FileWatchEnabled is
	/// set to true and some external source has changed the value for the Variable where the event is subscribed.
	/// </summary>
	/// <param name="sender">The CfgVarNode base class for the Variable where the event is subscribed.</param>
	/// <param name="e">CfgEventArgs</param>
	public delegate void ConfigFileVariableChanged (object sender, CfgEventArgs e);
	/// <summary>
	/// Delegate signature for the VariableValueChanged event. This event is fired when there are several
	/// Variable instances that shares the same value instance. It is raised for all the Variable instances
	/// except the one that made the change.
	/// </summary>
	/// <param name="sender">The CfgVar base class instance for the Variable which has had its value changed.</param>
	/// <param name="e">CfgEventArgs</param>
	public delegate void VariableValueChanged (object sender, CfgEventArgs e);	/// <remarks>
	/// This class is the base class for a Variable. Besides holding information about the variable it is 
	/// also the common base type used to reference the variable with a comman class type, for function 
	/// arguments, type in linked list and dictionaries, for example.
	/// This class does not hold the actual variable value which means that it does not have to be a generic class.
	/// It is the class that is derived from this that holds the actual variable value.
	/// </remarks>
	public abstract class CfgVarNode
	{
		/// <summary>
		/// Public FileVariableChanged event, raised when the FileVariable for this config
		/// variable is changed.
		/// </summary>
		public event ConfigFileVariableChanged OnConfigFileVariableChanged;
		/// <summary>
		/// The Config object where the Variable exists.
		/// </summary>
		public Config cfg;
		/// <summary>
		/// The Section name for this Variable.
		/// </summary>
		protected string mSectionName;
		/// <summary>
		/// The Item name for this Variable.
		/// </summary>
		protected string mItemName;
		/// <summary>
		/// The name of this Variable.
		/// </summary>
		protected string mVariableName;
		/// <summary>
		/// Public read only property for the Section name for this variable.
		/// </summary>
		public string SectionName
		{
			get
			{
				return mSectionName;
			}
		}
		/// <summary>
		/// Public read only property for the Item name for this Variable.
		/// </summary>
		public string ItemName
		{
			get
			{
				return mItemName;
			}
		}
		/// <summary>
		/// Public read only property for the name of this Variable.
		/// </summary>
		public string VariableName
		{
			get
			{
				return mVariableName;
			}
		}
		/// <summary>
		/// Public read only property for the Section instance for this Variable.
		/// </summary>
		public Section Section
		{
			get
			{
				return cfg.Sections[mSectionName];
			}
		}
		/// <summary>
		/// Public read only property for the Item instance for this Variable.
		/// </summary>
		public Item Item
		{
			get
			{
				return Section[mItemName];
			}
		}
		/// <summary>
		/// Protected parameterless constructor.
		/// </summary>
		protected CfgVarNode ()
		{
		}
		/// <summary>
		/// Public constructor used to create a CfgVarNode object.
		/// </summary>
		/// <param name="c">Config object where the Variable exists</param>
		/// <param name="SectionName">The name of the Section where the Variable exists</param>
		/// <param name="ItemName">The name of the Item where the Variable exists</param>
		/// <param name="VariableName">The name of the Variable</param>
		public CfgVarNode (Config c, string SectionName, string ItemName, string VariableName)
		{
			cfg = c;
			mSectionName = SectionName;
			mItemName = ItemName;
			mVariableName = VariableName;
		}
		/// <summary>
		/// Abstract function which must be implemented in the derived Variable class. This function converts the
		/// value of a variable in string format to its actual type.
		/// </summary>
		/// <param name="str">The string representation of the Variables valus as it is in the config file</param>
		public abstract void ParseString (string str);
		/// <summary>
		/// Abstract function which must be implemented in the derived Variable class. This function converts the
		/// value for a Variable to a string.
		/// </summary>
		/// <returns>The string representation of the value for this Variable as it is stored in the config file</returns>
		public abstract string MakeString ();
		/// <summary>
		/// Abstract function which must be implemented in the derived Variable class. This function checks if
		/// the value for the variable in the RJFileConfig for this variable is equal to the value for this
		/// config variable. Used after a file change event for the associated XML file.
		/// </summary>
		/// <returns></returns>
		public abstract bool IsFileValueEqual ();
		/// <summary>
		/// This function is called for all Variables for a Config object when its underlying file has been
		/// changed and FileWatchEnabled is set to true.
		/// If the Variable and the File Variable does not match, the ConfigFileVariableChanged event is raised.
		/// Note that the value for this Variable isn't changed to the value of the Variable in the file. This
		/// has to be done with the Config.FromFileConfig function. This way the subscriber can check both the
		/// current and changed values.
		/// </summary>
		/// <returns>true if the Variable has a different value than its corresponding FileConfig Variable.</returns>
		public bool CheckFileVariableChange ()
		{
			if (!IsFileValueEqual()) {
				if (OnConfigFileVariableChanged != null) {
					OnConfigFileVariableChanged(this, new CfgEventArgs());
				}
				return true;
			}
			return false;
		}
		/// <summary>
		/// Abstract function to restore the value for this Variable to the default it was originally 
		/// created with. This is used if an external source has removed the File Variable and the Variable
		/// should be set to reflect this.
		/// </summary>
		public abstract void RestoreToDefault ();
		/// <summary>
		/// Save the variable value to the FileConfig variable. Note that nothing is saved to
		/// file until Config.Flush() is called.
		/// </summary>
		public void FromFileConfig ()
		{
			cfg.FromFileConfig(this);
		}
		/// <summary>
		/// Load the FileConfig variable value to Config variable.
		/// </summary>
		public void ToFileConfig ()
		{
			cfg.ToFileConfig(this);
		}
	}
	/// <remarks>
	/// The generic base class for a Variable which is used to access the actual value of the Variable.
	/// The value class CfgTypeT must be derived from generic class CfgType
	/// See RJConfigTypes.cs for examples on how to use this for different types of variables.
	/// </remarks>
	/// <typeparam name="CfgValueTypeT">The value class derived from the generic class CfgValueType that holds the Variable value of type T.</typeparam>
	/// <typeparam name="T">The actual type of the value for this Variable.</typeparam>
	public class CfgVar<CfgValueTypeT, T> : CfgVarNode where CfgValueTypeT : CfgValueType<T>, new()
	{
		/// <summary>
		/// This is the instance of a value class derived from the generic class CfgValueType
		/// that holds the value of the Variable.
		/// </summary>
		protected CfgValueTypeT cfgValueType;
		/// <summary>
		/// Property to access the Variable value through the generic CfgValueTypeT class.
		/// </summary>
		public virtual T CfgData
		{
			get
			{
				return cfgValueType.Data;
			}
			set
			{
				Changer = true;
				// Set this instance as the changer for the value
				// Needed in order to only raise the value changed event to other
				// variable instances that shares this value.
				try {
					cfgValueType.Data = value;
				} finally {
					Changer = false;
				}
			}
		}
		/// <summary>
		/// Internal member for the default value for this variable.
		/// </summary>
		protected T _defValue;
		/// <summary>
		/// Public read only property for the default value for this variable.
		/// </summary>
		public T DefValue
		{
			get
			{
				return _defValue;
			}
		}
		/// <summary>
		/// Constructor used to connect the Variable to the file. Def is the default value
		/// the variable will get if it doesn't exist in the file.
		/// </summary>
		/// <param name="c">Config objet for this variable.</param>
		/// <param name="SectionName">Name of the Section for this Variable.</param>
		/// <param name="ItemName">Name of the Item for this Variable.</param>
		/// <param name="VariableName">The name of this Variable.</param>
		/// <param name="Def">Default value for the Variable if it doesn't exist in the file.</param>
		public CfgVar (Config c, string SectionName, string ItemName, string VariableName, T Def)
			: base(c, SectionName, ItemName, VariableName)
		{
			_defValue = Def;
			CfgVarNode cvn;
			cvn = c.Sections.FindVariable(SectionName, ItemName, VariableName);
			if (cvn != null) {
				// If this variable already exists, the same instance of the CfgTypeT class is used.
				// which means that the instance of the CfgVar actually uses the same FileConfig variable.
				// Two different objects can then have two separate CfgVar objects (for the same variable)
				// that uses the same FileConfig value. No need to share the CfgVar between several objects,
				// just create two or more instances of CfgVar's for the same variable (same type, sectionname,
				// itemname and variablename).
				//
				// This could also be used to share variables between objects with no other connection
				// than the config class.
				CfgVar<CfgValueTypeT, T> other = cvn as CfgVar<CfgValueTypeT, T>;
				cfgValueType = other.cfgValueType;
			} else {
				cfgValueType = new CfgValueTypeT();
				CfgData = Def;
				c.AddVariable(this);
			}
		}
		/// <summary>
		/// Implementation of the abstract function ParseString in the CfgVarNode base class.
		/// </summary>
		/// <param name="str">The value for this Variable as a string. As it is represented in the config file.</param>
		public override void ParseString (string str)
		{
			cfgValueType.ParseString(str);
		}
		/// <summary>
		/// Implementation of the abstract function MakeString() in the CfgVarNode base class.
		/// </summary>
		/// <returns>The value for this Variable as a string. As it is represented in the config file.</returns>
		public override string MakeString ()
		{
			return cfgValueType.MakeString();
		}
		/// <summary>
		/// Private member to indicate that it is this instance of the variable that is changing the value
		/// of the variable in order to be able to only raise the value changed event to all other variable
		/// instances that shares this value.
		/// </summary>
		private bool Changer = false;
		/// <summary>
		/// Internal VariableValueChanged event. Used to keep track of the public OnValueChanged
		/// event so that this class can add the ValueChanged event handler to its value class when event handlers 
		/// for the VariableValueChanged event. 
		/// <seealso cref="OnValueChanged"/>
		/// </summary>
		private event VariableValueChanged _e;
		/// <summary>
		/// Event that is raised when the value of this variable has been changed by another variable instance
		/// that shares the same value object. The event is only raised for the instances that hasn't changed
		/// the value. 
		/// </summary>
		public event VariableValueChanged OnValueChanged
		{
			add
			{
				_e += value;
				cfgValueType.OnValueChanged += cfgValueChanged;
			}
			remove
			{
				_e -= value;
				cfgValueType.OnValueChanged -= cfgValueChanged;
			}
		}
		/// <summary>
		/// Event handler for the ValueChanged event for the value of this variable.
		/// This raises the VariableValueChanged event for all instances for this 
		/// varaible value except the one that changed the value.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void cfgValueChanged (object sender, CfgEventArgs e)
		{
			if (_e != null && !Changer) {
				_e(this, new CfgEventArgs());
			}
		}
		/// <summary>
		/// Implementation of the abstract base class function that compares if the value in the RJFileConfig object
		/// for this variable is equal to the value of this config variable.
		/// This is used to check if the file variable has changed due to a file change event in order
		/// to be able to take action on a changed variable from an external source.
		/// </summary>
		/// <returns>true if the filevalue in the RJFileConfig object for this variable is equal to
		/// the value of this variable.</returns>
		public override bool IsFileValueEqual ()
		{
			FileVariable fv = cfg.fc.FindVariable(SectionName, ItemName, VariableName);
			if (fv != null) {	// FileVariable exists.
				return (fv.Data.CompareTo(MakeString()) == 0);
			} else {			// FileVariable does not exist (or is removed). Compare with the default value.
				return CfgData.Equals(_defValue);
			}
		}
		/// <summary>
		/// Implementation of the abstract base class function to restore the value to the default value.
		/// </summary>
		public override void RestoreToDefault ()
		{
			CfgData = _defValue;
		}

	}
	/// <remarks>
	/// VariableDict is a class that extends a Dictionary for Variables. The key is the name of the Variable
	/// and the value (in the dictionary) is the CfgVarNode base class for the Variable object.
	/// </remarks>
	public class VariableDict : Dictionary<string, CfgVarNode>
	{
		/// <summary>
		/// Adds a variable, referenced through its CfgVarNode base class, to the dictionary.
		/// </summary>
		/// <param name="cfgn">the Variable object which is derived from a CfgVar generic class which in turn is 
		/// derived from CfgVarNode which is used as the least common denominator in the dictionary</param>
		public void AddCfgVarNode (CfgVarNode cfgn)
		{
			if (!ContainsKey(cfgn.VariableName)) {
				Add(cfgn.VariableName, cfgn);
			}
		}
		/// <summary>
		/// Find a Variable in the dictionary from its name.
		/// </summary>
		/// <param name="VariableName">The name of the Variable</param>
		/// <returns>The CfgVarNode base class for the Variable if it exists, otherwize null.</returns>
		public CfgVarNode FindVariable (string VariableName)
		{
			CfgVarNode v;
			if (TryGetValue(VariableName, out v))
				return v;
			return null;
		}
		/// <summary>
		/// Save all Variables in the dictionary to the config file.
		/// </summary>
		public void SaveToFileConf ()
		{
			foreach (KeyValuePair<string, CfgVarNode> kvp in this) {
				kvp.Value.cfg.ToFileConfig(kvp.Value);
			}
		}
	}

}

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Svep DesignCenter
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions