5

我有一个DataTable复杂的对象。

例如,

class ComplexDataWrapper
{
    public string Name{ get; set; }

    public ComplexData Data{ get; set; }

    public ComplexDataWrapper(ComplexData data)
    {
        this.Data = data;
        this.Name = "Something";
    }

    public override string ToString()
    {
        return Name;
    }
}

现在我想将单元格从DataTable对象绑定到ComplexDataWrapper 所以,我尝试这样的事情:

...
var column = new DataColumn() { ColumnName = columnName, DataType = typeof(ComplexDataWrapper)};
row[column] = new ComplexDataWrapper(data);

但是,我只想绑定一个属性,例如Name. 在gridview(DataTable是这个视图的数据源)中,我想编辑这个属性(名称)。

var complexDataWrapper = row[column] as ComplexDataWrapper;

complexDataWrapper 始终等于 NULL。

我知道我错过了什么。

所以我的问题是:如何将我的单元格绑定DataTable到复杂对象?另外在网格视图中我想编辑复杂对象的一个​​属性。

谢谢。希望一切都清楚。

4

2 回答 2

3

所以我的问题是:如何将我的 DataTable 单元格绑定到复杂对象?另外在网格视图中我想编辑复杂对象的一个​​属性。

您在这里需要的是绑定到所谓的属性路径(例如obj.Prop1.Prop2)的能力。不幸的是,WinForms 对此的支持有限 - 它支持简单的数据绑定(如control.DataBindings.Add(...)),但不支持控件和类似 使用的列表数据绑定。DataGridView

幸运的是,它仍然可以通过一些(大多数时候是微不足道的)编码来实现,因为数据绑定是围绕一个名为PropertyDescriptor. 默认情况下,它是通过反射实现的,但没有什么能阻止您创建自己的实现并在其中做任何您喜欢的事情。这使您可以做许多反射无法完成的事情,特别是模拟实际上不存在的“属性”。

在这里,我们将利用这种可能性来创建一个“属性”,该“属性”实际上从原始属性的子属性获取/设置其值,而从外部看,它仍然看起来像单个属性,因此允许数据绑定到它:

public class ChildPropertyDescriptor : PropertyDescriptor
{
    public static PropertyDescriptor Create(PropertyDescriptor sourceProperty, string childPropertyPath, string displayName = null)
    {
        var propertyNames = childPropertyPath.Split('.');
        var propertyPath = new PropertyDescriptor[1 + propertyNames.Length];
        propertyPath[0] = sourceProperty;
        for (int i = 0; i < propertyNames.Length; i++)
            propertyPath[i + 1] = propertyPath[i].GetChildProperties()[propertyNames[i]];
        return new ChildPropertyDescriptor(propertyPath, displayName);
    }
    private ChildPropertyDescriptor(PropertyDescriptor[] propertyPath, string displayName)
        : base(propertyPath[0].Name, null)
    {
        this.propertyPath = propertyPath;
        this.displayName = displayName;
    }
    private PropertyDescriptor[] propertyPath;
    private string displayName;
    private PropertyDescriptor RootProperty { get { return propertyPath[0]; } }
    private PropertyDescriptor ValueProperty { get { return propertyPath[propertyPath.Length - 1]; } }
    public override Type ComponentType { get { return RootProperty.ComponentType; } }
    public override bool IsReadOnly { get { return ValueProperty.IsReadOnly; } }
    public override Type PropertyType { get { return ValueProperty.PropertyType; } }
    public override bool CanResetValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.CanResetValue(target); }
    public override object GetValue(object component) { var target = GetTarget(component); return target != null ? ValueProperty.GetValue(target) : null; }
    public override void ResetValue(object component) { ValueProperty.ResetValue(GetTarget(component)); }
    public override void SetValue(object component, object value) { ValueProperty.SetValue(GetTarget(component), value); }
    public override bool ShouldSerializeValue(object component) { var target = GetTarget(component); return target != null && ValueProperty.ShouldSerializeValue(target); }
    public override AttributeCollection Attributes { get { return ValueProperty.Attributes; } }
    public override string Category { get { return ValueProperty.Category; } }
    public override TypeConverter Converter { get { return ValueProperty.Converter; } }
    public override string Description { get { return ValueProperty.Description; } }
    public override bool IsBrowsable { get { return ValueProperty.IsBrowsable; } }
    public override bool IsLocalizable { get { return ValueProperty.IsLocalizable; } }
    public override string DisplayName { get { return displayName ?? RootProperty.DisplayName; } }
    public override object GetEditor(Type editorBaseType) { return ValueProperty.GetEditor(editorBaseType); }
    public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) { return ValueProperty.GetChildProperties(GetTarget(instance), filter); }
    public override bool SupportsChangeEvents { get { return ValueProperty.SupportsChangeEvents; } }
    public override void AddValueChanged(object component, EventHandler handler)
    {
        var target = GetTarget(component);
        if (target != null)
            ValueProperty.AddValueChanged(target, handler);
    }
    public override void RemoveValueChanged(object component, EventHandler handler)
    {
        var target = GetTarget(component);
        if (target != null)
            ValueProperty.RemoveValueChanged(target, handler);
    }
    private object GetTarget(object source)
    {
        var target = source;
        for (int i = 0; target != null && target != DBNull.Value && i < propertyPath.Length - 1; i++)
            target = propertyPath[i].GetValue(target);
        return target != DBNull.Value ? target : null;
    }
}

代码不是那么小,但它所做的基本上就是将调用委托给表示从原始属性到子属性的路径的属性描述符链的相应方法。另请注意,许多方法PropertyDescriptor仅在设计时使用,因此创建自定义具体运行时属性描述符通常只需要实现ComponentType、、PropertyTypeGetValueSetValue如果支持)。

到目前为止,一切都很好。这只是谜题的第一部分。我们可以创建一个“属性”,现在我们需要一种方法让数据绑定使用它。

为了做到这一点,我们将使用另一个数据绑定相关的接口,称为ITypedList

提供发现可绑定列表架构的功能,其中可用于绑定的属性与要绑定到的对象的公共属性不同。

换句话说,它允许我们为列表元素提供“属性”。但是怎么做?如果我们正在实现数据源列表,那将很容易。但是在这里,我们希望对我们事先不知道的列表执行此操作(我正在尝试保持解决方案的通用性)。

解决方案是将原始列表包装在另一个将实现IList(列表数据绑定的最低要求)的列表中,方法是将所有调用委托给底层列表,但通过实现ITypedList将控制用于绑定的属性:

public static class ListDataView
{
    public static IList Create(object dataSource, string dataMember, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper)
    {
        var source = (IList)ListBindingHelper.GetList(dataSource, dataMember);
        if (source == null) return null;
        if (source is IBindingListView) return new BindingListView((IBindingListView)source, propertyMapper);
        if (source is IBindingList) return new BindingList((IBindingList)source, propertyMapper);
        return new List(source, propertyMapper);
    }

    private class List : IList, ITypedList
    {
        private readonly IList source;
        private readonly Func<PropertyDescriptor, PropertyDescriptor> propertyMapper;
        public List(IList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) { this.source = source; this.propertyMapper = propertyMapper; }
        // IList
        public object this[int index] { get { return source[index]; } set { source[index] = value; } }
        public int Count { get { return source.Count; } }
        public bool IsFixedSize { get { return source.IsFixedSize; } }
        public bool IsReadOnly { get { return source.IsReadOnly; } }
        public bool IsSynchronized { get { return source.IsSynchronized; } }
        public object SyncRoot { get { return source.SyncRoot; } }
        public int Add(object value) { return source.Add(value); }
        public void Clear() { source.Clear(); }
        public bool Contains(object value) { return source.Contains(value); }
        public void CopyTo(Array array, int index) { source.CopyTo(array, index); }
        public IEnumerator GetEnumerator() { return source.GetEnumerator(); }
        public int IndexOf(object value) { return source.IndexOf(value); }
        public void Insert(int index, object value) { source.Insert(index, value); }
        public void Remove(object value) { source.Remove(value); }
        public void RemoveAt(int index) { source.RemoveAt(index); }
        // ITypedList
        public string GetListName(PropertyDescriptor[] listAccessors) { return ListBindingHelper.GetListName(source, listAccessors); }
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            var properties = ListBindingHelper.GetListItemProperties(source, listAccessors);
            if (propertyMapper != null)
                properties = new PropertyDescriptorCollection(properties.Cast<PropertyDescriptor>()
                    .Select(propertyMapper).Where(p => p != null).ToArray());
            return properties;
        }
    }

    private class BindingList : List, IBindingList
    {
        private IBindingList source;
        public BindingList(IBindingList source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
        private ListChangedEventHandler listChanged;
        public event ListChangedEventHandler ListChanged
        {
            add
            {
                var oldHandler = listChanged;
                if ((listChanged = oldHandler + value) != null && oldHandler == null)
                    source.ListChanged += OnListChanged;
            }
            remove
            {
                var oldHandler = listChanged;
                if ((listChanged = oldHandler - value) == null && oldHandler != null)
                    source.ListChanged -= OnListChanged;
            }
        }
        private void OnListChanged(object sender, ListChangedEventArgs e)
        {
            var handler = listChanged;
            if (handler != null)
                handler(this, e);
        }
        public bool AllowNew { get { return source.AllowNew; } }
        public bool AllowEdit { get { return source.AllowEdit; } }
        public bool AllowRemove { get { return source.AllowRemove; } }
        public bool SupportsChangeNotification { get { return source.SupportsChangeNotification; } }
        public bool SupportsSearching { get { return source.SupportsSearching; } }
        public bool SupportsSorting { get { return source.SupportsSorting; } }
        public bool IsSorted { get { return source.IsSorted; } }
        public PropertyDescriptor SortProperty { get { return source.SortProperty; } }
        public ListSortDirection SortDirection { get { return source.SortDirection; } }
        public object AddNew() { return source.AddNew(); }
        public void AddIndex(PropertyDescriptor property) { source.AddIndex(property); }
        public void ApplySort(PropertyDescriptor property, ListSortDirection direction) { source.ApplySort(property, direction); }
        public int Find(PropertyDescriptor property, object key) { return source.Find(property, key); }
        public void RemoveIndex(PropertyDescriptor property) { source.RemoveIndex(property); }
        public void RemoveSort() { source.RemoveSort(); }
    }

    private class BindingListView : BindingList, IBindingListView
    {
        private IBindingListView source;
        public BindingListView(IBindingListView source, Func<PropertyDescriptor, PropertyDescriptor> propertyMapper) : base(source, propertyMapper) { this.source = source; }
        public string Filter { get { return source.Filter; } set { source.Filter = value; } }
        public ListSortDescriptionCollection SortDescriptions { get { return source.SortDescriptions; } }
        public bool SupportsAdvancedSorting { get { return source.SupportsAdvancedSorting; } }
        public bool SupportsFiltering { get { return source.SupportsFiltering; } }
        public void ApplySort(ListSortDescriptionCollection sorts) { source.ApplySort(sorts); }
        public void RemoveFilter() { source.RemoveFilter(); }
    }
}

实际上,如您所见,我已经为其他数据源接口(如IBindingList和)添加了包装器IBindingListView。同样,代码并不是那么小,但它只是将调用委托给底层对象(为您的具体数据创建一个时,您通常会继承List<T>BiundingList<T>仅实现这两个ITypedList成员)。必不可少的部分是GetItemProperties方法实现,它与propertyMapperlambda 一起允许您用另一个属性替换一个属性。

有了所有这些,解决帖子中的特定问题很简单,只需将属性包装DataTable并将属性映射ComplexComplex.Name属性:

class ComplexData
{
    public int Value { get; set; }
}

class ComplexDataWrapper
{
    public string Name { get; set; }
    public ComplexData Data { get; set; } = new ComplexData();
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var form = new Form();
        var gridView = new DataGridView { Dock = DockStyle.Fill, Parent = form };
        gridView.DataSource = ListDataView.Create(GetData(), null, p =>
        {
            if (p.PropertyType == typeof(ComplexDataWrapper))
                return ChildPropertyDescriptor.Create(p, "Name", "Complex Name");
            return p;
        });
        Application.Run(form);
    }

    static DataTable GetData()
    {
        var dt = new DataTable();
        dt.Columns.Add("Id", typeof(int));
        dt.Columns.Add("Complex", typeof(ComplexDataWrapper));
        for (int i = 1; i <= 10; i++)
            dt.Rows.Add(i, new ComplexDataWrapper { Name = "Name#" + i, Data = new ComplexData { Value = i } });
        return dt;
    }
}

回顾一下,自定义PropertyDescriptorITypedList允许您创建无限类型的数据视图,然后可以由任何数据绑定感知控件使用。

于 2016-06-13T19:01:59.337 回答
2

我相信您的架构对于您想要实现的目标存在缺陷。

如果您使用 gridView 编辑复杂类型的单个属性,则无需将整个类型绑定到数据表中,因为数据表是网格的数据源。

相反,您应该只绑定您希望编辑的属性,当数据返回时,只需将其分配给正确位置的复杂类型。

于 2016-06-09T00:03:13.413 回答