前言 :
一般使用 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还有很多地方需要加工,例如 : 加入数据验证、或是将 CommitTrack扩充更完整。
这些功能将会在后续的文章内一一实作,不过都还是以本章节的思路来做扩充。