asp.net控件開發基礎(17)
本篇將開始介紹如自定義數據綁定控件,這里感謝很多人的支持,有你們的支持很高興。這里首先需要大家熟悉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)定義控件成員屬性
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;






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

















2.CreateControlHierarchy方法
/// 創建一個帶或不帶指定數據源的控件層次結構
/// </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,其主要實現了一個迭代
{
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控件開發基礎(18)