Click here to Skip to main content
15,893,814 members
Articles / Programming Languages / C#

Implementing complex data binding in custom controls

Rate me:
Please Sign up or sign in to vote.
4.83/5 (49 votes)
29 Aug 2006CPOL9 min read 209.4K   5.1K   164  
An article on implementing complex data binding (DataSource and DataMember) on a custom control.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;

namespace ComplexDataBindingSample
{
	/// <summary>
	/// Extends the <see cref="ListView"/> with DataBinding-ability.
	/// </summary>
	public class BoundListView : ListView
	{
		private ListChangedEventHandler listChangedHandler;
		private EventHandler positionChangedHandler;
		private object dataSource;
		private string dataMember;
		private CurrencyManager dataManager;

		#region Properties
		#region DataSource
		/// <summary>
		/// Ruft die Datenquelle ab, f�r die Daten angezeigt werden sollen, oder legt diese fest.
		/// </summary>
		[TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
		[Category("Data")]
		[Description("Gibt die Datenquelle f�r das Control an.")]
		[DefaultValue(null)]
		public object DataSource
		{
			get
			{
				return this.dataSource;
			}
			set
			{
				if (this.dataSource != value)
				{
					this.dataSource = value;
					tryDataBinding();
				}
			}
		}
		#endregion

		#region DataMember
		/// <summary>
		/// Ruft die bestimmte Liste in DataSource ab, f�r die das Control 
		/// ein Datenblatt anzeigt, oder legt diese fest.
		/// </summary>
		[Category("Data")]
		[Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design",
			 "System.Drawing.Design.UITypeEditor, System.Drawing")]
		[Description("Gibt eine untergeordnete Liste von DataSource an, um sie anzuzeigen.")]
		[DefaultValue(null)]
		public string DataMember
		{
			get
			{
				return this.dataMember;
			}
			set
			{
				if (this.dataMember != value)
				{
					this.dataMember = value;
					tryDataBinding();
				}
			}
		}
		#endregion

		#region CurrencyManager
		/// <summary>
		/// Ruft den CurrencyManager der gebundenen Liste ab.
		/// </summary>
		protected CurrencyManager DataManager
		{
			get
			{
				return this.dataManager;
			}
		}
		#endregion
		#endregion
		
		/// <summary>
		/// Renew the Databinding. BindingContext is changed, if you change the Parent.
		/// </summary>
		protected override void OnBindingContextChanged(EventArgs e)
		{
			this.tryDataBinding();
			base.OnBindingContextChanged(e);
		}

		public BoundListView()
		{
			listChangedHandler = new ListChangedEventHandler(dataManager_ListChanged);
			positionChangedHandler = new EventHandler(dataManager_PositionChanged);

			this.View = System.Windows.Forms.View.Details;
			this.FullRowSelect = true;
			this.HideSelection = false;
			this.MultiSelect = false;
			this.LabelEdit = true;

			this.SelectedIndexChanged += new EventHandler(ListViewDataBinding_SelectedIndexChanged);
		}

		#region tryDataBinding
		/// <summary>
		/// Tries to get a new CurrencyManager for new DataBinding
		/// </summary>
		private void tryDataBinding()
		{
			if (this.DataSource == null ||
				base.BindingContext == null)
				return;

			CurrencyManager cm;
			try
			{
				cm = (CurrencyManager)base.BindingContext[this.DataSource, this.DataMember];
			}
			catch (System.ArgumentException)
			{
				// If no CurrencyManager was found
				return;
			}
			if (this.dataManager != cm)
			{
				// Unwire the old CurrencyManager
				if (this.dataManager != null)
				{
					this.dataManager.ListChanged -= listChangedHandler;
					this.dataManager.PositionChanged -= positionChangedHandler;
				}
				this.dataManager = cm;
				// Wire the new CurrencyManager
				if (this.dataManager != null)
				{
					this.dataManager.ListChanged += listChangedHandler;
					this.dataManager.PositionChanged += positionChangedHandler;
				}

				// Update metadata and data
				calculateColumns();
				updateAllData();
			}
		}
		#endregion

		#region Item Methods
		/// <summary>
		/// Updates all Items.
		/// </summary>
		private void updateAllData()
		{
			this.Items.Clear();
			for (int i = 0; i < DataManager.Count; i++ )
			{
				addItem(i);
			}
		}
		
		/// <summary>
		/// Adds a new item.
		/// </summary>
		/// <param name="index">The index of the item.</param>
		private void addItem(int index)
		{
			ListViewItem item = getListViewItem(index);
			this.Items.Insert(index, item);
		}
		
		/// <summary>
		/// Updates the data of the item with the DataSource.
		/// </summary>
		/// <param name="index">The index of the item.</param>
		private void updateItem(int index)
		{
			if (index >= 0 &&
			    index < this.Items.Count)
			{
				ListViewItem item = getListViewItem(index);
				this.Items[index] = item;
			}
		}

		/// <summary>
		/// Returns a <see cref="ListViewItem"/> wich contains the row-data at given index.
		/// </summary>
		/// <param name="index">The index of the row.</param>
		/// <returns>A item wich contains the data.</returns>
		private ListViewItem getListViewItem(int index)
		{
			object row = DataManager.List[index];
			PropertyDescriptorCollection propColl = DataManager.GetItemProperties();
			ArrayList items = new ArrayList();
			
			// Fill value for each column
			foreach(ColumnHeader column in this.Columns)
			{
				PropertyDescriptor prop = null;
				prop = propColl.Find(column.Text, false);
				if (prop != null)
				{
					items.Add(prop.GetValue(row).ToString());
				}
			}
			return new ListViewItem((string[])items.ToArray(typeof(string)));
		}

		/// <summary>
		/// Delete the item at the given index.
		/// </summary>
		/// <param name="index">The index of the item.</param>
		private void deleteItem(int index)
		{
			if (index >= 0 &&
			    index < this.Items.Count)
				this.Items.RemoveAt(index);
		}
		
		/// <summary>
		/// Calculates the Colums of the <see cref="BoundListView"/>.
		/// </summary>
		private void calculateColumns()
		{
			this.Columns.Clear();
			if (dataManager == null)
				return;
			foreach (PropertyDescriptor prop in DataManager.GetItemProperties())
			{
				ColumnHeader column = new ColumnHeader();
				column.Text = prop.Name;
				this.Columns.Add(column);
			}
		}
		#endregion
		
		#region Position changed from DataSource
		private void dataManager_PositionChanged(object sender, EventArgs e)
		{
			if (this.Items.Count > DataManager.Position)
			{
				this.Items[DataManager.Position].Selected = true;
				this.EnsureVisible(DataManager.Position);
			}
		}
		#endregion

		#region Item(s) changed from DataSource
		private void dataManager_ListChanged(object sender, ListChangedEventArgs e)
		{
			if (e.ListChangedType == ListChangedType.Reset ||
				e.ListChangedType == ListChangedType.ItemMoved)
			{
				// Update all data
				updateAllData();
			}
			else if (e.ListChangedType == ListChangedType.ItemAdded)
			{
				// Add new Item
				addItem(e.NewIndex);
			}
			else if (e.ListChangedType == ListChangedType.ItemChanged)
			{
				// Change Item
				updateItem(e.NewIndex);
			}
			else if (e.ListChangedType == ListChangedType.ItemDeleted)
			{
				// Delete Item
				deleteItem(e.NewIndex);
			}
			else
			{
				// Update metadata and all data
				calculateColumns();
				updateAllData();
			}
		}
		#endregion

		#region Position changed from ListView
		private void ListViewDataBinding_SelectedIndexChanged(object sender, EventArgs e)
		{
			try
			{
				if (this.SelectedIndices.Count > 0 &&
					DataManager.Position != this.SelectedIndices[0])
					DataManager.Position = this.SelectedIndices[0];
			}
			catch
			{
				// Could appear, if you change the position while someone edits a row with invalid data.
			}
		}
		#endregion

		#region Item changed from ListView
		protected override void OnAfterLabelEdit(LabelEditEventArgs e)
		{
			base.OnAfterLabelEdit(e);
			if (e.Label == null)
			{
				// If you press ESC while editing.
				e.CancelEdit = true;
				return;
			}
			
			if (DataManager.List.Count > e.Item)
			{
				object row = DataManager.List[e.Item];
				// In a ListView you are only able to edit the first Column.
				PropertyDescriptor col = DataManager.GetItemProperties().Find(this.Columns[0].Text, false);
				try
				{
					if (row != null &&
						col != null)
						col.SetValue(row, e.Label);
					DataManager.EndCurrentEdit();
				}
				catch(Exception ex)
				{
					// If you try to enter strings in number-columns, too long strings or something
					// else wich is not allowed by the DataSource.
					MessageBox.Show("Edit failed:\r\n" + ex.Message, "Edit failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
					dataManager.CancelCurrentEdit();
					e.CancelEdit = true;
				}
			}
		}
		#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
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions