[Windows Forms] : BindingSource使用模式 加强版BindingList<T>

前言 :

一般使用 BindingSource做 Data Binding的工作,不管是用 ADO.NET对象或是自定义数据对象当作数据源。
运作流程大多类似
1.读取数据并将数据填写进 DataSet(or BindingList)
2.将DataSet(or BindingList)系结至BindingSource
3.画面Control触发事件时,操作数据库(or 集合)变更数据,并且操作BindingSource显示数据。

这样的运作流程,因为靠画面Control触发的事件,来当作操作函式的进入点。
把这样的软件架构,会显得各层之间的职责略显模糊。

职责模糊范例程序 : 按此下载

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {       
        public Form1()
        {
            InitializeComponent();

            // Fill
            BindingList<County> bindingList = new BindingList<County>();
            foreach (County county in this.GetList())
            {
                bindingList.Add(county);
            }

            // Binding
            countyBindingSource.DataSource = bindingList;
        }
        
        private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e)
        {
            // Operate 
            County item = countyBindingSource.Current as County;
            if (item != null)
            {
                this.Add(item);

                MessageBox.Show("Database Added");
            }
        }


        private const string _connectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database1.mdf;Integrated Security=True;User Instance=True";

        public void Add(County item)
        {
            #region Require

            if (item == null) throw new ArgumentNullException();

            #endregion
            SqlCommand command;
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                // Connection
                connection.Open();

                // Insert County
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "INSERT INTO [CountyTable](CountyID, CountyName, CountyDescription) VALUES (@CountyID, @CountyName, @CountyDescription)";
                    command.Parameters.AddWithValue("@CountyID", item.CountyID);
                    command.Parameters.AddWithValue("@CountyName", item.CountyName);
                    command.Parameters.AddWithValue("@CountyDescription", item.CountyDescription);
                    command.ExecuteNonQuery();
                }
            }
        }

        public IEnumerable<County> GetList()
        {
            SqlCommand command;
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                // Connection
                connection.Open();

                // Result
                List<County> itemCollection = new List<County>();

                // Select County          
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "SELECT * FROM [CountyTable]";
                    using (SqlDataReader dataReader = command.ExecuteReader())
                    {
                        while (dataReader.Read())
                        {
                            County item = new County();
                            item.CountyID = Convert.ToInt32(dataReader["CountyID"]);
                            item.CountyName = Convert.ToString(dataReader["CountyName"]);
                            item.CountyDescription = Convert.ToString(dataReader["CountyDescription"]);
                            itemCollection.Add(item);
                        }
                    }
                }

                // Return 
                return itemCollection;
            }
        }
    }
}

本篇文章介绍如何开发加强版BindingList<T>,用来将 Data Binding的运作流程作封装。
将原本各层之间模糊不清的职责,做一定程度的分派。
让开发人员在设计 Data Binding相关程序代码时,能将焦点集中在数据对象的操作工作上。

相关资料 :
[.NET] : BindingSource使用模式 - Data Binding基础知识 (一)
[.NET] : BindingSource使用模式 - Data Binding基础知识 (二)

实作 :

首先看看开发人员如何使用加强版BindingList<T>完成工作。
主要使用的接口及对象为
•StandardBindingList<T>类别,将Data Binding的运作流程封装在内,用来取代 .NET内建提供的 System.ComponentModel.BindingList<T>。
•IStandardBindingListStrategy<T>接口,开发人员实作 IStandardBindingListStrategy<T>并且注入后,就完成 Data Binding的数据源的开发工作。

加强版BindingList<T>范例程序 : 按此下载

先展示实际使用的程序代码及成果。
可以看到开发人员,只需要建立跟数据库沟通的对象,就可以完成画面到数据库一连串的开发工作。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CLK.ComponentModel;

namespace StandardBindingListSample
{
    public partial class Form1 : Form
    {
        // Properties
        private const string _connectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\CountyBindingListStrategyDatabase.mdf;Integrated Security=True;User Instance=True";
            
        // Constructor
        public Form1()
        {
            InitializeComponent();

            this.countyBindingSource.DataSource = new StandardBindingList<County>(new SqlCountyBindingListStrategy(_connectionString));   
        }
    }
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using CLK.ComponentModel;

namespace StandardBindingListSample
{
    public class SqlCountyBindingListStrategy : IStandardBindingListStrategy<County>
    {
        // Properties
        private readonly string _connectionString = string.Empty;


        // Constructor
        public SqlCountyBindingListStrategy(string connectionString)
        {
            #region Require

            if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentNullException();

            #endregion
            _connectionString = connectionString;
        }


        // Methods
        private SqlConnection CreateConnection()
        {
            return new SqlConnection(_connectionString);
        }
        

        public void Add(County item)
        {
            #region Require

            if (item == null) throw new ArgumentNullException();

            #endregion
            SqlCommand command;
            using (SqlConnection connection = this.CreateConnection())
            {
                // Connection
                connection.Open();

                // Insert County
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "INSERT INTO [CountyTable](CountyID, CountyName, CountyDescription) VALUES (@CountyID, @CountyName, @CountyDescription)";
                    command.Parameters.AddWithValue("@CountyID", item.CountyID);
                    command.Parameters.AddWithValue("@CountyName", item.CountyName);
                    command.Parameters.AddWithValue("@CountyDescription", item.CountyDescription);
                    command.ExecuteNonQuery();
                }
            }
        }

        public void Modify(County item)
        {
            #region Require

            if (item == null) throw new ArgumentNullException();

            #endregion
            SqlCommand command;
            using (SqlConnection connection = this.CreateConnection())
            {
                // Connection
                connection.Open();

                // Update County          
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "UPDATE [CountyTable] SET CountyName=@CountyName, CountyDescription=@CountyDescription WHERE CountyID=@CountyID";
                    command.Parameters.AddWithValue("@CountyID", item.CountyID);
                    command.Parameters.AddWithValue("@CountyName", item.CountyName);
                    command.Parameters.AddWithValue("@CountyDescription", item.CountyDescription);
                    command.ExecuteNonQuery();
                }
            }
        }

        public void Remove(County item)
        {
            #region Require

            if (item == null) throw new ArgumentNullException();

            #endregion
            SqlCommand command;
            using (SqlConnection connection = this.CreateConnection())
            {
                // Connection
                connection.Open();

                // Delete County          
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "DELETE FROM [CountyTable] WHERE CountyID=@CountyID";
                    command.Parameters.AddWithValue("@CountyID", item.CountyID);
                    command.ExecuteNonQuery();
                }
            }
        }

        public IEnumerable<County> GetList()
        {
            SqlCommand command;
            using (SqlConnection connection = this.CreateConnection())
            {
                // Connection
                connection.Open();

                // Result
                List<County> itemCollection = new List<County>();

                // Select County          
                using (command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = "SELECT * FROM [CountyTable]";
                    using (SqlDataReader dataReader = command.ExecuteReader())
                    {
                        while (dataReader.Read())
                        {
                            County item = new County();
                            item.CountyID = Convert.ToInt32(dataReader["CountyID"]);
                            item.CountyName = Convert.ToString(dataReader["CountyName"]);
                            item.CountyDescription = Convert.ToString(dataReader["CountyDescription"]);
                            itemCollection.Add(item);
                        }
                    }
                }

                // Return 
                return itemCollection;
            }
        }
    }
}

image

再来展开StandardBindingList的软件架构图,并且建立架构图里的类别。

image

首先是 StandardBindingObject<T>类别。
-StandardBindingObject<T>封装实际要做 Data Binding的数据对象 T,让后续的程序代码能够取得数据对象 T。
-StandardBindingObject<T>提供多个属性,让 StandardBindingList<T>将发生过的 Data Binding流程做纪录。
-StandardBindingObject<T>也聆听,有实做INotifyPropertyChanged的数据对象 T,用来记录数据变更的流程。

public class StandardBindingObject<T>
    where T : class, new()
{
    // Properties
    private PropertyChangedEventHandler PropertyChangedDelegate { get; set; }

    public T NativeBindingObject { get; private set; }

    public bool IsEmptyTrack
    {
        get
        {
            if (this.IsDirty == true) return false;
            if (this.IsInsertItem == true) return false;
            if (this.IsRemoveItem == true) return false;
            if (this.IsSetItem == true) return false;
            if (this.IsClearItems == true) return false;
            if (this.IsCancelNew == true) return false;
            if (this.IsEndNew == true) return false;
            return true;
        }
    }


    public bool IsDirty { get; set; }

    public bool IsInsertItem { get; set; }

    public bool IsRemoveItem { get; set; }

    public bool IsSetItem { get; set; }

    public bool IsClearItems { get; set; }

    public bool IsCancelNew { get; set; }

    public bool IsEndNew { get; set; }


    // Constructor
    public StandardBindingObject() : this(new T()) { }

    public StandardBindingObject(T nativeBindingObject)
    {
        #region Require

        if (nativeBindingObject == null) throw new ArgumentNullException("nativeBindingObject");

        #endregion

        // Properties
        this.PropertyChangedDelegate = delegate(object sender, PropertyChangedEventArgs e) { this.IsDirty = true; };
        this.NativeBindingObject = nativeBindingObject;
        this.ResetTrack();
    }


    // Methods
    public void ResetTrack()
    {
        this.IsDirty = false;
        this.IsInsertItem = false;
        this.IsRemoveItem = false;
        this.IsSetItem = false;
        this.IsClearItems = false;
        this.IsCancelNew = false;
        this.IsEndNew = false;
    }

    public void HookPropertyChanged()
    {
        // INotifyPropertyChanged
        INotifyPropertyChanged notifyPropertyChanged = this.NativeBindingObject as INotifyPropertyChanged;
        if (notifyPropertyChanged != null) notifyPropertyChanged.PropertyChanged += this.PropertyChangedDelegate;
    }

    public void UnhookPropertyChanged()
    {
        // INotifyPropertyChanged
        INotifyPropertyChanged notifyPropertyChanged = this.NativeBindingObject as INotifyPropertyChanged;
        if (notifyPropertyChanged != null) notifyPropertyChanged.PropertyChanged -= this.PropertyChangedDelegate;
    }
}

再来是 StandardBindingPropertyDescriptor<T>类别。
-StandardBindingPropertyDescriptor<T>封装实际要做 Data Binding的数据对象 T的属性对象 PropertyDescriptor,让自己对外表现的就跟被封装的对象一样。
-StandardBindingPropertyDescriptor<T>内部存取数据对象的属性时,是读取 StandardBindingObject<T>封装的数据对象 T的属性。
-StandardBindingPropertyDescriptor<T>透过 PropertyDescriptor的机制,聆听没有实做INotifyPropertyChanged的数据对象 T的属性数据变更,并用StandardBindingObject<T>来记录变化。

public sealed class StandardBindingPropertyDescriptor<T> : PropertyDescriptor
    where T : class, new()
{
    // Properties
    private readonly PropertyDescriptor _nativeBindingPropertyDescriptor = null;

    private readonly bool _raiseStandardBindingObjectSetDirty = false;


    // Constructor
    public StandardBindingPropertyDescriptor(PropertyDescriptor nativeBindingPropertyDescriptor)
        : base(nativeBindingPropertyDescriptor)
    {
        #region Require

        if (nativeBindingPropertyDescriptor == null) throw new ArgumentNullException("component");

        #endregion
        _nativeBindingPropertyDescriptor = nativeBindingPropertyDescriptor;
        if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T)) == false) _raiseStandardBindingObjectSetDirty = true;
    }


    // Properties
    public override Type ComponentType
    {
        get
        {
            return _nativeBindingPropertyDescriptor.ComponentType;
        }
    }

    public override TypeConverter Converter
    {
        get
        {
            return _nativeBindingPropertyDescriptor.Converter;
        }
    }

    public override bool IsLocalizable
    {
        get
        {
            return _nativeBindingPropertyDescriptor.IsLocalizable;
        }
    }

    public override bool IsReadOnly
    {
        get
        {
            return _nativeBindingPropertyDescriptor.IsReadOnly;
        }
    }

    public override Type PropertyType
    {
        get
        {
            return _nativeBindingPropertyDescriptor.PropertyType;
        }
    }


    // Methods
    private void GetBindingObject(object component, out StandardBindingObject<T> standardBindingObject, out T nativeBindingObject)
    {
        #region Require

        if (component == null) throw new ArgumentNullException("component");

        #endregion

        // StandardBindingObject
        standardBindingObject = component as StandardBindingObject<T>;
        if (standardBindingObject == null) throw new ArgumentNullException("standardBindingObject");

        // NativeBindingObject
        nativeBindingObject = standardBindingObject.NativeBindingObject;
        if (nativeBindingObject == null) throw new ArgumentNullException("nativeBindingObject");
    }

    private StandardBindingObject<T> GetStandardBindingObject(object component)
    {
        #region Require

        if (component == null) throw new ArgumentNullException("component");

        #endregion

        // GetBindingObject
        StandardBindingObject<T> standardBindingObject = null;
        T nativeBindingObject = null;
        this.GetBindingObject(component, out standardBindingObject, out nativeBindingObject);

        // Return
        return standardBindingObject;
    }

    private T GetNativeBindingObject(object component)
    {
        #region Require

        if (component == null) throw new ArgumentNullException("component");

        #endregion

        // GetBindingObject
        StandardBindingObject<T> standardBindingObject = null;
        T nativeBindingObject = null;
        this.GetBindingObject(component, out standardBindingObject, out nativeBindingObject);

        // Return
        return nativeBindingObject;
    }


    public override void SetValue(object component, object value)
    {
        // GetBindingObject
        StandardBindingObject<T> standardBindingObject = null;
        T nativeBindingObject = null;
        this.GetBindingObject(component, out standardBindingObject, out nativeBindingObject);

        // RaiseStandardBindingObjectSetDirty
        if (_raiseStandardBindingObjectSetDirty == false)
        {
            // SetValue
            _nativeBindingPropertyDescriptor.SetValue(nativeBindingObject, value);
        }
        else
        {
            // SetDirty
            EventHandler setDirtyDelegate = delegate(object sender, EventArgs e)
            {
                standardBindingObject.IsDirty = true;
            };

            // SetValue
            _nativeBindingPropertyDescriptor.AddValueChanged(nativeBindingObject, setDirtyDelegate);
            _nativeBindingPropertyDescriptor.SetValue(nativeBindingObject, value);
            _nativeBindingPropertyDescriptor.RemoveValueChanged(nativeBindingObject, setDirtyDelegate);
        }
    }

    public override object GetValue(object component)
    {
        return _nativeBindingPropertyDescriptor.GetValue(this.GetNativeBindingObject(component));
    }

    public override void ResetValue(object component)
    {
        _nativeBindingPropertyDescriptor.ResetValue(this.GetNativeBindingObject(component));
    }

    public override bool CanResetValue(object component)
    {
        return _nativeBindingPropertyDescriptor.CanResetValue(this.GetNativeBindingObject(component));
    }

    public override bool ShouldSerializeValue(object component)
    {
        return _nativeBindingPropertyDescriptor.ShouldSerializeValue(this.GetNativeBindingObject(component));
    }


    public override object GetEditor(Type editorBaseType)
    {
        return _nativeBindingPropertyDescriptor.GetEditor(editorBaseType);
    }

    public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
    {
        return _nativeBindingPropertyDescriptor.GetChildProperties(this.GetNativeBindingObject(instance), filter);
    }


    public override void AddValueChanged(object component, EventHandler handler)
    {
        _nativeBindingPropertyDescriptor.AddValueChanged(this.GetNativeBindingObject(component), handler);
    }

    public override void RemoveValueChanged(object component, EventHandler handler)
    {
        _nativeBindingPropertyDescriptor.RemoveValueChanged(this.GetNativeBindingObject(component), handler);
    }
}

再来定义 IStandardBindingListStrategy<T>界面。
-IStandardBindingListStrategy<T>定义,要做 Data Binding的数据对象 T,进出系统边界应该要实作的功能。

public interface IStandardBindingListStrategy<T>
    where T : class, new()
{
    void Add(T item);

    void Modify(T item);

    void Remove(T item);       

    IEnumerable<T> GetList();
}

最后是StandardBindingList<T>类别。
-StandardBindingList<T>将Data Binding的运作流程封装在内,用来取代 .NET内建提供的 System.ComponentModel.BindingList<T>。
-StandardBindingList<T>透过继承 Override的方式来聆听发生过的 Data Binding流程,使用 StandardBindingObject<T>来记录变化。
-StandardBindingList<T>实作了ITypedList接口,取代原本 Data Binding流程里取得PropertyDescriptor的流程。将原本应该取得数据对象T的属性对象,改为取得StandardBindingPropertyDescriptor<T>。
-StandardBindingList<T>开放了Refresh()函式,执行这个函式StandardBindingList<T>就会透过IStandardBindingListStrategy<T>取得数据对象 T的数据做 Data Binding的动作。
-StandardBindingList<T>会在每个 Data Binding流程里,检查StandardBindingObject<T>发生过的纪录。当记录满足条件,就会呼叫IStandardBindingListStrategy<T>的函式处理数据对象 T。

public class StandardBindingList<T> : BindingList<StandardBindingObject<T>>, ITypedList
    where T : class, new()
{
    // Properties       
    private readonly IStandardBindingListStrategy<T> _strategy = null;
    
    private readonly PropertyDescriptorCollection _propertyDescriptorCollection = null;

    private bool _isRefreshing = false;


    // Constructor
    public StandardBindingList(IStandardBindingListStrategy<T> strategy) : this(strategy, true) { }

    public StandardBindingList(IStandardBindingListStrategy<T> strategy, bool runRefresh)
    {
        #region Require

        if (strategy == null) throw new ArgumentNullException();

        #endregion

        // Properties 
        _strategy = strategy;
        _propertyDescriptorCollection = this.CreateStandardBindingPropertyDescriptorCollection();

        // Refresh
        if (runRefresh == true)
        {
            this.Refresh();
        }
    }


    // Methods        
    private PropertyDescriptorCollection CreateStandardBindingPropertyDescriptorCollection()
    {
        // Result
        List<PropertyDescriptor> standardBindingPropertyDescriptorCollection = new List<PropertyDescriptor>();

        // Create           
        foreach (PropertyDescriptor nativePropertyDescriptor in TypeDescriptor.GetProperties(typeof(T)))
        {
            standardBindingPropertyDescriptorCollection.Add(new StandardBindingPropertyDescriptor<T>(nativePropertyDescriptor));
        }

        // Return
        return new PropertyDescriptorCollection(standardBindingPropertyDescriptorCollection.ToArray());
    }
    
    private void CommitTrack(StandardBindingObject<T> standardBindingObject)
    {
        #region Require

        if (standardBindingObject == null) throw new ArgumentNullException("standardBindingObject");

        #endregion
        if (_isRefreshing == false)
        {
            if (standardBindingObject.IsEmptyTrack == false)
            {
                if (this.CommitTrack(standardBindingObject, _strategy) == true)
                {
                    standardBindingObject.ResetTrack();
                }
            }
        }
    }

    protected virtual bool CommitTrack(StandardBindingObject<T> standardBindingObject, IStandardBindingListStrategy<T> strategy)
    {
        #region Require

        if (standardBindingObject == null) throw new ArgumentNullException("standardBindingObject");
        if (strategy == null) throw new ArgumentNullException("strategy");

        #endregion
                    
        // Add
        if (standardBindingObject.IsInsertItem == true)
        {
            if (standardBindingObject.IsRemoveItem == false)
            {
                if (standardBindingObject.IsCancelNew == false)
                {
                    if (standardBindingObject.IsEndNew == true)
                    {
                        strategy.Add(standardBindingObject.NativeBindingObject);
                        return true;
                    }
                }
            }
        }

        // Remove
        if (standardBindingObject.IsInsertItem == false)
        {
            if (standardBindingObject.IsCancelNew == false)
            {
                if (standardBindingObject.IsRemoveItem == true)
                {
                    strategy.Remove(standardBindingObject.NativeBindingObject);
                    return true;
                }
            }
        }     

        // Modify
        if (standardBindingObject.IsInsertItem == false)
        {
            if (standardBindingObject.IsRemoveItem == false)
            {
                if (standardBindingObject.IsCancelNew == false)
                {
                    if (standardBindingObject.IsEndNew == true)
                    {
                        if (standardBindingObject.IsDirty == true)
                        {
                            strategy.Modify(standardBindingObject.NativeBindingObject);
                            return true;
                        }
                    }
                }
            }
        }                

        // Return
        return false;
    }
    

    public void Refresh()
    {
        try
        {
            // BeginRefresh
            _isRefreshing = true;

            // Clear
            this.Clear();

            // Add
            foreach (T item in _strategy.GetList())
            {
                StandardBindingObject<T> standardBindingObject = new StandardBindingObject<T>(item);
                this.Add(standardBindingObject);
                standardBindingObject.ResetTrack();
            }
        }
        finally
        {
            // EndRefresh
            _isRefreshing = false;
        }

        // ResetBindings
        this.ResetBindings();
    }


    #region BindingList<T>

    protected override void OnListChanged(ListChangedEventArgs e)
    {
        #region Require

        if (e == null) throw new ArgumentNullException("e");

        #endregion
        if (_isRefreshing == false)
        {
            base.OnListChanged(e);
        }
    }

    #endregion

    #region Collection<T>

    protected override void InsertItem(int index, StandardBindingObject<T> item)
    {
        #region Require

        if (item == null) throw new ArgumentNullException("item");

        #endregion

        // Base
        base.InsertItem(index, item);

        // NewItem
        item.HookPropertyChanged();
        item.IsInsertItem = true;
        this.CommitTrack(item);
    }

    protected override void SetItem(int index, StandardBindingObject<T> item)
    {
        #region Require

        if (item == null) throw new ArgumentNullException("item");

        #endregion

        // OldItem
        StandardBindingObject<T> oldItem = this[index];
        oldItem.UnhookPropertyChanged();

        // Base
        base.SetItem(index, item);

        // NewItem
        item.HookPropertyChanged();
        item.IsSetItem = true;
        this.CommitTrack(item);
    }

    protected override void RemoveItem(int index)
    {
        // OldItem
        StandardBindingObject<T> oldItem = this[index];
        oldItem.UnhookPropertyChanged();
        oldItem.IsRemoveItem = true;
        this.CommitTrack(oldItem);

        // Base
        base.RemoveItem(index);
    }

    protected override void ClearItems()
    {
        // OldItem
        foreach (StandardBindingObject<T> oldItem in this.Items.ToArray())
        {
            oldItem.UnhookPropertyChanged();
            oldItem.IsClearItems = true;
            this.CommitTrack(oldItem);
        }

        // Base
        base.ClearItems();
    }

    #endregion

    #region ICancelAddNew

    public override void CancelNew(int itemIndex)
    {
        // StandardBindingObject
        if (0 <= itemIndex && itemIndex < this.Count)
        {
            StandardBindingObject<T> standardBindingObject = this[itemIndex];
            standardBindingObject.IsCancelNew = true;
            this.CommitTrack(standardBindingObject);
        }

        // Base
        base.CancelNew(itemIndex);
    }

    public override void EndNew(int itemIndex)
    {
        // StandardBindingObject
        if (0 <= itemIndex && itemIndex < this.Count)
        {
            StandardBindingObject<T> standardBindingObject = this[itemIndex];
            standardBindingObject.IsEndNew = true;
            this.CommitTrack(standardBindingObject);
        }

        // Base
        base.EndNew(itemIndex);
    }

    #endregion

    #region ITypedList

    public string GetListName(PropertyDescriptor[] listAccessors)
    {
        return typeof(StandardBindingObject<T>).Name;
    }

    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        if (listAccessors != null && listAccessors.Length > 0)
        {
            throw new InvalidOperationException();
            // return this.CreateStandardBindingPropertyDescriptorCollection(ListBindingHelper.GetListItemProperties(listAccessors[0].PropertyType));
        }
        else
        {
            return _propertyDescriptorCollection;
        }
    }

    #endregion        
}

后记 :

StandardBindingList还有很多地方需要加工,例如 : 加入数据验证、或是将 CommitTrack扩充更完整。
这些功能将会在后续的文章内一一实作,不过都还是以本章节的思路来做扩充。

原文地址:https://www.cnblogs.com/clark159/p/2205156.html