Click here to Skip to main content
15,894,955 members
Articles / Desktop Programming / Windows Forms

Edit Almost Anything in a DataGrid

Rate me:
Please Sign up or sign in to vote.
4.92/5 (52 votes)
27 Feb 20054 min read 353.6K   7.3K   256  
Allow your DataGrid to edit pictures and a whole range of other data types.
//
// SekosPD.Windows.Forms Copyright Sekos Technology, www.sekos.com
// This source has been released to accompany the "Edit almost anything" series of articles.
//
// Based on portions of AgileStudio.
// AgileStudio extends Visual Studio .NET to accelerate the development of Windows and Web database applications.
// Free evaluations are available for download from www.sekos.com
//
// You are granted unlimited rights to use this code unaltered or modified
// in your own projects including commercial ones provided:
// (a) You do not remove this copyright message from any source files.
// (b) You include a link to the Sekos Website (www.sekos.com) in an appropriate
//     place in your product, for example an "About Box".
//
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Drawing.Design;
using System.ComponentModel.Design.Serialization;

namespace SekosPD.Windows.Forms
{
	/// <summary>
	/// This control allows the editing of any type that has a TypeConverter.
	/// The value property can be bound either to a property of another control or to a column in a dataset.
	/// In the latter case the UseStringAsUnderlyingType is set to true as DataSet currently only handles
	/// a small set of types.
	/// </summary>
	[
	ToolboxBitmap(typeof(PropertyEditor),"PropertyEditor"),
	DefaultProperty("Value")
	]
	public class PropertyEditor : System.Windows.Forms.UserControl, IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext, System.ComponentModel.ISupportInitialize
	{

		/// <summary>
		/// Initialize a new copy of the PropertyEditor control
		/// </summary>
		public PropertyEditor()
		{
			this.SetStyle(ControlStyles.FixedHeight|ControlStyles.Selectable,true);
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();
			this.DropDown.Visible= false;
		}

		/// <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();
			System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(PropertyEditor));
			this.DropDown = new System.Windows.Forms.Button();
			this.imageList1 = new System.Windows.Forms.ImageList(this.components);
			this.textBox1 = new TextBox();
			this.SuspendLayout();
			// 
			// DropDown
			// 
			this.DropDown.BackColor = System.Drawing.SystemColors.Control;
			this.DropDown.ImageIndex = 0;
			this.DropDown.ImageList = this.imageList1;
			this.DropDown.Location = new System.Drawing.Point(133, 1);
			this.DropDown.Name = "DropDown";
			this.DropDown.TabIndex = 0;
			this.DropDown.TabStop = false;
			this.DropDown.Click += new System.EventHandler(this.DropDown_Click);
			// 
			// imageList1
			// 
			this.imageList1.ImageSize = new System.Drawing.Size(16, 16);
			this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream")));
			this.imageList1.TransparentColor = System.Drawing.Color.Transparent;
			// 
			// textBox1
			// 
			this.textBox1.BorderStyle = System.Windows.Forms.BorderStyle.None;
			this.textBox1.Location = new System.Drawing.Point(0, 0);
			this.textBox1.Name = "textBox1";
			this.textBox1.Size = new System.Drawing.Size(132, 15);
			this.textBox1.TabIndex = 1;
			this.textBox1.Text = "";
			this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox1_KeyDown);
			this.textBox1.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.textBox1_KeyPress);
			this.textBox1.DoubleClick += new System.EventHandler(this.textBox1_DoubleClick);
			this.textBox1.Leave += new System.EventHandler(this.textBox1_Leave);
			// 
			// PropertyEditor
			// 
			this.BackColor = System.Drawing.SystemColors.Window;
			this.Controls.Add(this.textBox1);
			this.Controls.Add(this.DropDown);
			this.DockPadding.All = 1;
			this.Name = "PropertyEditor";
			this.Size = new System.Drawing.Size(150, 16);
			this.ResumeLayout(false);

		}
		#endregion

		#region Events
		/// <summary>
		/// Occurs whenever the Value property is changed
		/// </summary>
		public event EventHandler ValueChanged;

		/// <summary>
		/// Raises the ValueChanged event
		/// </summary>
		protected virtual void OnValueChanged()
		{
			if (ValueChanged!=null)
				ValueChanged(this,EventArgs.Empty);
		}

		/// <summary>
		/// Occurs when the PropertyType property is changed
		/// </summary>
		public event EventHandler PropertyTypeChanged;

		/// <summary>
		/// Raised the PropertyTypeChanged event
		/// </summary>
		protected virtual void OnPropertyTypeChanged()
		{
			if (PropertyTypeChanged!=null)
				PropertyTypeChanged(this,EventArgs.Empty);
		}

		/// <summary>
		/// Occurs when the UseStringAsUnderlyingType property is changed
		/// </summary>
		public event EventHandler UseStringAsUnderlyingTypeChanged;

		/// <summary>
		/// Raise the UseStringAsUnderlyingType event
		/// </summary>
		protected virtual void OnUseStringAsUnderlyingTypeChanged()
		{
			if (UseStringAsUnderlyingTypeChanged!=null)
				UseStringAsUnderlyingTypeChanged(this,EventArgs.Empty);
		}

		#endregion events

		#region Properties
		/// <summary>
		/// Returns the PreferredHeight of this control
		/// </summary>
		[Browsable(false)]
		public int PreferredHeight
		{
			get
			{
				return this.textBox1.PreferredHeight+4;
			}
		}

		/// <summary>
		/// The type of the property that is being edited
		/// </summary>
		[
		TypeConverter(typeof(TypeTypeConverter)),
		Category("Appearance"),
		Description("The type of the property that is being editing.")
		]
		public Type PropertyType
		{
			get
			{
				return mType;
			}
			set
			{
				if (mType!=value)
				{
					mType= value;
					if (mType!=null)
					{
						mEditor= (UITypeEditor)TypeDescriptor.GetEditor(mType,typeof(UITypeEditor));
						mPaintValueSupported= mEditor!=null && mEditor.GetPaintValueSupported(this as ITypeDescriptorContext);
						mConverter= TypeDescriptor.GetConverter(mType);
						mStandardValues= null;
						if (mConverter!=null && this.mConverter.GetStandardValuesSupported())
						{
							mStandardValues= this.mConverter.GetStandardValues(this as ITypeDescriptorContext);
							mStandardValuesExclusive= this.mConverter.GetStandardValuesExclusive();
						}
						System.Drawing.Design.UITypeEditorEditStyle editStyle= System.Drawing.Design.UITypeEditorEditStyle.None;
						if (mEditor!=null)
							editStyle= mEditor.GetEditStyle(this as ITypeDescriptorContext);
						else if (mConverter!=null)
							if (mConverter.GetStandardValuesSupported())
								editStyle= System.Drawing.Design.UITypeEditorEditStyle.DropDown;
						if (editStyle==System.Drawing.Design.UITypeEditorEditStyle.None)
							this.DropDown.Visible= false;
						else
						{
							this.DropDown.Visible= true;
							if (editStyle==System.Drawing.Design.UITypeEditorEditStyle.Modal)
								this.DropDown.ImageIndex= 1;
							else
								this.DropDown.ImageIndex= 0;
						}
						if (mDropDownList!=null) // Make sure its refreshed if necessary
						{
							mDropDownList.Dispose();
							mDropDownList= null;
						}
						object v= Value;
						if (v!=null && v.GetType()!=mType)
							this.Value= null;
						this.PerformLayout();
					}
					OnPropertyTypeChanged();
				}
			}
		}

		/// <summary>
		/// Should the object be converted to a string before being written back to the data source?
		/// </summary>
		[
		Category("Data"),
		Description("Should the object be converted to a string before being written back to the data source?")
		]
		public bool UseStringAsUnderlyingType
		{
			get
			{	return mUseStringAsUnderlyingType;
			}
			set
			{	mUseStringAsUnderlyingType= value;
				OnUseStringAsUnderlyingTypeChanged();
			}
		}	

		/// <summary>
		/// The value of the data that is being edited
		/// </summary>
		[
		Category("Data"),
		Description("The value of the data that is being edited")
		]
		public object Value
		{
			get
			{
				if (mUseStringAsUnderlyingType)
				{
					if (mObject==null)
						return "";
					else
						return mConverter.ConvertToInvariantString(mObject);
				}
				else
					return (mObject==null && ! DesignMode)?Convert.DBNull:mObject;
			}
			set
			{
				if (mInitializing)
				{	mDelayedValue= value;
					return;
				}
				object newObject= null;
				if (value==Convert.DBNull)
					newObject= null;
				else if (mUseStringAsUnderlyingType)
				{
					string s= value as String;
					if (s!=null)
					{
						try
						{
							newObject= mConverter.ConvertFromInvariantString(s);
						}
						catch
						{
						}
					}
				}
				else
					newObject= value;
				if (newObject!=mObject)
				{	mObject= newObject;

					if (mObject!=null && mConverter!=null)
					{
						this.textBox1.Text= mConverter.ConvertToString(mObject);
					}
					else
						this.textBox1.Text="";
					OnValueChanged();
					this.Invalidate();
				}
			}
		}
		#endregion Properties

		#region public methods
		/// <summary>
		/// Called to achieve a rendering of a PropertyEditor control onto a Graphics object for a specified data object
		/// This is called by DataGridPropertyEditorColumn to draw all cells that don't have the input focus
		/// </summary>
		/// <param name="g">The graphics object to draw to</param>
		/// <param name="rect">The bounding Rectangle to paint into.</param>
		/// <param name="data">The data object to render</param>
		/// <param name="foreBrush">The brush with which to render the Foreground</param>
		/// <param name="format">The StringFormat used to layout text</param>
		public void DrawData(Graphics g,Rectangle rect,object data,Brush foreBrush,StringFormat format)
		{
			if (mEditor!=null)
			{
				rect.Offset(0, 2);
				rect.Height -= 2;

				if (data==null || data==System.DBNull.Value)
					return;
				if (this.mUseStringAsUnderlyingType)
					data= mConverter.ConvertFromInvariantString((string)data);
				if (this.mPaintValueSupported)
				{
					Rectangle paintRectangle= new Rectangle(rect.Left,rect.Top-1,mHeight,mHeight);
					paintRectangle.Inflate(new Size(-2,-2));
					paintRectangle.Height-= 1;
					mEditor.PaintValue(data,g,paintRectangle);
					g.DrawRectangle(System.Drawing.Pens.Black,paintRectangle.Left,paintRectangle.Top,paintRectangle.Width,paintRectangle.Height);
					rect= new Rectangle(rect.Left+mHeight,rect.Top,rect.Width-mHeight,mHeight);
				}
				
				g.DrawString(mConverter.ConvertToString(data), this.Font, foreBrush, rect, format);
			}
		}

		#endregion public methods

		#region overriden functionality

		/// <summary>
		/// Override OnPaint to handle 
		/// </summary>
		/// <param name="e">Arguments for the PaintEvent</param>
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint (e);
			if (mPaintValueSupported && this.Enabled)
			{
				Rectangle r= mPaintRectangle;
				System.Drawing.Graphics g= e.Graphics;
				mEditor.PaintValue(mObject,g,r); 
				g.DrawRectangle(System.Drawing.Pens.Black,r.Left,r.Top,r.Width,r.Height);
			}
		}
		#endregion overridden functionality

		/// <summary>
		/// Overridden to draw a 3D border for this control
		/// </summary>
		/// <param name="pevent">The arguments for the Paint event</param>
		protected override void OnPaintBackground(PaintEventArgs pevent)
		{
			base.OnPaintBackground (pevent);
			ControlPaint.DrawBorder3D(pevent.Graphics,new Rectangle(Point.Empty,this.Size),System.Windows.Forms.Border3DStyle.Sunken);
		}

		/// <summary>
		/// Overriden to ensure the children of this control are layed out correctly
		/// </summary>
		/// <param name="levent">The arguments for the Layout event</param>
		protected override void OnLayout(LayoutEventArgs levent)
		{
			base.OnLayout (levent);
			if (this.Height!= this.textBox1.Height+8)
				this.Height= this.textBox1.Height+8;
			Rectangle clientRect= this.ClientRectangle;
			clientRect.Inflate(new Size(-1,-1));
			int left= clientRect.Left;
			int top= clientRect.Top;
			int width= clientRect.Width;
			int height= clientRect.Height;
			int buttonWidth= 0;
			if (this.DropDown.Visible)
				buttonWidth= height;
			mHeight= height;
			if (this.mPaintValueSupported)
			{
				mPaintRectangle= new Rectangle(left,top,height,height);
				mPaintRectangle.Inflate(new Size(-2,-2));
				mPaintRectangle.Height-= 1;
				left+= height;
				width-= height;
			}
			int textWidth= width;
			if (this.DropDown.Visible)
				textWidth-= height;
			else
				textWidth-= 1;
			
			// A bug in Window.Forms.TextBox causes it to calculate client height incorrectly when the BorderStyle is set to none
			// This causes descenders to get clipped.
			this.textBox1.SetBounds(left+2,top+2,textWidth-2,height);
			
			this.DropDown.SetBounds(left+textWidth+1,top+1,height-2,height-2);
			this.Invalidate();
		}

		/// <summary>
		/// Overriden to ensure that the DropDownList will be rerendered if the ForeColor is changed
		/// </summary>
		/// <param name="e">The arguments for this event</param>
		protected override void OnForeColorChanged(EventArgs e)
		{
			base.OnForeColorChanged (e);
			if (mDropDownList!=null)
			{
				mDropDownList.Dispose();
				mDropDownList= null;
			}
		}

		/// <summary>
		/// Overriden to ensure that the DropDownList will be rerendered if the BackColor is changed
		/// </summary>
		/// <param name="e">The arguments for this event</param>
		protected override void OnBackColorChanged(EventArgs e)
		{
			base.OnBackColorChanged (e);
			if (mDropDownList!=null)
			{
				mDropDownList.Dispose();
				mDropDownList= null;
			}
		}


		/// <summary>
		/// Overriden to ensure that the DropDownList will be rerendered if the Font is changed
		/// </summary>
		/// <param name="e">The arguments for this event</param>
		protected override void OnFontChanged(EventArgs e)
		{
			base.OnFontChanged (e);
			if (mDropDownList!=null)
			{
				mDropDownList.Dispose();
				mDropDownList= null;
			}
		}

		/// <summary>
		/// Raise the EnabledChanged event
		/// Overriden here to change appearance by altering the status of the children
		/// </summary>
		/// <param name="e">The arguments for the event</param>
		protected override void OnEnabledChanged(EventArgs e)
		{
			base.OnEnabledChanged (e);
			if (this.Enabled)
			{
				this.textBox1.Visible= true;
				this.DropDown.Enabled= true;
				if (mHasPreviousBackColor)
				{
					this.BackColor= mPreviousBackColor;
					mHasPreviousBackColor= false;
				}
			}
			else
			{	this.textBox1.Visible= false;
				this.DropDown.Enabled= false;
				mPreviousBackColor= this.BackColor;
				mHasPreviousBackColor= true;
				this.BackColor= SystemColors.Control;
			}
		}

		bool mHasPreviousBackColor= false;
		System.Drawing.Color mPreviousBackColor;

		#region ISupportInitialize Members

		/// <summary>
		/// Signals PropertyEditor that initialization is starting.
		/// </summary>
		public void BeginInit()
		{
			mInitializing= true;
		}

		/// <summary>
		/// Signals PropertyEditor that initialization is complete.
		/// </summary>
		public void EndInit()
		{
			mInitializing= false;
			// We can now act upon the DelayedValue as we can be sure that PropertyType has been assigned. 
			if (mDelayedValue!=null)
				this.Value= mDelayedValue;
			mDelayedValue= null;
		}

		#endregion

		#region IWindowsFormsEditorService Members

		/// <summary>
		/// Displays the specified control in a drop down area below a value field of the property grid that provides this service.
		/// </summary>
		/// <param name="control">The control to display in the dropdown</param>
		public void DropDownControl(Control control)
		{
			Point p= DropDown.Location;
			p.Y+= DropDown.Height;
			p.X+= DropDown.Width;
			
			Point l= this.PointToScreen(p);
			System.Windows.Forms.Form frm= new Form();

			frm.StartPosition= System.Windows.Forms.FormStartPosition.Manual;
			
			Size size= control.Size;
			frm.Size= size;
			l.X-= (frm.Width-1);
			frm.Location= l;
			frm.FormBorderStyle= FormBorderStyle.None;
			frm.ShowInTaskbar= false;
			frm.BackColor= System.Drawing.SystemColors.ActiveCaption;
			control.Location= new System.Drawing.Point(0,0);
			control.Size= frm.Size;
			control.Layout+=new LayoutEventHandler(control_Layout);
			System.Windows.Forms.Panel panel= new System.Windows.Forms.Panel();
			panel.BorderStyle= System.Windows.Forms.BorderStyle.FixedSingle;
			panel.Dock= DockStyle.Fill;
			frm.Controls.Add(panel);
			panel.Controls.Add(control);
			mDropDownFrm= frm;
			frm.Deactivate+=new EventHandler(frm_Deactivate);
			frm.Show();
			doModalLoop();
			panel.Controls.Remove(control);
			frm.Close();
		}


		const int defaultHeight= 95;
		private void control_Layout(object sender, LayoutEventArgs e)
		{
			if (mDropDownFrm!=null)
			{
				System.Drawing.Size size= ((Control)sender).Size;
				
				if (sender==mDropDownList)
				{
					int height= defaultHeight;
					int maxHeight= mDropDownList.ItemHeight*mDropDownList.Items.Count+4;
					if (height>maxHeight)
						height= maxHeight;
					if (size.Height!=height)
					{
						size.Height= height;
						mDropDownList.Size= size;
					}
				}
				
				mDropDownFrm.Size= size;
			}
		}

		/// <summary>
		/// Closes any previously opened drop down control area.
		/// </summary>
		public void CloseDropDown()
		{
			if (mDropDownFrm!=null)
				mDropDownFrm.Visible= false;
		}

		/// <summary>
		/// Shows the specified Form.
		/// </summary>
		/// <param name="dialog">The form to show</param>
		/// <returns></returns>
		public System.Windows.Forms.DialogResult ShowDialog(Form dialog)
		{
			// throw new NotSupportedException("ExpressionEditor doesn't support a Dialog item editor");
			// return new System.Windows.Forms.DialogResult ();
			return dialog.ShowDialog(this.TopLevelControl);
		}

		#endregion IWindowsFormsEditorService Members

		#region IServiceProvider Members

		/// <summary>
		/// Gets the service object of the specified type.
		/// </summary>
		/// <param name="serviceType">The type of service object to get</param>
		/// <returns>A service object of type serviceType or null if no such service is available</returns>
		object IServiceProvider.GetService(Type serviceType)
		{
			if (serviceType==typeof(IWindowsFormsEditorService))
				return this as IWindowsFormsEditorService;
			else
				return base.GetService(serviceType);
		}

		#endregion

		#region ITypeDescriptorContext Members

		/// <summary>
		/// Returns a value indicating whether the component can be changed
		/// </summary>
		/// <returns>true if the component can be changed</returns>
		bool ITypeDescriptorContext.OnComponentChanging()
		{
			return false;
		}

		/// <summary>
		/// Raises the ComponentChanged event.
		/// </summary>
		void ITypeDescriptorContext.OnComponentChanged()
		{
		}

		/// <summary>
		/// Gets the container representing this TypeDescriptor request
		/// </summary>
		IContainer ITypeDescriptorContext.Container
		{
			get
			{
				return null;
			}
		}

		/// <summary>
		/// Gets the instance of the object that is connected with this TypeDescriptor request
		/// </summary>
		object ITypeDescriptorContext.Instance
		{
			get
			{
				return null;
			}
		}

		/// <summary>
		/// Gets the PropertyDescriptor that describes the given context item.
		/// </summary>
		PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
		{
			get
			{
				return null;
			}
		}

		#endregion ITypeDescriptorContext

		#region private functionality
		/// <summary>
		/// Event handler to ensure the dropdown is closed if the form containing this control looses activation
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void frm_Deactivate(object sender, EventArgs e)
		{
			CloseDropDown();
		}

		/// <summary>
		/// Return the ListBox used as a dropdown for this control if the standard values exist
		/// </summary>
		private System.Windows.Forms.ListBox dropDownList
		{
			get
			{
				if (mDropDownList==null && mStandardValues!=null)
				{
					mDropDownList= new System.Windows.Forms.ListBox();
					foreach (object obj in this.mStandardValues)
						mDropDownList.Items.Add(obj);
					mDropDownList.MouseUp+=new MouseEventHandler(dropDownList_MouseUp);
					mDropDownList.KeyPress+= new KeyPressEventHandler(dropDownList_KeyPress);
					mDropDownList.Font= this.Font;
					mDropDownList.ForeColor= this.ForeColor;
					mDropDownList.BackColor= this.BackColor;
					mDropDownList.Size= new System.Drawing.Size(this.Size.Width, 95);
					mDropDownList.IntegralHeight= true;
				}
				return mDropDownList;
			}
		}

		/// <summary>
		/// Event handler that is called when the dropdown is clicked upon
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void DropDown_Click(object sender, System.EventArgs e)
		{
			if (mEditor!=null)
			{
				mObject= mEditor.EditValue(this as ITypeDescriptorContext,this as IServiceProvider,mObject);
				if (mObject!=null)
					this.textBox1.Text= mConverter.ConvertToString(mObject);
				else
					this.textBox1.Text= "";
				OnValueChanged();
				this.Invalidate();
			}
			else if (mStandardValues!=null)
			{
					dropDownList.SelectedItem= this.Value;
				this.DropDownControl(dropDownList);
			}
		}
		
		/// <summary>
		/// Handler for MouseUp event for the DropDownList
		/// Used to update the Value property
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void dropDownList_MouseUp(object sender, MouseEventArgs e)
		{
			this.Value= mDropDownList.SelectedItem;
			this.CloseDropDown();
		}

		/// <summary>
		/// Handler for the KeyPress event for textBox1
		/// Used to locate a match in the StandardValues if these exist
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
		{
			if (mStandardValues!=null && mStandardValuesExclusive)
			{
				string key= new string(e.KeyChar,1);
				string lowerKey= key.ToLower();
				string upperKey= key.ToUpper();

				bool foundCurrent= false;
				object firstMatch= null;
				object match= null;
				foreach (object obj in mStandardValues)
				{
					if (obj==mObject)
						foundCurrent= true;
					else
					{
						string s= obj.ToString();
						if (s.StartsWith(lowerKey)||s.StartsWith(upperKey))
						{
							if (foundCurrent)
							{
								match= obj;
								break;
							}
							else if (firstMatch==null)
								firstMatch= obj;
						}
					}
				}
				if (match==null)
					match=firstMatch;
				if (match!=null)
				{
					this.Value= match;
					this.textBox1.SelectAll();
				}
				e.Handled= true;
			}
		}

		/// <summary>
		/// Handler for the DoubleClick event for textBox1
		/// Used to move to the next match in the Standard Values if these exist
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void textBox1_DoubleClick(object sender, System.EventArgs e)
		{
			if (mStandardValues!=null)
			{
				object firstObj= null;
				bool wantNextObj= false;
				foreach (object obj in mStandardValues)
				{
					if (wantNextObj)
					{
						wantNextObj= false;
						this.Value= obj;
						break;
					}
					if (firstObj==null)
						firstObj= obj;
					if (obj.Equals(mObject))
						wantNextObj= true;
				}
				if (wantNextObj)
					this.Value= firstObj;
				this.textBox1.SelectAll();
			}
		}

		/// <summary>
		/// The handler for the KeyPress event for the dropDownList
		/// Used to close the dropDown if the return key has been pressed
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void dropDownList_KeyPress(object sender, KeyPressEventArgs e)
		{
			if ((Keys)e.KeyChar==Keys.Return)
			{
				this.Value= mDropDownList.SelectedItem;
				this.CloseDropDown();
				e.Handled= true;
			}
		}

		/// <summary>
		/// The handler for the KeyDown event for textBox1
		/// Used to select an item in the DropDown, if any, when the F4 key is pressed
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void textBox1_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode==Keys.F4 && this.DropDown.Visible)
				this.DropDown.PerformClick();
		}

		/// <summary>
		/// This event handler that is called when the contained textbox looses the input focus
		/// It ensures that the value object is updated internally
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void textBox1_Leave(object sender, System.EventArgs e)
		{
			object newObject= null;
			if (mConverter!=null)
			{
				try
				{
					string s= this.textBox1.Text;
					if (s.Length>0)
					{
						newObject= mConverter.ConvertFromString(s);
					}
					else
					{
						newObject= null;
					}
				}
				catch
				{
					newObject= mObject;
					mObject= null;
				}
			}
			if (newObject!=mObject)
			{
				mObject= newObject;

				if (mObject!=null && mConverter!=null)
				{
					this.textBox1.Text= mConverter.ConvertToString(mObject);
				}
				else
					this.textBox1.Text="";
				this.OnValueChanged();
				this.Invalidate();
			}
		}

		/// <summary>
		/// The MsgWaitForMultipleObjects function returns when one of the following occurs:
		///		Either any one or all of the specified objects are in the signaled state. The objects can include input event objects, which you specify using the dwWakeMask parameter.
		///		Or the time-out interval elapses. 
		/// </summary>
		/// <param name="nCount">Number of object handles</param>
		/// <param name="pHandles">Pointer to an array of handles</param>
		/// <param name="bWaitAll">If this parameter is TRUE, the function returns when the states of all objects in the pHandles array have been set to signaled and an input event has been received. 
		/// If this parameter is FALSE, the function returns when the state of any one of the objects is set to signaled or an input event has been received. In this case, the return</param>
		/// <param name="dwMilliseconds">Time-out interval, in milliseconds</param>
		/// <param name="dwWakeMask">Input types for which an input event object handle will be added to the array of object handles</param>
		/// <returns></returns>
		[System.Runtime.InteropServices.DllImport("User32",SetLastError=true)]
		private static extern Int32 MsgWaitForMultipleObjects(Int32 nCount,IntPtr pHandles,Int16 bWaitAll,Int32 dwMilliseconds,Int32 dwWakeMask);

		/// <summary>
		/// Called by DropDownControl to implement a modal loop
		/// </summary>
		private void doModalLoop()
		{
			// Based on http://www.vbinfozine.com/a_colorpicker.shtml
			while (mDropDownFrm.Visible)
			{
				Application.DoEvents();
				MsgWaitForMultipleObjects(1,IntPtr.Zero,1,5,255);
			}
		}

		/// <summary>
		///	The contained dropdown/popup button 
		/// </summary>
		private System.Windows.Forms.Button DropDown;
		/// <summary>
		/// The contained textbox
		/// </summary>
		private TextBox textBox1;
		/// <summary>
		/// The type being edited
		/// </summary>
		private Type mType= null;
		/// <summary>
		/// The editor appropriate for this type
		/// </summary>
		private UITypeEditor mEditor;
		/// <summary>
		/// The TypeConverter appropriate for this type
		/// </summary>
		private TypeConverter mConverter;
		/// <summary>
		/// The dropdown form if any
		/// </summary>
		private System.Windows.Forms.Form mDropDownFrm;
		/// <summary>
		/// The value object
		/// </summary>
		private object mObject;
		/// <summary>
		/// The images for the dropdown button
		/// </summary>
		private System.Windows.Forms.ImageList imageList1;
		/// <summary>
		/// The component container
		/// </summary>
		private System.ComponentModel.IContainer components;
		private System.Windows.Forms.ListBox mDropDownList;
		private System.ComponentModel.TypeConverter.StandardValuesCollection mStandardValues= null;
		private bool mStandardValuesExclusive= false;
		private bool mPaintValueSupported= false;
		private Rectangle mPaintRectangle;
		private bool mInitializing= false;
		private object mDelayedValue= null;
		private bool mUseStringAsUnderlyingType= false;
		private int mHeight;

		#endregion Private Functionality		
	}

	/// <summary>
	/// A TypeConverter intended for objects of type "Type", for example the PropertyType property
	/// </summary>
	public class TypeTypeConverter: TypeConverter
	{
		/// <summary>
		/// Returns whether this converter can convert an object of one type to the type of this converter.
		/// </summary>
		/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
		/// <param name="sourceType">A Type that represents the type you want to convert from.</param>
		/// <returns>true if the conversion is possible</returns>
		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType==typeof(string))
				return true;
			return base.CanConvertFrom (context, sourceType);
		}

		/// <summary>
		/// Returns whether this converter can convert the object to the specified type.
		/// </summary>
		/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
		/// <param name="destinationType">A Type that represents the type you want to convert to.</param>
		/// <returns></returns>
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType==typeof(string))
				return true;
			return base.CanConvertTo (context, destinationType);
		}

		/// <summary>
		/// Converts the given object to the type of this converter, using the specified context and culture information
		/// This overrides the base method to handle the conversion from a string representing a type name to a Type
		/// </summary>
		/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
		/// <param name="culture">The CultureInfo to use as the current culture.</param>
		/// <param name="value">The Object to convert.</param>
		/// <returns>An Object that represents the converted value.</returns>
		public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
		{
			if (value is string)
			{
				System.ComponentModel.Design.ITypeResolutionService typeResolver= (System.ComponentModel.Design.ITypeResolutionService)context.GetService(typeof(System.ComponentModel.Design.ITypeResolutionService));
				Type t= null;
				string typeName= (string)value;
				if (typeResolver!=null)
					t= typeResolver.GetType(typeName);
				else
				{
					// In the unlikely case that this property is bound to another PropertyEditor
					// so it can be changed at runtime, ITypeResolutionService won't be available
					// so we resort to GetType instead.
					t= Type.GetType(typeName);
					// Hard Code System.Drawing in as an additional assembly to search
					if (t==null)
						t= typeof(System.Drawing.Color).Assembly.GetType(typeName);
				}
				return t;
			}
			return base.ConvertFrom (context, culture, value);
		}

		/// <summary>
		/// Converts the given value object to the specified type, using the specified context and culture information.
		/// This overrides the base method to add functionality to handle InstanceDescriptors used to output code to the InitComponents method
		/// </summary>
		/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
		/// <param name="culture">The CultureInfo to use as the current culture.</param>
		/// <param name="value">The Object to convert.</param>
		/// <param name="destinationType">The Type to convert the value parameter to. </param>
		/// <returns>An Object that represents the converted value.</returns>
		public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType==typeof(string))
				return value.ToString();
			else if (destinationType==typeof(InstanceDescriptor))
			{
				return new InstanceDescriptor(typeof(Type).GetConstructor(new Type[1]{ typeof(string) }),new object[]{ value.ToString() });
			}

			return base.ConvertTo (context, culture, value, destinationType);
		}
	}
}

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
Web Developer
Ireland Ireland
Declan Brennan is Chief Architect for Sekos Technology (www.sekos.com). Over the years he has worked in a huge range of technologies and environments and still gets a great thrill from the magic of computers.

Comments and Discussions