Asp.net自定义控件开发任我行(8)-数据集绑定

  • 摘要

  已经有好几天没有写博客了,今天继续,前几天写到了注册自定义事件,今天我们来讲数据集绑定。   

  先把运行效果截个图给大家看,让大家心里也有个底。(大家要从第一章开始看起,我们每一章都是接着前面没做完的,一步步的完善)

  

  • 内容

  在ASP.NET数据绑定控件分为三种:

    1. 简单数据绑定:简单数据绑定将一个对象与某个控件的属性绑定在一起。数据源只是绑定单个数据项,而不是绑定一个数据项列表。简单数据绑定使用数据绑定表达式完成,数据绑定表达式是用<%#...%>封装的任何可执行代码。
    2. 列表控件:列表控件是通过一个固定不变的用户界面显示一个数据项列表的控件。常见的列表控件包含RadioButtonList控件、CheckBoxList控件和ASP.NET2.0中新引入的BulletedList控件。
    3. 复杂数据绑定:复杂数据绑定控件通常是显示一组数据项的组合控件,它们有着灵活的呈现机制,例如GridView控件就是一个复杂数据绑定控件。

  我们这里只讲第3种数据绑定,使用时数据绑定方法代码看起来可能如下所示

protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                DataTable dt = new DataTable();
                dt.Columns.Add("name");
                dt.Columns.Add("id");
                DataRow dr = dt.NewRow();
                dr[0] = "张三1";
                dr[1] = 0;
                dt.Rows.Add(dr);

DataRow dr1
= dt.NewRow(); dr1[0] = "张三2"; dr1[1] = 1; dt.Rows.Add(dr1); DataRow dr2 = dt.NewRow(); dr2[0] = "张三3"; dr2[1] = 2; dt.Rows.Add(dr2); DataRow dr3 = dt.NewRow(); dr3[0] = "张三4"; dr3[1] = 3; dt.Rows.Add(dr3); DropDwonCheckList1.DataTextField = "name"; DropDwonCheckList1.DataValueField = "id"; DropDwonCheckList1.DataSource = dt; DropDwonCheckList1.DataBind();
} }
 <form id="form1" runat="server">    
  
    
    <XYB:DropDwonCheckList ID="DropDwonCheckList1" runat="server" />

    </form>

我们都知道DropDwonList,CheckBoxList等这些控件都可以绑定数据源,我们先看看他们底层是什么样子的,

public class DropDownList : ListControl, IPostBackDataHandler
public class CheckBoxList : ListControl, IRepeatInfoUser, INamingContainer, IPostBackDataHandler
public abstract class ListControl : DataBoundControl, IEditableTextControl, ITextControl

我们可以看到,DropDwonList,CheckBoxList都继承了ListControl,而ListControl继承了DataBoundControl类。我们再来看看他们的Asp.net2.0提供的数据集绑定类的层次结构图


所以我们在原来项目的基础上做修改,继承DataBoundControl类,这个类里面有我们想要的DataSource和DataSourceID属性,提供DataBind方法,并且只要我们执行DataBind()方法时,自动执行绑定。

1.重新继承DataBoundControl,代码如下

public class DropDwonCheckList:DataBoundControl,IPostBackEventHandler,INamingContainer

IPostBackEventHandler 是用于处理点击回发事件的接口,我们在上一章的注册自定义事件里面详细介绍过了。INamingContainer接口只为解决控件ID命名冲突问题。
2.定义属性

[Description("要显示的文本项"), Category("数据")]
        public string DataTextField
        {
            get { return ViewState["DataTextField"].ToString() ?? "name"; }
            set { ViewState["DataTextField"] = value; }
        }
        [Description("文本关联值"), Category("数据")]
        public string DataValueField
        {
            get { return ViewState["DataValueField"].ToString() ?? "id"; }
            set { ViewState["DataValueField"] = value; }
        }
       
        [Description("下拉框的高度"),Category("下拉框")]
        public int DropDwonHeight
        {           
            get { return Convert.ToInt32(ViewState["DropDwonHeight"] ?? 0); }
            set { ViewState["DropDwonHeight"] = value; }           
        }

        [Description("下拉框的宽度"),Category("下拉框")]
        public int DropdwonWidth
        {
            get { return ViewState["DropdwonWidth"] == null ? 150 : Convert.ToInt32(ViewState["DropdwonWidth"]); }
            set { ViewState["DropdwonWidth"] = value; }
        } 

3.实现IPostBackEventHandler 接口,我在这里就直接把代码粘出来了,这个不是我们本章的重点,旨在读者看到后,有个印象,起到复习的作用,最后大家不要忘记导火线,用这个导火线来产生事件回发。我们只需为控件在页面类中注册onclick事件。

Control.Attributes["onclick"]=this.Page.GetPostBackEventReference(this);//引发回发

 #region  行为与事件
        private static readonly object EventClick = new object();//键值对象,指明点击事件,名称随便取
        [Description("点击文本框时发生"), Category("Action")]
        public event EventHandler Click
        {
            add
            {
                base.Events.AddHandler(EventClick, value);
            }
            remove
            {
                base.Events.RemoveHandler(EventClick, value);
            }

        }
        protected virtual void OnClick(EventArgs e)
        {
            EventHandler handler = (EventHandler)base.Events[EventClick];
            if (handler != null)
            {
                handler(this, e);
            }
        } 
        #endregion

        void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
        {
            this.OnClick(new EventArgs()); 
        }

4.定义数据项。
我们都知道DropDwonList控件的数据项是ListItem,当然我们DropDwonCheckList控件(我们最终要实现的控件) 也要定义一个数据项。新建一个类,命名为CheckDataItem,因为我们要保存CheckDataItem的数据状态,所以我们还需继承一个IStateManager接口,ViewState不能很好的保存一个对象,IStateManager 是保存对象状态的最佳选择,代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Runtime;

namespace XYB.Controls
{
    public class CheckDataItem:IStateManager
    {
        #region  字段与属性
        private bool _mark;
        /// <summary>
        /// 要显示的文本项
        /// </summary>
        public string ItemText { get; set; }
        /// <summary>
        /// 文本关联值
        /// </summary>
        public string ItemValue { get; set; }
        /// <summary>
        /// 该数据项是否被选中
        /// </summary>
        public bool Checked { get; set; } 
        #endregion       

        #region  构造函数
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public CheckDataItem()
            : this(null, null)
        {
        }

        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public CheckDataItem(string text)
            : this(text, null)
        {

        }

        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public CheckDataItem(string text, string value)
            : this(text, value, false)
        {

        }

        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public CheckDataItem(string text, string value, bool isChecked)
        {
            this.ItemText = text;
            this.ItemValue = value;
            this.Checked = isChecked;
        } 
        #endregion

        /// <summary>
        ///  指示服务器控件是否正在发生改变
        /// </summary>
        public bool IsTrackingViewState
        {
            get { return _mark; }
        }

        /// <summary>
        /// 将服务器控件还原到保存前的视图状态
        /// </summary>
        /// <param name="state"></param>
        public void LoadViewState(object state)
        {
            if (state != null)
            {
                Triplet t = state as Triplet;
                ItemText = t.First.ToString();
                ItemValue = t.Second.ToString();
                Checked = Convert.ToBoolean(t.Third);
            }
        }
        /// <summary>
        /// 将服务器控件的视图状态保存到 Object
        /// </summary>
        /// <returns></returns>
        public object SaveViewState()
        {
            return new Triplet(ItemText, ItemValue, Checked);
        }
        /// <summary>
        /// 指示服务器控跟踪其视图状态更改
        /// </summary>
        public void TrackViewState()
        {
            _mark = true;
        }

       
    }
}

Triplet 类是用于保存三个相关联的Object对象,多个对象用数组来保存,LoadViewState是还原状态,先将其强制成Triplet对象,然后再从Triplet三个对象中取值,达到还原的目的,SaveViewState保存状态,我们直接返回一个Triplet对象。记住这三个关联对象顺序要一致。

5.定义数据集合。

ListItem保存在ListItemCollection中,于是我们的CheckDataItem也需要保存在CheckDataItemColllection中。新建一个类,命名为CheckDataItemCollection。CheckDataItemCollection需要继承一个Collection泛型集合,使用泛型的好处在我们找工作的时候经常在面试题中提到,好处就是只让添加一种类型,安全性类型,减少装箱拆箱所带来的开销(顺便在这里扯一下,嘿嘿)。CheckDataItemCollection只让添加CheckDataItem这一种类型,同时我们也还需要实现IStateManager接口,来保存这个集合。代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.ObjectModel;
using System.Web.UI;

namespace XYB.Controls
{
    public class CheckDataItemCollection : Collection<CheckDataItem>, IStateManager
    {
        private bool marked;
        public CheckDataItemCollection()
        {
            marked = false;
        }

        public bool IsTrackingViewState
        {
            get
            {
                return this.marked;
            }
        }
        public void TrackViewState()
        {
            marked = true;
        }
        public void LoadViewState(object state)
        {
            if (state == null)
                return;
            Clear();
            Triplet trip = (Triplet)state;
            string[] ItemTexts = (string[])trip.First;
            string[] ItemValues = (string[])trip.Second;
            bool[] ItemCheckeds = (bool[])trip.Third;
            for (int i = 0; i < ItemCheckeds.Length; i++)
            {
                Add(new CheckDataItem(ItemTexts[i], ItemValues[i], ItemCheckeds[i]));
            }
        }
        public object SaveViewState()
        {
            int num = Count;
            object[] ItemTexts = new string[num];
            object[] ItemValues = new string[num];
            bool[] ItemCheckeds = new bool[num];
            for (int i = 0; i < num; i++)
            {
                ItemTexts[i] = Items[i].ItemText;
                ItemValues[i] = Items[i].ItemValue;
                ItemCheckeds[i] = Items[i].Checked;
            }
            return new Triplet(ItemTexts, ItemValues, ItemCheckeds);
        }
    }
}

我们把CheckDataItemCollection定义好了,回到DropDwonCheckList.cs文件中来,现在我们来定义一个变量Items,它就是用来保存所有的CheckDataItem数据项。

private CheckDataItemCollection _items;
        public CheckDataItemCollection Items
        {
            get
            {
                if (_items == null)
                {
                    _items = new CheckDataItemCollection();
                }
                if (IsTrackingViewState)
                {
                    _items.TrackViewState();
                }
                return _items;
            }
        }

5.从数据源中读取数据,并进行绑定。

 我们可能在疑惑,我们传值给DataSouce,而DataSouce是一个Object类型,而且它又是如何把值给Items的呢,DataBoundControl类为我们提供了一个方法PerformDataBinding,此方法根据映射从数据源迭代读取数据,并在执行了DataBind方法后自动执行。我们只需要重写这个方法即可,并在这里作一些手脚。

 /// <summary>
        /// 重写PerformDataBinding方法根据映射从数据源迭代读取数据,此方法在数据绑定时执行
        /// </summary>
        /// <param name="data"></param>
        protected override void PerformDataBinding(IEnumerable data)
        {
            base.PerformDataBinding(data);

            if (data != null)
            {
                foreach (object o in data)
                {
                    CheckDataItem item = new CheckDataItem();
                    item.ItemText = DataBinder.GetPropertyValue(o, DataTextField, null);
                    item.ItemValue = DataBinder.GetPropertyValue(o, DataValueField, null);
                    item.Checked = false;
                    Items.Add(item);
                }
            }
        } 

该方法接收IEnumerable类型的参数用于迭带访问数据源中的数据,在读取数据源中的数据时使用了DataBinder类,该类上有一个实用的GetPropertyValue方法,用于根据属性反射的读取数据源中的值,此处我们使用该方法并传递了DataTextField和DataValueField以读取文本和文本所关联的值。

6.重写LoadViewState,和SaveViewState方法将CheckDataItem数据项保存到视图状态中并能够正确的恢复。

 protected override object SaveViewState()
        {
            return new Pair(base.SaveViewState(), Items.SaveViewState());
        }

        protected override void LoadViewState(object savedState)
        {
            if (savedState != null)
            {
                Pair p = (Pair)savedState;
                base.LoadViewState(p.First);
                Items.LoadViewState(p.Second);
            }
        }     

Pair类是用于保存两个相关联的Object对象。
7.呈现控件,大功生成。

我们可以把我们的最终控件分成四部分,文本框部分,头部,内容部,和尾部。

#region  重写呈现
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            //如果文件已经被加载了,就不用重复加载
            if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(this.GetType(), "XYB.Controls.JS.dropDwon.js"))
            {
                #region  加载嵌入资源.css文件
                //加载嵌入资源.css文件
                string cssHref = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "XYB.Controls.CSS.dropDwon.css");
                string cssLink = string.Format("<link href='{0}' rel='stylesheet' type='text/css' />", cssHref);
                LiteralControl litLink = new LiteralControl(cssLink);
                litLink.ID = "XYB_Controls_dropDwonCss";
                this.Page.Header.Controls.Add(litLink);
                #endregion
                //加载嵌入资源.js文件
                this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), "XYB.Controls.JS.dropDwon.js");
                //this.Attributes["onclick"] = "dropDwonClick()";//给文本框注册点击事件

                //调用GetPostBackEventReference方法,产生页面回发。
                //this.Attributes["onclick"] = this.Page.GetPostBackEventReference(this);


            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            HtmlInputText txt = new HtmlInputText();
            txt.Style.Add(HtmlTextWriterStyle.Width, DropdwonWidth + "px");
            this.Controls.Add(txt);

            Panel pnlDropDown = new Panel();
            //名字写这么长,只为防止别人与我的控件ID相同
            pnlDropDown.ID = "XYB_Controls_DropDownPanelID";
            pnlDropDown.Style["height"] = "auto";
            pnlDropDown.Width = DropdwonWidth;
            pnlDropDown.Style["border"] = "1px solid #ccc";
            CreateControlHierarchy(pnlDropDown);
            
            this.Controls.Add(pnlDropDown);
            base.Render(writer);
        }


        /// <summary>
        /// 创建控件层次结构,头部,内容部,尾部
        /// </summary>
        /// <param name="dropDwonPanel"></param>
        private void CreateControlHierarchy(Panel dropDwonPanel)
        {
            CreateHeaderTemplate(dropDwonPanel);
            CreateContentTemplate(dropDwonPanel);
            CreateFooterTemplate(dropDwonPanel);
        }

        /// <summary>
        /// 创建头部
        /// </summary>
        /// <param name="dropDwonPanel"></param>
        private void CreateHeaderTemplate(Panel dropDwonPanel)
        {
            StringBuilder strHeaderHtml = new StringBuilder();
            strHeaderHtml.Append("<div id="XYB_Controls_DropDownHeaderPanelID">");
            strHeaderHtml.Append("<input type='checkbox' id='XYB_Controls_SelectAllItemCheckBox' /><label>全选</label>");
            strHeaderHtml.Append("</div>");
            dropDwonPanel.Controls.Add(new LiteralControl(strHeaderHtml.ToString()));
        }

        /// <summary>
        /// 创建内容部
        /// </summary>
        /// <param name="dropDwonPanel"></param>
        private void CreateContentTemplate(Panel dropDwonPanel)
        {
            if (Items.Count>0)
            {

                foreach (CheckDataItem item in Items)
                {
                    Panel contentPanel = new Panel();
                    contentPanel.Style["width"] = "100%";
                    contentPanel.Style["text-align"] = "left";
                    contentPanel.Style["padding-left"] = "5px";
                    CheckBox cbx = new CheckBox();
                    cbx.Text = item.ItemText;
                    contentPanel.Controls.Add(cbx);
                    dropDwonPanel.Controls.Add(contentPanel);
                }
            }
        }

        /// <summary>
        /// 创建尾部
        /// </summary>
        /// <param name="dropDwonPanel"></param>
        private void CreateFooterTemplate(Panel dropDwonPanel)
        {           
            StringBuilder strFooterHtml = new StringBuilder();           
            strFooterHtml.Append("<div id='XYB_Controls_DropDwonFooterPanelID'>");
            strFooterHtml.Append("<div id="XYB_Controls_DropDwonFooterLeftPanelID">");
            strFooterHtml.Append("<input type="button" id="XYB_Controls_btnSure" value="确定" />");
            strFooterHtml.Append("</div>");
            strFooterHtml.Append("<div id="XYB_Controls_DropDwonFooterRightPanelID">");
            strFooterHtml.Append("<input type="button" id="XYB_Controls_btnCancel" value="取消" />");
            strFooterHtml.Append("</div>");
            strFooterHtml.Append("</div>");
            dropDwonPanel.Controls.Add(new LiteralControl(strFooterHtml.ToString()));
        }

dropDwon.js代码

function dropDwonClick() {
    $("XYB_Controls_DropDownPanelID").style.display = "block";   
}


function $(obj) { 
    return document.getElementById(obj);
}

dropDwon.css代码

#XYB_Controls_DropDownPanelID{
    border:1px solid #ccc;
    display:block;
}
#XYB_Controls_DropDownHeaderPanelID,#XYB_Controls_DropDownFooterPanelID{
    height:20px; line-height:20px;100%;
}
  
   #XYB_Controls_DropDwonFooterLeftPanelID,#XYB_Controls_DropDwonFooterRightPanelID{
       50%;float:left;text-align:center;
   }
#XYB_Controls_DropDownHeaderPanelID
{
    border-bottom:1px solid #ccc;
    
    }
#XYB_Controls_DropDwonFooterPanelID
{
    padding-top:10px;
    height:30px;
    line-height:30px;
   
    }
  • 下集预告

  上面的代码就是我全部的页面代码。运行起来的效果就是摘要的截图,下一章,全选与反选

  

  

    

原文地址:https://www.cnblogs.com/xuyubing/p/3337238.html