【问题标题】:Combine master-detail databinding结合主从数据绑定
【发布时间】:2011-02-02 17:55:52
【问题描述】:

我从我的项目中的对象创建了数据源,其中一些具有其他对象作为成员。当我想将一些对象绑定到数据网格时,我也想显示数据网格中成员对象的一些值,但是我遇到的示例似乎使用整个其他数据网格或控件来显示这些成员对象值。

如何将这些值合并到一个数据网格中? (最好不创建包装类) 我可以用 LINQ 以某种方式实现这一点吗?

非常感谢您的帮助。

谢谢

附: C# 或 VB 示例都可以。

【问题讨论】:

    标签: .net vb.net winforms data-binding


    【解决方案1】:

    在 WinForms 中没有干净的方法可以做到这一点。您有几个选项,从最简单到最难:

    LINQ

    (这里特指匿名类型)。

    你可以这样做:

    dataGridView.DataSource = from x in dataSource
                              select new
                              {
                                  Property = x.Property,
                                  SubProperty = x.ObjectProperty.SubProperty // etc.
                              };
    

    这是迄今为止最简单的方法,但也是单向绑定;您无法使用此机制更新数据。这也将为您提供no对网格的设计时支持,因为在设计时无法发现属性名称。

    包装类

    这个,我猜,你很熟悉。您创建一个包装实际数据绑定类的类,传递属性并将“子属性”提升到类级别。例如:

    public string SubProperty
    {
        get { return yourObject.ObjectProperty.SubProperty; }
        set { yourObject.ObjectProperty.SubProperty = value; }
    }
    

    这将为您提供对网格的设计时支持(假设您使用BindingSource 组件并为此对象正确设置对象数据源)并将允许双向绑定。但是,它确实要求您滚动一个包装类并创建要绑定到的每个属性。

    自定义类型描述符

    这是最困难的,但也是最灵活、最快和最干净的(从外部 API 角度来看)。我不会在这篇文章中详细介绍如何做到这一点,但是有关于此的文章。我会链接到自定义 TypeDescriptors 上的 MSDN 页面,但这有点令人生畏,并且超出了您想要做的范围。

    简而言之,您可以创建一个自定义 TypeDescriptor 类,该类提供 PropertyDescriptor 实例的列表。这些允许您完全控制绑定体验,而不是依赖反射(如上述两种情况中的任何一种情况)。不过,这是一段非常重要的代码,所以在开始之前,您需要确定这是否是您真正想要做的事情。

    This 很好地介绍了TypeDescriptor 类及其作用。你感兴趣的是PropertyDescriptorCollection。您只需为要与之交互的每个属性(包括嵌套属性)返回一个 PropertyDescriptor

    【讨论】:

    • 感谢亚当,这正是我所担心的。自定义类型描述符的链接实际上可能很有用,如果不是现在供将来参考的话。再次感谢。
    【解决方案2】:

    我最近遇到了同样的问题,并使用上面建议的第三种方法解决了它。下面的 DeepBindingList 类似于常规的 BindingList,但有两个增强:

    1) 它遍历每个属性的子属性,并将它们全部公开为可绑定属性。

    2) 它增加了对排序的支持。

    以任何你喜欢的方式使用,我希望它有用。

    using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Text;
    
    namespace DeepBindingList
    {
        /// Extends the BindingList to provide sorting and deep property
        /// binding (e.g. Address.Street).
        public class DeepBindingList<T> : BindingList<T>, ITypedList
        {
            //-----------------------------------------------------------------------
            #region ** IBindingList overrides (to provide sorting)
    
            PropertyDescriptor _sort;
            ListSortDirection _direction;
    
            protected override bool IsSortedCore
            {
                get { return _sort != null; }
            }
            protected override void RemoveSortCore()
            {
                _sort = null;
            }
            protected override ListSortDirection SortDirectionCore
            {
                get { return _direction; }
            }
            protected override PropertyDescriptor SortPropertyCore
            {
                get { return _sort; }
            }
            protected override bool SupportsSortingCore
            {
                get { return true; }
            }
            protected override void ApplySortCore(PropertyDescriptor pd, ListSortDirection direction)
            {
                // get list to sort
                var items = this.Items as List<T>;
    
                // apply the sort
                if (items != null)
                {
                    var pc = new PropertyComparer<T>(pd, direction);
                    items.Sort(pc);
                }
    
                // save new settings and notify listeners
                _sort = pd;
                _direction = direction;
                this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
            }
    
            // PropertyComparer (used to sort the list)
            class PropertyComparer<TC> : IComparer<TC>
            {
                PropertyDescriptor _pd;
                ListSortDirection _direction;
                public PropertyComparer(PropertyDescriptor pd, ListSortDirection direction)
                {
                    _pd = pd;
                    _direction = direction;
                }
                public int Compare(TC x, TC y)
                {
                    try
                    {
                        var v1 = _pd.GetValue(x) as IComparable;
                        var v2 = _pd.GetValue(y) as IComparable;
    
                        int cmp =
                            v1 == null && v2 == null ? 0 :
                            v1 == null ? +1 :
                            v2 == null ? -1 :
                            v1.CompareTo(v2);
    
                        return _direction == ListSortDirection.Ascending ? +cmp : -cmp;
                    }
                    catch 
                    {
                        return 0; // comparison failed...
                    }
                }
            }
    
            #endregion
    
            //-----------------------------------------------------------------------
            #region ** ITypedList (to expose inner properties)
    
            public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
            {
                var list = new List<PropertyDescriptor>();
                foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(typeof(T)))
                {
                    AddProperties(pd, null, list);
                }
                return new PropertyDescriptorCollection(list.ToArray());
            }
            void AddProperties(PropertyDescriptor pd, PropertyDescriptor parent, List<PropertyDescriptor> list)
            {
                // add this property
                pd = new DeepPropertyDescriptor(pd, parent);
                list.Add(pd);
    
                // and subproperties for non-value types
                if (!pd.PropertyType.IsValueType && pd.PropertyType != typeof(string))
                {
                    foreach (PropertyDescriptor sub in TypeDescriptor.GetProperties(pd.PropertyType))
                    {
                        AddProperties(sub, pd, list);
                    }
                }
            }
            public string GetListName(PropertyDescriptor[] listAccessors)
            {
                return null;
            }
    
            // property descriptor with support for sub properties (e.g. Address.Street)
            class DeepPropertyDescriptor : PropertyDescriptor
            {
                PropertyDescriptor _pd;
                PropertyDescriptor _parentPD;
    
                public DeepPropertyDescriptor(PropertyDescriptor pd, PropertyDescriptor parentPd)
                    : base(pd.Name, null)
                {
                    _pd = pd;
                    _parentPD = parentPd;
                }
                public override string Name
                {
                    get
                    {
                        return _parentPD != null
                            ? string.Format("{0}.{1}", _parentPD.Name, _pd.Name)
                            : _pd.Name;
                    }
                }
                public override bool IsReadOnly
                {
                    get { return _pd.IsReadOnly; }
                }
                public override void ResetValue(object component)
                {
                    _pd.ResetValue(component);
                }
                public override bool CanResetValue(object component)
                {
                    return _pd.CanResetValue(component);
                }
                public override bool ShouldSerializeValue(object component)
                {
                    return _pd.ShouldSerializeValue(component);
                }
                public override Type ComponentType
                {
                    get { return _pd.ComponentType; }
                }
                public override Type PropertyType
                {
                    get { return _pd.PropertyType; }
                }
                public override object GetValue(object component)
                {
                    if (_parentPD != null)
                    {
                        component = _parentPD.GetValue(component);
                    }
                    return _pd.GetValue(component);
                }
                public override void SetValue(object component, object value)
                {
                    _pd.SetValue(_parentPD.GetValue(component), value);
                    OnValueChanged(component, EventArgs.Empty);
                }
            }
    
            #endregion
        }
    }
    

    【讨论】:

      【解决方案3】:

      糟糕...我的意思是

              public override void SetValue(object component, object value)
              {
                  if (_parentPD != null)
                  {
                      component = _parentPD.GetValue(component);
                  }
                  _pd.SetValue(component, value);
                  OnValueChanged(component, EventArgs.Empty);
              }
      

      【讨论】:

        【解决方案4】:

        我发现 Bernardo 的 DeepBindingList 可以挽救生命,现在我想通过指出代码存在循环问题来回馈一些东西。意思是,如果 T 有一个 T 类型的成员,或者它本身有一个 T 类型的成员,那么 GetItemProperties() 中的递归将导致堆栈爆炸。

        有不同的方法来处理这样的问题,但我实施的方法利用了这样一个事实,即我总是提前知道哪些属性将用作我的网格的列。因此,我创建了一个方法来获取这些列的属性:

        private PropertyDescriptorCollection propertyDescriptors;
        
        public void SetItemProperties(IList<string> names)
        {
            var list = new List<PropertyDescriptor>();
            foreach (var name in names)
                AddProperty(name, list);
            propertyDescriptors = new PropertyDescriptorCollection(list.ToArray());
        }
        
        private void AddProperty(string name, List<PropertyDescriptor> list)
        {
            Type propType = typeof (T);
            PropertyDescriptor parent = null;
            foreach (var part in name.Split('.'))
            {
                PropertyDescriptor prop = TypeDescriptor.GetProperties(propType).Cast<PropertyDescriptor>().FirstOrDefault(pd => pd.Name == part);
                if (prop == null)
                    return;
                list.Add(new DeepPropertyDescriptor(prop, parent));
                propType = prop.PropertyType;
                parent = prop;
            }
        }
        

        (我没有费心避免重复的 PropertyDescriptors,当两个或多个“孙子”共享同一个父级时,可能会出现重复的 PropertyDescriptors,如 Tab 和 Tac 他们没有给我带来任何麻烦,所以我把它留给下一个人修复。)

        我在GetItemProperties()的开头添加了一个测试,所以:

        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            if (propertyDescriptors != null)
                return propertyDescriptors;
        

        这样,如果我已经调用了 SetItemProperties(),它什么也不做。

        我也发现接受列表参数的构造函数限制太多,所以我替换了

        public DeepBindingList(IList<T> list)
            : base(list)
        {
        }
        

        public DeepBindingList(IEnumerable<T> list)
        {
            foreach (var t in list)
            {
                Add(t);
            }
        }
        

        我希望有人觉得这很有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-02
          相关资源
          最近更新 更多