动态对象双向数据绑定 [英] Dynamic object two way data binding

查看:127
本文介绍了动态对象双向数据绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个动态数据容器,该容器允许(某些)动态添加的属性绑定到WinForm元素。到目前为止,当我绑定常规对象属性时,绑定可以正常工作。

I am trying to build a dynamic data container that allows (some of) the dynamically added properties to be bound to WinForm elements. So far, when I bind a regular object property the binding works fine.

示例:

public class CompileTimePropertiesDataContainer {
    public string TestString = "Hello World";
}

,然后在表单中进行绑定就可以了:

and then binding within the form works fine:

var component = new CompileTimePropertiesDataContainer();
lblTestString.DataBinding.Add(
    "Text", component, "TestString", false, DataSourceUpdateMode.OnPropertyChanged);
// >>> lblTestString.Text == "Hello World"
component.TestString = "Another Sample";
// >>> lblTestString.Text == "Another Sample";

此时,上面的示例有效,并假设对object属性的更新是在UI上完成的线。因此,现在我需要实现一个具有动态属性的对象(以确保该项目和其他项目的可恢复性)。

At this point the above sample works and assumes that the updates to the objects property is done on the UI thread. So now I need to implement an object that has dynamic properties (for resuability across this project and other projects).

因此,我实现了以下类(替换上面的CompileTimePropertiesDataContainer) :

So I have implemented the following class (replacing CompileTimePropertiesDataContainer above):

public class DataContainer : DynamicObject, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _data = 
        new Dictionary<string, object>();
    private readonly object _lock = new object();

    public object this[string name]
    {
        get {
            object value;
            lock (_lock) {
                value = (_data.ContainsKey(name)) ? _data[name] : null;
            }
            return value;
        }
        set {
            lock (_lock) {
                _data[name] = value;
            }
            OnPropertyChanged(name);
        }
    }

    #region DynamicObject
    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        result = this[binder.Name];
        return result != null;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value) {
        this[binder.Name] = value;
        return true;
    }
    #endregion

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(
        [CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region ICustomTypeDescriptor (DataContainer)
    public AttributeCollection GetAttributes()
        => TypeDescriptor.GetAttributes(typeof(DataContainer));
    public string GetClassName() 
        => TypeDescriptor.GetClassName(typeof(DataContainer));
    public string GetComponentName() 
        => TypeDescriptor.GetComponentName(typeof(DataContainer));
    public TypeConverter GetConverter() 
        => TypeDescriptor.GetConverter(typeof(DataContainer));
    public EventDescriptor GetDefaultEvent() 
        => TypeDescriptor.GetDefaultEvent(typeof(DataContainer));
    public PropertyDescriptor GetDefaultProperty() 
        => TypeDescriptor.GetDefaultProperty(typeof(DataContainer));
    public object GetEditor(Type editorBaseType)
        => TypeDescriptor.GetEditor(typeof(DataContainer), editorBaseType);
    public EventDescriptorCollection GetEvents() 
        => TypeDescriptor.GetEvents(typeof(DataContainer));
    public EventDescriptorCollection GetEvents(Attribute[] attributes)
        => TypeDescriptor.GetEvents(typeof(DataContainer), attributes);
    public PropertyDescriptorCollection GetProperties() 
        => GetProperties(new Attribute[0]);
    public PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
        Dictionary<string, object> data;
        lock (_lock) {
            data = _data;
        }
        // Add the dynamic properties from the class
        var properties = data
            .Select(p => new DynamicPropertyDescriptor(p.Key, p.Value.GetType()))
            .Cast<PropertyDescriptor>()
            .ToList();
        // Include concrete properties that belong to the class
        properties.AddRange(
            TypeDescriptor
                .GetProperties(GetType(), attributes)
                .Cast<PropertyDescriptor>());
        return new PropertyDescriptorCollection(properties.ToArray());
    }
    public object GetPropertyOwner(PropertyDescriptor pd) => this;
    #endregion
}

并按以下方式实现DynamicPropertyDescriptor(以设置在DataContainer上使用GetProperties()时动态添加的属性的属性描述符:

And implemented DynamicPropertyDescriptor as follows (to set up the property descriptor for dynamically added properties when using GetProperties() on the DataContainer:

public class DynamicPropertyDescriptor : PropertyDescriptor
{
    #region Properties
    public override Type ComponentType => typeof(DataContainer);
    public override bool IsReadOnly => false;
    public override Type PropertyType { get; }
    #endregion

    #region Constructor
    public DynamicPropertyDescriptor(string key, Type valueType) : base(key, null)
    {
        PropertyType = valueType;
    }
    #endregion

    #region Methods
    public override bool CanResetValue(object component) 
        => true;
    public override object GetValue(object component)
        => ((DataContainer)component)[Name];
    public override void ResetValue(object component)
        => ((DataContainer)component)[Name] = null;
    public override void SetValue(object component, object value)
        => ((DataContainer)component)[Name] = value;
    public override bool ShouldSerializeValue(object component)
        => false;
    #endregion Methods
}

在上面的代码中,我实现了INotifyPropertyChanged按照我的理解,可以满足绑定到winforms控件的要求,并定义了DataContainer及其提供的动态属性的属性描述符。

In the code above, I have implemented INotifyPropertyChanged to meet the requirements of binding to the winforms control as I understand it, and defined the property descriptors for both the DataContainer and the dynamic properties it provides.

现在回到在示例实现中,我将对象调整为动态,现在绑定似乎不会粘住。

Now back to the sample implementation, I adjusted the object to be 'dynamic' and now the binding won't seem to 'stick'.

dynamic component = new DataContainer();
// *EDIT* forgot to initialize component.TestString in original post
component.TestString  = "Hello World";
lblTestString.DataBinding.Add(
    "Text", component, "TestString", false, DataSourceUpdateMode.OnPropertyChanged);
// >>> lblTestString.Text == "Hello World"
component.TestString = "Another Sample";
// >>> lblTestString.Text == "Hello World";

,另外请注意,DataContainer对象中的'event PropertyChangedEventHandler PropertyChanged'为null,事件正在触发(

and another note the 'event PropertyChangedEventHandler PropertyChanged' in the DataContainer object is null, the event is firing (confirmed through debugging), but because PropertyChanged is null (nothing listening for the event), its not updating.

我觉得问题出在我的ICustomTypeDescriptor实现上在DataContainer OR DynamicPropertyDescriptor中。

I have a feeling that the problem lies with my implementation of ICustomTypeDescriptor in the DataContainer OR the DynamicPropertyDescriptor.

推荐答案

设置与属性的数据绑定时,框架会调用该属性的 PropertyDescriptor AddValueChanged 方法。要提供双向数据绑定,您的属性描述符应重写该方法,并订阅组件的 PropertyChanged 事件,然后调用 OnValueChanged 属性描述符的方法:

When setting up data binding to a property, framework invokes AddValueChanged method of the PropertyDescriptor of that property. To provide two-way data binding, your property descriptor should override that method and subscribe for PropertyChanged event of the component and call OnValueChanged method of the property descriptor:

void PropertyChanged(object sender, EventArgs e)
{
    OnValueChanged(sender, e);
}
public override void AddValueChanged(object component, EventHandler handler)
{
    base.AddValueChanged(component, handler);
    ((INotifyPropertyChanged)component).PropertyChanged += PropertyChanged;
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
    base.RemoveValueChanged(component, handler);
    ((INotifyPropertyChanged)component).PropertyChanged -= PropertyChanged;
}

示例

您可以在以下存储库中找到有效的实现:

You can find a working implementation in the following repository:

  • r-aghaei/DynamicObjectTwoWayDataBinding

这篇关于动态对象双向数据绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆