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!
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'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); }
}
}
}
for auditory reasons I stores the arguments of the business methods serialized into the database using the binaryformatter.
The problem is that when an argument is a generic list I don't find the way to cast the deserialized object because I don't know the type, or If I will know the type I don't know how to cast the object at runtime.
Anybody knows how to cast an object containing a generic list dinamically at runtime?
I need to do this because I need to show the deserialized data in a property grid:
object objArg = bformatter.Deserialize(memStr);
//If the type is a clr type (int, string, etc)
if (objArg.GetType().Module.Name == "mscorlib.dll")
{
//If the type is a generic type (List<>, etc)
//(I'm only use List for these cases)
if (objArg.GetType().IsGenericType)
{
// here is the problem
pgArgsIn.SelectedObject = new { Value = objArg};
//In the previous line I need to do something like...
//new { Value = (List<objArg.GetYpe()>) objArg};
}
else
{
pgArgsIn.SelectedObject = new { Value = objArg.ToString() };
}
}
else
{
//An entity object
pgArgsIn.SelectedObject = objArg;
}
With BinaryFormatter you don't need to know the type; the metadata is included in the stream (making it bigger, but hey!). However, you can't cast unless you know the type. Often in this scenario you have to use common known interfaces (non-generic IList etc) and reflection. And lots of it.
I also can't think of a huge requirement to know the type to show in a PropertyGrid - since this accepts object, just give it what BinaryFormatter provides. Is there a specific issue you are seeing there? Again, you might want to check for IList (non-generic) - but it isn't worth worrying about IList<T>, since this isn't what PropertyGrid checks for!
You can of course find the T if you want (like so) - and use MakeGenericType() and Activator.CreateInstance - not pretty.
OK; here's a way using custom descriptors that doesn't involve knowing anything about the object or the list type; if you really want it is possible to expand the list items directly into the properties, so in this example you'd see 2 fake properties ("Fred" and "Wilma") - that is extra work, though ;-p
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public override string ToString() {
return Name;
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Person fred = new Person();
fred.Name = "Fred";
fred.DateOfBirth = DateTime.Today.AddYears(-23);
Person wilma = new Person();
wilma.Name = "Wilma";
wilma.DateOfBirth = DateTime.Today.AddYears(-20);
ShowUnknownObject(fred, "Single object");
List<Person> list = new List<Person>();
list.Add(fred);
list.Add(wilma);
ShowUnknownObject(list, "List");
}
static void ShowUnknownObject(object obj, string caption)
{
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = caption;
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = ListWrapper.Wrap(obj);
Application.Run(form);
}
}
}
[TypeConverter(typeof(ListWrapperConverter))]
public class ListWrapper
{
public static object Wrap(object obj)
{
IListSource ls = obj as IListSource;
if (ls != null) obj = ls.GetList(); // list expansions
IList list = obj as IList;
return list == null ? obj : new ListWrapper(list);
}
private readonly IList list;
private ListWrapper(IList list)
{
if (list == null) throw new ArgumentNullException("list");
this.list = list;
}
internal class ListWrapperConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(
ITypeDescriptorContext context, object value, Attribute[] attributes) {
return new PropertyDescriptorCollection(
new PropertyDescriptor[] { new ListWrapperDescriptor(value as ListWrapper) });
}
}
internal class ListWrapperDescriptor : PropertyDescriptor {
private readonly ListWrapper wrapper;
internal ListWrapperDescriptor(ListWrapper wrapper) : base("Wrapper", null)
{
if (wrapper == null) throw new ArgumentNullException("wrapper");
this.wrapper = wrapper;
}
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) {
throw new NotSupportedException();
}
public override bool CanResetValue(object component) { return false; }
public override bool IsReadOnly {get {return true;}}
public override void SetValue(object component, object value) {
throw new NotSupportedException();
}
public override object GetValue(object component) {
return ((ListWrapper)component).list;
}
public override Type ComponentType {
get { return typeof(ListWrapper); }
}
public override Type PropertyType {
get { return wrapper.list.GetType(); }
}
public override string DisplayName {
get {
IList list = wrapper.list;
if (list.Count == 0) return "Empty list";
return "List of " + list.Count
+ " " + list[0].GetType().Name;
}
}
}
}
If the serializer you are using does not retain the type - at the least, you must store the type of T along with the data, and use that to create the generic list reflectively:
//during storage:
Type elementType = myList.GetType().GetGenericTypeDefinition().GetGenericArguments[0];
string typeNameToSave = elementType.FullName;
//during retrieval
string typeNameFromDatabase = GetTypeNameFromDB();
Type elementType = Type.GetType(typeNameFromDatabase);
Type listType = typeof(List<>).MakeGenericType(new Type[] { elementType });
Now you have listType, which is the exact List<T> you used (say, List<Foo>). You can pass that type into your deserialization routine.
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
}}
});
}
}