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)定義控件成員屬性


        #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)定義控件成員事件

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



(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[
0is 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);
        }


終於差不多了,經過這麼多步驟,我們終於完成了,讓我們來使用控件,看一下效果


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章