Click here to Skip to main content
15,885,365 members
Articles / Programming Languages / XML

Binding any object data members at design time

Rate me:
Please Sign up or sign in to vote.
4.00/5 (5 votes)
8 Nov 20045 min read 40.7K   896   16  
Enables binding of any public data member of any object into a control at design time.
using System;
using System.Data;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Windows.Forms;
using System.Reflection;
using System.Collections;

namespace AG1206
{
	/// <summary>
	/// Simple object XML serializing and support for binding object instance members 
	/// directly to data bound contorls, with automatic reflection of changes made to 
	/// data bound controls to corresponding instance members.
	/// </summary>
	public class AgConfig
	{
		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		public AgConfig() 
		{
			Data = new object();
		}

		/// <summary>
		/// Initializes a new instance, and specifies the Data object and DataSet.
		/// </summary>
		/// <param name="data">The data object to use</param>
		public AgConfig(object data)
		{
			Data = data;
		}

		#region Properties...

		private string _FileName;
		private DataSet _DataSet;
		private string _XmlRootName;
		private string _XmlNamespace;
		private Object _Data;

		/// <summary>
		/// A Type array of additional object types to serialize.
		/// </summary>
		public Type[] XmlExtraTypes;

		/// <summary>
		/// Gets or stets the XML file for serialization.
		/// </summary>
		/// <remarks>
		/// By default, this is a file located in the application startup directory with 
		/// the same name as the Data object and with the extension ".xml".
		/// </remarks>
		public string FileName { set { _FileName = value; } get { return _FileName; } }

		/// <summary>
		/// Gets or stets a DataSet associated with this instance.
		/// </summary>
		/// <remarks>
		/// DataSet can be used for binding Data members directly to data bound controls.
		/// When the contents of a data bound control is changed, the change is 
		/// automatically reflected to the corresponding instance member.
		/// </remarks>
		public DataSet DataSet
		{ 
			set
			{
				_DataSet = value; 

				if(_DataSet != null)
				{
					_DataSet.DataSetName = XmlRootName + "DataSet";
					_DataSet.Namespace	 = XmlNamespace;

					WriteToDataSet();
				}
			}

			get
			{
				if(_DataSet != null)
					return _DataSet;

				DataSet = new DataSet();
				return _DataSet;
			}
		}

		/// <summary>
		/// Gets or sets the name of the root XML elemtnt for serialization.
		/// </summary>
		/// <remarks>
		/// By default, this is the same as the nama of Data object.
		/// </remarks>
		public string XmlRootName { set { _XmlRootName = value; } get { return _XmlRootName; } }

		/// <summary>
		/// Gets or sets the XML namespace for serialization.
		/// </summary>
		/// <remarks>
		/// By default, this is in the form "Application name"."Data object name".
		/// </remarks>
		public string XmlNamespace { set { _XmlNamespace = value; } get { return _XmlNamespace; } }

		/// <summary>
		/// Data object
		/// </summary>
		public Object Data { get { return _Data; } set { _Data = value; Initialize(); } }

		#endregion

		/// <summary>
		/// Serializes Data into XML file.
		/// </summary>
		public void WriteToXmlFile()
		{
			WriteToXmlFile(FileName);
		}

		/// <summary>
		/// Serializes Data into XML file, and specifies file to use.
		/// </summary>
		/// <param name="file">File to use</param>
		public void WriteToXmlFile(string file)
		{
			Stream s = File.Open(file, FileMode.Create);
			using(s) XmlSerializer.Serialize(s, Wrapper);
		}

		/// <summary>
		/// Deserializes Data from XML file.
		/// </summary>
		public void ReadFromXmlFile()
		{
			ReadFromXmlFile(FileName);
		}

		/// <summary>
		/// Deserializes Data from XML file, and specifies file to use.
		/// </summary>
		/// <param name="file">File to use</param>
		public void ReadFromXmlFile(string file)
		{
			Object data;

			if(File.Exists(file))
			{
				Stream s = File.Open(file, FileMode.Open);

				DataWrapper wr;
				using(s) wr = (DataWrapper)XmlSerializer.Deserialize(s);

				data = wr.Root;
			}
			else
			{
				data = Activator.CreateInstance(Data.GetType());
			}

			AssignData(data);
			WriteToDataSet();
		}

		/// <summary>
		/// Deserializes Data from stream, and specifies stream to use.
		/// </summary>
		/// <param name="s">Stream to use</param>
		public void ReadFromXmlStream(Stream s)
		{
			ReadFromXmlStreamInternal(s);
			WriteToDataSet();
		}

		/// <summary>
		/// Serializes Data to stream, and specifies stream to use.
		/// </summary>
		/// <param name="s">Stream to use</param>
		public void WriteToXmlStream(Stream s)
		{
			XmlSerializer.Serialize(s, Wrapper);
		}

		/// <summary>
		/// Serializes Data into DataSet.
		/// </summary>
		public void WriteToDataSet()
		{
			WriteToDataSet(DataSet);
		}

		/// <summary>
		/// Serializes Data into DataSet, and specifies DataSet to use.
		/// </summary>
		/// <param name="ds">DataSet to use</param>
		public void WriteToDataSet(DataSet ds)
		{
			MemoryStream s = new MemoryStream();
			WriteToXmlStream(s);
			s.Seek(0, SeekOrigin.Begin);

			ds.Clear();

			bool containsData = (ds.Tables.Count > 0);

			try
			{
				LockUpdates = true;
				ds.ReadXml(s, containsData ? XmlReadMode.IgnoreSchema : XmlReadMode.InferSchema);
			}
			finally
			{
				LockUpdates = false;
			}

			foreach(DataTable table in ds.Tables)
			{
				table.RowDeleted += new DataRowChangeEventHandler(OnTableDataRowDeleted);
				table.RowChanged += new DataRowChangeEventHandler(OnTableDataRowChanged);
				table.ColumnChanged += new DataColumnChangeEventHandler(OnTableDataColumnChanged);
			}
		}

		/// <summary>
		/// Deserializes Data from DataSet, and specifies DataSet to use.
		/// </summary>
		/// <param name="ds">DataSet to use</param>
		public void ReadFromDataSet(DataSet ds)
		{
			MemoryStream s = new MemoryStream();
			ds.WriteXml(s, XmlWriteMode.IgnoreSchema);
			s.Seek(0, SeekOrigin.Begin);

			ReadFromXmlStreamInternal(s);
		}

		/// <summary>
		/// Writes XML schema into file.
		/// </summary>
		/// <remarks>
		/// The target file is FileName with an extension ".xsd".
		/// </remarks>
		public void WriteSchemaToFile()
		{
			WriteSchemaToFile(FileName);
		}

		/// <summary>
		/// Writes XML schema into file, and specifies file to use.
		/// </summary>
		/// <remarks>
		/// The target file always has an extension ".xsd".
		/// </remarks>
		/// <param name="file">File to use</param>
		public void WriteSchemaToFile(string file)
		{
			DataSet ds = DataSet;

			Stream s = File.Open(Path.ChangeExtension(file, ".xsd"), FileMode.Create);
			using(s) ds.WriteXmlSchema(s);
		}

		#region Private code...

		/// <summary>
		/// Internal wrapper class. Must have public access.
		/// </summary>
		public class DataWrapper
		{
			/// <summary>
			/// Internal wrapper member.
			/// </summary>
			public Object Root;
		}

		private bool LockUpdates = false;
		private XmlSerializer _XmlSerializer = null;

		private XmlSerializer XmlSerializer { get { return _XmlSerializer; } }

		private void OnTableDataRowChanged(object sender, DataRowChangeEventArgs e)
		{
			if(!LockUpdates)
			{
				ReadFromDataSet(DataSet);
			}
		}
		
		private void OnTableDataColumnChanged(object sender, DataColumnChangeEventArgs e)
		{
			if(!LockUpdates)
			{
				ReadFromDataSet(DataSet);
			}
		}
		
		private void OnTableDataRowDeleted(object sender, DataRowChangeEventArgs e)
		{
			DataTable table = (DataTable)sender;

			if(!LockUpdates)
			{
				if(e.Action == DataRowAction.Delete)
					table.AcceptChanges();

				ReadFromDataSet(DataSet);
			}
		}

		private DataWrapper Wrapper = new DataWrapper();

		private void Initialize()
		{
			FileName = Path.Combine(Application.StartupPath, Path.ChangeExtension(Data.GetType().Name, ".xml"));
			XmlRootName = Data.GetType().Name;
			XmlNamespace = "";

			_XmlSerializer = CreateXmlSerializer();
		}

		private XmlSerializer CreateXmlSerializer()
		{
			Wrapper.Root = Data;

			XmlRootAttribute xmlRoot = new XmlRootAttribute(XmlRootName + "DataSet");
			xmlRoot.Namespace = XmlNamespace;

			XmlAttributes attrs = new XmlAttributes();
			XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();

			attrs.XmlElements.Add(new XmlElementAttribute(XmlRootName, Data.GetType()));
			attrOverrides.Add(Wrapper.GetType(), "Root", attrs);

			int extraTypesLength = (XmlExtraTypes == null) ? 0 : XmlExtraTypes.Length;
			Type[] types = new Type[extraTypesLength + 1];
			types[0] = Data.GetType();

			if(extraTypesLength > 0)
				XmlExtraTypes.CopyTo(types, 1);

			return new XmlSerializer(Wrapper.GetType(), attrOverrides, types, xmlRoot, XmlNamespace);
		}
		
		private void AssignData(Object other)
		{
			Type type = Data.GetType();
			BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
			FieldInfo[] fields = type.GetFields(flags);

			foreach(FieldInfo fi in fields)
				fi.SetValue(Data, fi.GetValue(other));
		}

		private void ReadFromXmlStreamInternal(Stream s)
		{
			Object data;
			
			DataWrapper wr = (DataWrapper)XmlSerializer.Deserialize(s);

			data = wr.Root;

			AssignData(data);
		}

		#endregion
	}
}

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
Software Developer (Senior)
Slovakia Slovakia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions