asp.net控件開發基礎(18)

作者: Clingingboy  來源: 博客園  發布時間: 2010-10-02 19:52  閱讀: 991 次  推薦: 0   原文鏈接   [收藏]  

  本篇繼續上篇的討論,可能大家已經在使用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)定義成員屬性和事件

 
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進行解析
            if (useDataSource)
            
{
                
//解析DataSource
                ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
                   DataMember);
            }
  下面我們來重點看DataSourceHelper類,DataSourceHelper類可謂是這一篇的重頭戲,關鍵就在于這里的理解.這里搞明白了,才算是明白.一起來看吧

 

 
// <summary>
/// 一個解析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的話,很好,可以直接返回

            if (dataSource is IEnumerable)
            
{
                
return (IEnumerable)dataSource;
            }
(2).轉化實現IListSource接口的類 

 

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

                IList list = null;
                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的數組,大家可能注意到了傳入的數組為一個空數組,你還可以傳入一個空引用
PropertyDescriptorCollection propDescCol =
                       typedList.GetItemProperties(null);  //was (null)
  如果你為DataTable創建了DataView,將調用空引用返回DataSet中的一個DataTable,其將返回一個表集合列的屬性描述符,繼續看下去,該到DataMember出場的時候了,DataMember可以選擇數據集中的特定表,如何不設置DataMember,將獲取默認表,看下面代碼片段
 
#region 判斷dataMember字符數給propDesc賦值
//獲取屬性描述符
//若不指定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 listitem = list[0];

                    
//獲取組件屬性當前值
                    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用法

        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(Button1);
        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了。

上一篇:asp.net控件開發基礎(17)

下一篇:asp.net控件開發基礎(19)
0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()