上篇最後說過了,討論還剛剛開始,我們大致把核心的方法都寫出來了.下面我們繼續.
一.控件對比
我們可以使用上篇製作的TemplatedList控件跟內置控件做一下對比異同
在2.0未到來的時候,我們只有Repeater,DataList,DataGrid,現在我們也根據這三個控件進行討論,下面把
TemplatedList與DataList進行對比
(1)佈局樣式沒DataList多...
(2)模板沒DataList多...
(3)TemplatedList沒ItemCollection
(4)TemplatedList沒有預定義Command事件(如EditCommand,UpdateCommand等)
或者還有更多的,上面的都是次要的,佈局上面我們可以改善,我們也可以添加ItemCollection,也可以預定義Command事件,但發現TemplatedList跟內置的綁定控件有幾個跟數據操作嚴重的不同點
(1)DataSource屬性類型不同 IEnumerable和Object
爲什麼要將其類型設置爲Object呢?
IEnumerable支持Array,ArrayList等返回類型,但卻不支持DataSet類型,這是一個很嚴重的問題,設置其類型爲Object,可以讓控件支持更廣泛的數據源(當然也要根據需求)這個是本次討論的重點
(2)DataMember
其用於指定數據源的特定表,由於DataSet的介入,其可能含有多個表,所以也就有了這個屬性,否則的話就不需要他
(3)DataKeyField鍵字段
由於預定義Command事件的介入,實現對數據的操作,DataKeyField用於幫助數據特定記錄的操作
二.確定目標
根據上面的對比,我們已經知道接下來要做什麼了,要讓控件DataSouce屬性支持更多的數據源(只要還是DataSet)
本次的demo我們將要模仿Repeater來製作,爲什麼不用TemplatedList?因爲這樣我們可以對更多控件的實現更加的熟悉,這樣在使用內置控件的時候,你將明白的更透徹.此處的demo來自Building ASP.NET Server Controls書中的例子
Repeater與TemplatedList的異同
不同點
大家都知道Repeater可以靈活的進行佈局,所以去掉了模板樣式屬性,我們爲其添加了多個模板屬性,Repeater控件沒有預定義Command事件,所以不需要DataKeyField屬性.
還爲Repeater定義了TemplatedListmy沒有的ItemCollection集合,當然也可以爲TemplatedList添加這個集合
最大的不同
Repeater支持DataSet,TemplatedList不支持
相同點
都是數據綁定控件,所以裏面很多的實現方法幾乎相同,如果你看過TemplatedList的實現,再看Repeater的代碼,基本沒有難度,Repeater的實現比TemplatedList還要簡單.
好了,下面我們開始吧.
三.實現
1.爲數據控件做好準備
幾乎跟上篇一樣,所以不再介紹
2.編寫Repeater
(1)定義成員屬性和事件
private object dataSource;
/// <summary>
/// 綁定的列表的數據源
/// </summary>
[Category("Data"), Description("綁定的列表的數據源"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
DefaultValue(null), Bindable(true)]
public object DataSource
{
get
{
return dataSource;
}
set
{
if ((value is IEnumerable) || (value is IListSource) || (value == null))
dataSource = value;
else
throw new Exception("錯誤的數據源類型");
}
}
/// <summary>
/// 當數據綁定到列表數據源時要提取的數據成員
/// </summary>
[Category("Data"), Description("當數據綁定到列表數據源時要提取的數據成員")]
public virtual string DataMember
{
get
{
object member = ViewState["DataMember"];
if (member == null)
return string.Empty;
else
return (string)member;
}
set
{
ViewState["DataMember"] = value;
}
}
主要的變化在於DataSource,類型更改爲object其對傳入的數據源進行判斷,另外還加入了DataMember屬性
(2)關鍵實現
1.因爲Repeater模板不具有樣式屬性,所以去掉了PrepareControlHierarchy方法,
2.由於不涉及到複雜的樣式屬性,所以不必重載視圖狀態管理的三個方法
這兩點就可以讓控件減少很多代碼的編寫
3.CreateControlHierarchy方法和CreateItem方法
Repeater模板的實現方法和TemplatedList稍有不同,但變化不大,應該容易理解.看下面代碼
/// <summary>
/// 創建控件各種項
/// </summary>
/// <param name="itemIndex"></param>
/// <param name="itemType"></param>
/// <param name="dataBind"></param>
/// <param name="dataItem"></param>
/// <returns></returns>
private RepeaterItem CreateItem(int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
ITemplate selectedTemplate;
//根據不同類型創建不同項
switch (itemType)
{
case ListItemType.Header:
selectedTemplate = headerTemplate;
break;
case ListItemType.Item:
selectedTemplate = itemTemplate;
break;
case ListItemType.AlternatingItem:
selectedTemplate = alternatingItemTemplate;
break;
case ListItemType.Separator:
selectedTemplate = separatorTemplate;
break;
case ListItemType.Footer:
selectedTemplate = footerTemplate;
break;
default:
selectedTemplate = null;
break;
}
if ((itemType == ListItemType.AlternatingItem) &&
(alternatingItemTemplate == null))
{
selectedTemplate = itemTemplate;
itemType = ListItemType.Item;
}
RepeaterItem item = new RepeaterItem(itemIndex, itemType, dataItem);
if (selectedTemplate != null)
{
selectedTemplate.InstantiateIn(item);
}
OnItemCreated(new RepeaterItemEventArgs(item));
Controls.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(new RepeaterItemEventArgs(item));
}
return item;
}
private ArrayList items = null;
private void CreateControlHierarchy(bool useDataSource)
{
items = new ArrayList();
IEnumerable ds = null;
if (HeaderTemplate != null)
{
RepeaterItem header = CreateItem(-1, ListItemType.Header, false, null);
}
int count = -1;
if (useDataSource)
{
//解析DataSource
ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
DataMember);
}
else
{
count = (int)ViewState["ItemCount"];
if (count != -1)
{
ds = new DummyDataSource(count);
}
}
if (ds != null)
{
int index = 0;
count = 0;
RepeaterItem item;
ListItemType itemType = ListItemType.Item;
foreach (object dataItem in (IEnumerable)ds)
{
if (index != 0)
{
RepeaterItem separator = CreateItem(-1, ListItemType.Separator, false, null);
}
item = CreateItem(index, itemType, useDataSource, dataItem);
items.Add(item);
index++;
count++;
if (itemType == ListItemType.Item)
itemType = ListItemType.AlternatingItem;
else
itemType = ListItemType.Item;
}
}
if (FooterTemplate != null)
{
RepeaterItem footer = CreateItem(-1, ListItemType.Footer, false, null);
}
if (useDataSource)
{
ViewState["ItemCount"] = ((ds != null) ? count : -1);
}
}
其中最大的變化在於這裏,因爲還需要支持DataSet,DataSourceHelper類負責解析傳入的數據源DataSouce進行解析
{
//解析DataSource
ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
DataMember);
}
下面我們來重點看DataSourceHelper類
DataSourceHelper類可謂是這一篇的重頭戲,關鍵就在於這裏的理解.這裏搞明白了,纔算是明白.一起來看吧
1 /// <summary>
2 /// 一個解析DataSource的輔助類
3 /// </summary>
4 public class DataSourceHelper
5 {
6 public static object ResolveDataSource(object dataSource, string dataMember)
7 {
8 #region 如果數據源爲空,則返回空值
9
10 if (dataSource == null)
11 return null;
12
13 #endregion
14
15 #region 如果數據源不爲空,且爲IEnumerable類型,則返回IEnumerable
16
17 if (dataSource is IEnumerable)
18 {
19 return (IEnumerable)dataSource;
20 }
21
22 #endregion
23
24 #region 如果數據源不爲空,且爲IListSource類型,則返回IListSource
25
26 else if (dataSource is IListSource)
27 {
28 IList list = null;
29 IListSource listSource = (IListSource)dataSource;
30 list = listSource.GetList();
31 #region 判斷是否爲IList對象集合的值
32 if (listSource.ContainsListCollection)
33 {
34 //提供發現可綁定列表架構的功能,其中可用於綁定的屬性不同於要綁定到的對象的公共屬性
35 ITypedList typedList = (ITypedList)list;
36 //返回表示用於綁定數據的每項上屬性集合
37 PropertyDescriptorCollection propDescCol =
38 typedList.GetItemProperties(new PropertyDescriptor[0]); //was (null)
39
40 //如果屬性說明符數目爲0
41 if (propDescCol.Count == 0)
42 throw new Exception("ListSource without DataMembers");
43
44 PropertyDescriptor propDesc = null;
45
46 #region 判斷dataMember字符數給propDesc賦值
47 //獲取屬性描述符
48 //若不指定dataMember屬性則獲取默認數據成員
49 if ((dataMember == null) || (dataMember.Length < 1))
50 {
51 propDesc = propDescCol[0];
52 }
53 else
54 //嘗試在屬性集合衆尋找數據成員
55 propDesc = propDescCol.Find(dataMember, true);
56
57 #endregion
58
59 if (propDesc == null)
60 throw new Exception("ListSource missing DataMember");
61
62 object listitem = list[0];
63
64 //獲取DataTable
65 object member = propDesc.GetValue(listitem);
66
67 if ((member == null) || !(member is IEnumerable))
68 throw new Exception("ListSource missing DataMember");
69
70 return (IEnumerable)member;
71 }
72 else
73 //若不包含Ilist集合,則直接返回
74 return (IEnumerable)list; //robcamer added (IEnumerable)
75
76 #endregion
77 }
78
79 #endregion
80 return null;
81
82 }
83 }
這個輔助類判斷太多,剛看會看暈掉的,所以在if判斷這裏把代碼摺疊起來,有助於理解
這裏有幾個類可能沒見過,我們把關鍵用到的類一一列出來,希望大家查查MSDN
1.IListSource 向對象提供返回可以綁定到數據源列表的功能
2.ITypedList 提供發現可綁定列表架構的功能,其中可用於綁定的屬性不同於要綁定到的對象的公共屬性
3.PropertyDescriptor 提供類上的屬性的抽象化
4.PropertyDescriptorCollection 表示 PropertyDescriptor 對象的集合
下面開始
(1).首先如果傳入的數據源類型是IEnumerable的話,很好,可以直接返回
{
return (IEnumerable)dataSource;
}
(2).轉化實現IListSource接口的類
雖然傳入的類型非IEnumerable,如DataSet類實現了IListSource接口,其目的就是使用此接口的GetList方法返回一個IList(IList繼承IEnumerable,可以進行數據綁定),大家可以參考MSDN的原話
IListSource listSource = (IListSource)dataSource;
list = listSource.GetList();
假設傳入的是DataSet,list將會得到System.Data.DataViewManager集合
DataViewManager是什麼呢?爲默認DataTable默認的DataViewSettingCollection
DataViewSettingCollection是什麼呢?表示DataTable的DataViewSetting的集合
DataViewSetting是什麼呢?表示從 DataViewManager 創建的 DataView 的 的默認設置
上面的我們不熟,DataView大家應該熟悉,其可以對數據進行排序,過濾等
DataViewManager爲一個默認的DataView設置集合,不知這樣是否可以理解的好些.
我們的目的則是將其轉化到IEnumerable類型,繼續
DataViewManager實現了ITypedList接口
我們需要將DataViewManager(即list)轉化到ITypedList ,爲什麼?ITypedList的GetItemProperties方法將幫助你獲取DataView數據綁定的數據對象,而非DataView本身屬性
ITypedList的GetItemProperties方法綁定數據的每項屬性的PropertyDescriptorCollection集合,
PropertyDescriptorCollection表示PropertyDescriptor集合,PropertyDescriptor這個類很好玩,等同於屬性的說明書,即用了.net的反射技術,大家可以嘗試一下,其實以前也用過這個類.下面來看代碼片段
ITypedList typedList = (ITypedList)list;
//返回表示用於綁定數據的每項上屬性集合
PropertyDescriptor[] pd = new PropertyDescriptor[0];
PropertyDescriptorCollection propDescCol =
typedList.GetItemProperties(pd); //was (null)
//如果屬性說明符數目爲0
if (propDescCol.Count == 0)
throw new Exception("ListSource without DataMembers");
GetItemProperties方法傳入了一個PropertyDescriptor的數組,大家可能注意到了傳入的數組爲一個空數組,你還可以傳入一個空引用
typedList.GetItemProperties(null); //was (null)
如果你爲DataTable創建了DataView,將調用空引用返回DataSet中的一個DataTable,其將返回一個表集合列的屬性描述符,繼續看下去,該到DataMember出場的時候了,DataMember可以選擇數據集中的特定表,
如何不設置DataMember,將獲取默認表,看下面代碼片段
//獲取屬性描述符
//若不指定dataMember屬性則獲取默認數據成員
if ((dataMember == null) || (dataMember.Length < 1))
{
propDesc = propDescCol[0];
}
else
//嘗試在屬性集合中尋找數據成員
propDesc = propDescCol.Find(dataMember, true);
#endregion
if (propDesc == null)
throw new Exception("ListSource missing DataMember");
這樣我們就得到了一個DataTablePropertyDescriptor屬性描述符,繼續
//獲取組件屬性當前值
object member = propDesc.GetValue(listitem);
if ((member == null) || !(member is IEnumerable))
throw new Exception("ListSource missing DataMember");
return (IEnumerable)member;
此處實現原理:
DataViewManager會在其DataSet中的DataTableCollection中搜索datamember的值進行匹配,看下圖,做這麼多事情,我們一直在轉換
注GetValue用法
PropertyDescriptor pd = properties.Find("Text", false);
Button b=new Button();
b.Text = "cc";
object c=pd.GetValue(b);
Response.Write(c);
//return cc
用GetValue方法獲取listitem屬性值,此屬性跟datamember匹配,最後member得到的是一個DataView,
DataView實現了IEnumerable,現在終於可以轉換了
到此爲止就結束了,現在你可以成功的傳入DataSet了.