I am coding a C# forms application where an object has custom properties for a PropertyGrid. These custom properties are displayed at runtime.
Here is my code:
private readonly List<CustomProperty> props = new List<CustomProperty>();
[Browsable(false)]
public List<CustomProperty> properties { get { return props; } }
private Dictionary<string, object> values = new Dictionary<string, object>();
public object this[string name]
{
get { object val; values.TryGetValue(name, out val); return val; }
set
{
values[name] = value;
}
}
private class CustomWebpageObjectTypeUserObjectConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var stdProps = base.GetProperties(context, value, attributes);
CustomWebpageObjectTypeUserObject obj = value as CustomWebpageObjectTypeUserObject;
List<CustomProperty> customProps = obj == null ? null : obj.properties;
PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + (customProps == null ? 0 : customProps.Count)];
stdProps.CopyTo(props, 0);
if (customProps != null)
{
int index = stdProps.Count;
foreach (CustomProperty prop in customProps)
{
CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor customWebpageObjectTypeUserObjectCustomPropertyDescriptor = new CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor(prop);
props[index++] = customWebpageObjectTypeUserObjectCustomPropertyDescriptor;
}
}
return new PropertyDescriptorCollection(props);
}
}
public class CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor : PropertyDescriptor
{
private readonly CustomProperty prop;
public CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor(CustomProperty prop)
: base(prop.name, null)
{
this.prop = prop;
}
public override string Category { get { return prop.category; } }
public override string Description { get { return prop.description; } }
public override string Name { get { return prop.name; } }
public override bool ShouldSerializeValue(object component) { return ((CustomWebpageObjectTypeUserObject)component)[prop.name] != null; }
public override void ResetValue(object component) { ((CustomWebpageObjectTypeUserObject)component)[prop.name] = null; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return prop.type; } }
public override bool CanResetValue(object component) { return true; }
public override Type ComponentType { get { return typeof(CustomWebpageObjectTypeUserObject); } }
public override void SetValue(object component, object value)
{
((CustomWebpageObjectTypeUserObject)component)[prop.name] = value;
prop._value = value;
}
public override object GetValue(object component)
{
return ((CustomWebpageObjectTypeUserObject)component)[prop.name] ?? prop._value;
}
}
This code works correctly with simple set and get values. What I am needing some advice with is how to display a drop down list with dynamic properties that are created at runtime.
I understand how to use a TypeConverter to get a StandardValuesCollection, yet am not sure how to do this with dynamic properties. Do I need to add this attribute to a dynamic property, and does this need to be done in the CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor class? Is there an override that I need to implement to add this attribute? Do I need to do this another way?
Thanks in advance.
Related
I have a list of items which contain a dynamic property.
The goal is to have classes with defined properties, but also allow setting new properties to its instances on the fly. One sample is the 'MyDynamicObject' class in the code below.
The list (an instance of 'DynamicITypedList' in the code below) is then bound to a DataGridView, which will show and bind every property of the list items to a column.
The problem I face is: as soon as I make the 'DynamicITypedList' class implement 'ITypedList', all the rows in the DataGridView show the properties of the first item in the list !If I don't make the list class implement 'ITypedList' (comment out 'ITypedList' at the very end of the code below), the dynamically added properties values are not shown (because the specific GetItemProperties() is not called), but all rows show up in the DataGridView...
Can you please drive me towards a solution ?
Here is the calling code, ready to run in a WinForm containing one "datagridView1" control:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Tests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonITypedList_Click(object sender, EventArgs e)
{
MyDynamicObject myDynamicObject1 = new MyDynamicObject();
myDynamicObject1.Id = 1;
myDynamicObject1.AsDynamic.NewProperty = "NEWPROP1";
MyDynamicObject myDynamicObject2 = new MyDynamicObject();
myDynamicObject2.Id = 2;
DynamicITypedList<MyDynamicObject> myDynamicObjectDynamicITypedList = new DynamicITypedList<MyDynamicObject>();
myDynamicObjectDynamicITypedList.Add(myDynamicObject1);
myDynamicObjectDynamicITypedList.Add(myDynamicObject2);
dataGridView1.AutoGenerateColumns = false;
BindingSource bindingSource = new BindingSource();
bindingSource.DataSource = myDynamicObjectDynamicITypedList;
bindingSource.AllowNew = true;
dataGridView1.DataSource = bindingSource;
foreach (KeyValuePair<string, object> property in myDynamicObject1.Data.Properties)
{
DataGridViewTextBoxColumn dynamicPropertyColumn = new DataGridViewTextBoxColumn();
dynamicPropertyColumn.DataPropertyName = property.Key;
dynamicPropertyColumn.HeaderText = property.Key;
dynamicPropertyColumn.Name = property.Key;
dataGridView1.Columns.Add(dynamicPropertyColumn);
}
}
}
}
Below are the classes:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
namespace Tests
{
public interface IMyDynamic
{
MyDynamic Data { get; set; }
IEnumerable<string> GetDynamicMemberNames();
}
public class MyDynamic : DynamicObject, IDynamicMetaObjectProvider
{
object Instance { get; set; }
Type InstanceType { get; set; }
Dictionary<string, MemberInfo> MemberInfos { get; set; }
PropertyInfo[] instancePropertyInfo;
PropertyInfo[] InstancePropertyInfo
{
get
{
if (instancePropertyInfo == null && Instance != null)
instancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
return instancePropertyInfo;
}
}
public MyDynamic()
{
Initialize(this);
}
public MyDynamic(object instance)
{
Initialize(instance);
}
public IEnumerable<KeyValuePair<string, object>> GetProperties(bool includeInstanceProperties = false)
{
if (includeInstanceProperties && Instance != null)
{
foreach (var prop in this.InstancePropertyInfo)
yield return new KeyValuePair<string, object>(prop.Name, prop.GetValue(Instance, null));
}
foreach (var key in this.Properties.Keys)
yield return new KeyValuePair<string, object>(key, this.Properties[key]);
}
public override IEnumerable<string> GetDynamicMemberNames()
{
foreach (var prop in GetProperties(true))
yield return prop.Key;
}
public Dictionary<string, object> Properties { get; private set; }
public object this[string key]
{
get
{
try
{
//Try to get from properties collection first
return Properties[key];
}
catch (KeyNotFoundException)
{
//Try reflection on instanceType
object result = null;
if (GetProperty(Instance, key, out result))
return result;
//Nope doesn't exist
//throw; //Preserve the stack trace
return null;
}
}
set
{
if (Properties.ContainsKey(key))
{
Properties[key] = value;
return;
}
//Check instance for existance of type first
var miArray = InstanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty);
if (miArray != null && miArray.Length > 0)
SetProperty(Instance, key, value);
else
Properties[key] = value;
}
}
protected virtual void Initialize(object instance)
{
Instance = instance;
if (instance != null)
InstanceType = instance.GetType();
Properties = new Dictionary<string, object>();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
//First check the Properties collection for member
if (Properties.Keys.Contains(binder.Name))
{
result = Properties[binder.Name];
return true;
}
//Next check for Public properties via Reflection
if (Instance != null)
{
try
{
return GetProperty(Instance, binder.Name, out result);
}
catch { }
}
//Failed to retrieve a property
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
//First check to see if there's a native property to set
if (Instance != null)
{
try
{
if (SetProperty(Instance, binder.Name, value))
return true;
}
catch { }
}
//No match - set or add to dictionary
Properties[binder.Name] = value;
return true;
}
protected bool GetProperty(object instance, string name, out object result)
{
if (instance == null)
instance = this;
if (MemberInfos == null)
{
MemberInfos = new Dictionary<string, MemberInfo>();
MemberInfo[] memberInfos = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
foreach (MemberInfo instanceMemberInfo in InstanceType.GetMembers())
MemberInfos.Add(instanceMemberInfo.Name, instanceMemberInfo);
}
result = null;
MemberInfo propertyMemberInfo;
if (!MemberInfos.TryGetValue(name, out propertyMemberInfo))
return false;
if (propertyMemberInfo.MemberType == MemberTypes.Property)
{
result = ((PropertyInfo)propertyMemberInfo).GetValue(instance, null);
return true;
}
return false;
}
protected bool SetProperty(object instance, string name, object value)
{
if (instance == null)
instance = this;
if (MemberInfos == null)
{
MemberInfos = new Dictionary<string, MemberInfo>();
MemberInfo[] memberInfos = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
foreach (MemberInfo instanceMemberInfo in InstanceType.GetMembers())
MemberInfos.Add(instanceMemberInfo.Name, instanceMemberInfo);
}
MemberInfo propertyMemberInfo;
if (!MemberInfos.TryGetValue(name, out propertyMemberInfo))
return false;
if (propertyMemberInfo.MemberType == MemberTypes.Property)
{
((PropertyInfo)propertyMemberInfo).SetValue(Instance, value, null);
return true;
}
return false;
}
}
public /*abstract*/ class MyDynamicObject : MyDynamic, IMyDynamic
{
public MyDynamic Data
{
get { return data; }
set { data = dynamicData = value; }
}
public dynamic AsDynamic
{
get { return dynamicData; }
}
public Int64 Id { get { return AsDynamic.Id; } set { AsDynamic.Id = value; } }
MyDynamic data;
dynamic dynamicData;
public MyDynamicObject() : this(new MyDynamic())
{ }
public MyDynamicObject(MyDynamic data)
{
Data = data;
TypeDescriptor.AddProvider(new MyDynamicTypeDescriptionProvider(), this); //Data);
}
}
public class IMyDynamicTypeDescriptor : ICustomTypeDescriptor
{
private readonly IMyDynamic m_Instance;
public IMyDynamicTypeDescriptor(object instance)
{
if (instance is MyDynamicObject)
m_Instance = (MyDynamicObject)instance;
else if (instance is IMyDynamic)
m_Instance = (IMyDynamic)instance;
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return m_Instance;
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return new PropertyDescriptorCollection(
m_Instance.Data.Properties.Keys
.Select(x => new MyDynamicPropertyDescriptor(m_Instance, x))
.ToArray<PropertyDescriptor>());
}
}
public class MyDynamicPropertyDescriptor : PropertyDescriptor
{
private readonly IMyDynamic m_Instance;
private readonly string m_Name;
public MyDynamicPropertyDescriptor(IMyDynamic instance, string name)
: base(name, null)
{
m_Instance = instance;
m_Name = name;
}
public override Type PropertyType
{
get
{
if (m_Instance.Data[m_Name] != null)
return m_Instance.Data[m_Name].GetType();
else
{
//...
return typeof(string);
}
}
}
public override void SetValue(object component, object value)
{
m_Instance.Data[m_Name] = value;
}
public override object GetValue(object component)
{
return m_Instance.Data[m_Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type ComponentType
{
get { return null; }
}
public override bool CanResetValue(object component)
{
return false;
}
public override void ResetValue(object component)
{ }
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override string Category
{
get { return string.Empty; }
}
public override string Description
{
get { return string.Empty; }
}
}
public class MyDynamicTypeDescriptionProvider : TypeDescriptionProvider
{
private static readonly TypeDescriptionProvider m_Default = TypeDescriptor.GetProvider(typeof(ExpandoObject));
public MyDynamicTypeDescriptionProvider() : base(m_Default)
{ }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
var defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
return (instance == null ? defaultDescriptor : new IMyDynamicTypeDescriptor(instance));
}
}
public class DynamicITypedList<T> : ObservableCollection<T>, /*List<T>, IList<T>,*/ ITypedList where T : IMyDynamic, new()
{
public string GetListName(PropertyDescriptor[] listAccessors)
{
return null;
}
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return TypeDescriptor.GetProperties(this.FirstOrDefault());
}
}
}
this is not really an answer to the question directly, but...
I had the same task some days ago. Bind dynamic objects to a datagrid.
My solution is:
Add the columns to the datagrid manually
Set the databinding-properties manually
Use BindingSource, bind this BindingSource to your List then bind your grid to the bindingsource
Be sure to do all of this in the correct order, because if you don't, your dynamic properties are unknown to the grid/bindingsource and therefore won't be shown.
I don't remember the exact order of all of this, maybe you should try around a little with my hints and come back and ask further?
I am working on a winforms application that uses the standard winforms property grid in a fairly involved way.
I provides custom type descriptors and type converters for class and also specified if certain properties are to be readonly, and not editable in the property grid.
I am having problems determining how to have an expandable type that is also readonly - in the documentation it seems that overriding public bool IsReadOnly is the way to go but as soon as I specify in the type converter that there are child properties it stops working.
I have managed to distill my code to a small example:
Here is the custom object:
public sealed class TestNode : ICustomTypeDescriptor
{
private readonly bool _isEditable;
private readonly List<TestNode> _childNodes = new List<TestNode>();
public TestNode(string name, bool isEditable = true)
{
_isEditable = isEditable;
Name = name;
}
public string Name { get; private set; }
public bool IsEditable
{
get { return _isEditable; }
}
public List<TestNode> ChildNodes
{
get { return _childNodes; }
}
public void AddChild(TestNode testNode)
{
ChildNodes.Add(testNode);
}
#region ICustomTypeDescriptor
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(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 null;
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(new Attribute[0]);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return new PropertyDescriptorCollection(new PropertyDescriptor[]{new TestNodeDescriptor(this)}, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
Which has a descriptor like this, where I can specify if it shows as readonly in the propertyGrid:
public class TestNodeDescriptor : PropertyDescriptor
{
private readonly TestNode _testNode;
public TestNodeDescriptor(TestNode testNode) : base("Test", null)
{
_testNode = testNode;
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return _testNode;
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return null; }
}
public override bool IsReadOnly
{
get { return !_testNode.IsEditable; }
}
public override Type PropertyType
{
get { return typeof(TestNode); }
}
public override TypeConverter Converter
{
get
{
return new TestTypeConverter(_testNode);
}
}
}
And finally the type converter, where I specify the conversion to string for display and the required expandable type converter to show child properties:
public class TestTypeConverter : ExpandableObjectConverter
{
private readonly TestNode _testNode;
public TestTypeConverter(TestNode testNode)
{
_testNode = testNode;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return _testNode.Name;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var descriptors = _testNode.ChildNodes.Select(c => (PropertyDescriptor) (new TestNodeDescriptor(c))).ToArray();
return new PropertyDescriptorCollection(descriptors);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return _testNode.ChildNodes.Any();
}
}
The odd thing is that if the method in the type converter public override bool GetPropertiesSupported(ITypeDescriptorContext context) return true then the children are shown as expected but its not as readonly.
For example with this test code:
public partial class Form1 : Form
{
readonly TestNode _parentNode = new TestNode("Parent");
public Form1()
{
InitializeComponent();
var child1 = new TestNode("Child1", false);
var child2 = new TestNode("Child2", false);
child2.AddChild(new TestNode("Grandchild", false));
_parentNode.AddChild(child2);
_parentNode.AddChild(child1);
propertyGrid1.SelectedObject = _parentNode;
}
}
I would get a dialog show like this:
I was expecting all child nodes to be readonly. Has anyone got something like this to work successfully with the PropertyGrid?
Its been a very frustrating experience and any tips would be greatly appreciated.
I have a class
class demo
{
public int var1 {set; get;}
}
I want to add Display attributes on that variable on runtime instead of doing this,
class Demo
{
[Display (Name="Any Name", GroupName= "My Group 1")]
public int var1 {set; get;}
}
Any possible way to change or Assign those attributes from any other class?
Very simple example, it can be build and in PropertyGrid you will see something.
You need read about ICustomTypeDescriptor and PropertyDescriptor.
On Form Load:
propertyGrid1.SelectedObject = new MyType(new[] { "Property1", "Property2" });
Types:
public class MyType : ICustomTypeDescriptor
{
private string[] _properties;
public MyType(string[] properties)
{
_properties = properties;
}
public AttributeCollection GetAttributes()
{
return null;
}
public string GetClassName()
{
return nameof(MyType);
}
public string GetComponentName()
{
throw new NotImplementedException();
}
public TypeConverter GetConverter()
{
return null;
}
public EventDescriptor GetDefaultEvent()
{
throw new NotImplementedException();
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return null;
}
public EventDescriptorCollection GetEvents()
{
throw new NotImplementedException();
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var props = new PropertyDescriptor[_properties.Length];
for (int i = 0; i < _properties.Length; i++)
props[i] = new CustomPropertyDescriptor(_properties[i],
new Attribute[]
{
new DisplayNameAttribute(#"Displ Value " + i),
new CategoryAttribute("Category" + i%2)
});
return new PropertyDescriptorCollection(props);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}
public class CustomPropertyDescriptor : PropertyDescriptor
{
public CustomPropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return "1";
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType { get; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof (string); } }
}
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 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 PropertyDescriptors 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:
I want to use property grid to show differences between objects. Everything is fine but how I can highlight (change back or fore color) for properties which have differences?
Is it possible?
Maybe someone suggest alternatives of this control to have nice visualization of object differences?
During searching across different articles I fond that it's not possible in clear way
Nice explanation of why pointed by #Hans Passant
Can we change the text/background color of an individual property in PropertyGrid
Also it has nice but not free alternative from VisualHint company
Smart PropertyGrid.Net
But it my case it's possible to hack existing functionality little bit as I need just one color for visualization of differences in properties. What I did is just used property DisabledItemForeColor which defining color for ReadOnly properties, and made mapping of ReadOnly property to custom property HasDifference.
So here is my sample code:
public Form1()
{
InitializeComponent();
Application.EnableVisualStyles();
var obj = new CustomObjectType
{
ObjectName = "CompositeFirst",
Properties =
{
new CustomProperty { Name = "Property1", Type = typeof(int), Desc = "Property1 desc", DefaultValue = 1, HasDifference = true},
new CustomProperty { Name = "Property2", Type = typeof(DateTime), Desc = "Property2 desc"},
new CustomProperty { Name = "Property1", Type = typeof(CustomObjectType), HasDifference = true},
}
};
var customObjectType = obj.Properties[2].DefaultValue as CustomObjectType;
if (customObjectType != null)
customObjectType.ObjectName = "CompositSecond";
customObjectType.Properties = new List<CustomProperty>
{
new CustomProperty { Name = "Property4", Type = typeof(int), DefaultValue = 5, HasDifference = true},
new CustomProperty { Name = "Property5", Type = typeof(DateTime), Desc = "Property2 desc", DefaultValue = DateTime.Now},
};
propertyGrid1.SelectedObject = obj;
propertyGrid1.DisabledItemForeColor = Color.Red;
}
[TypeConverter(typeof(CustomObjectConverter))]
public class CustomObjectType : TypeConverter
{
private List<CustomProperty> _props = new List<CustomProperty>();
[Browsable(false)]
public string ObjectName
{
get;
set;
}
[Browsable(false)]
public List<CustomProperty> Properties
{
get { return _props; }
set { _props = value; }
}
private readonly Dictionary<string, object> values = new Dictionary<string, object>();
public object this[string name]
{
get { object val; values.TryGetValue(name, out val); return val; }
set { values.Remove(name); }
}
private class CustomObjectConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var stdProps = base.GetProperties(context, value, attributes);
var obj = value as CustomObjectType;
var customProps = obj == null ? null : obj.Properties;
var props = new PropertyDescriptor[stdProps.Count + (customProps == null ? 0 : customProps.Count)];
stdProps.CopyTo(props, 0);
if (customProps != null)
{
int index = stdProps.Count;
foreach (CustomProperty prop in customProps)
{
props[index++] = new CustomPropertyDescriptor(prop);
}
}
return new PropertyDescriptorCollection(props);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is CustomObjectType)
{
return (value as CustomObjectType).ObjectName;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
private class CustomPropertyDescriptor : PropertyDescriptor
{
private readonly CustomProperty _prop;
public CustomPropertyDescriptor(CustomProperty prop)
: base(prop.Name, null)
{
_prop = prop;
}
public override bool IsReadOnly { get { return _prop.HasDifference; } }
public override string Category { get { return "Main Category"; } }
public override string Description { get { return _prop.Desc; } }
public override string Name { get { return _prop.Name; } }
public override bool ShouldSerializeValue(object component) { return ((CustomObjectType)component)[_prop.Name] != null; }
public override void ResetValue(object component) { ((CustomObjectType)component)[_prop.Name] = null; }
public override Type PropertyType { get { return _prop.Type; } }
public override bool CanResetValue(object component) { return true; }
public override Type ComponentType { get { return typeof(CustomObjectType); } }
public override void SetValue(object component, object value) { ((CustomObjectType)component)[_prop.Name] = value; }
public override object GetValue(object component) { return ((CustomObjectType)component)[_prop.Name] ?? _prop.DefaultValue; }
}
}
public class CustomProperty
{
public string Name { get; set; }
public string Desc { get; set; }
public object DefaultValue { get; set; }
public bool HasDifference { get; set; }
Type _type;
public Type Type
{
get
{
return _type;
}
set
{
_type = value;
DefaultValue = Activator.CreateInstance(value);
}
}
}
}
And it looks like this:
Hope it will help to someone.