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.
Related
I am trying to bind my objects by columns in the datagridview component but I could not find any way to do it.
This is an example of what I am trying to achieve.
I have the Emp class
public class Emp
{
public int ID { get; set; }
public string Name { get; set; }
public string City { get; set; }
public Emp(int id, string name, string city)
{
this.ID = id;
this.Name = name;
this.City = city;
}
}
and an array of Emp
var arrEmp = new[] {
new Emp( 1, "Devesh Omar", "Noida"),
new Emp( 2, "Roli", "Kanpur"),
new Emp( 3, "Roli Gupta", "Mainpuri"),
new Emp( 3, "Roli Gupta", "Kanpur"),
new Emp( 3, "Devesh Roli ", "Noida"),
};
When I bind the data to the grid
dataGridView1.DataSource = arrEmp;
I get this (which is ok)
I would like that the grid has only 3 fixed rows (Id, Name, City) and the column all the values. (the matrix transpose)
Also, if I add or remove an element to/from arrEmp, that the element will be added as a column.
The example was taken from here
There's not such a built-in way or built-in component for that.
You need to create your own component, or if you want to use DataGridView for that purpose, you can achieve it by writing custom code. Here I've achieved that using a custom TypeDescriptor.
Type descriptor provide information about type, including list of properties and getting and setting property values. DataTable also works the same way, to show list of columns in DataGridView, it returns a list of property descriptors containing properties per column. Here I've used such technique.
As you can see in the screen capture, when you edit the rotated list, you are actually editing the original list:
//Set Data Source
dgv.DataSource = new RotatedListDataSource<Employee>(list);
//Hide Column Headers
dgv.ColumnHeadersVisible = false;
//Set Row Headers Autosize
dgv.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;
//Show PropertyName on RowHeader
dgv.RowPrePaint += (o, a) =>
{
var value = ((RotatedItem)dgv.Rows[a.RowIndex].DataBoundItem).PropertyName;
if (a.RowIndex > -1 && $"{dgv.Rows[a.RowIndex].HeaderCell.Value}" != value)
dgv.Rows[a.RowIndex].HeaderCell.Value = value;
};
If you liked the idea and want to give it a try for learning purpose to see how type descriptor works, here is the code for type descriptor and property descriptors that I created:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class RotatedListDataSource<T> : List<RotatedItem>
{
public List<T> List { get; }
public RotatedListDataSource(List<T> list)
{
List = list;
this.AddRange(typeof(T).GetProperties().Select(p =>
new RotatedItem(
p.Name,
list.Cast<object>().ToArray(),
p.PropertyType)));
}
}
public class RotatedItem : CustomTypeDescriptor
{
public string PropertyName { get; }
private object[] data;
public Type Type { get; }
public RotatedItem(string propertyName, object[] data, Type type)
{
this.PropertyName = propertyName;
this.data = data;
this.Type = type;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = new List<PropertyDescriptor>();
properties.Add(new PropertyNameProperty(new Attribute[] {
new BrowsableAttribute(false)}));
for (int i = 0; i < data.Length; i++)
{
properties.Add(new IndexProperty(i, typeof(object), new Attribute[] { }));
}
return new PropertyDescriptorCollection(properties.ToArray());
}
public object this[int i]
{
get => data[i].GetType().GetProperty(PropertyName).GetValue(data[i]);
set => data[i].GetType().GetProperty(PropertyName).SetValue(
data[i], Convert.ChangeType(value, Type));
}
}
public class IndexProperty : PropertyDescriptor
{
int index;
Type type;
public IndexProperty(int index, Type type, Attribute[] attributes)
: base(index.ToString(), attributes)
{
this.index = index;
this.type = type;
}
public override Type ComponentType => typeof(RotatedItem);
public override bool IsReadOnly => false;
public override Type PropertyType => type;
public override bool CanResetValue(object component) => false;
public override object GetValue(object component) =>
((RotatedItem)component)[index];
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) =>
((RotatedItem)component)[index] = value;
public override bool ShouldSerializeValue(object component) => true;
}
public class PropertyNameProperty : PropertyDescriptor
{
public PropertyNameProperty(Attribute[] attributes)
: base(nameof(RotatedItem.PropertyName), attributes) { }
public override Type ComponentType => typeof(RotatedItem);
public override bool IsReadOnly => true;
public override Type PropertyType => typeof(string);
public override bool CanResetValue(object component) => false;
public override object GetValue(object component) =>
((RotatedItem)component).PropertyName;
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { }
public override bool ShouldSerializeValue(object component) => true;
}
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'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 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.
UPDATED
I am updating this post because I did some more reading and decided to re-implement my solution.
Original Problem: I have a class with static properties and one Property that is a dynamic collection of properties (via a dictionary). I want to databind my class to a wpf datagrid where each static property should be a column and each dictionary entry should be a column in the grid.
After doing some more research, I decided to implement a PropertyBag class that will contain my Dictionary of properties and values. Almost everything is working now. I have my grid being displayed with all the correct columns and the static property values are being applied correctly.
However, now I am not able to get any of the values from the dictionary to be applied to the grid, and I am not sure where to go from here.
More info:
My database has 3 tables, a plate, a category, and a categoryplateassociation table. Each plate can have 0 to many categories. For now, I am populating each plate with all the categories and setting the strings to empty. Then, when an association is returned (between a plate and category), I am setting the real value on the specific category name. This all happens before the grid is created.
Property Bag:
public class PropertyBag
{
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public string this[string key]
{
get
{
string value;
values.TryGetValue(key, out value);
return value;
}
set
{
if (value == null) values.Remove(key);
else values[key] = value;
}
}
}
Revised Plate class
[TypeDescriptionProvider(typeof(PlateTypeDescriptionProvider))]
public class Plate : INotifyPropertyChanged
{
public int ID;
private string name;
private string status;
private string creator;
private Uri location;
private string description;
public Plate()
{
CustomCategories = new PropertyBag();
}
public PropertyBag CustomCategories { get; set; }
public string Name
{
get { return name;}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
public string Status
{
get { return status; }
set
{
status = value;
NotifyPropertyChanged("Status");
}
}
public string Creator
{
get { return creator; }
set
{
creator = value;
NotifyPropertyChanged("Creator");
}
}
public Uri Location
{
get { return location; }
set
{
location = value;
NotifyPropertyChanged("Location");
}
}
public string Description
{
get { return description; }
set
{
description = value;
NotifyPropertyChanged("Description");
}
}
public static Plate ConvertDataPlateToBusinessPlate(TestPlate dataPlate)
{
var plate = new Plate
{
Name = dataPlate.Name,
Status = dataPlate.Status,
Creator = dataPlate.Creator,
Description = dataPlate.Description,
Location = new Uri(dataPlate.Location)
};
return plate;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Revised CustomTypeDescriptor:
public override PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = new ArrayList();
foreach (PropertyDescriptor propertyDescriptor in base.GetProperties(attributes))
{
if(propertyDescriptor.PropertyType.Equals(typeof(PropertyBag)))
{
//Static list of all category names
var categoryNames = Categories.GetAll();
foreach (var categoryName in categoryNames)
{
properties.Add(new PropertyBagPropertyDescriptor(categoryName));
}
}
else
{
properties.Add(propertyDescriptor);
}
}
var props = (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
return new PropertyDescriptorCollection(props);
}
Revised PropertyDescriptor
public class PropertyBagPropertyDescriptor : PropertyDescriptor
{
public PropertyBagPropertyDescriptor(string name) : base(name, null)
{}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return ((PropertyBag) component)[Name];
}
public override void ResetValue(object component)
{
((PropertyBag)component)[Name] = null;
}
public override void SetValue(object component, object value)
{
((PropertyBag) component)[Name] = (string) value;
}
public override bool ShouldSerializeValue(object component)
{
return ((PropertyBag)component)[Name] != null;
}
public override Type ComponentType
{
get { return typeof(PropertyBag); }
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
}
simple ViewModel
public TestPlateAdministratorViewModel()
{
CommandAggregator = new TestPlateAdministratorCommandAggregator(this);
LoadData();
}
public static TestPlateAdministratorCommandAggregator CommandAggregator { get; set; }
public ObservableCollection<Plate> TestPlates{ get; set; }
private static void LoadData()
{
CommandAggregator.LoadPlatesCommand.Execute(null);
CommandAggregator.LoadCategoriesCommand.Execute(null);
}
}
does your Dictionary in your PropertyBag has a fixed size or does the keys are known?
you did not post your xaml for your datagrid, but the binding from the propertybag to one column could look like this:
<DataGridTextColumn Header="Col4TestKey" Binding="{Binding CustomCategories[test]}"/>
i really dont know wether your PropertyBag setter will work with binding. all in all this just would work if you have a know set of keys for your dictionary.
btw, i use flat datatables in my projects for such dynamic stuff, there are really easy to handle.