Click here to Skip to main content
15,896,453 members
Articles / Programming Languages / C#

ControlExtender Library

Rate me:
Please Sign up or sign in to vote.
4.63/5 (12 votes)
5 Jul 20032 min read 110.7K   1.5K   49  
A component that enhances standard ListView and ListBox controls. (full designer support)
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Diagnostics;

namespace RR.Windows.Forms
{
	/// <summary>
	/// Provides a standard Winform ListBox with editing capabilities.
	/// </summary>
	[DefaultEvent("ButtonClick"),]
	public class ListBoxExEdit : System.ComponentModel.Component
	{
		private System.ComponentModel.IContainer components;

		public ListBoxExEdit(System.ComponentModel.IContainer container)
		{
			///
			/// Required for Windows.Forms Class Composition Designer support
			///
			container.Add(this);
			InitializeComponent();

		}

		public ListBoxExEdit()
		{
			///
			/// Required for Windows.Forms Class Composition Designer support
			///
			InitializeComponent();

		}

		/// <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 Component 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.components = new System.ComponentModel.Container();
			this.textBox = new RR.Windows.Forms.ListBoxExEdit.TextBoxEx();
			this.button = new System.Windows.Forms.Button();
			this.toolTip = new System.Windows.Forms.ToolTip(this.components);
			// 
			// textBox
			// 
			this.textBox.Location = new System.Drawing.Point(13, 11);
			this.textBox.Name = "textBox";
			this.textBox.TabIndex = 0;
			this.textBox.Text = "";
			this.textBox.Visible = false;
			this.textBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox_KeyDown);
			this.textBox.Leave += new System.EventHandler(this.textBox_Leave);
			// 
			// button
			// 
			this.button.FlatStyle = System.Windows.Forms.FlatStyle.System;
			this.button.Location = new System.Drawing.Point(106, 11);
			this.button.Name = "button";
			this.button.Size = new System.Drawing.Size(30, 20);
			this.button.TabIndex = 0;
			this.button.TabStop = false;
			this.button.Text = "...";
			this.button.Visible = false;
			this.button.Click += new System.EventHandler(this.button_Click);

		}
		#endregion

		private RR.Windows.Forms.ListBoxExEdit.TextBoxEx textBox;
		private System.Windows.Forms.ToolTip toolTip;
		private System.Windows.Forms.Button button;

		#region public 

		[Description("The listbox this extender works with. If the ListBox is not set, the extender has no function.")]
		public ListBox ListBox
		{
			get 
			{
				return listBox;
			}
			set 
			{
				// unhook events from old listbox
				if (listBox!=null) 
				{
					this.listBox.MouseDown -= new System.Windows.Forms.MouseEventHandler(this.listBox_MouseDown);
					this.listBox.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.listBox_MouseMove);
					this.listBox.KeyDown -= new System.Windows.Forms.KeyEventHandler(this.listBox_KeyDown);
					this.ListBox.SelectedIndexChanged -= new System.EventHandler(this.listBox_SelectedIndexChanged);

					this.listBox.Controls.Remove(this.textBox);
					this.listBox.Controls.Remove(this.button);
				}

				listBox=value;

				// hookup events and add to parents control collection
				if (listBox!=null) 
				{
					this.listBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.listBox_MouseDown);
					this.listBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.listBox_MouseMove);
					this.listBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listBox_KeyDown);
					this.ListBox.SelectedIndexChanged += new System.EventHandler(this.listBox_SelectedIndexChanged);

					this.listBox.Controls.Add(this.textBox);
					this.listBox.Controls.Add(this.button);
				}
			}
		}

		[Description("If true, a button will be showed while in edit mode."), Category("Behavior"), DefaultValue(false)]
		public bool DisplayButton 
		{
			get 
			{
				return displayButton;
			}
			set 
			{
				displayButton=value;
			}
		}

		public enum TipBehavior 
		{
			Never,
			Always,
			LongLinesOnly,
		}


		private TipBehavior showTip=TipBehavior.LongLinesOnly;

		[Description("Indicates how tooltips are displayed"), Category("Behavior"), DefaultValue(TipBehavior.LongLinesOnly)]
		public TipBehavior ShowTip 
		{
			get 
			{
				return showTip;
			}
			set 
			{
				showTip=value;
			}
		}




		[Description("The embedded button that will show when the listbox is in editmode and DisplayButton is true")]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public Button Button 
		{
			get 
			{
				return this.button;
			}
		}

		[Description("The embedded textbox that will show when the listbox is in editmode")]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public TextBox TextBox 
		{
			get 
			{
				return this.textBox;
			}
		}


		[Description("If true, the control handles navigation keys directly."), Category("Behavior"), DefaultValue(false)]
		public bool KeyNavigation 
		{
			get 
			{
				return keyNavigation;
			}
			set 
			{
				keyNavigation=value;
			}

		}

		/// <summary>
		/// insert a new line and display the textbox.
		/// </summary>
		public void NewLine()
		{	
			if (this.listBox==null) return;

			int index=(this.listBox.SelectedIndex>=0)?this.listBox.SelectedIndex:0;
			this.listBox.Items.Insert(index, "");
			this.listBox.SelectedIndex=index;
			this.DisplayEditBox(index);
		}

		/// <summary>
		/// remove the selected line from the editbox.
		/// </summary>
		public void CutLine() 
		{
			if (this.listBox==null) return;

			int index=this.listBox.SelectedIndex;

			if (index<0) return;

			this.listBox.Items.RemoveAt(index);

			if (index>=this.listBox.Items.Count) 
			{
				this.listBox.SelectedIndex=this.listBox.Items.Count-1;
			} 
			else 
			{
				this.listBox.SelectedIndex=index;
			}
		}

		/// <summary>
		/// move the selected line up
		/// </summary>
		public void MoveLineDown() 
		{
			if (this.listBox==null) return;

			int index=(this.listBox.SelectedIndex>=0)?this.listBox.SelectedIndex:0;

			// give up if already on bottom
			if (index+2>this.listBox.Items.Count) return;

			this.listBox.Items.Insert(index+2, this.listBox.Items[index]);
			this.listBox.Items.RemoveAt(index);
			
			this.listBox.SelectedIndex=index+1;
		}

		/// <summary>
		/// move the selected line down.
		/// </summary>
		public void MoveLineUp() 
		{
			if (this.listBox==null) return;

			int index=this.listBox.SelectedIndex;
			if (index<1) return;

			this.listBox.Items.Insert(index-1, this.listBox.Items[index]);
			this.listBox.Items.RemoveAt(index+1);
			this.listBox.SelectedIndex=index-1;
		}






//				private Keys lineUpShortcut=Keys. RShiftKey;
//				public Keys LineUpShortcut 
//				{
//					get 
//					{
//						return lineUpShortcut;
//					}
//					set 
//					{
//						lineUpShortcut=value;
//					}
//				}



		#endregion

		#region private

		private ListBox listBox;
		private int previousSelectedIndex=-1;
		private int lastTipIndex = -1;
		private bool displayButton=false;
		private bool keyNavigation=false;

		/// <summary>
		/// start editing text at given index.
		/// </summary>
		/// <param name="index"></param>
		public void DisplayEditBox(int index) 
		{
			Rectangle rect=listBox.GetItemRectangle(index);
			if (displayButton) 
			{
				this.textBox.Bounds=new Rectangle(rect.X, rect.Y, rect.Width-this.button.Bounds.Width, rect.Height);
				this.button.Bounds=new Rectangle(rect.X+this.textBox.Width, this.textBox.Bounds.Y, this.button.Bounds.Width, this.textBox.Bounds.Height);
			}
			else 
			{
				this.textBox.Bounds=rect;
			}

			this.button.Tag=this.textBox.Tag=index;
			this.textBox.Text=(string)listBox.Items[index];
			this.textBox.SelectionStart=this.textBox.Text.Length;

			// make it visible on give focus
			this.textBox.Visible=true;
			this.button.Visible=displayButton;
			this.textBox.Focus();
		}


		private void HandleCommonKeys(System.Windows.Forms.KeyEventArgs e) 
		{
			if (!keyNavigation) return;

			if (e.KeyCode==Keys.Insert) 
			{
				if (e.Control&&(!(e.Alt||e.Shift))) 
				{
					if (this.textBox.Visible) this.listBox.Focus();
					this.NewLine();
					e.Handled=true;
				}
			} 
			else if (e.KeyCode==Keys.Delete) 
			{
				if (e.Control&&(!(e.Alt||e.Shift)))
				{
					if (this.textBox.Visible) this.listBox.Focus();
					this.CutLine();
					e.Handled=true;
				}
			}
			else if (e.KeyCode==Keys.Down) 
			{
				if (e.Control&&(!(e.Alt||e.Shift))) 
				{
					if (this.textBox.Visible) this.listBox.Focus();
					this.MoveLineDown();
					e.Handled=true;
				}
			}
			else if (e.KeyCode==Keys.Up) 
			{
				if (e.Control&&(!(e.Alt||e.Shift))) 
				{
					if (this.textBox.Visible) this.listBox.Focus();
					this.MoveLineUp();
					e.Handled=true;
				}
			}
		}

		#endregion

		#region delegate stuff

		public class ButtonClickedEventArgs : System.EventArgs
		{
			public ButtonClickedEventArgs(string text, int index) 
			{
				this.Text=text;
				this.Index=index;
			}
			public readonly string Text;
			public readonly int Index;
		}

		public delegate void ButtonClickedEventHandler(object sender, ButtonClickedEventArgs e);

		[Description("Occurs when the embedded button is clicked."), Category("Action")]
		public event ButtonClickedEventHandler ButtonClick;

		protected virtual void OnButtonClick(ButtonClickedEventArgs e) 
		{
			if (ButtonClick!=null)
			{
				ButtonClick(this, e);
			}
		}

		public class ModifiedChangedEventArgs : System.EventArgs
		{
			public ModifiedChangedEventArgs(string oldText, string newText, int index) 
			{
				this.OldText=oldText;
				this.NewText=newText;
				this.Index=index;
			}
			public readonly string OldText;
			public readonly string NewText;
			public readonly int Index;
		}

		public delegate void ModifiedChangedEventHandler(object sender, ModifiedChangedEventArgs e);

		[Description("Event fired when the value of the embedded textbox is changed."), Category("Property Changed")]
		public event ModifiedChangedEventHandler ModifiedChanged;

		protected virtual void OnModifiedChanged(ModifiedChangedEventArgs e) 
		{
			if (ModifiedChanged!=null) 
			{
				ModifiedChanged(this, e);
			}
		}
		//OnModifiedChanged(new ModifiedChangedEventArgs("old", "new", 1));

		#endregion

		#region listview event handlers

		private void listBox_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			int index=listBox.IndexFromPoint(e.X, e.Y);

			// start edit if index is valid and same as previous index
			if ((index>=0)&&(index==previousSelectedIndex)) 
			{
				DisplayEditBox(index);
			}
		}


		private void listBox_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if (showTip==TipBehavior.Never) return;

			// don't show tips if edit is active
			if (this.textBox.Visible) return;

			int index=this.listBox.IndexFromPoint(e.X,e.Y);
			if (index==lastTipIndex) return;
			lastTipIndex=index;

			string tipText=null;
			if (index>=0) 
			{
				tipText=this.listBox.Items[index].ToString();

				if (showTip==TipBehavior.LongLinesOnly) 
				{
					Graphics graphics = this.listBox.CreateGraphics();
					// Add one character to be on the save side
					int iWidth = (int)(graphics.MeasureString(tipText+"X", this.listBox.Font).Width);
					if (iWidth<this.listBox.Width) 
					{
						tipText=null;
					}
				}
			}
			toolTip.Active = false;
			toolTip.SetToolTip(this.listBox, tipText);
			toolTip.Active = true; //make it active so it can show itself
		}


		private void listBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter) 
			{
				if (!(e.Alt||e.Control||e.Shift)) 
				{
					DisplayEditBox(listBox.SelectedIndex);
					e.Handled=true;
				}
			}
			if (!e.Handled) 
			{
				HandleCommonKeys(e);
			}
		}

		private void listBox_SelectedIndexChanged(object sender, System.EventArgs e) 
		{
			previousSelectedIndex=listBox.SelectedIndex;
		}

		#endregion

		#region textbox event handlers

		private void textBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape) 
			{
				if (!(e.Alt||e.Control||e.Shift)) 
				{
					// clear tag so leave will not copy text
					this.button.Tag=this.textBox.Tag=null;

					this.listBox.Focus();
					e.Handled=true;
				}
			} 
			else if (e.KeyCode == Keys.Enter) 
			{
				if (!(e.Alt||e.Control||e.Shift)) 
				{
					this.listBox.Focus();
					e.Handled=true;
				}
			}
			else if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Tab) 
			{
				if (!(e.Alt||e.Control)) 
				{
					bool up=(e.KeyCode==Keys.Up)||((e.KeyCode == Keys.Tab)&&e.Shift);
			
					int index=(int)(this.textBox.Tag);
			
					// copy data from textbox into list
					string oldText=(string)listBox.Items[index];
					listBox.Items[index]=this.textBox.Text;
					OnModifiedChanged(new ModifiedChangedEventArgs(oldText, this.textBox.Text, index));
			
					// decide where to move the editbox
					index+=up?-1:1;
					if (index>=listBox.Items.Count) index=0;
					if (index<0) index=listBox.Items.Count-1;
			
					// display the editbox at new index
					listBox.SelectedIndex=index;
					DisplayEditBox(index);

					e.Handled=true;
				}
			}

			if (!e.Handled) 
			{
				HandleCommonKeys(e);
			}
		}

		private void textBox_Leave(object sender, System.EventArgs e)
		{
			if (this.textBox.Tag!=null) 
			{
				// don't hide editbox and button if focus is on button  
				if (button.Visible && button.Bounds.Contains(this.listBox.PointToClient(Cursor.Position))) 
				{
					return;
				}

				this.button.Visible=this.textBox.Visible=false;

				int index=(int)(this.textBox.Tag);
			
				string oldText=(string)listBox.Items[index];
				listBox.Items[index]=this.textBox.Text;
				OnModifiedChanged(new ModifiedChangedEventArgs(oldText, this.textBox.Text, index));
			
				this.textBox.Tag=null;
			}
			else 
			{
				this.button.Visible=this.textBox.Visible=false;
			}
		}

		#endregion

		#region button event handlers
		private void button_Click(object sender, System.EventArgs e)
		{
			if (((Control)sender).Tag==null) return;
			
			int index=(int)(((Control)sender).Tag);
			
			OnButtonClick(new ButtonClickedEventArgs(textBox.Text, index));
			textBox.Focus();
		}
		#endregion

		#region private classes

		private class TextBoxEx : System.Windows.Forms.TextBox
		{	
			#region overide from control
			protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
			{
				if ((keyData==Keys.Tab)||(keyData == (Keys.Shift|Keys.Tab)))
				{
					// grab Tab and Shift-Tab before the framework takes them
					this.OnKeyDown(new KeyEventArgs(keyData));
					return true;
				}
				else if (keyData==Keys.Escape) 
				{
					// grab escape key before framework takes it
					this.OnKeyDown(new KeyEventArgs(keyData));
					return true;
				} 
				else if (keyData==Keys.Enter) 
				{
					// grab enter before someone sounds the bell
					this.OnKeyDown(new KeyEventArgs(keyData));
					return true;
				} 
				else 
				{
					// normal processing for all other keys
					return base.ProcessCmdKey (ref msg, keyData);
				}
			}
			#endregion
		}

		#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
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions