Download Source Code
I like to use the scheme authored by Tony Allowatt called Bending the .NET PropertyGrid to Your Will. It makes it much easier to create UITypeEditors of your own and incorporate them into the PropertyGrid control.
The code sample below goes into detail regarding how to easily create reusable UITypeEditors for common tasks such as ListBox, TextBox, ComboBox, or other standard control display as well as literally showing custom forms as your UITypeEditor.
Probably the most confusing aspect of working with UITypeEditors is how to pass complex data or sets of data in and out of them. In my experience, this is most easily accomplished by creating a generic custom container with collections of generic custom items. I use these custom items a lot for reusable methods to populate various controls in both a web and windows environment.
In the EggHeadCafe.Shared assembly of the sample, you'll find the classes CustomItemList and CustomItem. This forms the generic data container as a single object that can be set as the PropertyGrid item's "value". You send in the CustomItemList or CustomItem to the UITypeEditor, modify the class or classes from within the UITypeEditor, and return the whole object back to the PropertyGrid control.
The big picture to grasp here is that if you create a container with everything you need, you can pass anything you want in and out of any UITypeEditor you create. Most of the time, you can make your UITypeEditors reusable. However, there will be times when you'll want to make some that are aware of your business classes or other user interface control population methods.
When the PropertyGrid control attempts to render the selected "value", it will call the .ToString() of whichever object it has. So, you can use any object type and override it's .ToString() method to render whatever you want. You can see this in my code sample in the ListBox editor as well as the MyClassEditor via the EggHeadCafe.TypeEditors assembly. The MyClassEditor also shows how to force the PropertyGrid to raise a SetValue event on the same referenced object. It will not raise the event on its own if you change a property value on the custom class.
You'll want to review the SetPropertyGridFromRecord and SetRecordFromPropertyGrid methods to learn how to get the complex objects from the PropertyGrid control and use their values.
On a side note, I've left some code in the PropertyGridController to resize the PropertyGrid control's columns. It does have some paint and usability issues but it is there if you want to tweak it.
Here are a few screen shots followed by some of the source code



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Data;
using System.Drawing;
using System.Text;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using EggHeadCafe.WindowsForms;
using EggHeadCafe.Shared;
using EggHeadCafe.TypeEditors;
namespace Demo
{
public partial class Form3 : Form
{
private PropertyTable _recordPropertyBag = new PropertyTable();
private const string _propertyDescription = "Description";
private const string _propertyTextBox = "TextBox";
private const string _propertyHtml = "Html Editor";
private const string _propertyMyClass = "My Class";
private const string _propertyFileName = "Filename";
private const string _propertyListBox = "ListBox Editor";
public Form3()
{
InitializeComponent();
}
private void Form3_Load(object sender, EventArgs e)
{
try
{
LoadPropertyGridForRecord();
SetPropertyGridFromRecord();
// PropertyGridController.SetLabelWidth(this.propertyGridRecord,.25);
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
#region Load Property Grid For Record
private void LoadPropertyGridForRecord()
{
try
{
this._recordPropertyBag = new PropertyTable();
this._recordPropertyBag.SetValue += new PropertySpecEventHandler(RecordBag_SetValue);
this._recordPropertyBag.Properties.Add(new PropertySpec(
_propertyDescription,
typeof(string),
"Properties",
"Description"));
this._recordPropertyBag.Properties.Add(new PropertySpec(_propertyFileName,
typeof(UIFileNameEditor),
"Properties",
"Location of the file.",
"",
typeof(UIFileNameEditor),
typeof(System.Drawing.Design.UITypeEditor)));
this._recordPropertyBag.Properties.Add(new PropertySpec(_propertyTextBox,
typeof(UITextBoxEditor),
"Properties",
"Some large text value goes in this box",
"",
typeof(UITextBoxEditor),
typeof(System.Drawing.Design.UITypeEditor)));
this._recordPropertyBag.Properties.Add(new PropertySpec(_propertyHtml,
typeof(UIHtmlEditor),
"Properties",
_propertyHtml,
"some sample html",
typeof(UIHtmlEditor),
typeof(System.Drawing.Design.UITypeEditor)));
this._recordPropertyBag.Properties.Add(new PropertySpec(_propertyMyClass,
typeof(UIMyClassEditor),
"Properties",
_propertyMyClass,
"",
typeof(UIMyClassEditor),
typeof(System.Drawing.Design.UITypeEditor)));
this._recordPropertyBag.Properties.Add(new PropertySpec(_propertyListBox,
typeof(UIListBoxEditor),
"Properties",
_propertyListBox,
"",
typeof(UIListBoxEditor),
typeof(System.Drawing.Design.UITypeEditor)));
this.propertyGridRecord.SelectedObject = this._recordPropertyBag;
this.propertyGridRecord.Refresh();
}
catch (Exception) { throw; }
}
#endregion
#region Record Bag Set Value
private void RecordBag_SetValue(object sender,
PropertySpecEventArgs e)
{
try
{
SetRecordFromPropertyGrid(e.Property.Name);
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
#endregion
#region Set Record From Property Grid
private void SetRecordFromPropertyGrid(string propertyName)
{
CustomItemList list = null;
CustomItem myClass = null;
try
{
// this sample method simulates populating your data class or data set
// with data returned from the property grid.
switch (propertyName)
{
case _propertyDescription:
this.txtOutput.Text += (string)this._recordPropertyBag[propertyName] + "\r\n";
break;
case _propertyFileName:
this.txtOutput.Text += (string)this._recordPropertyBag[propertyName] + "\r\n";
break;
case _propertyTextBox:
this.txtOutput.Text += (string)this._recordPropertyBag[propertyName] + "\r\n";
break;
case _propertyHtml:
this.txtOutput.Text += (string)this._recordPropertyBag[propertyName] + "\r\n";
break;
case _propertyMyClass:
myClass = (CustomItem)this._recordPropertyBag[propertyName];
this.txtOutput.Text += myClass.Description + "\r\n";
break;
case _propertyListBox:
list = (CustomItemList)this._recordPropertyBag[propertyName];
if (list.SelectedItems != null)
{
for(int i=0;i<list.SelectedItems.Count;i++)
{
this.txtOutput.Text += list.SelectedItems[i].Description + "\r\n";
}
}
break;
default:
break;
}
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
#endregion
#region Set Property Grid From Record
private void SetPropertyGridFromRecord()
{
try
{
this._recordPropertyBag[_propertyDescription] = "text description of record";
this._recordPropertyBag[_propertyFileName] = @"c:\temp";
this._recordPropertyBag[_propertyTextBox] = "Some long text goes here";
this._recordPropertyBag[_propertyMyClass] = new CustomItem(Guid.Empty, "", false);
CustomItemList list = new CustomItemList(true);
list.Items = new List<CustomItem>();
bool selected = false;
for (int i = 0; i < 3; i++)
{
if (i == 2) { selected = true; }
list.Items.Add(new CustomItem(Guid.NewGuid(), "Test " + i.ToString(), selected));
selected = false;
}
this._recordPropertyBag[_propertyListBox] = list;
this.propertyGridRecord.SelectedObject = this._recordPropertyBag;
this.propertyGridRecord.Refresh();
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
#endregion
private void propertyGridRecord_Resize(object sender, EventArgs e)
{
// PropertyGridController.SetLabelWidth(this.propertyGridRecord, .25);
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Design;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using EggHeadCafe.Shared;
namespace EggHeadCafe.TypeEditors
{
public class UIListBoxEditor : UITypeEditor
{
IWindowsFormsEditorService _wfes = null;
private ListBox _control = null;
private CustomItemList _list = null;
public override UITypeEditorEditStyle GetEditStyle(
ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
_wfes = provider.GetService(typeof(IWindowsFormsEditorService))
as IWindowsFormsEditorService;
if (_wfes == null) { return value; }
if (value == null) { return value; }
_control = new ListBox();
_control.Name = "listbox1";
_control.Height = 325;
_control.Width = 300;
_control.Dock = DockStyle.Fill;
_control.SelectedIndexChanged += new EventHandler(ListItemSelected);
_list = (CustomItemList)value;
_control.Height = _list.Height;
_control.Width = _list.Width;
if (_list.EnableMultiSelect)
{
_control.SelectionMode = SelectionMode.MultiExtended;
}
for (int i = 0; i < _list.Items.Count; i++)
{
_control.Items.Add(_list.Items[i]);
_control.SetSelected(i, _list.Items[i].Selected);
}
_wfes.DropDownControl(_control);
RefreshList();
value = _list;
return value;
}
private void RefreshList()
{
CustomItem item = null;
_list = new CustomItemList(_list.EnableMultiSelect,_list.Height,_list.Width);
for (int i = 0; i < _control.Items.Count; i++)
{
item = (CustomItem)_control.Items[i];
item.Selected = _control.GetSelected(i);
_list.Items.Add(item);
}
}
private void ListItemSelected(object sender,EventArgs e)
{
switch (_control.SelectionMode)
{
case SelectionMode.MultiExtended:
case SelectionMode.MultiSimple:
break;
default:
_wfes.CloseDropDown();
break;
}
}
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Design;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using EggHeadCafe.Shared;
namespace EggHeadCafe.TypeEditors
{
public class UIMyClassEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(
ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
#region Edit Value
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
IWindowsFormsEditorService wfes = provider.GetService(
typeof(IWindowsFormsEditorService)) as
IWindowsFormsEditorService;
if (wfes == null) { return value; }
UIMyClassEditorForm form = new UIMyClassEditorForm();
if (value != null)
{
form.EditorValue = (CustomItem)value;
}
form._wfes = wfes;
wfes.DropDownControl(form);
// Force the property grid to accept your referenced "equal"
// copy of the object and raise the SetValue event.
context.PropertyDescriptor.SetValue(this,form.EditorValue);
value = form.EditorValue;
return value;
}
#endregion
}
}using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Reflection;
using System.Text;
namespace EggHeadCafe.WindowsForms
{
public class PropertyGridController
{
// This is a copied hack to set the property grid label width I found somewhere
// online. However, it does come with some usability issues with where
// the user has to click to launch the editor and some paint issues.
// I'll placed it in here for you to play with.
#region Set Label Width
public static void SetLabelWidth(PropertyGrid propertyGrid, int width)
{
Control grid = propertyGrid;
Control propertyGridView = grid.Controls[2];
Type propertyGridViewType = propertyGridView.GetType();
FieldInfo fldLabelWidth = propertyGridViewType.GetField("labelWidth",
BindingFlags.Instance | BindingFlags.NonPublic);
fldLabelWidth.SetValue(propertyGridView, width);
}
#endregion
#region Set Label Width
public static void SetLabelWidth(PropertyGrid propertyGrid, double percentage)
{
try
{
Control propertyGridView = propertyGrid.Controls[2];
Type propertyGridViewType = propertyGridView.GetType();
FieldInfo fldLabelWidth = propertyGridViewType.GetField("labelWidth",
BindingFlags.Instance
| BindingFlags.NonPublic);
if (propertyGridView.Width < 1) { return; }
fldLabelWidth.SetValue(propertyGridView, (int)((propertyGridView.Width * percentage)));
propertyGrid.Invalidate();
}
catch (Exception) { throw; }
}
#endregion
}
}
The rest of the code is in the download sample.