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

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

  本篇將開始介紹如自定義數據綁定控件,這里感謝很多人的支持,有你們的支持很高興。這里首先需要大家熟悉asp.net模板控件的使用,還有自定義模板控件.因為數據綁定控件多是基于模板控件的.

  一.回顧

  如果你使用過asp.net內置的數據控件(如DataList,Repeater),你一定會這么做

  1.設置數據源 DataSource屬性

  2.調用數據綁定  DataBind方法

  3.在控件的不同模板內使用綁定語法顯示數據

  這三步應該是必須要做的

  其他更多的

  你可能需要對綁定的數據進行統一的一些操作(如時間格式化),或者對數據的某一項進行操作(對某一項進行格式化),或者需要觸發模板控件內的一些事件(如databound事件)。

  根據上面的一些需求,我們需要這樣做

  1.對綁定的數據進行統一的一些操作: 為數據綁定控件定義Item項(表示列表的一條數據, 如Repeater的RepeaterItem)

  2.對數據的某一項進行操作: 因為定義了Item項,那你肯定需要一個ItemCollection集合,其可以方便的為你檢索數據

  3.因為定義了RepeaterItem,原先的EventArgs和CommandEventArgs已經無法滿足需求,我們需要自定義委托及其一個為控件提供數據的的ItemEventArgs

  上面三點有些并非必須定義,如第2點,還需要根據具體需求來定.但一個完成的控件是需要的。

  二.為數據控件做好準備

  這次的demo為不完整的Datalist控件,來源還是MSDN的例子,我們命名為TemplatedList,此控件未定義ItemCollection集合,好了,根據上面的分析我們先為TemplatedList提供項和委托及為事件提供數據的幾個EventArgs,請看下面類圖

  1.TemplatedListCommandEventArgs為Command事件提供數據

  2.TemplatedListItemEventArgs為一般項提供數據

  3.TemplatedListItem表示TemplatedList的項

  三.編寫TemplatedList

  1.TemplatedList主要功能簡介

  提供一個ItemTemplate模板屬性,提供三種不同項樣式,ItemCommand 事件冒泡事件及4個事件

  2.實現主要步驟

  以下為必須

  (1)控件必須實現 System.Web.UI.INamingContainer 接口

  (2)定義至少一個模板屬性

  (3)定義DataSource數據源屬性

  (4)定義控件項DataItem,即模板的一個容器

  (5)重寫DataBind 方法及復合控件相關方法(模板控件為特殊的復合控件)

  當然還有其他額外的屬性,樣式,事件

  3.具體實現

  下面我們來具體看實現方法

  (1)定義控件成員屬性

 
#region 靜態變量

private static readonly object EventSelectedIndexChanged = new object();
private static readonly object EventItemCreated = new object();
private static readonly object EventItemDataBound = new object();
private static readonly object EventItemCommand = new object();
#endregion

#region 成員變量
private IEnumerable dataSource;
private TableItemStyle itemStyle;
private TableItemStyle alternatingItemStyle;
private TableItemStyle selectedItemStyle;
private ITemplate itemTemplate;
#endregion

#region 控件屬性

[
Category(
"Style"),
Description(
"交替項樣式"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
]

public virtual TableItemStyle AlternatingItemStyle
{

get
{
if (alternatingItemStyle == null)
{
alternatingItemStyle
= new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)alternatingItemStyle).TrackViewState();
}

return alternatingItemStyle;
}
}


[
Category(
"Style"),
Description(
"一般項樣式"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
]

public virtual TableItemStyle ItemStyle
{

get
{
if (itemStyle == null)
{
itemStyle
= new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)itemStyle).TrackViewState();
}

return itemStyle;
}
}

[
Category(
"Style"),
Description(
"選中項樣式"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(
true),
PersistenceMode(PersistenceMode.InnerProperty),
]

public virtual TableItemStyle SelectedItemStyle
{

get
{
if (selectedItemStyle == null)
{
selectedItemStyle
= new TableItemStyle();
if (IsTrackingViewState)
((IStateManager)selectedItemStyle).TrackViewState();
}

return selectedItemStyle;
}
}



[
Bindable(
true),
Category(
"Appearance"),
DefaultValue(
-1),
Description(
"The cell padding of the rendered table.")
]

public virtual int CellPadding
{

get
{
if (ControlStyleCreated == false)
{

return -1;
}

return ((TableStyle)ControlStyle).CellPadding;
}

set
{
((TableStyle)ControlStyle).CellPadding
= value;
}
}

[
Bindable(
true),
Category(
"Appearance"),
DefaultValue(
0),
Description(
"The cell spacing of the rendered table.")
]

public virtual int CellSpacing
{

get
{
if (ControlStyleCreated == false)
{

return 0;
}

return ((TableStyle)ControlStyle).CellSpacing;
}

set
{
((TableStyle)ControlStyle).CellSpacing
= value;
}
}



[
Bindable(
true),
Category(
"Appearance"),
DefaultValue(GridLines.None),
Description(
"The grid lines to be shown in the rendered table.")
]

public virtual GridLines GridLines
{

get
{
if (ControlStyleCreated == false)
{

return GridLines.None;
}

return ((TableStyle)ControlStyle).GridLines;
}

set
{
((TableStyle)ControlStyle).GridLines
= value;
}
}

[
Bindable(
true),
Category(
"Data"),
DefaultValue(
null),
Description(
"數據源"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]

public IEnumerable DataSource
{

get
{
return dataSource;
}

set
{
dataSource
= value;
}
}


[
Browsable(
false),
DefaultValue(
null),
Description(
"項模板"),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(
typeof(TemplatedListItem))
]

public virtual ITemplate ItemTemplate
{

get
{
return itemTemplate;
}

set
{
itemTemplate
= value;
}
}


[
Bindable(
true),
DefaultValue(
-1),
Description(
"選中項索引,默認為-1")
]

public virtual int SelectedIndex
{

get
{
object o = ViewState["SelectedIndex"];
if (o != null)
return (int)o;
return -1;
}

set
{
if (value < -1)
{

throw new ArgumentOutOfRangeException();
}

//獲取上次選中項
int oldSelectedIndex = SelectedIndex;
ViewState[
"SelectedIndex"] = value;

if (HasControls())
{
Table table
= (Table)Controls[0];
TemplatedListItem item;


//第一次選中項不執行
if ((oldSelectedIndex != -1) && (table.Rows.Count > oldSelectedIndex))
{
item
= (TemplatedListItem)table.Rows[oldSelectedIndex];
//判斷項類型,為了將選中項還原為數據項
if (item.ItemType != ListItemType.EditItem)
{
ListItemType itemType
= ListItemType.Item;
if (oldSelectedIndex % 2 != 0)
itemType
= ListItemType.AlternatingItem;
item.SetItemType(itemType);
}
}

//第一次執行此項,并一直執行
if ((value != -1) && (table.Rows.Count > value))
{
item
= (TemplatedListItem)table.Rows[value];
item.SetItemType(ListItemType.SelectedItem);
}
}
}
}



#endregion
成員如下(可以看上面類圖)

 

  1.三個項樣式和三個樣式屬性

  2.公開DataSource數據源屬性,一個模板屬性

  3.SelectedIndex索引屬性

  前面的相信大家都很容易明白,其中的三個項樣式我們需要為其重寫視圖狀態管理,不熟悉可以看以前的隨筆,這里不再重復。SelectedIndex屬性比較復雜,這里重點介紹此屬性

  SelectedIndex索引屬性默認為-1,我給出了注釋,在賦值前先記錄下了上次的選中項,為恢復樣式而做準備 

                //獲取上次選中項
                int oldSelectedIndex = SelectedIndex;
                ViewState[
"SelectedIndex"= value;

當第一次更改SelectedIndex屬性時只執行下列代碼(將此項標記為選中項),因為初始化時的沒有oldSelectedIndex,不需要恢復樣式
//第一次執行此項,并一直執行
                    if ((value != -1&& (table.Rows.Count > value))
                    
{
                        item 
= (TemplatedListItem)table.Rows[value];
                        item.SetItemType(ListItemType.SelectedItem);
                    }
再次執行時,恢復oldSelectedIndex選中項樣式

 

 
//第一次選中項不執行
if ((oldSelectedIndex != -1) && (table.Rows.Count > oldSelectedIndex))
{
item
= (TemplatedListItem)table.Rows[oldSelectedIndex];
//判斷項類型,為了將選中項還原為數據項
if (item.ItemType != ListItemType.EditItem)
{
ListItemType itemType
= ListItemType.Item;
if (oldSelectedIndex % 2 != 0)
itemType
= ListItemType.AlternatingItem;
item.SetItemType(itemType);
}
}

 

相信這樣的解釋你會明白

 

  (2)定義控件成員事件

  我們可以用上剛才我們聲明的委托了,即然你定義了這么多事件,就該為其安排觸發的先后.所以這個要特別注意,等下會再次提到.

 
#region 事件
protected virtual void OnItemCommand(TemplatedListCommandEventArgs e)
{
TemplatedListCommandEventHandler onItemCommandHandler
= (TemplatedListCommandEventHandler)Events[EventItemCommand];
if (onItemCommandHandler != null) onItemCommandHandler(this, e);
}


protected virtual void OnItemCreated(TemplatedListItemEventArgs e)
{
TemplatedListItemEventHandler onItemCreatedHandler
= (TemplatedListItemEventHandler)Events[EventItemCreated];
if (onItemCreatedHandler != null) onItemCreatedHandler(this, e);
}


protected virtual void OnItemDataBound(TemplatedListItemEventArgs e)
{
TemplatedListItemEventHandler onItemDataBoundHandler
= (TemplatedListItemEventHandler)Events[EventItemDataBound];
if (onItemDataBoundHandler != null) onItemDataBoundHandler(this, e);
}


protected virtual void OnSelectedIndexChanged(EventArgs e)
{
EventHandler handler
= (EventHandler)Events[EventSelectedIndexChanged];
if (handler != null) handler(this, e);
}

[
Category(
"Action"),
Description(
"Raised when a CommandEvent occurs within an item.")
]

public event TemplatedListCommandEventHandler ItemCommand
{
add
{
Events.AddHandler(EventItemCommand, value);
}
remove
{
Events.RemoveHandler(EventItemCommand, value);
}
}

[
Category(
"Behavior"),
Description(
"Raised when an item is created and is ready for customization.")
]

public event TemplatedListItemEventHandler ItemCreated
{
add
{
Events.AddHandler(EventItemCreated, value);
}
remove
{
Events.RemoveHandler(EventItemCreated, value);
}
}

[
Category(
"Behavior"),
Description(
"Raised when an item is data-bound.")
]

public event TemplatedListItemEventHandler ItemDataBound
{
add
{
Events.AddHandler(EventItemDataBound, value);
}
remove
{
Events.RemoveHandler(EventItemDataBound, value);
}
}

[
Category(
"Action"),
Description(
"Raised when the SelectedIndex property has changed.")
]

public event EventHandler SelectedIndexChanged
{
add
{
Events.AddHandler(EventSelectedIndexChanged, value);
}
remove
{
Events.RemoveHandler(EventSelectedIndexChanged, value);
}
}

#endregion

  (3)關鍵實現

  我們為控件提供了這么多東西,剩下的事情就是要真正去實現功能了

  1.重寫DataBind方法

  當控件綁定數據時首先會執行此方法觸發DataBinding事件

        //控件執行綁定時執行
        public override void DataBind()
        
{

            
base.OnDataBinding(EventArgs.Empty);

            
//移除控件
            Controls.Clear();
            
//清除視圖狀態信息
            ClearChildViewState();

            
//創建一個帶或不帶指定數據源的控件層次結構
            CreateControlHierarchy(true);
            ChildControlsCreated 
= true;

            TrackViewState();
        }

  2.CreateControlHierarchy方法

 
/// <summary>
/// 創建一個帶或不帶指定數據源的控件層次結構
/// </summary>
/// <param name="useDataSource">指示是否要使用指定的數據源</param>
//注意:當第二次執行數據綁定時,會執行兩遍
private void CreateControlHierarchy(bool useDataSource)
{
IEnumerable dataSource
= null;
int count = -1;


if (useDataSource == false)
{

// ViewState must have a non-null value for ItemCount because this is checked
// by CreateChildControls.
count = (int)ViewState["ItemCount"];
if (count != -1)
{
dataSource
= new DummyDataSource(count);
}
}

else
{
dataSource
= this.dataSource;
}


//根據項類型開始創建子控件
if (dataSource != null)
{
Table table
= new Table();
Controls.Add(table);


//選中項索引
int selectedItemIndex = SelectedIndex;
//項索引
int index = 0;
//項數量
count = 0;
foreach (object dataItem in dataSource)
{

ListItemType itemType
= ListItemType.Item;
if (index == selectedItemIndex)
{

itemType
= ListItemType.SelectedItem;
}

else if (index % 2 != 0)
{
itemType
= ListItemType.AlternatingItem;
}


//根據不同項索引創建樣式
CreateItem(table, index, itemType, useDataSource, dataItem);
count
++;
index
++;
}
}

//執行綁定時執行時執行
if (useDataSource)
{

//保存項數量
ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
}
}

//創建項
private TemplatedListItem CreateItem(Table table, int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
TemplatedListItem item
= new TemplatedListItem(itemIndex, itemType);
TemplatedListItemEventArgs e
= new TemplatedListItemEventArgs(item);

if (itemTemplate != null)
{
itemTemplate.InstantiateIn(item.Cells[
0]);
}

if (dataBind)
{
item.DataItem
= dataItem;
}

//注意事件觸發順序
OnItemCreated(e);
table.Rows.Add(item);


if (dataBind)
{
item.DataBind();
OnItemDataBound(e);

item.DataItem
= null;
}


return item;
}

  CreateItem方法輔助用于創建項模板,此處注意事件觸發順序,上面已經提到過。此方法根據項索引創建控件中不同的Item項 ,ViewState["ItemCount"]表示項的數量,第一次觸發時或者重新執行DataBind方法時方法參數為true,并在初始化以后(回發期間)CreateChildControls方法會調用此方法,其參數為false。數據源不再是實際的數據源,而是新定義的DummyDataSource,其主要實現了一個迭代

 
internal sealed class DummyDataSource : ICollection
{

private int dataItemCount;

public DummyDataSource(int dataItemCount)
{

this.dataItemCount = dataItemCount;
}


public int Count
{

get
{
return dataItemCount;
}
}


public bool IsReadOnly
{

get
{
return false;
}
}


public bool IsSynchronized
{

get
{
return false;
}
}


public object SyncRoot
{

get
{
return this;
}
}

public void CopyTo(Array array, int index)
{

for (IEnumerator e = this.GetEnumerator(); e.MoveNext(); )
array.SetValue(e.Current, index
++);
}


public IEnumerator GetEnumerator()
{

return new DummyDataSourceEnumerator(dataItemCount);
}


private class DummyDataSourceEnumerator : IEnumerator
{


private int count;
private int index;

public DummyDataSourceEnumerator(int count)
{

this.count = count;
this.index = -1;
}


public object Current
{

get
{
return null;
}
}


public bool MoveNext()
{
index
++;
return index < count;
}


public void Reset()
{

this.index = -1;
}
}
}

  原因很明顯,為了減少對數據源的訪問,所以我們平時操作數據的時候,必須重新執行DataBind方法,原因就在此。好了,到了這里差不多主要的事情我們已經完成.接著把剩下的也完成

  3.呈現

  又到了Render方法這里了,此方法體只要執行了PrepareControlHierarchy方法,不同的方法做不同的事情,CreateControlHierarchy方法根據索引值指定了不同的項,PrepareControlHierarchy則為不同項呈現不同的樣式效果

 
//為不同類型項加載樣式
private void PrepareControlHierarchy()
{

if (HasControls() == false)
{

return;
}

Debug.Assert(Controls[
0] is Table);
Table table
= (Table)Controls[0];

table.CopyBaseAttributes(
this);
if (ControlStyleCreated)
{
table.ApplyStyle(ControlStyle);
}


// The composite alternating item style; do just one
// merge style on the actual item.
Style altItemStyle = null;
if (alternatingItemStyle != null)
{
altItemStyle
= new TableItemStyle();
altItemStyle.CopyFrom(itemStyle);
altItemStyle.CopyFrom(alternatingItemStyle);
}

else
{
altItemStyle
= itemStyle;
}


int rowCount = table.Rows.Count;
for (int i = 0; i < rowCount; i++)
{
TemplatedListItem item
= (TemplatedListItem)table.Rows[i];
Style compositeStyle
= null;
//根據不同項加載不同樣式
switch (item.ItemType)
{

case ListItemType.Item:
compositeStyle
= itemStyle;
break;

case ListItemType.AlternatingItem:
compositeStyle
= altItemStyle;
break;

case ListItemType.SelectedItem:
{
compositeStyle
= new TableItemStyle();

if (item.ItemIndex % 2 != 0)
compositeStyle.CopyFrom(altItemStyle);

else
compositeStyle.CopyFrom(itemStyle);
compositeStyle.CopyFrom(selectedItemStyle);
}

break;
}


if (compositeStyle != null)
{
item.MergeStyle(compositeStyle);
}
}
}


//控件呈現
protected override void Render(HtmlTextWriter writer)
{

// Apply styles to the control hierarchy
// and then render it out.

// Apply styles during render phase, so the user can change styles
// after calling DataBind without the property changes ending
// up in view state.
PrepareControlHierarchy();

RenderContents(writer);
}

  終于差不多了,經過這么多步驟,我們終于完成了,讓我們來使用控件,看一下效果
  又完成一個并不完美的控件,本來還該繼續下去的,怕篇幅太大,到這里還沒結束,只是剛開始,下次我們繼續

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

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

文章列表

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

    IT工程師數位筆記本

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