前言 :
在文章「[.NET] : BindingSource使用模式 - Data Binding基礎知識 (一)」。
介紹了如何將對象的屬性包裝成屬性對象 「PropertyDescriptor」,並用它來做存取、監看變更等工作。
將資料對象的屬性包裝成屬性對象是 Data Binding運作基礎,在瞭解這個運作之後。
這邊再來討論,Data Binding時會用到的「資料來源」。
在大部分的書裡描述,Data Binding透過 ADO.NET裡的對象與資料庫做互動,用來顯示及存取資料庫內的資料。
在這架構下,ADO.NET裡的物件是一種 Data Binding的資料來源。
相關資料 : HOW TO:使用 Windows Form BindingSource 組件排序和篩選 ADO.NET 資料
也有一部份的資料提到的是, Data Binding也可以封裝自訂資料對象,來做自訂資料對象的顯示及存取。
在這架構下,自訂資料對象封裝後也是一種 Data Binding的資料來源。
相關資料 : 具有 ADO.NET 和自訂對象的資料繫結應用程式
關於 Data Binding的資料來源,細分下去有許多不同的實作與定義。
相關資料可以參考 : Windows Form 支援的資料來源、 與資料繫結相關的介面
本篇文章簡略介紹,幾個設計開發 Data Binding用來封裝資料來源用的相關對象。
讓軟體開發人員在設計 Data Binding相關程式碼時,能對對象運作模式有基礎的理解。
BindingList :
在上列文章提供的相關資料裡,能找到大量針對 Data Binding的資料來源定義的介面。
照著資料檔案可以實作出,符合自己需求的資料來源對象,但這是一件工作量不小的工作。
在 System.ComponentModel命名空間裡,可以找到 BindingList<T>這個物件。
BindingList<T>實作針對 Data Binding的資料來源定義的主要介面。
並且 BindingList<T>是一個泛型類別,可以接受類別 T當作自訂資料對象。
開發人員可以使用 BindingList<T>,來將自訂資料對象封裝成資料來源。
首先建立自訂資料對象
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WindowsFormsApplication1{ public class County { // Properties public int CountyID { get; set; } public string CountyName { get; set; } public string CountyDescription { get; set; } // Constructor public County() { this.CountyID = 0; this.CountyName = string.Empty; this.CountyDescription = string.Empty; } }}
再來建立 DataGridView、BindingNavigator、BindingSource繫結資料
最後建立資料來源並繫結資料
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;namespace WindowsFormsApplication1{ public partial class Form1 : Form { BindingList<County> _bindingList = new BindingList<County>(); public Form1() { InitializeComponent(); // Add Item County county1 = new County(); county1.CountyID = 1; county1.CountyName = "台北市"; county1.CountyDescription = "買不起"; _bindingList.Add(county1); County county2 = new County(); county2.CountyID = 2; county2.CountyName = "新北市"; county2.CountyDescription = "還是買不起"; _bindingList.Add(county2); // Data Binding this.countyBindingSource.DataSource = _bindingList; } }}
完成,看成果。
使用 BindingList<T>有一個地方需要特別注意的,就是關於 AddingNew事件。
AddingNew事件,主要用來通知要建立一個新的資料對象。
當有處理 AddingNew事件,BindingList<T>會加入 AddingNew事件裡帶回的 NewObject。
修改本文範例為
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WindowsFormsApplication1{ public class County { // Properties public int CountyID { get; set; } public string CountyName { get; set; } public string CountyDescription { get; set; } // Constructor public County() { this.CountyID = 0; this.CountyName = string.Empty; this.CountyDescription = string.Empty; } }}
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;namespace WindowsFormsApplication1{ public partial class Form1 : Form { BindingList<County> _bindingList = new BindingList<County>(); public Form1() { InitializeComponent(); // Add Item County county1 = new County(); county1.CountyID = 1; county1.CountyName = "台北市"; county1.CountyDescription = "買不起"; _bindingList.Add(county1); County county2 = new County(); county2.CountyID = 2; county2.CountyName = "新北市"; county2.CountyDescription = "還是買不起"; _bindingList.Add(county2); // EventHandler _bindingList.AddingNew += new AddingNewEventHandler(_bindingList_AddingNew); // Data Binding this.countyBindingSource.DataSource = _bindingList; } void _bindingList_AddingNew(object sender, AddingNewEventArgs e) { County county3 = new County(); county3.CountyID = 3; county3.CountyName = "桃園縣"; county3.CountyDescription = "依然買不起"; e.NewObject = county3; } }}
編譯執行後,按下bindingNavigator上的『+』按鈕看成果。
當沒有處理 AddingNew事件並且資料對象有預設建構函式,BindingList<T>會加入資料對象預設建構函式建立新對象。
修改本文範例為
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WindowsFormsApplication1{ public class County { // Properties public int CountyID { get; set; } public string CountyName { get; set; } public string CountyDescription { get; set; } // Constructor public County() { this.CountyID = 4; this.CountyName = "新竹市"; this.CountyDescription = "園區無敵貴"; } }}
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;namespace WindowsFormsApplication1{ public partial class Form1 : Form { BindingList<County> _bindingList = new BindingList<County>(); public Form1() { InitializeComponent(); // Add Item County county1 = new County(); county1.CountyID = 1; county1.CountyName = "台北市"; county1.CountyDescription = "買不起"; _bindingList.Add(county1); County county2 = new County(); county2.CountyID = 2; county2.CountyName = "新北市"; county2.CountyDescription = "還是買不起"; _bindingList.Add(county2); // Data Binding this.countyBindingSource.DataSource = _bindingList; } }}
編譯執行後,按下bindingNavigator上的『+』按鈕看成果。
當沒有處理 AddingNew事件並且資料對象沒有預設建構函式,BindingList<T>會將自己的 AllowNew屬性設定為 false,不允許新增對象。
修改本文範例為
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WindowsFormsApplication1{ public class County { // Properties public int CountyID { get; set; } public string CountyName { get; set; } public string CountyDescription { get; set; } // Constructor public County(int countyID) { this.CountyID = countyID; this.CountyName = string.Empty; this.CountyDescription = string.Empty; } }}
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;namespace WindowsFormsApplication1{ public partial class Form1 : Form { BindingList<County> _bindingList = new BindingList<County>(); public Form1() { InitializeComponent(); // Add Item County county1 = new County(1); county1.CountyName = "台北市"; county1.CountyDescription = "買不起"; _bindingList.Add(county1); County county2 = new County(2); county2.CountyName = "新北市"; county2.CountyDescription = "還是買不起"; _bindingList.Add(county2); // Data Binding this.countyBindingSource.DataSource = _bindingList; } }}
編譯執行後,會發現禁止按下bindingNavigator上的『+』按鈕,不允許新增。
相關資料 : BindingList(Of T)、 BindingSource.AddingNew
ITypedList :
Data Binding在運作的時候,會依照資料來源解析出資料對象,再將資料對象的屬性包裝成屬性對象 PropertyDescriptor。
運作模式的相關資料可以參考 : [.NET] : BindingSource使用模式 - Data Binding基礎知識 (一)。
當開發人員要在 Data Binding時使用自訂 PropertyDescriptor來做屬性的存取顯示時,實作 ITypedList介面就可以取代預設的運作流程。
首先建立自訂 PropertyDescriptor
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ComponentModel;namespace WindowsFormsApplication1{ public class SamplePropertyDescriptor : PropertyDescriptor { // Properties private readonly PropertyDescriptor _component = null; // Constructor public SamplePropertyDescriptor(PropertyDescriptor component) : base(component) { #region Require if (component == null) throw new ArgumentNullException("component"); #endregion _component = component; } // Properties public override Type ComponentType { get { return _component.ComponentType; } } public override TypeConverter Converter { get { return _component.Converter; } } public override bool IsLocalizable { get { return _component.IsLocalizable; } } public override bool IsReadOnly { get { return _component.IsReadOnly; } } public override Type PropertyType { get { return _component.PropertyType; } } // Methods public override object GetValue(object component) { return (component as County).CountyDescription + "$$$$$$$$"; } public override void SetValue(object component, object value) { _component.SetValue(component, (value as string).Replace("$$$$$$$$", string.Empty)); } public override void ResetValue(object component) { _component.ResetValue(component); } public override bool CanResetValue(object component) { return _component.CanResetValue(component); } public override bool ShouldSerializeValue(object component) { return _component.ShouldSerializeValue(component); } public override object GetEditor(Type editorBaseType) { return _component.GetEditor(editorBaseType); } public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) { return _component.GetChildProperties(instance, filter); } public override void AddValueChanged(object component, EventHandler handler) { _component.AddValueChanged(component, handler); } public override void RemoveValueChanged(object component, EventHandler handler) { _component.RemoveValueChanged(component, handler); } }}
再來建立繼承 BindingList及實作 ITypedList的自訂BindingList
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ComponentModel;namespace WindowsFormsApplication1{ public class SampleBindingList : BindingList<County>, ITypedList { public string GetListName(PropertyDescriptor[] listAccessors) { return typeof(County).Name; } public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { if (listAccessors != null && listAccessors.Length > 0) { throw new InvalidOperationException(); } else { // Result List<PropertyDescriptor> propertyDescriptorCollection = new List<PropertyDescriptor>(); // Create foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(County))) { if (propertyDescriptor.Name == "CountyDescription") { propertyDescriptorCollection.Add(new SamplePropertyDescriptor(propertyDescriptor)); } else { propertyDescriptorCollection.Add(propertyDescriptor); } } // Return return new PropertyDescriptorCollection(propertyDescriptorCollection.ToArray()); } } }}
最後修改本文範例為
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace WindowsFormsApplication1{ public class County { // Properties public int CountyID { get; set; } public string CountyName { get; set; } public string CountyDescription { get; set; } // Constructor public County() { this.CountyID = 0; this.CountyName = string.Empty; this.CountyDescription = string.Empty; } }}
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;namespace WindowsFormsApplication1{ public partial class Form1 : Form { SampleBindingList _bindingList = new SampleBindingList(); public Form1() { InitializeComponent(); // Add Item County county1 = new County(); county1.CountyID = 1; county1.CountyName = "台北市"; county1.CountyDescription = "買不起"; _bindingList.Add(county1); County county2 = new County(); county2.CountyID = 2; county2.CountyName = "新北市"; county2.CountyDescription = "還是買不起"; _bindingList.Add(county2); // Data Binding this.countyBindingSource.DataSource = _bindingList; } }}
完成,看成果。
相關資料 : ITypedList、 HOW TO:實作 ITypedList 介面