asp.net控件開發基礎(18)
本篇繼續上篇的討論,可能大家已經在使用asp.net2.0了,DataSource屬性不再使用,而是跟數據源控件搭配使用.現在討論的綁定技術都是基于1.1版本,先熟悉一下,本質上是一樣的,這樣一步步的學習.對以后絕對有幫助.因為當你使用數據源控件,只需要設置一個DataSourceID,方便的同時你是否知道數據源控件幫你做了什么事情,如果你想覺的夠用了,可以不用了解,但我相信你一定會有需求。上篇最后說過了,討論還剛剛開始,我們大致把核心的方法都寫出來了.下面我們繼續。
一.控件對比
我們可以使用上篇制作的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)定義成員屬性和事件
/// <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;
}
}
(2)關鍵實現
1.因為Repeater模板不具有樣式屬性,所以去掉了PrepareControlHierarchy方法。
2.由于不涉及到復雜的樣式屬性,所以不必重載視圖狀態管理的三個方法。
這兩點就可以讓控件減少很多代碼的編寫
3.CreateControlHierarchy方法和CreateItem方法
Repeater模板的實現方法和TemplatedList稍有不同,但變化不大,應該容易理解.看下面代碼
/// 創建控件各種項
/// </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);
}
}






/// 一個解析DataSource的輔助類
/// </summary>
public class DataSourceHelper
{
public static object ResolveDataSource(object dataSource, string dataMember)
{
如果數據源為空,則返回空值
如果數據源不為空,且為IEnumerable類型,則返回IEnumerable
如果數據源不為空,且為IListSource類型,則返回IListSource
return null;
}
}
這個輔助類判斷太多,剛看會看暈掉的,所以在if判斷這里把代碼折疊起來,有助于理解。這里有幾個類可能沒見過,我們把關鍵用到的類一一列出來,希望大家查查MSDN
1.IListSource 向對象提供返回可以綁定到數據源列表的功能
2.ITypedList 提供發現可綁定列表架構的功能,其中可用于綁定的屬性不同于要綁定到的對象的公共屬性
3.PropertyDescriptor 提供類上的屬性的抽象化
4.PropertyDescriptorCollection 表示 PropertyDescriptor 對象的集合
下面開始
(1).首先如果傳入的數據源類型是IEnumerable的話,很好,可以直接返回




雖然傳入的類型非IEnumerable,如DataSet類實現了IListSource接口,其目的就是使用此接口的GetList方法返回一個IList(IList繼承IEnumerable,可以進行數據綁定),大家可以參考MSDN的原話



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的反射技術,大家可以嘗試一下,其實以前也用過這個類.下面來看代碼片段












//獲取屬性描述符
//若不指定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屬性描述符,繼續









此處實現原理:
DataViewManager會在其DataSet中的DataTableCollection中搜索datamember的值進行匹配,看下圖,做這么多事情,我們一直在轉換
注:GetValue用法







用GetValue方法獲取listitem屬性值,此屬性跟datamember匹配,最后member得到的是一個DataView,DataView實現了IEnumerable,現在終于可以轉換了。到此為止就結束了,現在你可以成功的傳入DataSet了。
下一篇:asp.net控件開發基礎(19)