【问题标题】:winforms databinding best practiceswinforms 数据绑定最佳实践
【发布时间】:2010-03-16 19:35:32
【问题描述】:

需求/问题:

  1. 我想将一个实体的多个属性绑定到表单中的控件。 其中一些是不时只读的(根据业务逻辑)。 - 编辑: 逻辑基于绑定实例,而不仅仅是其类型。
  2. 当使用将INotifyPropertyChanged 实现为DataSource 的实体时,每个更改通知刷新绑定到该数据源的所有控件(易于验证 - 只需将两个属性绑定到两个控件并在其中一个上调用更改通知,您将看到两个属性都被命中并重新评估)。
  3. 应该有用户友好的错误通知(实体实现IDataErrorInfo)。 (可能使用ErrorProvider

将实体用作控件的DataSource 会导致性能问题,并在控件变为只读时使生活变得更加困难。

我想创建某种包装器来保存实体和特定属性,以便每个控件都绑定到不同的DataSource。此外,该包装器可以保存该属性的ReadOnly 指示符,因此控件将直接绑定到该值。

包装器可能如下所示:

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

    bool IsReadOnly { get; }
}

但这也意味着每个属性(属性包装器)都有不同的ErrorProvider

我觉得我正在尝试重新发明轮子......处理此类复杂绑定需求的“正确”方法是什么?

先谢谢了。

【问题讨论】:

    标签: c# winforms data-binding


    【解决方案1】:

    您可以为实现ICustomTypeDescriptor 的实体编写一个包装器。这样一来,您就可以决定哪些属性是只读的……但是对于一个不太复杂的场景来说,这是相当多的工作。

    当您希望属性为只读时,更简单的解决方案是将绑定的DataSourceUpdateMode 更改为Never


    更新:这是一个实现 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);
            }
        }
    }
    

    如您所见,完全可以将属性设置为只读一个实例:

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

    【讨论】:

    • ICustomTypeDescriptor 接口允许您描述一个类型,而不是单个实体,因此只读逻辑将是每个类型,而不是每个实例。设置DataSourceUpdateMode.Never 仍然允许用户键入/更改控件值,并且控件不会显示为只读...
    • “所以只读逻辑将是每个类型,而不是每个实例”:不一定,请参阅我的更新答案
    • 不幸的是,我以前尝试过这条路,根据我的经验,这个解决方案不是最佳的。当考虑属性描述符的职责时,让它实现INotityPropertyChanged(因为我们希望显示发生变化)并拥有EntityWrapper&lt;T&gt;成员,但将组件传递给包装的_property...好吧,这似乎是错误的(并且示例/测试实现不起作用)。此外 - 这迫使我们将控件绑定到两个不同的数据源(这个实体 - 用于值,而描述符 - 用于只读指示)
    • 好的,尝试让ICustomTypeDescriptor 工作(/play nice...) - 如何在将属性设置为只读状态后刷新 UI?
    • 好问题...也许为属性引发 PropertyChanged 事件也会刷新该属性的只读状态?
    【解决方案2】:

    我不会单独包装每个属性...我会包装根域对象。在那里我将实现只读逻辑......并且只有在只读标志设置为 false 时才在真实域对象上设置值。

    【讨论】:

    • 嘿,注意每个属性都有不同的只读逻辑。您将如何包装域对象以启用它?如果将相同的数据源绑定到多个控件,性能会受到怎样的影响?
    猜你喜欢
    • 2010-10-10
    • 2011-03-09
    • 2018-11-03
    • 1970-01-01
    • 2020-01-10
    • 2023-03-25
    • 1970-01-01
    • 2015-12-14
    • 1970-01-01
    相关资源
    最近更新 更多