So for this project I'm working on, we've decided to use the .NET PropertyGrid control. The propertygrid gets populated with an object that is built during run time, based off of what item is selected by the user in a ListView control.
So, if they select the first item in the ListView such as "Base", the PropertyGrid will show the properties for that component, such as its dimensions. Then they select "Top" and it will show color in the PropertyGrid. Either way, every item in the list is one single "component" object.
Basically, when a ListView item is selected, a loop iterates through a data set to find what properties are associated with that selected component object, then gets thrown into the propertybag class which is displayed in the propertygrid.
What I'm trying to figure out, since these components and properties are all one class, how can I dynamically determine which properties should be a displayed as a drop down menu, or image box, or a text-field.
I'm using Visual Studios 2010 / C#.NET, and for the dynamic property generation I'm using the apparently-popular "Property Bag" class I found on CodeProject by Tony Allowatt. The only thing I can think of would be to maybe add an extra column to the database for the properties and use that to tell the PropertyBag what data type to add? It seems to be a popular topic, but I'm having difficulties figuring out how to do it in conjunction with a dynamically built object.
Its not an answer per se, but I too have been working up to building such a beast. Here is stackoverflow's greatest hits on the topic...
How to modify PropertyGrid at runtime (add/remove property and dynamic types/enums)
How to display a dynamic object in property grid?
PropertyGrid and Dynamic Types of Objects
At first I though I actually needed dynamic objects based on the Expando object, for me it turns out not to be the case. You might want to make sure you to don't fall into that trap.
In my case what I really needed was a collection of custom objects that can have a variable set of properties added to them. Where each property is an instantiation of one of three custom types (stringType, rangeType, or enumType). Once I realized the "dynamic" properties were not going to be of arbitrary class types the project became an easy twist on the code discussed in the three stackoverflow examples. With the question How to modify PropertyGrid at runtime (add/remove property and dynamic types/enums) being almost a direct example of what I am ending up with.
Hope my ramblings help you find your path...
For wpf this may solve whole problem :
link to source https://xceed.com/forums/topic/propertygrid-dictionary-not-displaying-values-using-icustomtypedescriptor/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
[RefreshProperties(RefreshProperties.All)]
public class DictionaryPropertyGridAdapter<TKey, TValue> : ICustomTypeDescriptor, INotifyPropertyChanged
{
#region Fields
private readonly IDictionary<TKey, PropertyAttributes> propertyAttributeDictionary;
private readonly IDictionary<TKey, TValue> propertyValueDictionary;
#endregion
#region Constructors and Destructors
public DictionaryPropertyGridAdapter(
IDictionary<TKey, TValue> propertyValueDictionary,
IDictionary<TKey, PropertyAttributes> propertyAttributeDictionary = null)
{
this.propertyValueDictionary = propertyValueDictionary;
this.propertyAttributeDictionary = propertyAttributeDictionary;
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
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 object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
ArrayList properties = new ArrayList();
foreach (var kvp in this.propertyValueDictionary)
{
properties.Add(
new DictionaryPropertyDescriptor(
kvp.Key,
this.propertyValueDictionary,
this.propertyAttributeDictionary));
}
PropertyDescriptor[] props = (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
return new PropertyDescriptorCollection(props);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
}
//[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class PropertyAttributes
{
public string Category { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public bool IsReadOnly { get; set; }
}
internal class DictionaryPropertyDescriptor : PropertyDescriptor
{
#region Fields
private readonly IDictionary<TKey, PropertyAttributes> attributeDictionary;
private readonly TKey key;
private readonly IDictionary<TKey, TValue> valueDictionary;
#endregion
#region Constructors and Destructors
internal DictionaryPropertyDescriptor(
TKey key,
IDictionary<TKey, TValue> valueDictionary,
IDictionary<TKey, PropertyAttributes> attributeDictionary = null)
: base(key.ToString(), null)
{
this.valueDictionary = valueDictionary;
this.attributeDictionary = attributeDictionary;
this.key = key;
}
#endregion
public override string Category => this.attributeDictionary?[this.key].Category ?? base.Category;
public override Type ComponentType => null;
public override string Description => this.attributeDictionary?[this.key].Description ?? base.Description;
public override string DisplayName => this.attributeDictionary?[this.key].DisplayName ?? base.DisplayName;
public override bool IsReadOnly => this.attributeDictionary?[this.key].IsReadOnly ?? false;
public override Type PropertyType => this.valueDictionary[this.key].GetType();
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return this.valueDictionary[this.key];
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
this.valueDictionary[this.key] = (TValue)value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
}
for viewmodel
class classViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private IDictionary<string, object> Variables { get; set; } = new ConcurrentDictionary<string, object>();
private DictionaryPropertyGridAdapter<string, object> _selectedObj;
public DictionaryPropertyGridAdapter<string, object> SelectedObj
{
get
{
this.Variables["Bool"] = false;
this.Variables["Int"] = 200;
this.Variables["Float"] = 200.5;
this.Variables["String"] = "help";
_selectedObj = new DictionaryPropertyGridAdapter<string, object>(this.Variables);
return _selectedObj;
}
set {
_selectedObj = value;
OnPropertyChanged(nameof(this.SelectedObj));
}
}
}
and xaml
<Window x:Class="testPropertyGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:testPropertyGrid"
xmlns:wpftoolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:classViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<wpftoolkit:PropertyGrid Name="propertyGrid1" SelectedObject="{Binding SelectedObj ,Source={StaticResource ViewModel}}">
</wpftoolkit:PropertyGrid>
</Grid>
I believe this link will provide you with decent input to solve your problem. Hpe it helps.
Edit to include the link's contents:
Declaration of dynamic properties:
dynamic employee = new BusinessObject();
employee.FirstName = "John";
employee.LastName = "Doe";
Class that supports dynamic properties:
public class BusinessObject : DynamicObject
{
private readonly IDictionary<string, object> dynamicProperties =
new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var memberName = binder.Name;
return dynamicProperties.TryGetValue(memberName, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var memberName = binder.Name;
dynamicProperties[memberName] = value;
return true;
}
}
Related
I want to generate a datagrid dynamically using mvvm. In each cell of the datagrid i have to display an object. Column name is one of the properties of the object. Itemsource of the datagrid will be list of object . How to generate the datagrid dynamically using mvvm?
Update
I have created a custom class which has been extended usiing ICustomTypeDescriptor.
public class MyCustomType : ICustomTypeDescriptor
{
// This is instance data.
private readonly BindingList<PropertyDescriptor> _propertyDescriptors = new BindingList<PropertyDescriptor>();
// The data is stored on the type instance.
private readonly IDictionary<string, object> _propertyValues = new Dictionary<string, object>();
// The property descriptor now takes an extra argument.
public void AddProperty(string name, Type type)
{
_propertyDescriptors.Add(new MyPropertyDescriptor(name, type));
}
public PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(_propertyDescriptors.ToArray());
}
public PropertyDescriptorCollection GetProperties(Type type)
{
return new PropertyDescriptorCollection(_propertyDescriptors.ToArray());
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
throw new NotImplementedException();
}
public AttributeCollection GetAttributes()
{
throw new NotImplementedException();
}
public string GetClassName()
{
throw new NotImplementedException();
}
public string GetComponentName()
{
throw new NotImplementedException();
}
public TypeConverter GetConverter()
{
throw new NotImplementedException();
}
public EventDescriptor GetDefaultEvent()
{
throw new NotImplementedException();
}
public PropertyDescriptor GetDefaultProperty()
{
throw new NotImplementedException();
}
public object GetEditor(Type editorBaseType)
{
throw new NotImplementedException();
}
public EventDescriptorCollection GetEvents()
{
return null;
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return null;
}
private class MyPropertyDescriptor : PropertyDescriptor
{
// This data is here to indicate that different instances of the type
// object may have properties of the same name, but with different
// characteristics.
private readonly Type _type;
public MyPropertyDescriptor(string name, Type type)
: base(name, null)
{
_type = type;
}
public override bool CanResetValue(object component)
{
throw new NotImplementedException();
}
public override Type ComponentType
{
get { throw new NotImplementedException(); }
}
public override object GetValue(object component)
{
MyCustomType obj = (MyCustomType) component;
object value = null;
obj._propertyValues.TryGetValue(Name, out value);
return value;
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return _type; }
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
var oldValue = GetValue(component);
if (oldValue != value)
{
MyCustomType obj = (MyCustomType) component;
obj._propertyValues[Name] = value;
OnValueChanged(component, new PropertyChangedEventArgs(Name));
}
}
public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
public override void AddValueChanged(object component, EventHandler handler)
{
// set a breakpoint here to see WPF attaching a value changed handler
base.AddValueChanged(component, handler);
}
}
}
I m binding a list of this customtype objects to datagrid itemsource. But datagrid is not showing any content?
The way of using the DataGrid is to have an IEnumerable<T> as its ItemsSource where T is a class or interface that has the properties, which you would like to generate your columns for. You should use the types DataGrid supports. Most of the value types are supported. You can find a list HERE about the supported types as well as the usage.
So your list should contain the objects for the rows, not for the cells. The cells will be automatically generated by DataGrid for each public property of the type of the generic argument of the list.
You can then use MVVM to bind the list as follows:
<DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="True"></DataGrid>
Certainly the DataContext of the DataGrid (either inherited or explicit) should contain an public IEnumerable<T> MyList { get; } with the items.
UPDATE
Actually each row object in turn has list of objects for the cells.
Each cell object has multiple properties. I want to display 1 of the
property value as column name and each cell object will have different
cell style based on the type of the object. How to dynamically create
the datagrid with theses many conditions?
In WPF the DataGrid has the same column set for every row. So, in your case even you could define different column data for every cell, you can use only the first row (or the one you need) to define the columns. I would rather suggest creating Custom Attributes in order to declare the column properties (data type, header..etc), since it is a declarative information not a dynamic (should not change from row to row or form time to time). The style information still can have some dynamism by using triggers as described in THIS thread. This is more of a WPF way.
Anyway, if you want to stick to your schema, you should implement the column exploration logic in a class derived from DataGrid. In the ItemsSourceChanged handler you check the type of the ItemsSource. If that is an IEnumerable you get out the generic argument (T) with reflection and check if that type supports your column describing schema. Something like:
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (AutoGenerateColumns && newValue != null && newValue.GetType().IsGenericType)
{
this.Columns.Clear();
var type = newValue.GetType().GetGenericArguments().First();
if (typeof(IMyColumnDescriptionStructure).IsAssignableFrom(type))
{
var rows = newValue as IEnumerable<IMyColumnDescriptionStructure>;
var firstRow = rows.First() as IMyColumnDescriptionStructure;
// TODO: explore your structure and add column definitions
///this.Columns.Add(...)
}
}
}
I'm displaying a list of objects in a DataGridView. Everything was working fine. Columns were automagicaly added to the DataGridView based on the properties of the objects.
Now I changed the class I'm displaying in the grid to implement ICustomTypeDescriptor. But now the grid now no longer shows any columns or rows when I set it's DataSource to a list of my custom object.
I'm guessing this has something to do with the fact that with ICustomTypeDescriptor each instance shown in each row of each grid could be returning a different set of properties.
I'm implementing ICustomTypeDescriptor so that I can allow the users to dynamically add custom properties to objects at run time. These custom properties should be visible and editable through the DataGridView.
Why does DataGridView not see my ICustomTypeDescriptor methods? Is there another way that I can dynamically add properties to an object that will be displayed in a DataGridView?
DataGridView looks at the list version of metadata; the rules for this are... complex:
if the data-source implements IListSource, GetList() is evaluated and used as the data-source (continue at 2)
if the data-source implements ITypedList, GetProperties() is used to obtain metadata (exit)
if a typed (non-object) indexer can be found (i.e. public T this[int index]), then T is used as the source via TypeDescriptor.GetProperties(type):
if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit)
otherwise reflection is used for metadata (exit)
if the list is non-empty, the first object is used for metadata via TypeDescriptor.GetProperties(list[0]):
if ICustomTypeDescriptor is implemented, then it is used (exit) [*]
if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit) [*]
otherwise reflection is used (exit)
else metadata is unavailable (exit)
([*]=I can't remember which way around these two go...)
If you are using List<T> (or similar), then you hit the "simplest" (IMO) case - #3. If you want to provide custom metadata, then; you best bet is to write a TypeDescriptionProvider and associate it with the type. I can write an example but it'll take a while (on the train, probably)...
Edit: here's an example that uses ITypedList; I'll try to tweak it to use TypeDescriptionProvider instead...
Second edit: a full (yet minimal) example using TypeDescriptionProvider follows; long code warning...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
// example
static class Program {
[STAThread]
static void Main() {
PropertyBag.AddProperty("UserName", typeof(string), new DisplayNameAttribute("User Name"));
PropertyBag.AddProperty("DateOfBirth", typeof(DateTime), new DisplayNameAttribute("Date of Birth"));
BindingList<PropertyBag> list = new BindingList<PropertyBag>() {
new PropertyBag().With("UserName", "Fred").With("DateOfBirth", new DateTime(1998,12,1)),
new PropertyBag().With("UserName", "William").With("DateOfBirth", new DateTime(1997,4,23))
};
Application.Run(new Form {
Controls = {
new DataGridView { // prove it works for complex bindings
Dock = DockStyle.Fill,
DataSource = list,
ReadOnly = false, AllowUserToAddRows = true
}
},
DataBindings = {
{"Text", list, "UserName"} // prove it works for simple bindings
}
});
}
}
// PropertyBag file 1; the core bag
partial class PropertyBag : INotifyPropertyChanged {
private static PropertyDescriptorCollection props;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
static PropertyBag() {
props = new PropertyDescriptorCollection(new PropertyDescriptor[0], true);
// init the provider; I'm avoiding TypeDescriptionProviderAttribute so that we
// can exploit the default implementation for fun and profit
TypeDescriptionProvider defaultProvider = TypeDescriptor.GetProvider(typeof(PropertyBag)),
customProvider = new PropertyBagTypeDescriptionProvider(defaultProvider);
TypeDescriptor.AddProvider(customProvider, typeof(PropertyBag));
}
private static readonly object syncLock = new object();
public static void AddProperty(string name, Type type, params Attribute[] attributes) {
lock (syncLock)
{ // append the new prop, into a *new* collection, so that downstream
// callers don't have to worry about the complexities
PropertyDescriptor[] newProps = new PropertyDescriptor[props.Count + 1];
props.CopyTo(newProps, 0);
newProps[newProps.Length - 1] = new PropertyBagPropertyDescriptor(name, type, attributes);
props = new PropertyDescriptorCollection(newProps, true);
}
}
private readonly Dictionary<string, object> values;
public PropertyBag()
{ // mainly want to enforce that we have a public parameterless ctor
values = new Dictionary<string, object>();
}
public object this[string key] {
get {
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
object value;
values.TryGetValue(key, out value);
return value;
}
set {
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
var prop = props[key];
if (prop == null) throw new ArgumentException("Invalid property: " + key, "key");
values[key] = value;
OnPropertyChanged(key);
}
}
internal void Reset(string key) {
values.Remove(key);
}
internal bool ShouldSerialize(string key) {
return values.ContainsKey(key);
}
}
static class PropertyBagExt
{
// cheeky fluent API to make the example code easier:
public static PropertyBag With(this PropertyBag obj, string name, object value) {
obj[name] = value;
return obj;
}
}
// PropertyBag file 2: provider / type-descriptor
partial class PropertyBag {
class PropertyBagTypeDescriptionProvider : TypeDescriptionProvider, ICustomTypeDescriptor {
readonly ICustomTypeDescriptor defaultDescriptor;
public PropertyBagTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) {
this.defaultDescriptor = parent.GetTypeDescriptor(typeof(PropertyBag));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
return this;
}
AttributeCollection ICustomTypeDescriptor.GetAttributes() {
return defaultDescriptor.GetAttributes();
}
string ICustomTypeDescriptor.GetClassName() {
return defaultDescriptor.GetClassName();
}
string ICustomTypeDescriptor.GetComponentName() {
return defaultDescriptor.GetComponentName();
}
TypeConverter ICustomTypeDescriptor.GetConverter() {
return defaultDescriptor.GetConverter();
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() {
return defaultDescriptor.GetDefaultEvent();
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() {
return defaultDescriptor.GetDefaultProperty();
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType) {
return defaultDescriptor.GetEditor(editorBaseType);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) {
return defaultDescriptor.GetEvents(attributes);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() {
return defaultDescriptor.GetEvents();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) {
return PropertyBag.props; // should really be filtered, but meh!
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() {
return PropertyBag.props;
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) {
return defaultDescriptor.GetPropertyOwner(pd);
}
}
}
// PropertyBag file 3: property descriptor
partial class PropertyBag {
class PropertyBagPropertyDescriptor : PropertyDescriptor {
private readonly Type type;
public PropertyBagPropertyDescriptor(string name, Type type, Attribute[] attributes)
: base(name, attributes) {
this.type = type;
}
public override object GetValue(object component) {
return ((PropertyBag)component)[Name];
}
public override void SetValue(object component, object value) {
((PropertyBag)component)[Name] = value;
}
public override void ResetValue(object component) {
((PropertyBag)component).Reset(Name);
}
public override bool CanResetValue(object component) {
return true;
}
public override bool ShouldSerializeValue(object component) {
return ((PropertyBag)component).ShouldSerialize(Name);
}
public override Type PropertyType {
get { return type; }
}
public override bool IsReadOnly {
get { return false; }
}
public override Type ComponentType {
get { return typeof(PropertyBag); }
}
}
}
I have a class which the programmer can use to dynamically add new properties. For that it implements the ICustomTypeDescriptor to be able to override GetProperties() method.
public class DynamicProperty
{
public object Value { get; set; }
public Type Type { get; set; }
public string Name { get; set; }
public Collection<Attribute> Attributes { get; set; }
}
public class DynamicClass : ICustomTypeDescriptor
{
// Collection to code add dynamic properties
public KeyedCollection<string, DynamicProperty> Properties
{
get;
private set;
}
// ICustomTypeDescriptor implementation
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
// Properties founded within instance
PropertyInfo[] instanceProps = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
// Fill property collection with founded properties
PropertyDescriptorCollection propsCollection =
new PropertyDescriptorCollection(instanceProps.Cast<PropertyDescriptor>().ToArray());
// Fill property collection with dynamic properties (Properties)
foreach (var prop in Properties)
{
// HOW TO?
}
return propsCollection;
}
}
Is it possible to iterate over the Properties list to add each property to PropertyDescriptorCollection?
Basically I want the programmer to be able to add a DynamicProperty to a collection which will be handled by GetProperties. Something like:
new DynamicClass()
{
Properties = {
new DynamicProperty() {
Name = "StringProp",
Type = System.String,
Value = "My string here"
},
new DynamicProperty() {
Name = "IntProp",
Type = System.Int32,
Value = 10
}
}
}
Now those Properties would be setted to instance properties whenever GetPropertiesis called. Am I thinking this the right way?
You are already creating a collection like this:
PropertyDescriptorCollection propsCollection =
new PropertyDescriptorCollection(instanceProps.Cast<PropertyDescriptor>().ToArray());
But the collection you are creating only has the existing properties, not your new properties.
You need to supply a single array concatenated from the existing properties and your new properties.
Something like this:
instanceProps.Cast<PropertyDescriptor>().Concat(customProperties).ToArray()
Next problem: you need customProperties which is a collection of PropertyDescriptor. Unfortunately PropertyDescriptor is an abstract class so you don't have an easy way to create one.
We can fix this though, just define your own CustomPropertyDescriptor class by deriving from PropertyDescriptor and implementing all the abstract methods.
Something like this:
public class CustomPropertyDescriptor : PropertyDescriptor
{
private Type propertyType;
private Type componentType;
public CustomPropertyDescriptor(string propertyName, Type propertyType, Type componentType)
: base(propertyName, new Attribute[] { })
{
this.propertyType = propertyType;
this.componentType = componentType;
}
public override bool CanResetValue(object component) { return true; }
public override Type ComponentType { get { return componentType; } }
public override object GetValue(object component) { return 0; /* your code here to get a value */; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return propertyType; } }
public override void ResetValue(object component) { SetValue(component, null); }
public override void SetValue(object component, object value) { /* your code here to set a value */; }
public override bool ShouldSerializeValue(object component) { return true; }
}
I haven't filled in the calls to get and set your properties; those calls depend on how you've implemented the dynamic properties under the hood.
Then you need to create an array of CustomPropertyDescriptor filled in with information appropriate to your dynamic properties, and concatenate it to the basic properties as I described initially.
The PropertyDescriptor not only describes your properties, it also enables client to actually get and set the values of those properties. And that's the whole point, isn't it!
I'm displaying a list of objects in a DataGridView. Everything was working fine. Columns were automagicaly added to the DataGridView based on the properties of the objects.
Now I changed the class I'm displaying in the grid to implement ICustomTypeDescriptor. But now the grid now no longer shows any columns or rows when I set it's DataSource to a list of my custom object.
I'm guessing this has something to do with the fact that with ICustomTypeDescriptor each instance shown in each row of each grid could be returning a different set of properties.
I'm implementing ICustomTypeDescriptor so that I can allow the users to dynamically add custom properties to objects at run time. These custom properties should be visible and editable through the DataGridView.
Why does DataGridView not see my ICustomTypeDescriptor methods? Is there another way that I can dynamically add properties to an object that will be displayed in a DataGridView?
DataGridView looks at the list version of metadata; the rules for this are... complex:
if the data-source implements IListSource, GetList() is evaluated and used as the data-source (continue at 2)
if the data-source implements ITypedList, GetProperties() is used to obtain metadata (exit)
if a typed (non-object) indexer can be found (i.e. public T this[int index]), then T is used as the source via TypeDescriptor.GetProperties(type):
if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit)
otherwise reflection is used for metadata (exit)
if the list is non-empty, the first object is used for metadata via TypeDescriptor.GetProperties(list[0]):
if ICustomTypeDescriptor is implemented, then it is used (exit) [*]
if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit) [*]
otherwise reflection is used (exit)
else metadata is unavailable (exit)
([*]=I can't remember which way around these two go...)
If you are using List<T> (or similar), then you hit the "simplest" (IMO) case - #3. If you want to provide custom metadata, then; you best bet is to write a TypeDescriptionProvider and associate it with the type. I can write an example but it'll take a while (on the train, probably)...
Edit: here's an example that uses ITypedList; I'll try to tweak it to use TypeDescriptionProvider instead...
Second edit: a full (yet minimal) example using TypeDescriptionProvider follows; long code warning...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
// example
static class Program {
[STAThread]
static void Main() {
PropertyBag.AddProperty("UserName", typeof(string), new DisplayNameAttribute("User Name"));
PropertyBag.AddProperty("DateOfBirth", typeof(DateTime), new DisplayNameAttribute("Date of Birth"));
BindingList<PropertyBag> list = new BindingList<PropertyBag>() {
new PropertyBag().With("UserName", "Fred").With("DateOfBirth", new DateTime(1998,12,1)),
new PropertyBag().With("UserName", "William").With("DateOfBirth", new DateTime(1997,4,23))
};
Application.Run(new Form {
Controls = {
new DataGridView { // prove it works for complex bindings
Dock = DockStyle.Fill,
DataSource = list,
ReadOnly = false, AllowUserToAddRows = true
}
},
DataBindings = {
{"Text", list, "UserName"} // prove it works for simple bindings
}
});
}
}
// PropertyBag file 1; the core bag
partial class PropertyBag : INotifyPropertyChanged {
private static PropertyDescriptorCollection props;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
static PropertyBag() {
props = new PropertyDescriptorCollection(new PropertyDescriptor[0], true);
// init the provider; I'm avoiding TypeDescriptionProviderAttribute so that we
// can exploit the default implementation for fun and profit
TypeDescriptionProvider defaultProvider = TypeDescriptor.GetProvider(typeof(PropertyBag)),
customProvider = new PropertyBagTypeDescriptionProvider(defaultProvider);
TypeDescriptor.AddProvider(customProvider, typeof(PropertyBag));
}
private static readonly object syncLock = new object();
public static void AddProperty(string name, Type type, params Attribute[] attributes) {
lock (syncLock)
{ // append the new prop, into a *new* collection, so that downstream
// callers don't have to worry about the complexities
PropertyDescriptor[] newProps = new PropertyDescriptor[props.Count + 1];
props.CopyTo(newProps, 0);
newProps[newProps.Length - 1] = new PropertyBagPropertyDescriptor(name, type, attributes);
props = new PropertyDescriptorCollection(newProps, true);
}
}
private readonly Dictionary<string, object> values;
public PropertyBag()
{ // mainly want to enforce that we have a public parameterless ctor
values = new Dictionary<string, object>();
}
public object this[string key] {
get {
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
object value;
values.TryGetValue(key, out value);
return value;
}
set {
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
var prop = props[key];
if (prop == null) throw new ArgumentException("Invalid property: " + key, "key");
values[key] = value;
OnPropertyChanged(key);
}
}
internal void Reset(string key) {
values.Remove(key);
}
internal bool ShouldSerialize(string key) {
return values.ContainsKey(key);
}
}
static class PropertyBagExt
{
// cheeky fluent API to make the example code easier:
public static PropertyBag With(this PropertyBag obj, string name, object value) {
obj[name] = value;
return obj;
}
}
// PropertyBag file 2: provider / type-descriptor
partial class PropertyBag {
class PropertyBagTypeDescriptionProvider : TypeDescriptionProvider, ICustomTypeDescriptor {
readonly ICustomTypeDescriptor defaultDescriptor;
public PropertyBagTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) {
this.defaultDescriptor = parent.GetTypeDescriptor(typeof(PropertyBag));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
return this;
}
AttributeCollection ICustomTypeDescriptor.GetAttributes() {
return defaultDescriptor.GetAttributes();
}
string ICustomTypeDescriptor.GetClassName() {
return defaultDescriptor.GetClassName();
}
string ICustomTypeDescriptor.GetComponentName() {
return defaultDescriptor.GetComponentName();
}
TypeConverter ICustomTypeDescriptor.GetConverter() {
return defaultDescriptor.GetConverter();
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() {
return defaultDescriptor.GetDefaultEvent();
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() {
return defaultDescriptor.GetDefaultProperty();
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType) {
return defaultDescriptor.GetEditor(editorBaseType);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) {
return defaultDescriptor.GetEvents(attributes);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() {
return defaultDescriptor.GetEvents();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) {
return PropertyBag.props; // should really be filtered, but meh!
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() {
return PropertyBag.props;
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) {
return defaultDescriptor.GetPropertyOwner(pd);
}
}
}
// PropertyBag file 3: property descriptor
partial class PropertyBag {
class PropertyBagPropertyDescriptor : PropertyDescriptor {
private readonly Type type;
public PropertyBagPropertyDescriptor(string name, Type type, Attribute[] attributes)
: base(name, attributes) {
this.type = type;
}
public override object GetValue(object component) {
return ((PropertyBag)component)[Name];
}
public override void SetValue(object component, object value) {
((PropertyBag)component)[Name] = value;
}
public override void ResetValue(object component) {
((PropertyBag)component).Reset(Name);
}
public override bool CanResetValue(object component) {
return true;
}
public override bool ShouldSerializeValue(object component) {
return ((PropertyBag)component).ShouldSerialize(Name);
}
public override Type PropertyType {
get { return type; }
}
public override bool IsReadOnly {
get { return false; }
}
public override Type ComponentType {
get { return typeof(PropertyBag); }
}
}
}
As I understood , The property grid is given an object which it can manipulate by extracting its Properties using reflections.
My problem is that I have a set of Parameters that is determined during run-time , thus I can't staticly compose a class with properties to represent this set.
I have two idea in mind to solve this problem but both are complex and will probably consume lot of time , infact i will say they are not practical under my time constraints. One is to use Reflection Emit in order to define a class dynamically and the other is to dynamiclly build a C# source file and then compile it using CodeDom.
Can Property grid behave in a different manner( other then extracting the Properties of an object using reflections ) that can suite my problem?
If no do you know any other control that can do the job for me?
I want to say that the reason I went to the property grid from the begining was its ability to provide realy nice Data Retrieval UI for common types.For color you autometically get a palette , For dataTime you automatically have a nice calender. I would like to get those things automatically , If possible.
Yes, PropertyGrid can display things other than just the compile-time properties, by using any of TypeConverter, ICustomTypeDescriptor or TypeDescriptionProvider to provide runtime pseudo-properties. Can you give an example of what your parameters look like? I should be able to provide an example...
here's a basic example (everything is string, etc) based on an earlier reply (related but different):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class PropertyBagPropertyDescriptor : PropertyDescriptor {
public PropertyBagPropertyDescriptor(string name) : base(name, null) { }
public override object GetValue(object component) {
return ((PropertyBag)component)[Name];
}
public override void SetValue(object component, object value) {
((PropertyBag)component)[Name] = (string)value;
}
public override void ResetValue(object component) {
((PropertyBag)component)[Name] = null;
}
public override bool CanResetValue(object component) {
return true;
}
public override bool ShouldSerializeValue(object component)
{ // *** this controls whether it appears bold or not; you could compare
// *** to a default value, or the last saved value...
return ((PropertyBag)component)[Name] != null;
}
public override Type PropertyType {
get { return typeof(string); }
}
public override bool IsReadOnly {
get { return false; }
}
public override Type ComponentType {
get { return typeof(PropertyBag); }
}
}
[TypeConverter(typeof(PropertyBagConverter))]
class PropertyBag {
public string[] GetKeys() {
string[] keys = new string[values.Keys.Count];
values.Keys.CopyTo(keys, 0);
Array.Sort(keys);
return keys;
}
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;
}
}
}
// has the job of (among other things) providing properties to the PropertyGrid
class PropertyBagConverter : TypeConverter {
public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
return true; // are we providing custom properties from here?
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, System.Attribute[] attributes) {
// get the pseudo-properties
PropertyBag bag = (PropertyBag)value;
string[] keys = bag.GetKeys();
PropertyDescriptor[] props = Array.ConvertAll(
keys, key => new PropertyBagPropertyDescriptor(key));
return new PropertyDescriptorCollection(props, true);
}
}
static class Program {
[STAThread]
static void Main() { // demo form app
PropertyBag bag = new PropertyBag();
bag["abc"] = "def";
bag["ghi"] = "jkl";
bag["mno"] = "pqr";
Application.EnableVisualStyles();
Application.Run(
new Form {
Controls = { new PropertyGrid {
Dock = DockStyle.Fill,
SelectedObject = bag
}}
});
}
}