Reputation: 1379
I want to bind my custom type (with properties, or data lets say, that are filled in runtime). As I don't know the number of properties at compile time, I need to create a list that contain them and they value.
Later, I want to bind a list with this objects to some grid or DropDownList.
I know that those controls receive a DataSource (object) through the DataSource property, and when I call the method DataBind(), the default implementation use reflection to get properties and data and write to that control.
I have a type, lets imagine:
class MyDynamicDataObject {
Dictionary<string, object> _properties;
public MyDynamicDataObject() {
_properties = new Dictionary<string, object>();
}
public void Add(string property, object value) {
_properties.Add(property, value);
}
}
But I need know to "override" or lets say, return in abstract way, the properties of my type, that are contained in _properties Dictionary data-structure.
How can I do this to let controls use DataBind and use default implementation (use Type.GetProperties) - but instead of return my type properties that are none, return an abstraction that are contained in the dictionary?
Thanks
Upvotes: 2
Views: 460
Reputation: 1379
public interface IDynamicObject
{
string[] Properties { get; }
void Clear();
object this[string property] { get; set; }
}
public class DynamicObject : IDynamicObject
{
readonly Dictionary<string, object> _dynamicProperties = new Dictionary<string, object>();
public string[] Properties
{
get { return _dynamicProperties.Keys.ToArray(); }
}
public void Clear() { _dynamicProperties.Clear(); }
public object this[string property]
{
get
{
object value;
if (!_dynamicProperties.TryGetValue(property, out value))
value = null;
return value;
}
set
{
_dynamicProperties[property] = value;
}
}
}
public class DynamicObjectPropertyDescriptor<T> : PropertyDescriptor
where T : IDynamicObject
{
public DynamicObjectPropertyDescriptor(string name) : base(name, new Attribute[0])
{
}
T Get(object component)
{
return (T)component;
}
public override bool IsReadOnly { get { return false; } }
public override bool CanResetValue(object component) { return true; }
public override void ResetValue(object component) { Get(component)[Name] = null; }
public override Type ComponentType { get { return typeof(T); } }
public override Type PropertyType { get { return typeof(object); } }
public override object GetValue(object component) {
return Get(component)[Name];
}
public override void SetValue(object component, object value) {
Get(component)[Name] = value;
}
public override bool ShouldSerializeValue(object component) { return false; }
}
public class DynamicList<T> : List<T>, ITypedList
where T : IDynamicObject
{
int bindingIndex = 0;
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
if (listAccessors != null && listAccessors.Length != 0)
throw new NotSupportedException();
if (Count == 0)
return new PropertyDescriptorCollection(new[] { new DynamicObjectPropertyDescriptor<T>("-") });
PropertyDescriptorCollection result = new PropertyDescriptorCollection(this[bindingIndex].Properties.Select(prop => new DynamicObjectPropertyDescriptor<T>(prop)).ToArray());
if (++bindingIndex == Count)
bindingIndex = 0;
return result;
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
return typeof(DynamicList<T>).Name;
}
}
To see this working:
DynamicList<DynamicObject> list = new DynamicList<DynamicObject>();
DynamicObject a1 = new DynamicObject();
a1["nome"] = "Goncalo Dias";
a1["numero"] = 30337;
DynamicObject a2 = new DynamicObject();
a2["nome"] = "Carlos Antunes";
a2["numero"] = 10222;
DynamicObject a3 = new DynamicObject();
a3["nome"] = "Tiago Rodrigues";
a3["numero"] = 4040;
DynamicObject a4 = new DynamicObject();
a4["nome"] = "Digoo Martins";
a4["numero"] = 1220;
a4["morada"] = "Rua da esquina";
list.Add(a1);
list.Add(a2);
list.Add(a3);
list.Add(a4);
ultraGrid1.DataSource = list;
ultraGrid1.DataBind();
Upvotes: 1
Reputation: 1063884
Most data-binding already supports dynamic data models, but not dynamic
data models. By which I mean: there are existing mechanisms for this, including ICustomTypeDescriptor
, (perhaps via TypeDescriptionProvider
), ITypedList
, and PropertyDescriptor
. This is the mechanism used by DataTable
and similar, so is well exercised. Since you are binding to a list, ITypedList
is probably the most appropriate interface to implement, which means you just need to write a custom PropertyDescriptor
that maps between your dictionary API and the descriptor API.
Here's a winforms example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
using(var form = new Form())
using(var grid = new DataGridView())
{
var list = new MySpecialList();
var obj = new MyDynamicDataObject();
obj["Foo"] = 123;
obj["Bar"] = "def";
list.Add(obj);
obj = new MyDynamicDataObject();
obj["Bar"] = "abc";
obj["Blap"] = 123.4F;
list.Add(obj);
grid.DataSource = list;
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
Application.Run(form);
}
}
}
class MySpecialList : List<MyDynamicDataObject>, ITypedList
{
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
// don't worry about sub-property access unless you need to
if(listAccessors != null && listAccessors.Length != 0) throw new NotSupportedException();
var allKeys = new HashSet<string>();
foreach(var item in this)
{
foreach (string key in item.GetKeys()) allKeys.Add(key);
}
var props = allKeys.Select(key => new MyDynamicDataObjectDescriptor(key));
return new PropertyDescriptorCollection(props.ToArray());
}
private class MyDynamicDataObjectDescriptor : PropertyDescriptor
{
public MyDynamicDataObjectDescriptor(string name) : base(name, new Attribute[0]) { }
public MyDynamicDataObject GetObject(object component)
{
return (MyDynamicDataObject) component;
}
public override object GetValue(object component)
{
return GetObject(component)[Name];
}
public override void SetValue(object component, object value)
{
GetObject(component)[Name] = value;
}
public override bool CanResetValue(object component)
{
return false;
}
public override void ResetValue(object component)
{
throw new NotSupportedException();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type ComponentType
{
get { return typeof (MyDynamicDataObject); }
}
public override Type PropertyType
{
get { return typeof (object); }
}
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
throw new System.NotImplementedException();
}
}
class MyDynamicDataObject
{
// don't recommend inheriting this; confuses matters a lot...
private Dictionary<string,object> props = new Dictionary<string, object>();
public string[] GetKeys()
{
return props.Keys.ToArray();
}
public void Clear(string key)
{
props.Remove(key);
}
public object this[string key]
{
get
{
object value;
if (!props.TryGetValue(key, out value)) value = null;
return value;
}
set
{
props[key] = value;
}
}
}
Upvotes: 3