The Collection Editor
The Collection Editor is a special form which is controlled by the PropertyGrid in order to manage the collection properties of the current selected object.Getting the full control
In order to be able to customize the Collection Editor, you have to add a reference to System.Data to your project. First of all let's consider the following class for our collection items:C# .NET
public class MyItem
{
private string itemAttribute1;
private string itemAttribute2;
public string ItemAttribute1
{
get { return this.itemAttribute1; }
set { this.itemAttribute1 = value; }
}
public string ItemAttribute2
{
get { return this.itemAttribute2; }
set { this.itemAttribute2 = value; }
}
public MyItem(string attr1, string attr2)
{
this.itemAttribute1 = attr1;
this.itemAttribute2 = attr2;
}
}
Now we create a class for our main object that will contains the items collection and will be exposed to the PropertyGrid. Please notice the attribute I used to mark the List
C# .NET
public class MyObject
{
private string objectAttribute;
private List<MyItem> myCollection;
public string ObjectAttribute
{
get { return this.objectAttribute; }
set { this.objectAttribute = value; }
}
// This attribute will enable our customized collection
// editor for this property...
[Editor(typeof(MyCollectionEditor), typeof(UITypeEditor))]
public List<MyItem> MyCollection
{
get { return this.myCollection; }
}
public MyObject(string attr)
{
this.objectAttribute = attr;
this.myCollection = new List<MyItem>();
}
}
The customized Collection Editor
The basic idea is to get a reference to the CollectionEditor's default form and find the inner PropertyGrid in its Control collection. Once we have found the inner PropertyGrid we can hook some event handlers to it and expose the corresponding event args using some static events defined for our custom Collection Editor.C# .NET
public class MyCollectionEditor : CollectionEditor
{
// Define a static event to expose the inner PropertyGrid's
// PropertyValueChanged event args...
public delegate void MyPropertyValueChangedEventHandler(object sender,
PropertyValueChangedEventArgs e);
public static event MyPropertyValueChangedEventHandler MyPropertyValueChanged;
// Inherit the default constructor from the standard
// Collection Editor...
public MyCollectionEditor(Type type) : base(type) { }
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
// Getting the default layout of the Collection Editor...
CollectionForm collectionForm = base.CreateCollectionForm();
Form frmCollectionEditorForm = collectionForm as Form;
TableLayoutPanel tlpLayout = frmCollectionEditorForm.Controls[0] as TableLayoutPanel;
if (tlpLayout != null)
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
if (tlpLayout.Controls[5] is PropertyGrid)
{
PropertyGrid propertyGrid = tlpLayout.Controls[5] as PropertyGrid;
propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(propertyGrid_PropertyValueChanged);
}
}
return collectionForm;
}
void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
// Fire our customized collection event...
if (MyCollectionEditor.MyPropertyValueChanged != null)
{
MyCollectionEditor.MyPropertyValueChanged(this, e);
}
}
}
How to use the static events
Here is a short sample of how the custom static events should be used:C# .NET
public Form1()
{
InitializeComponent();
MyCollectionEditor.MyPropertyValueChanged += new MyCollectionEditor.MyPropertyValueChangedEventHandler (MyCollectionEditor_MyPropertyValueChanged);
MyObject obj = new MyObject("Object 1");
obj.MyCollection.Add(new MyItem("Test1", "Item1"));
obj.MyCollection.Add(new MyItem("Test2", "Item2"));
this.propertyGrid1.SelectedObject = obj;
}
void MyCollectionEditor_MyPropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
// Now you know when a collection item has been updated!
}
If you want to know how to set a PropertyGrid as read-only, please read this article: How to: Set the PropertyGrid as Read-Only
15 comments:
Great and exclusive solution!
It is what I looked for!
Great, thank you
Excellent!!! its very simple, yet very obscure to find.
The last pice a was missing.
Thank you... thank you.
FDiderot
Error 1 The type or namespace name 'CollectionEditor' could not be found (are you missing a using directive or an assembly reference?) E:\Projects\TestProjects\TestProperty\TestProperty\MyCollectionEditor.cs 8 39 TestProperty
Not compiling in .Net 2.0
You need the References System.Design to access the CollectionEditor
and
using System.ComponentModel.Design;
does this includes when a new item is added?
Thanks. This gave me the clues I needed to handle the destruction of an item by overriding CanRemoveInstance() and DestroyInstance().
This works great for property changes...but it doesn't capture the Add and Delete Click events.
Awesome. Now I get PropertyValueChanged event triggered when I change a property inside an item inside a collection. However, no event is triggered when items are added or removed. So I've used your same skeleton above but changed the event to FormClosed. This way I get notified when the user closes the collection editor.
Post here: http://alibad.wordpress.com/2010/01/12/propertgrid-collection-events/
thanks, great article!
for all .net4 users: you need to add a reference to system.design which is not available in the ".net4 client profile". you have to switch to ".net4 framework". (in project properties)
Here is a way less awkward workaround for this problem that simulates a change by invoking the MemberwiseClone function of System.Object:
public class FixedCollectionEditor : CollectionEditor
{
bool modified;
public FixedCollectionEditor(Type type) : base(type)
{ }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
value = base.EditValue(context, provider, value);
if (modified && value != null)
{
value = value.GetType()
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(value, new object[] { });
}
return value;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
TableLayoutPanel tlpLayout = collectionForm.Controls[0] as TableLayoutPanel;
foreach(Control table in collectionForm.Controls)
{
if(!(table is TableLayoutPanel)) { continue; }
foreach(Control grid in table.Controls)
{
if(!(grid is PropertyGrid)) { continue; }
((PropertyGrid)grid).PropertyValueChanged +=new PropertyValueChangedEventHandler(FixedCollectionEditor_PropertyValueChanged);
}
}
return collectionForm;
}
void FixedCollectionEditor_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
modified = true;
}
}
there is an "error" in the example above .. remove the following line:
TableLayoutPanel tlpLayout = collectionForm.Controls[0] as TableLayoutPanel;
Hi - I've tried this (and this solution has been found elsewhere across the net) but the problem is, like you, my property doesn't have a setter -- it's read only. What this means is if I specify a custom editor for this property (which is of type List) then it's greyed out on my property grid. If I do not specify a custom editor, it's not greyed out, but then I cannot implement your solution. It seems very strange that it's greyed out (almost seems like a bug) when a custom editor is specified. Any thoughts?
We need add an Assembly reference System.Design. Project->Add reference.
Post a Comment