[Windows Forms] BindingSource使用模式 - 加強版BindingList<T>

[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擴充更完整。
這些功能將會在後續的文章內一一實作,不過都還是以本章節的思路來做擴充。

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。