Click here to Skip to main content
15,887,135 members
Articles / Programming Languages / C# 4.0

Relationship Oriented Programming - The IDE plus More on Agile Project Management

Rate me:
Please Sign up or sign in to vote.
4.98/5 (25 votes)
12 Mar 2012CPOL81 min read 77.6K   1.2K   49  
An Integrated Development Environment (IDE) for the Relationship Oriented Programming Tool.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Clifton.Tools.Strings.Extensions;

using MyXaml.Core;

using ROPLib;
using ROPLib.Data;
using ROPLib.Graphing;
using ROPLib.Interfaces;

using Interfaces.UI;

namespace IDE
{
	public class ModelEditor
	{
		[MyXamlAutoInitialize]
		IGrid dgvModel = null;
		[MyXamlAutoInitialize]
		ComboBox cbRootLevelEntity = null;
		[MyXamlAutoInitialize]
		TextBox tbNavigation = null;
		[MyXamlAutoInitialize]
		ComboBox cbMasterTables = null;
		[MyXamlAutoInitialize]
		ComboBox cbDetailTables = null;

		public BindingContext BindingContext { get; set; }
		public Form Form { get; set; }

		protected bool ignoreRootChange = false;
		protected DataSet dataSet;

		public void SetDataSet(DataSet ds)
		{
			dataSet = ds;
			dgvModel.AutoGenerateColumns = false;
			dgvModel.DataSource = dataSet;
		}

		public void SetEntities(List<string> entityNames)
		{
			cbRootLevelEntity.DataSource = entityNames;
		}

		private void cbRootLevelEntity_SelectedIndexChanged(object sender, EventArgs e)
		{
			string entity = cbRootLevelEntity.SelectedItem.ToString();
			SetRootLevelEntity(entity);
			InitializeColumns();

			if (!ignoreRootChange)
			{
				NavigateFrom(entity);
			}

			UpdateCaption(entity);
		}

		private void btnNavigateToMaster_Click(object sender, EventArgs e)
		{
			string entity = cbMasterTables.SelectedItem.ToString();
			UpdateGrid(entity);
			ShowNavigateToMaster(entity);
			ClearRowFilter(entity);
		}

		private void btnNavigateToDetail_Click(object sender, EventArgs e)
		{
			string entity = cbDetailTables.SelectedItem.ToString();
			UpdateGrid(entity);
			ShowNavigateToDetail(entity);
			ClearRowFilter(entity);
		}

		private void btnBack_Click(object sender, EventArgs e)
		{
			string qualifier;
			string navItem = PopNavigation(out qualifier);
			UpdateGrid(navItem);
			SetRowFilter(navItem, qualifier);
		}

		protected void ClearRowFilter(string entity)
		{
			DataView dv = (DataView)((CurrencyManager)BindingContext[dataSet, entity]).List;
			dv.RowFilter = String.Empty;
		}

		protected void SetRowFilter(string entity, string qualifier)
		{
			DataView dv = (DataView)((CurrencyManager)BindingContext[dataSet, entity]).List;
			dv.RowFilter = qualifier.ToString();
		}

		protected string PopNavigation(out string qualifier)
		{
			qualifier = String.Empty;
			string nav = tbNavigation.Text;
			string[] navList = nav.Split('\r');
			string ret = navList[0];			// Can't pop past the first item.

			// Go back one level (0 indexed, so we subtract 2), however, because the nav text always has a trailing \r\n, we need to subtract 3!
			int n = navList.Length - 3;

			if (n >= 0)
			{
				string navItem = navList[n].Trim();

				// If this not the root entity, it will be preceded with " <-- " or " --> ", but the leading whitespace is already trimmed.
				if ((navItem[0] == '<') || (navItem[0] == '-'))
				{
					navItem = navItem.RightOf(' ');
				}

				// Left of any qualifier.
				ret = navItem.LeftOf('[').Trim();
				qualifier = navItem.Between('[', ']');

				// Rebuild the navigation list (seems easier than finding the next to last \r\n)
				StringBuilder sb = new StringBuilder();

				for (int i = 0; i < navList.Length - 2; i++)
				{
					sb.Append(navList[i].Trim());
					sb.Append("\r\n");
				}

				tbNavigation.Text = sb.ToString();
			}

			return ret;
		}

		protected void InitializeMasterEntityList(string entity)
		{
			List<string> masterList = Schema.Instance.GetMasterEntities(entity);
			cbMasterTables.DataSource = masterList;

			if (masterList.Count == 0)
			{
				cbMasterTables.Text = String.Empty;
			}
		}

		protected void InitializeDetailEntityList(string entity)
		{
			List<string> detailList = Schema.Instance.GetDetailEntities(entity);
			cbDetailTables.DataSource = detailList;

			if (detailList.Count == 0)
			{
				cbDetailTables.Text = String.Empty;
			}
		}

		protected void SetRootLevelEntity(string entity)
		{
			dgvModel.DataMember = entity;
			InitializeMasterEntityList(entity);
			InitializeDetailEntityList(entity);
		}

		/// <summary>
		/// Always clears the navigation list and starts over.
		/// </summary>
		protected void NavigateFrom(string entity)
		{
			tbNavigation.Text = entity + "\r\n";
			ClearRowFilter(entity);
		}

		protected void ShowNavigateToMaster(string masterEntity)
		{
			tbNavigation.Text = tbNavigation.Text + " --> " + masterEntity + "\r\n";
		}

		protected void ShowNavigateToDetail(string detailEntity)
		{
			tbNavigation.Text = tbNavigation.Text + " <-- " + detailEntity + "\r\n";
		}

		protected void ShowNavigateToMaster(string masterEntity, string qualifier)
		{
			tbNavigation.Text = tbNavigation.Text + " --> " + masterEntity + " [" + qualifier + "]\r\n";
		}

		protected void ShowNavigateToDetail(string detailEntity, string qualifier)
		{
			tbNavigation.Text = tbNavigation.Text + " <-- " + detailEntity + " [" + qualifier + "]\r\n";
		}

		protected void UpdateCaption(string text)
		{
			Form.Text = text;
		}

		/// <summary>
		/// Manually populate the columns as either textboxes or combobox controls.
		/// </summary>
		protected void InitializeColumns()
		{
			dgvModel.ClearColumns();
			DataTable dt = dataSet.Tables[dgvModel.DataTableName];

			foreach (IDataColumn dc in dt.Columns)
			{
				bool isLookup = false;

				// Look at all the parent relations
				foreach (DataRelation dr in dt.ParentRelations)
				{
					// If the current column has a parent relation...
					if (dr.ChildColumns.Contains((DataColumn)dc))
					{
						DataView dvParent = new DataView(dr.ParentTable);
						string displayField = Schema.Instance.GetEntity(dr.ParentTable.TableName).DisplayField;

						if (String.IsNullOrEmpty(displayField))
						{
							displayField = dr.ParentTable.PrimaryKey[0].ColumnName;
						}

						dvParent.Sort = displayField;
						dgvModel.AddLookupColumn(dvParent, dr.ParentTable.PrimaryKey[0].ColumnName, displayField, (DataColumn)dc);
						isLookup = true;
						break;
					}
				}

				ROPLib.Attribute attr = Schema.Instance.GetAttribute(dc.AttributeName);

				if (!isLookup)
				{
					// If the attribute associated with this column is an application entity lookup, then map this to a lookup control.
					if (attr.HasApplicationLookup)
					{
						EntityProvider entityProvider = new EntityProvider(dataSet);
						entityProvider.InitializeWithApplicationEntity(attr.EntityName);
						entityProvider.LoadTable();
						string displayMember = entityProvider.GetColumnNameMapping(attr.DisplayMember);
						string valueMember = attr.ValueMember;		// assumes "ID", not requiring a lookup.
						DataTable dtSource = entityProvider.DataTable;
						DataView dvSource = new DataView(dtSource);
						dvSource.Sort = displayMember;
						dgvModel.AddLookupColumn(dvSource, valueMember, displayMember, (DataColumn)dc);
						isLookup = true;
					}
				}

				if (!isLookup)
				{
					// If the attribute associated with this column is a model entity lookup, then map this to a lookup control.
					if (attr.HasModelLookup)
					{
						string displayMember = attr.DisplayMember;
						string valueMember = attr.ValueMember;		// assumes "ID", not requiring a lookup.
						DataTable dtSource = dataSet.Tables[attr.ModelEntityName];
						DataView dvSource = new DataView(dtSource);
						dvSource.Sort = displayMember;
						dgvModel.AddLookupColumn(dvSource, valueMember, displayMember, (DataColumn)dc);
						isLookup = true;
					}
				}

				if (!isLookup)
				{
					if (attr.HasPickList)
					{
						List<ListItem> items = Schema.Instance.GetListItems(attr.PickList);
						DataTable dtSource = ConvertToTable(items);
						DataView dvSource = new DataView(dtSource);
						dvSource.Sort = "Ordinality";
						dgvModel.AddLookupColumn(dvSource, "Name", "Name", (DataColumn)dc);
						isLookup = true;
					}
				}

				if (!isLookup)
				{
					dgvModel.AddColumn((DataColumn)dc);
				}
			}

			dgvModel.AdjustColumnWidths();
		}

		protected DataTable ConvertToTable(List<ListItem> items)
		{
			DataTable dt = new DataTable();
			dt.Columns.Add("Name", typeof(string));
			dt.Columns.Add("Ordinality", typeof(int));

			foreach (ListItem item in items)
			{
				DataRow row = dt.NewRow();
				row["Name"] = item.Name;
				row["Ordinality"] = item.Ordinality;
				dt.Rows.Add(row);
			}

			return dt;
		}

		private void btnSelectedMasters_Click(object sender, EventArgs e)
		{
			string currentEntity = dgvModel.DataMember;
			string navToEntity = cbMasterTables.SelectedItem.ToString();
			List<DataRow> selectedRows = dgvModel.SelectedRows;
			StringBuilder qualifier = new StringBuilder();
			string orRow = String.Empty;

			if (selectedRows.Count > 0)
			{
				foreach (DataRow gridRow in selectedRows)
				{
					string orRelationship = String.Empty;

					// Build qualifier: "[PK]=[value]"
					foreach (DataRelation dr in dataSet.Relations)
					{
						if ((dr.ParentTable.TableName == navToEntity) && (dr.ChildTable.TableName == currentEntity))
						{
							string pkField = dr.ParentTable.PrimaryKey[0].ColumnName;
							string fkField = dr.ChildColumns[0].ColumnName;

							// Undefined relationship fields will not have an FK.
							// Could also check for DBNull.Value.
							if (!String.IsNullOrEmpty(gridRow[fkField].ToString()))
							{
								qualifier.Append(orRow);
								qualifier.Append(orRelationship);
								qualifier.Append(pkField);
								qualifier.Append("=");
								qualifier.Append(gridRow[fkField].ToString());
								orRelationship = " or ";
								orRow = String.Empty;
							}
						}
					}

					orRow = " or ";
				}

				UpdateGrid(navToEntity);
				SetRowFilter(navToEntity, qualifier.ToString());
				ShowNavigateToMaster(navToEntity, qualifier.ToString());
			}
		}

		private void btnSelectedDetails_Click(object sender, EventArgs e)
		{
			List<DataRow> selectedRows = dgvModel.SelectedRows;
			string orRow = String.Empty;
			string currentEntity = dgvModel.DataMember;
			string navToEntity = cbDetailTables.SelectedItem.ToString();
			StringBuilder qualifier = new StringBuilder();

			if (selectedRows.Count > 0)
			{
				foreach (DataRow gridRow in selectedRows)
				{
					string orRelationship = String.Empty;

					// Build qualifier: "[FK]=[value]"
					foreach (DataRelation dr in dataSet.Relations)
					{
						if ((dr.ChildTable.TableName == navToEntity) && (dr.ParentTable.TableName == currentEntity))
						{
							qualifier.Append(orRow);
							qualifier.Append(orRelationship);
							qualifier.Append(dr.ChildColumns[0].ColumnName);
							qualifier.Append("=");
							string pkField = dr.ParentTable.PrimaryKey[0].ColumnName;
							qualifier.Append(gridRow[pkField].ToString());
							orRelationship = " or ";
							orRow = String.Empty;
						}
					}

					orRow = " or ";
				}

				UpdateGrid(navToEntity);
				SetRowFilter(navToEntity, qualifier.ToString());

				// Doesn't work.  Not the right view.
				// dataSet.Tables[navToEntity].DefaultView.RowFilter = qualifier.ToString();

				// Doesn't work.  Not the right view.
				// dataSet.DefaultViewManager.DataViewSettings[navToEntity].RowFilter = qualifier.ToString();

				ShowNavigateToDetail(navToEntity, qualifier.ToString());
			}
		}

		protected void UpdateGrid(string entity)
		{
			ignoreRootChange = true;
			cbRootLevelEntity.SelectedItem = entity;
			Application.DoEvents();
			ignoreRootChange = false;
		}

		protected void OnSizeChanged(object sender, EventArgs args)
		{
			// Not required when attached to a DevExpress LayoutControlItem.
			((Control)dgvModel).Height = ((Form)sender).Height - 340;
		}
	}
}

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