I'm trying to use the DataGridView to display a list of objects. In the class that I want to present properties for I have some C# properties and I also want to create properties dynamically for some reasons.
Here I have an example, that works fine for the C# property (FeatureId) but the dynamically created property (Name) returns the value of the first instance for all the instances. Why?
First a class that implements the ICustomPropertyDescriptor interface
public abstract class PropertyPresentationSubBase : ICustomTypeDescriptor
{
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public virtual PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);
//rtn = FilterReadonly(rtn, attributes);
return new PropertyDescriptorCollection(rtn.Cast<PropertyDescriptor>().ToArray());
}
public virtual PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
[Browsable(false)]
public PropertyPresentationSubBase Parent
{
get
{
return m_Parent;
}
set
{
m_Parent = value;
}
}
PropertyPresentationSubBase m_Parent = null;
[Browsable(false)]
public Type ValueType
{
get
{
return valueType;
}
set
{
valueType = value;
}
}
private Type valueType = null;
[Browsable(false)]
public string Name
{
get
{
return sName;
}
set
{
sName = value;
}
}
public abstract object GetValue();
private string sName = string.Empty;
public abstract void Change(object value);
}
}
I also have a class that inherit from PropertyDescriptor
public class MyCustomPropertyDescriptor : PropertyDescriptor
{
PropertyPresentationSubBase m_Property;
public MyCustomPropertyDescriptor(PropertyPresentationSubBase myProperty, Attribute[] attrs, int propertyNo)
: base(myProperty.Name + propertyNo, attrs)
{
m_Property = myProperty;
}
#region PropertyDescriptor specific
public override bool CanResetValue(object component)
{
return false;
}
public override string Name
{
get
{
return "MyName";
}
}
public override Type ComponentType
{
get
{
return null;
}
}
public override object GetValue(object component)
{
return m_Property.GetValue();
}
public override string Description
{
get
{
return "Description";
}
}
public object Value
{
get
{
return m_Property;
}
}
public override string Category
{
get
{
return "Category";
}
}
public override string DisplayName
{
get
{
return m_Property.Name;
}
}
public override bool IsReadOnly
{
get
{
return false;
}
}
public override void ResetValue(object component)
{
//Have to implement
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override void SetValue(object component, object value)
{
m_Property.Change(value);
}
public override Type PropertyType
{
get
{
if ((m_Property != null) && (m_Property.ValueType != null))
{
return m_Property.ValueType;
}
else
{
return System.Type.Missing.GetType();
}
}
}
#endregion
}
A small class that holds the data:
public class QuadriFeatureItem
{
public QuadriFeatureItem(int featureId, string name)
{
m_featureId = featureId;
m_name = name;
}
public int m_featureId;
public string m_name;
}
My class that is sent to the grid (containing both the FeatureId property and the dynamically created property)
class FeaturePropertyPresentation : PropertyPresentationSubBase
{
public int FeatureId
{
get
{
return m_feature.m_featureId;
}
set { m_feature.m_featureId = value; }
}
public FeaturePropertyPresentation(QuadriFeatureItem item)
{
m_feature = item;
}
private QuadriFeatureItem m_feature;
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection rtn = base.GetProperties(attributes);
CreateNameAttribute(ref rtn, attributes);
return rtn;
}
private void CreateNameAttribute(ref PropertyDescriptorCollection pdc, Attribute[] attributes)
{
NameProperty namePres = null;
namePres = new NameProperty(m_feature, this);
pdc.Add(new MyCustomPropertyDescriptor(namePres, attributes, pdc.Count));
}
public override void Change(object value)
{
throw new NotImplementedException();
}
public override object GetValue()
{
return this;
}
}
A class that implements the nameproperty:
class NameProperty : PropertyPresentationSubBase
{
public NameProperty(QuadriFeatureItem feature, FeaturePropertyPresentation parent)
: base()
{
m_quadriFeatureItem = feature;
Parent = parent;
ValueType = typeof(string);
}
private QuadriFeatureItem m_quadriFeatureItem;
public override void Change(object value)
{
m_quadriFeatureItem.m_name = (string)value;
}
public override object GetValue()
{
return m_quadriFeatureItem.m_name;
}
}
And my formcode:
public Form1()
{
InitializeComponent();
ShowGrid();
}
private void ShowGrid()
{
QuadriFeatureItem no1 = new QuadriFeatureItem(1, "Nummer1");
QuadriFeatureItem no2 = new QuadriFeatureItem(2, "Nummer2");
QuadriFeatureItem no3 = new QuadriFeatureItem(3, "Nummer3");
BindingSource source = new BindingSource();
FeaturePropertyPresentation no1Pres = new FeaturePropertyPresentation(no1);
FeaturePropertyPresentation no2Pres = new FeaturePropertyPresentation(no2);
FeaturePropertyPresentation no3Pres = new FeaturePropertyPresentation(no3);
source.Add(no1Pres);
source.Add(no2Pres);
source.Add(no3Pres);
dataGridView1.DataSource = source;
Show();
}
But the grid shows "Nummer1" for all rows. Why? I use this presentation classes in a propertygrid and it works fine. I also use this MyCustomPropertyDescriptor in a propertygrid.
My wish is now to be able to reuse this presentationclasses and MyCustomPropertyDescriptor in a datagridview. Is it possible with any modification in the MyCustomPropertyDescriptor or PropertyPresentationSubBase?
The problem is that your custom property descriptor is bound to a concrete instance. Which works when you use single item data binding (like
TextBoxto your object property or selecting your object in aPropertyGridcontrol). However, when you use a control that requires list data binding (likeDataGridView,ListView,ListBox,ComboBoxlist etc.) this technique doesn't work. In order to auto populate the columns,DataGridViewneeds a set of properties that are common to all items. In order to do that, it tries several ways to obtain that information (a good explanation can be found here DataGridView not showing properites of objects which implement ICustomTypeDescriptor), and one of them is to take the first item of the list and ask for properties (hence your debugging experience). Anyway, in order to make this work in a list binding scenarios, your property descriptor needs to be implemented differently.Notice the signature of the
PropertyDescriptorsGetValue/SetValuemethods. Both they have an argumentobject component. This is the object instance you need to return or set the value. You can think of property descriptor being an inverse of what we usually use in a programming language. So instead ofwe have
In other words, you should not "embed" your object instance inside the property descriptor, but use the passed argument.
Here is a sample generic implementation of a property descriptor doing just that:
sample usage with your stuff:
and, putting it all together, a small example equivalent to yours:
result: