Reputation: 23
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?
Upvotes: 2
Views: 4379
Reputation: 205589
The problem is that your custom property descriptor is bound to a concrete instance. Which works when you use single item data binding (like TextBox
to your object property or selecting your object in a PropertyGrid
control). However, when you use a control that requires list data binding (like DataGridView
, ListView
, ListBox
, ComboBox
list etc.) this technique doesn't work. In order to auto populate the columns, DataGridView
needs 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 PropertyDescriptor
s GetValue/SetValue
methods. Both they have an argument object 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 of
var val = obj.Property;
obj.Property = val;
we have
var val = propertyDescriptor.GetValue(obj);
propertyDescriptor.SetValue(obj, val);
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:
public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
where TComponent : class
{
private readonly Func<TComponent, TValue> getValue;
private readonly Action<TComponent, TValue> setValue;
private readonly string displayName;
public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
: base(name, attrs)
{
Debug.Assert(getValue != null);
this.getValue = getValue;
this.setValue = setValue;
this.displayName = displayName;
}
public override string DisplayName { get { return displayName ?? base.DisplayName; } }
public override Type ComponentType { get { return typeof(TComponent); } }
public override bool IsReadOnly { get { return setValue == null; } }
public override Type PropertyType { get { return typeof(TValue); } }
public override bool CanResetValue(object component) { return false; }
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) { }
public override object GetValue(object component) { return getValue((TComponent)component); }
public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
}
sample usage with your stuff:
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes);
// Custom name property
properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
getValue: component => component.m_feature.m_name,
setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
displayName: "Feature Name"
));
return properties;
}
and, putting it all together, a small example equivalent to yours:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
namespace Samples
{
// Generic implemenation of a property descriptor
public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
where TComponent : class
{
private readonly Func<TComponent, TValue> getValue;
private readonly Action<TComponent, TValue> setValue;
private readonly string displayName;
public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
: base(name, attrs)
{
Debug.Assert(getValue != null);
this.getValue = getValue;
this.setValue = setValue;
this.displayName = displayName;
}
public override string DisplayName { get { return displayName ?? base.DisplayName; } }
public override Type ComponentType { get { return typeof(TComponent); } }
public override bool IsReadOnly { get { return setValue == null; } }
public override Type PropertyType { get { return typeof(TValue); } }
public override bool CanResetValue(object component) { return false; }
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) { }
public override object GetValue(object component) { return getValue((TComponent)component); }
public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
}
// Your stuff
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);
}
public class QuadriFeatureItem
{
public QuadriFeatureItem(int featureId, string name)
{
m_featureId = featureId;
m_name = name;
}
public int m_featureId;
public string m_name;
}
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)
{
var properties = base.GetProperties(attributes);
// Custom name property
properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
getValue: component => component.m_feature.m_name,
setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
displayName: "Feature Name"
));
return properties;
}
public override void Change(object value)
{
throw new NotImplementedException();
}
public override object GetValue()
{
return this;
}
}
// Test
static class Test
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var dataSet = Enumerable.Range(1, 10).Select(n => new FeaturePropertyPresentation(new QuadriFeatureItem(n, "Nummer" + n))).ToList();
var form = new Form();
var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
dg.DataSource = dataSet;
Application.Run(form);
}
}
}
result:
Upvotes: 1