views:

73

answers:

2

Demands / problems:

  1. I would like to bind multiple properties of an entity to controls in a form. Some of which are read only from time to time (according to business logic). - Edit: The logic is based on the bound instance, not only on its the type.
  2. When using an entity that implements INotifyPropertyChanged as the DataSource, every change notification refreshes all the controls bound to that data source (easy to verify - just bind two properties to two controls and invoke a change notification on one of them, you will see that both properties are hit and reevaluated).
  3. There should be user friendly error notifications (the entity implements IDataErrorInfo). (probably using ErrorProvider)

Using the entity as the DataSource of the controls leads to performance issues and makes life harder when its time for a control to be read only.

I thought of creating some kind of wrapper that holds the entity and a specific property so that each control would be bound to a different DataSource. Moreover, that wrapper could hold the ReadOnly indicator for that property so the control would be bound directly to that value.

The wrapper could look like this:

interface IPropertyWrapper : INotifyPropertyChanged, IDataErrorInfo
{
    object Value { get; set; }

    bool IsReadOnly { get; }
}

But this means also a different ErrorProvider for each property (property wrapper)

I feel like I'm trying to reinvent the wheel... What is the 'proper' way of handling complex binding demands like these?

Thanks ahead.

A: 

I wouldn't wrap each property individually ... I would wrap the root domain object. In there I would implement the readonly logic ... and only set the value on the real domain objet if the readonly flag is set to false.

Joel Martinez
Hey, note that the each property has a different read only logic. How would you wrap the domain object to enable that? And what about the performance hit related to having the same data source bound to many controls?
Kaiser Soze
+1  A: 

You could write a wrapper for you entity that implements ICustomTypeDescriptor. That way, you could decide which properties are read-only or not... but that's quite a lot of work for a not so complex scenario.

A simpler solution would be to change the DataSourceUpdateMode of the binding to Never when you want the property to be read-only.


UPDATE: here's a basic wrapper implementing ICustomTypeDescriptor :

class EntityWrapper<T> : CustomTypeDescriptor
{
    public EntityWrapper(T entity)
    {
        this.Entity = entity;
        var properties = TypeDescriptor.GetProperties(typeof(T))
                    .Cast<PropertyDescriptor>()
                    .ToArray();
        ReadOnly = properties.ToDictionary(p => p.Name, p => p.IsReadOnly);
        _properties = new PropertyDescriptorCollection(properties
                            .Select(p => new WrapperPropertyDescriptor(p, this))
                            .ToArray());
    }

    public T Entity { get; private set; }
    public Dictionary<string, bool> ReadOnly { get; private set; }

    public override PropertyDescriptorCollection GetProperties()
    {
        return _properties;
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return _properties;
    }

    private PropertyDescriptorCollection _properties;
    private class WrapperPropertyDescriptor : PropertyDescriptor
    {
        private EntityWrapper<T> _entityWrapper;
        private PropertyDescriptor _property;

        public WrapperPropertyDescriptor(PropertyDescriptor property, EntityWrapper<T> entityWrapper)
            : base(property)
        {
            _property = property;
            _entityWrapper = entityWrapper;
        }

        public override bool CanResetValue(object component)
        {
            return _property.CanResetValue(component);
        }

        public override Type ComponentType
        {
            get { return _property.ComponentType; }
        }

        public override object GetValue(object component)
        {
            return _property.GetValue(component);
        }

        public override bool IsReadOnly
        {
            get
            {
                return _entityWrapper.ReadOnly[this.Name];
            }
        }

        public override Type PropertyType
        {
            get { return _property.PropertyType; }
        }

        public override void ResetValue(object component)
        {
            _property.ResetValue(component);
        }

        public override void SetValue(object component, object value)
        {
            _property.SetValue(component, value);
        }

        public override bool ShouldSerializeValue(object component)
        {
            return _property.ShouldSerializeValue(component);
        }
    }
}

As you can see, it's perfectly possible to make a property read-only just for one instance :

        MyEntity a = new MyEntity { Foo = "hello", Bar = 42 };
        MyEntity b = new MyEntity { Foo = "world", Bar = 5 };
        EntityWrapper<MyEntity> wa = new EntityWrapper<MyEntity>(a);
        EntityWrapper<MyEntity> wb = new EntityWrapper<MyEntity>(b);

        var fooA = wa.GetProperties()["Foo"];
        var fooB = wb.GetProperties()["Foo"];

        wa.ReadOnly["Foo"] = false;
        wb.ReadOnly["Foo"] = true;

        Console.WriteLine("Property Foo of object a is read-only : {0}", fooA.IsReadOnly);
        Console.WriteLine("Property Foo of object b is read-only : {0}", fooB.IsReadOnly);
Thomas Levesque
The `ICustomTypeDescriptor` interface lets you describe a TYPE, not a single entity, so the read only logic would be per type, not per instance. Setting `DataSourceUpdateMode.Never` still lets the user type / change the controls value and the control wont appear as read only...
Kaiser Soze
"so the read only logic would be per type, not per instance" : not necesarily, see my updated answer
Thomas Levesque
Unfortunately, I've tried to go this path before, and from my experience, this solution isn't optimal. And when thinking of the *responsibility of an property descriptor*, then making it implement `INotityPropertyChanged` (as we want the display to change) and having the `EntityWrapper<T>` member, but passing the component on to the wrapped `_property`... Well it just seems wrong (and with the example / test implementation wont work). Moreover - This forces us to bind our control to *two different* data sources (this entity - for the value, and the descriptor - for the read only indication)
Kaiser Soze
OK, trying to make the `ICustomTypeDescriptor` way work ( / play nice...) - How do you make the UI refresh after setting a property to a ReadOnly state?
Kaiser Soze
Good question... perhaps raising a PropertyChanged event for a property would also refresh the readonly status for that property ?
Thomas Levesque