Reputation: 3555
I implemented ICustomTypeDescriptor in my application to be able to define custom properties at runtime. My basic implementation looks like:
public class DynamicClass <T> : ICustomTypeDescriptor
{
private readonly T _object;
public DynamicClass(T trackedObject)
{
_object = trackedObject;
}
// Collection to code add dynamic properties
public KeyedCollection<string, DynamicProperty> Properties
{
get;
private set;
}
// ICustomTypeDescriptor implementation
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(_object, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(_object, true);
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(_object, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(_object, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(_object, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(_object, true);
}
public object GetEditor(Type editorBaseType)
{
throw new NotImplementedException();
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(_object, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(_object, attributes, true);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return TypeDescriptor.GetProperties(_object, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(_object, attributes, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return _object;
}
}
The problem is that now when I bind the object to a text box using the DynamicClass binder it does not work any more.
I use it like this:
DynamicClass<ExtensionModel> binder = new DynamicClass<ExtensionModel>(ext);
_versionLabel.DataBindings.Add("Text", binder, "SelectedVersion", false, DataSourceUpdateMode.OnPropertyChanged);
and I got the exception: 'Object does not match target type.'
Object does not match target type.
at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.ComponentModel.ReflectEventDescriptor.AddEventHandler(Object component, Delegate value) at System.ComponentModel.ReflectPropertyDescriptor.AddValueChanged(Object component, EventHandler handler) at System.Windows.Forms.BindToObject.CheckBinding() at System.Windows.Forms.Binding.SetListManager(BindingManagerBase bindingManagerBase) at System.Windows.Forms.ListManagerBindingsCollection.AddCore(Binding dataBinding) at System.Windows.Forms.BindingsCollection.Add(Binding binding) at System.Windows.Forms.BindingContext.UpdateBinding(BindingContext newBindingContext, Binding binding) at System.Windows.Forms.Control.UpdateBindings()
The binding works if instead of binder I use the ext object. Did I missed something in the ICustomTypeDescriptor implementation?
Upvotes: 3
Views: 2984
Reputation: 11
You have to wrap original descriptors with custom ones. Here is the code:
#region IBindingProxy
public interface IBindingProxy
{
void CheckAndNotify();
}
#endregion
public class BindingProxy : CustomTypeDescriptor, INotifyPropertyChanged, IBindingProxy
{
#region Static Constructor
private static readonly IDictionary<Type, PropertyDescriptorCollection> propertyCache;
static BindingProxy()
{
propertyCache = new Dictionary<Type, PropertyDescriptorCollection>();
}
private static PropertyDescriptorCollection GetTypeProperties(Type type)
{
lock (propertyCache)
{
PropertyDescriptorCollection properties;
if (!propertyCache.TryGetValue(type, out properties))
{
var list = new List<PropertyDescriptor>();
GetTypeProperties(list, type);
properties = new PropertyDescriptorCollection(list.ToArray());
propertyCache.Add(type, properties);
}
return properties;
}
}
private static void GetTypeProperties(ICollection<PropertyDescriptor> list, Type type)
{
foreach (var @interface in type.GetInterfaces())
{
GetTypeProperties(list, @interface);
}
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(type))
{
list.Add(new ProxyPropertyDescriptor(property));
}
}
#endregion
private readonly PropertyDescriptorCollection properties;
private readonly object instance;
public event PropertyChangedEventHandler PropertyChanged;
public BindingProxy(object instance)
{
this.instance = instance;
properties = instance == null
? PropertyDescriptorCollection .Empty
: GetTypeProperties(instance.GetType());
}
public void CheckAndNotify()
{
OnPropertyChanged(null);
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public object Instance
{
get { return instance; }
}
public override PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return properties;
}
public override Object GetPropertyOwner(PropertyDescriptor property)
{
return this;
}
#region ProxyPropertyDescriptor
private class ProxyPropertyDescriptor : PropertyDescriptor
{
private readonly PropertyDescriptor property;
public ProxyPropertyDescriptor(PropertyDescriptor property) : base(property)
{
this.property = property;
}
//public override string DisplayName
//{
// get { return property.DisplayName; }
//}
//public override string Description
//{
// get { return property.Description; }
//}
//public override string Category
//{
// get { return property.Category; }
//}
//public override TypeConverter Converter
//{
// get { return converter; }
//}
public override bool IsReadOnly
{
get { return property.IsReadOnly; }
}
public override void ResetValue(object component)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return property.ComponentType; }
}
public override Type PropertyType
{
get { return property.PropertyType; }
}
public override object GetValue(object component)
{
return property.GetValue(((BindingProxy)component).instance);
}
public override void SetValue(object component, object value)
{
var instance = ((BindingProxy)component).instance;
property.SetValue(instance, value);
OnValueChanged(instance, EventArgs.Empty);
}
}
#endregion
}
Upvotes: 1
Reputation: 639
I have managed to reproduce your issue in my test code. I can see that if you don't implement INotifyPropertyChanged on ExtensionModel then it works!
So there is something with your implementation of ICustomTypeDescriptor that does not work with property classes that implement INotifyPropertyChanged.
This works, but if you uncomment the INotifyPropertyChange it will break.
public class BindingExample
{
public void Shows_Binding_To_A_Label_With_DynamicClass()
{
Form frm = new Form();
Label _versionLabel = new Label();
frm.Controls.Add(_versionLabel);
ExtensionModel ext = new ExtensionModel() { SelectedVersion = "DynamicClass Example" };
DynamicClass<ExtensionModel> binder = new DynamicClass<ExtensionModel>(ext);
_versionLabel.DataBindings.Add("Text", binder, "SelectedVersion", false, DataSourceUpdateMode.OnPropertyChanged);
frm.ShowDialog();
}
}
public class ExtensionModel// : INotifyPropertyChanged
{
string selectedVersion;
public string SelectedVersion
{
get { return selectedVersion; }
set
{
selectedVersion = value;
onPropertyChanged("SelectedVersion");
}
}
void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to get this going, so I'll continue to play with it.
Upvotes: 0