开发者

winforms databinding best practices

Demands / problems:

  1. I would like to bind multiple properties of an entity to controls in a form. Some of which are read only fr开发者_StackOverflowom 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.


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);


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜