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,请看下面类图


asp.net控件开发基础(17)


1.TemplatedListCommandEventArgs为Command事件提供数据

2.TemplatedListItemEventArgs为一般项提供数据

3.TemplatedListItem表示TemplatedList的项

三.编写TemplatedList

1.TemplatedList主要功能简介

提供一个ItemTemplate模板属性,提供三种不同项样式,ItemCommand 事件冒泡事件及4个事件

asp.net控件开发基础(17)



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则为不同项呈现不同的样式效果



终于差不多了,经过这么多步骤,我们终于完成了,让我们来使用控件,看一下效果

asp.net控件开发基础(17)

又完成一个并不完美的控件,本来还该继续下去的,怕篇幅太大,到这里还没结束,只是刚开始,下次我们继续

你可能感兴趣的:(asp.net)