Click here to Skip to main content
15,892,161 members
Articles / Programming Languages / C#

A Dynamically Generated XML Data Editor

Rate me:
Please Sign up or sign in to vote.
4.88/5 (79 votes)
14 Oct 2003CPOL8 min read 466.5K   9.1K   222  
Using an XML Schema Definition (XSD) document, this utility dynamically generates a data entry form to create and edit XML data.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;

/*
 * An XSD-driven dynamic form creator and XML data editor.
 * Using an Xml Schema Definition (XSD) document, this utility dynamically generates a data entry form to create and edit XML data.
 * 
 * Things to do:
 * 
 * Implement a "Close" menu item (closes both XSD and XML)
 * Look at XmlSchemaSimpleType and derived classes like XmlSchemaSimpleTypeList
 * 
 * Bugs:
 * 
 * A simple type that references a simple type with enumerations doesn't show up as a combo box.  Why?
 * A local element ref'ing a global element type'ing a complex element caused an assertion.  Why?
 * An element type'd as a complex type is causing problems
 * 
 * In the Schema Editor:
 *	1.	Can't remove elements of simple type (simple types that are a children to a complex type)
 *	2.	missing feature: DEL key to remove nodes
 *	3.	Renaming a complex type of a complex type caused the child complex type to have a name, instead of the parent element of complex type
 *	4.	Did min inclusive create a max inclusive node instead?
 *	5.	If a simple type has a global simple type restriction base (instead of a built-in one) it doesn't show up in the global list
 * 
 */

namespace xmlDataEditor
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Panel pnlDynForm;
		private System.Windows.Forms.MainMenu mnuMain;
		private System.Windows.Forms.MenuItem menuItem1;
		private System.Windows.Forms.MenuItem mnuOpenXSD;
		private System.Windows.Forms.MenuItem menuItem6;
		private System.Windows.Forms.OpenFileDialog dlgOpen;
		private System.Windows.Forms.MenuItem mnuExit;

		private XmlSchema schema=null;
		private XmlDataDocument doc=null;
		private string xmlFileName=null;
		private Hashtable tableInfo=new Hashtable();

		private System.Windows.Forms.MenuItem mnuAbout;
		private System.Windows.Forms.MenuItem mnuOpenXML;
		private System.Windows.Forms.MenuItem mnuSaveXmlAs;
		private System.Windows.Forms.MenuItem menuItem2;
		private System.Windows.Forms.SaveFileDialog saveXmlAsDlg;
		private System.Windows.Forms.MenuItem mnuSaveXML;
		private System.Windows.Forms.MenuItem mnuInferXSD;
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.Button btnXpathQuery;
		private System.Windows.Forms.TextBox edXPath;
		private System.Windows.Forms.Label label2;
		private System.Windows.Forms.TextBox edNewRowOn;


		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();


			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.pnlDynForm = new System.Windows.Forms.Panel();
			this.mnuMain = new System.Windows.Forms.MainMenu();
			this.menuItem1 = new System.Windows.Forms.MenuItem();
			this.mnuOpenXSD = new System.Windows.Forms.MenuItem();
			this.mnuInferXSD = new System.Windows.Forms.MenuItem();
			this.mnuOpenXML = new System.Windows.Forms.MenuItem();
			this.menuItem2 = new System.Windows.Forms.MenuItem();
			this.mnuSaveXML = new System.Windows.Forms.MenuItem();
			this.mnuSaveXmlAs = new System.Windows.Forms.MenuItem();
			this.menuItem6 = new System.Windows.Forms.MenuItem();
			this.mnuExit = new System.Windows.Forms.MenuItem();
			this.mnuAbout = new System.Windows.Forms.MenuItem();
			this.dlgOpen = new System.Windows.Forms.OpenFileDialog();
			this.saveXmlAsDlg = new System.Windows.Forms.SaveFileDialog();
			this.label1 = new System.Windows.Forms.Label();
			this.edXPath = new System.Windows.Forms.TextBox();
			this.btnXpathQuery = new System.Windows.Forms.Button();
			this.label2 = new System.Windows.Forms.Label();
			this.edNewRowOn = new System.Windows.Forms.TextBox();
			this.SuspendLayout();
			// 
			// pnlDynForm
			// 
			this.pnlDynForm.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
				| System.Windows.Forms.AnchorStyles.Left) 
				| System.Windows.Forms.AnchorStyles.Right)));
			this.pnlDynForm.AutoScroll = true;
			this.pnlDynForm.Location = new System.Drawing.Point(0, 80);
			this.pnlDynForm.Name = "pnlDynForm";
			this.pnlDynForm.Size = new System.Drawing.Size(528, 704);
			this.pnlDynForm.TabIndex = 0;
			// 
			// mnuMain
			// 
			this.mnuMain.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																	   this.menuItem1,
																	   this.mnuAbout});
			// 
			// menuItem1
			// 
			this.menuItem1.Index = 0;
			this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																		this.mnuOpenXSD,
																		this.mnuInferXSD,
																		this.mnuOpenXML,
																		this.menuItem2,
																		this.mnuSaveXML,
																		this.mnuSaveXmlAs,
																		this.menuItem6,
																		this.mnuExit});
			this.menuItem1.Text = "File";
			// 
			// mnuOpenXSD
			// 
			this.mnuOpenXSD.Index = 0;
			this.mnuOpenXSD.Text = "Open XS&D";
			this.mnuOpenXSD.Click += new System.EventHandler(this.mnuOpenXSD_Click);
			// 
			// mnuInferXSD
			// 
			this.mnuInferXSD.Enabled = false;
			this.mnuInferXSD.Index = 1;
			this.mnuInferXSD.Text = "&Infer XSD";
			this.mnuInferXSD.Click += new System.EventHandler(this.mnuInferXSD_Click);
			// 
			// mnuOpenXML
			// 
			this.mnuOpenXML.Index = 2;
			this.mnuOpenXML.Text = "Open XM&L";
			this.mnuOpenXML.Click += new System.EventHandler(this.mnuOpenXML_Click);
			// 
			// menuItem2
			// 
			this.menuItem2.Index = 3;
			this.menuItem2.Text = "-";
			// 
			// mnuSaveXML
			// 
			this.mnuSaveXML.Index = 4;
			this.mnuSaveXML.Text = "&Save XML";
			this.mnuSaveXML.Click += new System.EventHandler(this.mnuSaveXML_Click);
			// 
			// mnuSaveXmlAs
			// 
			this.mnuSaveXmlAs.Index = 5;
			this.mnuSaveXmlAs.Text = "Save XML &As";
			this.mnuSaveXmlAs.Click += new System.EventHandler(this.mnuSaveXmlAs_Click);
			// 
			// menuItem6
			// 
			this.menuItem6.Index = 6;
			this.menuItem6.Text = "-";
			// 
			// mnuExit
			// 
			this.mnuExit.Index = 7;
			this.mnuExit.Text = "E&xit";
			this.mnuExit.Click += new System.EventHandler(this.mnuExit_Click);
			// 
			// mnuAbout
			// 
			this.mnuAbout.Index = 1;
			this.mnuAbout.Text = "&About";
			// 
			// dlgOpen
			// 
			this.dlgOpen.DefaultExt = "xsd";
			// 
			// saveXmlAsDlg
			// 
			this.saveXmlAsDlg.DefaultExt = "xml";
			// 
			// label1
			// 
			this.label1.Location = new System.Drawing.Point(8, 16);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(48, 16);
			this.label1.TabIndex = 1;
			this.label1.Text = "XPath:";
			this.label1.TextAlign = System.Drawing.ContentAlignment.BottomLeft;
			// 
			// edXPath
			// 
			this.edXPath.Location = new System.Drawing.Point(56, 16);
			this.edXPath.Name = "edXPath";
			this.edXPath.Size = new System.Drawing.Size(400, 20);
			this.edXPath.TabIndex = 2;
			this.edXPath.Text = "";
			// 
			// btnXpathQuery
			// 
			this.btnXpathQuery.Location = new System.Drawing.Point(464, 16);
			this.btnXpathQuery.Name = "btnXpathQuery";
			this.btnXpathQuery.Size = new System.Drawing.Size(48, 20);
			this.btnXpathQuery.TabIndex = 3;
			this.btnXpathQuery.Text = "Query";
			this.btnXpathQuery.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
			this.btnXpathQuery.Click += new System.EventHandler(this.btnXpathQuery_Click);
			// 
			// label2
			// 
			this.label2.Location = new System.Drawing.Point(8, 48);
			this.label2.Name = "label2";
			this.label2.Size = new System.Drawing.Size(98, 16);
			this.label2.TabIndex = 4;
			this.label2.Text = "New Row On:";
			this.label2.TextAlign = System.Drawing.ContentAlignment.BottomLeft;
			// 
			// edNewRowOn
			// 
			this.edNewRowOn.Location = new System.Drawing.Point(96, 48);
			this.edNewRowOn.Name = "edNewRowOn";
			this.edNewRowOn.Size = new System.Drawing.Size(144, 20);
			this.edNewRowOn.TabIndex = 5;
			this.edNewRowOn.Text = "";
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(536, 793);
			this.Controls.Add(this.edNewRowOn);
			this.Controls.Add(this.label2);
			this.Controls.Add(this.btnXpathQuery);
			this.Controls.Add(this.edXPath);
			this.Controls.Add(this.label1);
			this.Controls.Add(this.pnlDynForm);
			this.Menu = this.mnuMain;
			this.Name = "Form1";
			this.Text = "XML Data Editor";
			this.ResumeLayout(false);

		}
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}

		public class TypeInfo
		{
			public TypeInfo() {typeName="";}
			public TypeInfo(string typeName)
			{
				this.typeName=typeName;
				length="-1";
				minLength="-1";
				maxLength="-1";
				pattern="";
				maxInclusive=null;
				maxExclusive=null;
				minInclusive=null;
				minExclusive=null;
				fractionDigits="-1";
				totalDigits="-1";
				whiteSpace="";
				enumeration=new ArrayList();
			}

			public string typeName;

			// facet information
			public string length;
			public string minLength;
			public string maxLength;
			public string pattern;
			public string maxInclusive;
			public string maxExclusive;
			public string minInclusive;
			public string minExclusive;
			public string fractionDigits;
			public string totalDigits;
			public string whiteSpace;			// "preserve", "replace", or "collapse"
			public ArrayList enumeration;
		}

		public class TableInfo
		{
			public TableInfo()
			{
				tb=null;
				rows=0;
				pos=0;
			}

			public TextBox tb;
			public int rows;
			public int pos;
			public DataRow[] dataRows;
		}

		#region Menu And Button Events
		#region Open XSD
		private void mnuOpenXSD_Click(object sender, System.EventArgs e)
		{
			dlgOpen.DefaultExt="xsd";
			dlgOpen.CheckFileExists=true;
			dlgOpen.Filter="XSD Files (*.xsd)|*.xsd|All Files (*.*)|*.*";
			DialogResult res=dlgOpen.ShowDialog();
			if (res==DialogResult.OK)
			{
//				try
				{
					// get a stream reader for the XSD file
					StreamReader tr=new StreamReader(dlgOpen.FileName);
					// read the file into the XmlSchema object
					schema=XmlSchema.Read(tr, new ValidationEventHandler(SchemaValidationHandler));
					tr.Close();
					// report any problems with the schema by compiling it
					CompileSchema();
					// create the document
					doc=new XmlDataDocument();
					// open the schema again
					tr=new StreamReader(dlgOpen.FileName);
					// read it into the DataSet member
					doc.DataSet.ReadXmlSchema(tr);
					tr.Close();

				ConstructGUI(doc.DataSet);
				}
//				catch(Exception err)
//				{
//					MessageBox.Show(err.ToString(), "Error processing XSD schema:");
//					schema=null;
//					doc=null;
//				}
			}
		}
		#endregion

		#region Infer XSD
		private void mnuInferXSD_Click(object sender, System.EventArgs e)
		{
			dlgOpen.DefaultExt="xml";
			dlgOpen.CheckFileExists=true;
			dlgOpen.Filter="XML Files (*.xml)|*.xml|All Files (*.*)|*.*";
			DialogResult res=dlgOpen.ShowDialog();
			if (res==DialogResult.OK)
			{
				try
				{
					// read into XmlSchema because we want the information in that format
					doc=new XmlDataDocument();
					StreamReader tr=new StreamReader(dlgOpen.FileName);
					doc.DataSet.InferXmlSchema(tr, null);
					tr.Close();
					string strSchema=doc.DataSet.GetXmlSchema();
					
					StreamWriter sw=new StreamWriter("temp.txt", false, System.Text.Encoding.UTF8);
					sw.Write(strSchema);
					sw.Flush();
					sw.Close();

					StringReader sr=new StringReader(strSchema);
					schema=XmlSchema.Read(sr, new ValidationEventHandler(SchemaValidationHandler));
					sr.Close();
					CompileSchema();
					ConstructGUI(doc.DataSet);
				}
				catch(Exception err)
				{
					MessageBox.Show(err.ToString(), "Error inferring schema:");
					schema=null;
					doc=null;
				}
			}
		}
		#endregion

		#region Open XML
		private void mnuOpenXML_Click(object sender, System.EventArgs e)
		{
			if (schema==null)
			{
				MessageBox.Show("Please load an XSD first.", "Cannot load XML");
				return;
			}
			dlgOpen.DefaultExt="xml";
			dlgOpen.CheckFileExists=true;
			dlgOpen.Filter="XML Files (*.xml)|*.xml|All Files (*.*)|*.*";
			DialogResult res=dlgOpen.ShowDialog();
			if (res==DialogResult.OK)
			{
				xmlFileName=dlgOpen.FileName;
//				try
				{
					XmlTextReader tr=new XmlTextReader(dlgOpen.FileName);
					doc.Load(tr);
					tr.Close();

					// setup to point to the first record in the root table
					foreach (DataTable dt in doc.DataSet.Tables)
					{
						// but we're only interested in the toplevel tables
						if (dt.ParentRelations.Count==0)
						{
							TableInfo ti=tableInfo[dt] as TableInfo;
							ti.pos=1;
							ti.rows=dt.Rows.Count;
							FirstRecord(dt, ti);
							UpdateRecordCountInfo(dt);
						}
					}
				}
//				catch(Exception err)
//				{
//					MessageBox.Show(err.ToString(), "Error processing XML document:");
//				}
			}
		}
		#endregion

		#region Save XML

		private void mnuSaveXML_Click(object sender, System.EventArgs e)
		{
			if (doc.DataSet==null)
			{
				MessageBox.Show("No XML data to save.");
				return;
			}

			if (xmlFileName != null)
			{
				doc.DataSet.AcceptChanges();
				StreamWriter sw=new StreamWriter(xmlFileName, false, System.Text.Encoding.UTF8);
				doc.DataSet.WriteXml(sw);
				sw.Flush();
				sw.Close();
			}
			else
			{
				mnuSaveXmlAs_Click(sender, e);
			}
		}

		private void mnuSaveXmlAs_Click(object sender, System.EventArgs e)
		{
			if (doc.DataSet==null)
			{
				MessageBox.Show("No XML data to save.");
				return;
			}

			doc.DataSet.AcceptChanges();
			DialogResult res=saveXmlAsDlg.ShowDialog();
			if (res==DialogResult.OK)
			{
				xmlFileName=saveXmlAsDlg.FileName;
				StreamWriter sw=new StreamWriter(xmlFileName, false, System.Text.Encoding.UTF8);
				doc.DataSet.WriteXml(sw);
				sw.Flush();
				sw.Close();
			}
		}

		#endregion

		#region Exit Program
		private void mnuExit_Click(object sender, System.EventArgs e)
		{
			Close();		// signal form to close
		}
		#endregion

		#region XPath Query
		private void btnXpathQuery_Click(object sender, System.EventArgs e)
		{
			if (doc==null)
			{
				MessageBox.Show("Please load a schema and XML data set first.");
				return;
			}

			XmlNodeList nodeList=null;
			try
			{
				nodeList=doc.SelectNodes(edXPath.Text);
			}
			catch(Exception err)
			{
				MessageBox.Show(err.Message, "Error processing XPath query:");
				return;
			}

			DlgXPathResult dlg=new DlgXPathResult(nodeList, edNewRowOn.Text);
			dlg.ShowDialog(this);
		}
		#endregion
		#endregion

		#region Schema Validation Handler
		// report any errors to the schema error edit box
		private void SchemaValidationHandler(object sender, ValidationEventArgs args) 
		{
			MessageBox.Show(args.Message, "Schema compilation error:");
		}
		#endregion

		#region CompileSchema
		// Compile the schema as it exists in the XmlSchema structure, displaying any
		// errors in the schema error edit box and displaying the schema itself in
		// the schema edit box.
		private void CompileSchema()
		{
			schema.Compile(new ValidationEventHandler(SchemaValidationHandler));
		}
		#endregion

		#region Functions That Inspect The Schema

		private XmlSchemaAttribute GetLocalAttribute(XmlSchemaComplexType ct, string name)
		{
			for (int i=0; i<ct.Attributes.Count; i++)
			{
				XmlSchemaAttribute attr=ct.Attributes[i] as XmlSchemaAttribute;
				if (attr != null)
				{
					if (attr.QualifiedName.Name==name)
					{
						return attr;
					}
				}
			}
			return null;
		}

		private XmlSchemaElement GetLocalElement(XmlSchemaComplexType ct, string name)
		{
			XmlSchemaSequence seq=ct.Particle as XmlSchemaSequence;
			if (seq != null)
			{
				for (int i=0; i<seq.Items.Count; i++)
				{
					XmlSchemaElement el=seq.Items[i] as XmlSchemaElement;
					if (el != null)
					{
						if (el.QualifiedName.Name==name)
						{
							return el;
						}
					}
				}
			}
			return null;
		}

		private XmlSchemaComplexType GetLocalComplexType(XmlSchemaElement el)
		{
			XmlSchemaComplexType obj=el.SchemaType as XmlSchemaComplexType;
			return obj;
		}

		private XmlSchemaElement GetGlobalElement(string name)
		{
			XmlQualifiedName qname=new XmlQualifiedName(name, schema.TargetNamespace);
			XmlSchemaObject obj=schema.Elements[qname];
			return obj as XmlSchemaElement;
		}

		private XmlSchemaSimpleType GetGlobalSimpleType(string name)
		{
			for (int i=0; i<schema.Items.Count; i++)
			{
				XmlSchemaSimpleType obj=schema.Items[i] as XmlSchemaSimpleType;
				if (obj != null)
				{
					if (obj.Name==name)
					{
						return obj;
					}
				}
			}
			return null;
		}

		private XmlSchemaAttribute GetGlobalAttribute(string name)
		{
			XmlSchemaAttribute obj=schema.Attributes[new XmlQualifiedName(name, schema.TargetNamespace)] as XmlSchemaAttribute;
			return obj;
		}
		
		private XmlSchemaComplexType GetGlobalComplexType(string name)
		{
			for (int i=0; i<schema.Items.Count; i++)
			{
				XmlSchemaComplexType obj=schema.Items[i] as XmlSchemaComplexType;
				if (obj != null)
				{
					if (obj.Name==name)
					{
						return obj;
					}
				}
				else
				{
					XmlSchemaElement el2=schema.Items[i] as XmlSchemaElement;
					if (el2 != null)
					{
						if (el2.SchemaType is XmlSchemaComplexType)
						{
							obj=el2.SchemaType as XmlSchemaComplexType;
							if (((XmlSchemaElement)schema.Items[i]).Name==name)
							{
								// *** The returning complex type does not have any associated qualified name!!! ****
								return obj;
							}
						}
					}
				}
			}
			return null;
		}

		private void GetFacetInfo(TypeInfo ti, XmlSchemaSimpleTypeRestriction rs)
		{
			foreach (XmlSchemaFacet facet in rs.Facets)
			{
				// There's probably a better way to do this with reflection or some such
				// process, but this old C++ programmers don't learn new tricks easily.
				switch(facet.GetType().ToString())
				{
					case "System.Xml.Schema.XmlSchemaLengthFacet":
					{
						ti.length=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaMinLengthFacet":
					{
						ti.minLength=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaMaxLengthFacet":
					{
						ti.maxLength=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaPatternFacet":
					{
						ti.pattern=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaEnumerationFacet":
					{
						ti.enumeration.Add(facet.Value);
						break;
					}
											
					case "System.Xml.Schema.XmlSchemaMaxInclusiveFacet":
					{
						ti.maxInclusive=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaMaxExclusiveFacet":
					{
						ti.maxExclusive=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaMinInclusiveFacet":
					{
						ti.minInclusive=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaMinExclusiveFacet":
					{
						ti.minExclusive=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaFractionDigitsFacet":
					{
						ti.fractionDigits=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaTotalDigitsFacet":
					{
						ti.totalDigits=facet.Value;
						break;
					}

					case "System.Xml.Schema.XmlSchemaWhiteSpaceFacet":
					{
						ti.whiteSpace=facet.Value;
						break;
					}
				}
			}
		}

		private TypeInfo GetTypeInfo(XmlSchemaComplexType ct, string name)
		{
			TypeInfo ti=null;

			// is it an element?
			XmlSchemaElement el=null;
			el=GetLocalElement(ct, name);

			// *** 5/10/2003 *** Search global element list also
			if (el == null)
			{
				el=GetGlobalElement(name);
			}

			// or is it an attribute?
			XmlSchemaAttribute attr=null;
			if (ct != null)
			{
				attr=GetLocalAttribute(ct, name);
			}

			if (el != null)
			{
				// is it an element of simple type?
				if (el.SchemaType is XmlSchemaSimpleType)
				{
					// (paths have been tested)
					// yes...get the restriction base to find the type
					XmlSchemaSimpleType st=el.SchemaType as XmlSchemaSimpleType;
					XmlSchemaSimpleTypeRestriction rest=st.Content as XmlSchemaSimpleTypeRestriction;
					if (rest != null)
					{
						if (rest.BaseTypeName.Namespace=="")
						{
							st=GetGlobalSimpleType(rest.BaseTypeName.Name);
							rest=st.Content as XmlSchemaSimpleTypeRestriction;
							string typename=rest.BaseTypeName.Name;
							ti=new TypeInfo(typename);
							GetFacetInfo(ti, rest);
						}
						else
						{
							string typename=rest.BaseTypeName.Name;
							ti=new TypeInfo(typename);
							GetFacetInfo(ti, rest);
						}
					}
				}
				else
				{
					// no...get the attribute type
					// does it reference a global element?
					if (el.RefName.Name != "")
					{
						// (path has been tested)
						el=GetGlobalElement(el.RefName.Name);
						if (el != null)
						{
							string typename=el.SchemaTypeName.Name;
							ti=new TypeInfo(typename);
						}
					}
					else
					{
						// (path has been tested)
						// no...the type is specified in the element
						
						// Does this reference a global simple type?
						string typename=el.SchemaTypeName.Name;
						if (el.SchemaTypeName.Namespace=="")
						{
							XmlSchemaSimpleType st=GetGlobalSimpleType(typename);
							XmlSchemaSimpleTypeRestriction rest=st.Content as XmlSchemaSimpleTypeRestriction;
							if (rest != null)
							{
								typename=rest.BaseTypeName.Name;
								ti=new TypeInfo(typename);
								GetFacetInfo(ti, rest);
							}
						}
						else
						{
							ti=new TypeInfo(typename);
						}
					}
				}
			}
			else
			if (attr != null)
			{
				// does it reference a global attribute?
				if (attr.RefName.Name != "")
				{
					// yes...get the type of the reference
					// !!! PATH HAS NOT BEEN TESTED !!!
					attr=GetGlobalAttribute(attr.RefName.Name);
					if (attr != null)
					{
						string typename=attr.SchemaTypeName.Name;
						ti=new TypeInfo(typename);
					}
				}
				else
				{
					// no...get the type of the attribute
					// is it of simple type defined globally?
					if (attr.AttributeType is XmlSchemaSimpleType)
					{
						// (path has been tested)
						XmlSchemaSimpleType st=GetGlobalSimpleType(attr.SchemaTypeName.Name);
						if (st != null)
						{
							XmlSchemaSimpleTypeRestriction rest=st.Content as XmlSchemaSimpleTypeRestriction;
							if (rest != null)
							{
								string typename=rest.BaseTypeName.Name;
								ti=new TypeInfo(typename);
								GetFacetInfo(ti, rest);
							}
						}
					}
					else
					{
						// (path has been tested)
						string typename=attr.SchemaTypeName.Name;
						ti=new TypeInfo(typename);
					}
				}
			}
			return ti;
		}

		#endregion

		#region Construct GUI

		private void ConstructGUI(DataSet dataSet)
		{
			// clear the GUI
			// not implemented

			Point pos=new Point(10, 10);
			// get all tables in the dataset
			foreach (DataTable dt in dataSet.Tables)
			{
				// but we're only interested in the toplevel tables
				if (dt.ParentRelations.Count==0)
				{
					/*
						* Rule 1:
						* A top level table will be a top level element in the schema that
						* is of a complex type.  The element name will be the table name.
						* What we want to identify is the complex type that the table references,
						* so that we can determine the data types of the columns.
						* 
						* Any other rules???
						*/
					XmlSchemaElement el=GetGlobalElement(dt.TableName);
					if (el != null)
					{
						XmlSchemaComplexType ct;
						// references a global complex type?
						if (el.SchemaTypeName.Name != "")
						{
							string name=el.RefName.Name != "" ? el.RefName.Name : el.SchemaTypeName.Name;
							ct=GetGlobalComplexType(name);
						}
						// contains a complex type?
						else
						{
							ct=el.SchemaType as XmlSchemaComplexType;
						}
						if (ct != null)
						{
							Point p2=ConstructGUI(pos.X, pos.Y, dt, pnlDynForm, ct);
							pos.Y+=p2.Y;
						}
					}
				}
			}
		}

		private Point ConstructGUI(int absx, int absy, DataTable dt, Control gbParent, XmlSchemaComplexType ct)
		{
			// for this data table, construct a groupbox as a container for
			//	record scrollbar
			//	table columns
//			if (dt.TableName=="TaxaNotes")
//			{
//				MessageBox.Show(dt.TableName);
//			}
			GroupBox gb1=new GroupBox();
			gb1.Font=new Font(gb1.Font, FontStyle.Bold);
			gb1.Text=dt.TableName;
			gb1.Location=new Point(absx, absy);
			gb1.Parent=gbParent;
			gb1.Visible=true;

			tableInfo[dt]=new TableInfo();
			CreateRecordNavigationBar(10, 15, gb1, dt);
			
			int relx=10;				// and indented by 10 pixels
			int rely=40;				// and 30 pixels from top of groupbox

			// For each column in the table...
			foreach (DataColumn col in dt.Columns)
			{
				// if it's not an internal ID...
				if (col.ColumnMapping != MappingType.Hidden)
				{
					// *** 5/10/2003 *** Check for a simple content type.
					XmlSchemaSimpleContent sc=ct.ContentModel as XmlSchemaSimpleContent;
					if (sc != null)
					{
						CreateLabel(relx, rely, dt.TableName, gb1);
						XmlSchemaSimpleContentExtension sce=sc.Content as XmlSchemaSimpleContentExtension;
						if (sce != null)
						{
							TypeInfo ti=new TypeInfo(sce.BaseTypeName.Name);
							CreateEditControl(relx+120, rely, ti, gb1, dt, col);
						}
					}
					else
					{
						// display its name
						CreateLabel(relx, rely, col.ColumnName, gb1);
						/*
							* INACCESSIBLE INFORMATION THAT WOULD HAVE BEEN REALLY USEFUL!!!
							* col.XmlDataType is the data type, simple or global
							* col.SimpleType contains the facets and XmlBaseType of the simple type
							* These properties are INTERNAL classes!  Since we have to get this
							* information ourselves...
							*/
						TypeInfo ti=GetTypeInfo(ct, col.ColumnName);
						if (ti != null)
						{
							/*Control editCtrl=*/CreateEditControl(relx+120, rely, ti, gb1, dt, col);
						}
					}
				}

				if (col.ColumnMapping==MappingType.Hidden)
				{
					// *** columns that define relationships do not need to be displayed ***

					Label lbl=CreateLabel(relx, rely, col.ColumnName, gb1);
					Control editCtrl=CreateEditControl(relx+120, rely, new TypeInfo("string"), gb1, dt, col);
					editCtrl.Size=new Size(50, 20);
					((TextBox)editCtrl).ReadOnly=true;

					// These are columns that are created when the schema is loaded into a dataset.
					// They are not part of the schema itself, but an artifact of the "tablization"
					// of the schema.

					// identify child relations
					// Does this column have a relationship with a child table?
					// data relations are always 1:1
					if (dt.ChildRelations.Count != 0)
					{
						DataRelation dr=dt.ChildRelations[0];
						for (int i=0; i<dr.ChildColumns.Length; i++)
						{
							if (dr.ChildColumns[i].ColumnName==col.ColumnName)
							{
								lbl.ForeColor=Color.Blue;
								break;
							}
						}
					}

					// identify parent relations.
					// Does this column have a relationship with a parent table?
					// data relations are always 1:1
					if (dt.ParentRelations.Count != 0)
					{
						DataRelation dr=dt.ParentRelations[0];
						for (int i=0; i<dr.ParentColumns.Length; i++)
						{
							if (dr.ParentColumns[i].ColumnName==col.ColumnName)
							{
								lbl.ForeColor=Color.Red;
								break;
							}
						}
					}
				}
				rely+=20;
			}

			int rx=0;					// assume no children
			int choiceIdx=0;
			// Get child relationships, which are displayed as indented groupboxes
			foreach (DataRelation childRelation in dt.ChildRelations)
			{
				DataTable dt2=childRelation.ChildTable;
				
				/* Rule 1: The data table references a global complex type.
					* The table name is the element name of complex type in the
					* current complex type object collection.  As with the root
					* table, we need to find the element, get the type, then get
					* the complex type.
					* 
					* Rule 2: If this isn't a global complex type (ct2==null), then
					* the element is a local complex type!
					*/ 
				XmlSchemaElement el2=null;
				XmlSchemaElement el3=null;
				XmlSchemaComplexType ct2=null;

//				if (dt2.TableName=="Author")
//				{
//					MessageBox.Show("Searching: "+dt2.TableName);
//				}

				// *** 5/10/2003 *** Handle complex types with "choice" schemas.
				// A "choice" is represented as separate tables, each indexing the same choice list,
				// and the child relation index tracks the choice items!
				XmlSchemaChoice choice=ct.ContentTypeParticle as XmlSchemaChoice;
				if (choice != null)
				{
//					for (int i=0; i<choice.Items.Count; i++)
					{
						XmlSchemaElement elChoice=choice.Items[choiceIdx] as XmlSchemaElement;
						if (elChoice != null)
						{
							ct2=GetGlobalComplexType(elChoice.RefName.Name);
							Point p=ConstructGUI(relx+20, rely+20, dt2, gb1, ct2);
							if (p.X > rx)
							{
								rx=p.X;				// indent level
							}
							rely+=p.Y+20;			// vertical spacing between child tables
//							break;
						}
					}
					++choiceIdx;
					continue;
				}
				else	if (ct != null)
				{
					// get the local element associated with the table name in this complex type
					el2=GetLocalElement(ct, dt2.TableName);

					// if it exists...
					if (el2 != null)
					{
						// get either the refname or the schema type name
						string name=el2.RefName.Name != "" ? el2.RefName.Name : el2.SchemaTypeName.Name;

						// find the complex type defining the element type.
						ct2=GetGlobalComplexType(name);
						if (ct2==null)
						{
							// if the complex type is not defined globally, then check if it's defnied locally
							ct2=GetLocalComplexType(el2);

							// if it's not defined locally, check if the element is referring to a global ELEMENT
							if (ct2==null)
							{
								el3=GetGlobalElement(name);
							}
						}
					}
				}

				if (ct2 != null)
				{

					Point p=ConstructGUI(relx+20, rely+20, dt2, gb1, ct2);
					if (p.X > rx)
					{
						rx=p.X;				// indent level
					}
					rely+=p.Y+20;			// vertical spacing between child tables
				}

				// *** 5/10/2003 *** This handles the case where a local element references a global element!
				// This structure must be created as a child node, complete with groupbox support.
				else if (el3 != null)
				{
					// *** 5/12/2003 *** Create a tableInfo entry.
					// NOTE THAT A SEPARATE GROUPBOX WITH RECORD NAVIGATION IS NOT CREATED!
					tableInfo[dt2]=new TableInfo();
					string name=el3.SchemaTypeName.Name;
					TypeInfo ti=new TypeInfo(name);
					CreateLabel(relx, rely, dt2.TableName, gb1);
					CreateEditControl(relx+120, rely, ti, gb1, dt2, dt2.Columns[0]);
					rely+=20;			// vertical spacing between rows
				}
				else
				{
					MessageBox.Show("Not found: "+dt.TableName+"."+dt2.TableName);
				}
			}
			
			// set our size based on number of child tables (indents)
			gb1.Size=new Size(300+rx, rely+10);

			// return the size of this groupbox, which includes all columns and child tables
			return new Point(40+rx, rely+10);
		}
		
		#endregion

		private Label CreateLabel(int x, int y, string name, Control parent)
		{
			Label lbl=new Label();
			lbl.Location=new Point(x, y+2);
			lbl.Size=new Size(120, 15);
			lbl.Text=name;
			lbl.Font=new Font(lbl.Font, FontStyle.Regular);
			lbl.Visible=true;
			lbl.Parent=parent;
			return lbl;
		}

		private Control CreateRecordNavigationBar(int x, int y, Control parent, DataTable tag)
		{
			Panel navPanel=new Panel();
			navPanel.Location=new Point(x, y);
			navPanel.Size=new Size(280, 19);
			navPanel.Visible=true;
			navPanel.Parent=parent;
			navPanel.BorderStyle=BorderStyle.FixedSingle;

			CreateNavButton("<<", 0, 0, new EventHandler(FirstRecord), navPanel, tag);
			CreateNavButton("<", 30, 0, new EventHandler(PrevRecord), navPanel, tag);
			CreateNavButton("*", 60, 0, new EventHandler(NewRecord), navPanel, tag);
			CreateNavButton(">", 90, 0, new EventHandler(NextRecord), navPanel, tag);
			CreateNavButton(">>", 120, 0, new EventHandler(LastRecord), navPanel, tag);

			TextBox tb=new TextBox();
			tb.Location=new Point(150, 2);
			tb.Size=new Size(100, 18);
			tb.BorderStyle=BorderStyle.None;
			tb.Font=new Font("Tahoma", 8, FontStyle.Regular);
			tb.Parent=navPanel;
			tb.Visible=true;
			tb.Text="   record 0 of 0";
			((TableInfo)tableInfo[tag]).tb=tb;

			CreateNavButton("X", 250, 0, new EventHandler(DeleteRecord), navPanel, tag);

			return navPanel;
		}

		private void CreateNavButton(string text, int x, int y, EventHandler ev, Control parent, object tag)
		{
			Button btnNewRecord=new Button();
			btnNewRecord.Text=text;
			btnNewRecord.Location=new Point(x, y);
			btnNewRecord.Size=new Size(30, 17);
			btnNewRecord.Parent=parent;
			btnNewRecord.Visible=true;
			btnNewRecord.Font=new Font("Tahoma", 8, FontStyle.Bold);
			btnNewRecord.Tag=tag;
			btnNewRecord.Click+=ev;
		}

		private Control CreateEditControl(int x, int y, TypeInfo ti, Control parent, DataTable dt, DataColumn dc)
		{
			Control ctrl=null;

			// if the schema has an enumeration, then display as a combo box
			if (ti.enumeration.Count != 0)
			{
				ComboBox cb=new ComboBox();
				cb.Location=new Point(x, y);
				cb.Size=new Size(140, 200);
				cb.Font=new Font(cb.Font, FontStyle.Regular);
				cb.Visible=true;
				cb.Parent=parent;
				foreach (object obj in ti.enumeration)
				{
					cb.Items.Add(obj);
				}
				ctrl=cb;
				ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
			}
			// if the schema has a min/max inclusive set, then display as an up-down control
			else if ( (ti.minInclusive != null) && (ti.maxInclusive != null) )
			{
				NumericUpDown nud=new NumericUpDown();
				nud.Location=new Point(x, y);
				nud.Size=new Size(80, 20);
				nud.Font=new Font(nud.Font, FontStyle.Regular);
				nud.Visible=true;
				nud.Parent=parent;

				nud.Minimum=Convert.ToInt32(ti.minInclusive);
				nud.Maximum=Convert.ToInt32(ti.maxInclusive);
				ctrl=nud;
				ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
			}
			else
			{
				// use MaxLength
				// use Tag???
				// NumericUpDown???
				switch(ti.typeName)
				{
					case "boolean":
					{
						// boolean is implemented as a checkbox
						CheckBox ck1=new CheckBox();
						ck1.Location=new Point(x, y);
						ck1.Size=new Size(140, 20);
						ck1.Font=new Font(ck1.Font, FontStyle.Regular);
						ck1.Visible=true;
						ck1.Text="";
						ck1.Parent=parent;
						ck1.AutoCheck=true;
						ctrl=ck1;

						Binding b=new Binding("Checked", dt, dc.ColumnName);
						b.Format+=new ConvertEventHandler(TrueFalseToBool);
						ctrl.DataBindings.Add(b);
						break;
					}

					case "string":
					{
						// textbox
						TextBox tb=new TextBox();
						tb.Location=new Point(x, y);
						tb.Size=new Size(140, 20);
						tb.Font=new Font(tb.Font, FontStyle.Regular);
						tb.Visible=true;
						tb.Parent=parent;
						ctrl=tb;
						ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
						break;
					}

					case "decimal":
					{
						// right justified
						TextBox tb=new TextBox();
						tb.Location=new Point(x, y);
						tb.Size=new Size(140, 20);
						tb.Font=new Font(tb.Font, FontStyle.Regular);
						tb.Visible=true;
						tb.Parent=parent;
						tb.TextAlign=HorizontalAlignment.Right;
						ctrl=tb;
						ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
						break;
					}

					case "positiveInteger":
					{
						// right justified
						TextBox tb=new TextBox();
						tb.Location=new Point(x, y);
						tb.Size=new Size(140, 20);
						tb.Font=new Font(tb.Font, FontStyle.Regular);
						tb.Visible=true;
						tb.Parent=parent;
						tb.TextAlign=HorizontalAlignment.Right;
						ctrl=tb;
						ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
						break;
					}

					default:
					{
						// use a text box for the default data type
						TextBox tb=new TextBox();
						tb.Location=new Point(x, y);
						tb.Size=new Size(140, 20);
						tb.Font=new Font(tb.Font, FontStyle.Regular);
						tb.Visible=true;
						tb.Parent=parent;
						ctrl=tb;
						ctrl.DataBindings.Add("Text", dt, dc.ColumnName);
						break;		
					}
				}
			}
			return ctrl;
		}

		private void TrueFalseToBool(object sender, ConvertEventArgs args)
		{
			if (args.Value.GetType()==typeof(System.DBNull))
			{
				args.Value=false;
			}
		}


		private void UpdateRecordCountInfo(DataTable dt)
		{
			// update the text box to reflect the record #
			TableInfo ti=tableInfo[dt] as TableInfo;
			if (dt.ParentRelations.Count==0)
			{
				ti.pos=BindingContext[dt].Position+1;
			}
			// *** 5/12/2003 *** If a child element references a global element, no navigational record is created.  Is this
			// a problem?
			if (ti.tb != null)
			{
				ti.tb.Text="   record "+ti.pos.ToString()+" of "+ti.rows.ToString();
			}
		}

		private int GetMatchingRows(DataTable dt)
		{
			TableInfo ti=tableInfo[dt] as TableInfo;
			// get parent relationship
			DataRelation parentRelation=dt.ParentRelations[0];
			// get the parent table
			DataTable dt2=parentRelation.ParentTable;
			// get the parent column (1:1 relationship always)
			DataColumn dcParent=parentRelation.ParentColumns[0];
			// get the current record # of the parent
			int n=BindingContext[dt2].Position;
			if (n != -1)
			{
				// get the ID
				string val=dt2.Rows[n][dcParent].ToString();
				// search the child for all records where child.parentID=parent.ID
				string expr=dcParent.ColumnName+"="+val;
				// save the rows, as we'll use them later on when navigating the child
				ti.dataRows=dt.Select(expr);
			}
			// return the length
			if (ti.dataRows==null)
			{
				return 0;
			}
			return ti.dataRows.Length;
		}

		#region Navigation Bar Event Handlers

		// Create a new record in the associated table.
		// This function also creates new records in all child tables and
		// sets the child-parent relationship key in the child tables.
		private void NewRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			dt.Rows.Add(dt.NewRow());
			int newRow=dt.Rows.Count-1;
			BindingContext[dt].Position=newRow;

			TableInfo ti=tableInfo[dt] as TableInfo;

			// Set the child relationship ID's to the parent!
			// There will be only one parent relationship except
			// for the root table.
			if (dt.ParentRelations.Count != 0)
			{
				DataRelation parentRelation=dt.ParentRelations[0];
				DataTable dt2=parentRelation.ParentTable;
				int n=BindingContext[dt2].Position;

				// this is always a 1:1 relationship
				DataColumn dcParent=parentRelation.ParentColumns[0];
				DataColumn dcChild=parentRelation.ChildColumns[0];
				string val=dt2.Rows[n][dcParent].ToString();
				dt.Rows[newRow][dcChild]=val;

				n=GetMatchingRows(dt);
				ti.pos=n;
				ti.rows=n;
			}
			else
			{
				ti.pos=newRow+1;
				ti.rows=newRow+1;
			}

			UpdateRecordCountInfo(dt);

			// for each child, also create a new row in the child's table
			foreach (DataRelation childRelation in dt.ChildRelations)
			{
				DataTable dtChild=childRelation.ChildTable;
				NewRecord(dt, dtChild, childRelation);
			}
		}

		private void NewRecord(DataTable parent, DataTable child, DataRelation dr)
		{
			// add the child record
			child.Rows.Add(child.NewRow());

			// get the last row of the parent (this is the new row)
			// and the new row in the child (also the last row)
			int newParentRow=parent.Rows.Count-1;
			int newChildRow=child.Rows.Count-1;

			// go to this record
			BindingContext[child].Position=newChildRow;

			// get the parent and child columns
			// copy the parent ID (auto sequencing) to the child to establish
			// the relationship.  This is always a 1:1 relationship
			DataColumn dcParent=dr.ParentColumns[0];
			DataColumn dcChild=dr.ChildColumns[0];
			string val=parent.Rows[newParentRow][dcParent].ToString();
			child.Rows[newChildRow][dcChild]=val;

			((TableInfo)tableInfo[child]).pos=1;
			((TableInfo)tableInfo[child]).rows=1;
			UpdateRecordCountInfo(child);

			// recurse into children of this child
			foreach (DataRelation childRelation in child.ChildRelations)
			{
				DataTable dt2=childRelation.ChildTable;
				NewRecord(child, dt2, childRelation);
			}
		}

		private void FirstRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			TableInfo ti=tableInfo[dt] as TableInfo;
			ti.pos=1;
			FirstRecord(dt, ti);
			UpdateRecordCountInfo(dt);
		}

		private void PrevRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			TableInfo ti=tableInfo[dt] as TableInfo;
			if (ti.pos > 1)
			{
				((TableInfo)tableInfo[dt]).pos--;
				PrevRecord(dt, ti);
				UpdateRecordCountInfo(dt);
			}
		}

		private void NextRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			TableInfo ti=tableInfo[dt] as TableInfo;
			if (ti.pos < ti.rows)
			{
				((TableInfo)tableInfo[dt]).pos++;
				NextRecord(dt, ti);
				UpdateRecordCountInfo(dt);
			}
		}

		private void LastRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			int n=dt.Rows.Count;
			TableInfo ti=tableInfo[dt] as TableInfo;
			ti.pos=ti.rows;
			LastRecord(dt, ti);
			UpdateRecordCountInfo(dt);
		}
		
		private void DeleteRecord(object sender, EventArgs e)
		{
			Button btn=sender as Button;
			DataTable dt=btn.Tag as DataTable;
			int n=BindingContext[dt].Position;
			dt.Rows.RemoveAt(n);

			// if a child table...
			if (dt.ParentRelations.Count != 0)
			{
				// ...get all rows matching the parent ID
				n=GetMatchingRows(dt);
			}
			else
			{
				// ...otherwise get all rows of the root table
				n=dt.Rows.Count;
			}
			TableInfo ti=tableInfo[dt] as TableInfo;
			ti.rows=n;

			// if position is now past # of rows...
			if (ti.pos > ti.rows)
			{
				// set position to last row
				ti.pos=ti.rows;
			}

			// update the display
			UpdateRecordCountInfo(dt);

			if (n != 0)		// table has records?
			{

				// set to the next available row
				if (dt.ParentRelations.Count != 0)
				{
					SetPositionToRow(dt, ti.dataRows[ti.pos-1]);
				}
				else
				{
					BindingContext[dt].Position=ti.pos-1;
				}
			}

			// reset all children to record #1
			ResetAllChildren(dt);
			
			// Cascading delete automatically implemented
			// by the table constraints.
		}

		private void FirstRecord(DataTable dt, TableInfo ti)
		{
			if (dt.ParentRelations.Count==0)
			{
				BindingContext[dt].Position=0;
			}
			else
			{
				// get the first row in the record set that matches the parent
				SetPositionToRow(dt, ti.dataRows[ti.pos-1]);
			}
			ResetAllChildren(dt);
		}

		private void PrevRecord(DataTable dt, TableInfo ti)
		{
			if (dt.ParentRelations.Count==0)
			{
				BindingContext[dt].Position--;
			}
			else
			{
				// get the previous row that matches the parent ID
				SetPositionToRow(dt, ti.dataRows[ti.pos-1]);
			}
			ResetAllChildren(dt);
		}

		private void NextRecord(DataTable dt, TableInfo ti)
		{
			if (dt.ParentRelations.Count==0)
			{
				BindingContext[dt].Position++;
			}
			else
			{
				// get the next row that matches the parent ID
				SetPositionToRow(dt, ti.dataRows[ti.pos-1]);

			}
			ResetAllChildren(dt);
		}

		private void LastRecord(DataTable dt, TableInfo ti)
		{
			if (dt.ParentRelations.Count==0)
			{
				BindingContext[dt].Position=dt.Rows.Count-1;
			}
			else
			{
				// get the last row that matches the parent ID
				SetPositionToRow(dt, ti.dataRows[ti.pos-1]);
			}
			ResetAllChildren(dt);
		}

		private void ResetAllChildren(DataTable dt)
		{
			// update all children of the table to match our new ID
			foreach (DataRelation dr in dt.ChildRelations)
			{
				DataTable dtChild=dr.ChildTable;
				ResetChildRecords(dtChild);
			}
		}

		private void ResetChildRecords(DataTable dt)
		{
			int n=GetMatchingRows(dt);
			TableInfo ti=tableInfo[dt] as TableInfo;
			ti.pos=1;
			ti.rows=n;
			UpdateRecordCountInfo(dt);
			if (n != 0)
			{
				SetPositionToRow(dt, ti.dataRows[0]);
			}
			foreach (DataRelation dr in dt.ChildRelations)
			{
				DataTable dtChild=dr.ChildTable;
				ResetChildRecords(dtChild);
			}
		}

		private void SetPositionToRow(DataTable dt, DataRow row)
		{
			for (int i=0; i<dt.Rows.Count; i++)
			{
				if (dt.Rows[i]==row)
				{
					BindingContext[dt].Position=i;
					break;
				}
			}
		}

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


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions