在C#中将复杂对象绑定到DataTable的单元格 [英] Bind complex object to cell of DataTable in C#

查看:75
本文介绍了在C#中将复杂对象绑定到DataTable的单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个包含复杂对象的DataTable.

I have a DataTable with complex objects.

例如

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的对象 所以,我尝试这样的事情:

And now I want to bind cells from DataTable to objects of ComplexDataWrapper So, I try something like this :

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

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

But, I want to bind for only one property, for example, Name. And in the gridview (DataTable is a data source for this view) I want to edit this property(Name).

var complexDataWrapper = row[column] as ComplexDataWrapper;

complexDataWrapper始终等于NULL.

complexDataWrapper always equals to NULL.

我知道我想念一些东西.

I know that I miss something.

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

So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.

谢谢.希望一切都清楚了.

Thanks. Hopefully, everything is clear.

推荐答案

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

So my questions : How I can bind my cell of DataTable to complex object? Plus in grid view I want to edit exactly one property of complex object.

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

What you need here is the ability to bind to a so called property path (e.g. obj.Prop1.Prop2). Unfortunately WinForms has limited support for that - it's supported for simple data binding (like control.DataBindings.Add(...)) but not for list data binding which is used by DataGridView control and similar.

幸运的是,它仍然可以通过某些(大部分时间都是微不足道的)编码来实现,因为数据绑定是围绕称为

Fortunately it's still doable with some (most of the time trivial) coding, because the data binding is build around an abstraction called PropertyDescriptor. By default it is implemented via reflection, but nothing prevents you to create your own implementation and do whatever you like inside it. That allows you to do many things that are not possible with reflection, in particular to simulate "properties" that actually do not exist.

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

Here we will utilize that possibility to create a "property" that actually gets/sets its value from a child property of the original property, while from outside it still looks like a single property, thus allowing to data bind to it:

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的许多方法仅在设计时使用,因此创建自定义的具体运行时属性描述符通常仅需要实现 PropertyType SetValue (如果支持).

The code is not so small, but all it does is basically delegating the calls to the corresponding methods of the property descriptor chain representing the path from the original property to the child property. Also please note that many methods of the PropertyDescriptor are used only during the design time, so creating a custom concrete runtime property descriptor usually needs only to implement ComponentType, PropertyType, GetValue and SetValue (if supported).

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

So far so good. This is just the first part of the puzzle. We can create a "property", now we need a way to let data binding use it.

为此,我们将利用另一个与数据绑定相关的接口,称为

In order to do that, we'll utilize another data binding related interface called ITypedList:

提供用于发现可绑定列表的模式的功能,其中可用于绑定的属性不同于要绑定到的对象的公共属性.

Provides functionality to discover the schema for a bindable list, where the properties available for binding differ from the public properties of the object to bind to.

换句话说,它允许我们为列表元素提供属性".但是如何?如果我们要实现数据源列表,那将很容易.但是在这里,我们要针对一个我们事先不知道的列表执行此操作(我正在尝试使解决方案保持通用).

In other words, it allows us to provide "properties" for the list elements. But how? If we were implementing the data source list, it would be easy. But here we want to do that for a list that we don't know in advance (I'm trying the keep the solution generic).

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

The solutions is to wrap the original list in onother one that will implement IList (the minimum requirement for list data binding) by delegating all the calls to the underlying list, but by implementing ITypedList will control the properties used for binding:

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

实际上,正如您所看到的,我为其他数据源接口(如IBindingListIBindingListView)添加了包装器.同样,代码不是那么小,它只是将调用委派给基础对象(在为具体数据创建一个对象时,通常将继承自List<T>BiundingList<T>,并且仅实现两个ITypedList成员) .基本部分是 GetItemProperties 方法实现,它与propertyMapper lambda一起使您可以将一个属性替换为另一个属性.

Actually as you can see, I've added wrappers for other data source interfaces like IBindingList and IBindingListView. Again, the code is not so small, but it's just delegating the calls to the underlying objects (when creating one for your concrete data, you usually would inherit from List<T> or BiundingList<T> and implement only the two ITypedList members). The essential part is the GetItemProperties method implementation which along with the propertyMapper lambda allows you to replace one property with another.

所有这些都准备就绪,解决帖子中的特定问题很简单,只需包装DataTable并将Complex属性映射到Complex.Name属性:

With all that in place, solving the specific problem from the post is simple a matter of wrapping the DataTable and mapping the Complex property to the Complex.Name property:

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允许您创建无限类型的数据视图,然后可将其用于任何数据绑定感知控件.

To recap, custom PropertyDescriptor and ITypedList allow you to create unlimited types of views of your data, which then can be used by any data bound aware control.

这篇关于在C#中将复杂对象绑定到DataTable的单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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