前言 :
一般使用 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; } } } }
再来展开StandardBindingList
首先是 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
这些功能将会在后续的文章内一一实作,不过都还是以本章节的思路来做扩充。