1. 引言

在ASP.NET中DataList可以实现数据展示,我们可以通过定制其模版实现丰富的格式,但是美中不足的时DataList默认情况下不支持分页,我们当然可以编写一个用户控件以实现分页功能,但是这种方案仍然不是很好,我们希望像使用普通ASP.NET服务器端控件一样,只需要放置一个DataList并设置分页样式就可以输出分页链接。

在上次任务中我们创建了DataPager类将创建分页的操作从GridView分离出来,本次任务将尝试重用DataPager类为DataList增加分页特性。

2. 分析

开发自定义GridView控件时,可以通过向控件中加入具有特定CommandName的按钮实现分页,但是对于DataList却不适用,因为DataList不能接收到客户端的回发事件,这也是DataList类和GridView类的一个区别—DataList类没有实现IPostBackEventHandler接口。为了能够使DataList接收客户端回发并触发分页事件,需要使自定义DataList实现IPostBackEventHandler接口,并使用自定义事件参数类在触发事件时传递页码信息。

IPostBackEventHandler接口定义了ASP.NET服务器控件为处理回发事件而必须实现的方法,它的成员只有一个方法:

void RaisePostBackEvent(string eventArgument)

该方法由类实现时,使服务器控件能够处理将窗体发送到服务器时引发的事件。

接下来需要考虑如何在客户端引起回发事件,即怎样生成回发脚本。这里使用到了ClientScriptManager类,该类作为Page类的一个属性ClientScript出现,通过调用该类的GetPostBackClientHyperlink方法生成客户端脚本以引起回发,该方法有两个形式的重载:

  • GetPostBackClientHyperlink (Control, String)

获取一个引用,并在其开头附加 javascript:,可以在客户端事件中使用该引用,并将该引用与指定的事件参数一起使用,以便回发到指定控件的服务器。

  • GetPostBackClientHyperlink (Control, String, Boolean)

获取一个引用,并在其开头附加 javascript:,该引用可用于在客户端事件中回发到指定控件的服务器,回发时使用指定的事件参数和一个指示是否为事件验证注册该回发的布尔值。

其中第一个参数指明了处理回发的服务器控件,第二个参数代表传递给服务器控件的参数,第三个参数代表是否验证注册回发事件。

接下来编写实现代码。

3. 实现

3.1 创建DataListPager类,该类继承自DataPager类实现了为分页链接添加回发脚本操作:

public class DataListPager : DataPager
{
    public DataListPager(PagerSettings setting, int pageIndex, int recordCount, int pageSize, DataList dal)
        : base(setting, pageIndex, recordCount, pageSize)
    {
        LinkButton btn = null;
        int _pageCount = recordCount % pageSize == 0 ? recordCount / pageSize : recordCount / pageSize + 1;
        int index;

        foreach (Control control in this.Controls)
        {
            if (control is LinkButton)
            {
                btn = (LinkButton)control;

                if (btn.Enabled)
                {
                    string argvalue = btn.CommandArgument;

                    bool isInt = int.TryParse(argvalue, out index);
                    string arg = string.Empty;
                    if (isInt)
                    {
                        index--;
                    }
                    else
                    {
                        switch (argvalue)
                        {
                            case Constant.FIRST_PAGE: index = 0;
                                break;
                            case Constant.PREV_PAGE: index = pageIndex - 1 < 0 ? 0 : pageIndex - 1;
                                break;
                            case Constant.NEXT_PAGE: index = pageIndex + 1 > _pageCount - 1 ? _pageCount - 1 : pageIndex + 1;
                                break;
                            case Constant.LAST_PAGE: index = _pageCount - 1;
                                break;
                        }
                    }

                    arg = Constant.PAGE_ARGUMENT + Constant.ARGUMENT_SPLITTER + index;
                    btn.Attributes.Add(HtmlTextWriterAttribute.Href.ToString(),
                                        dal.Page.ClientScript.GetPostBackClientHyperlink(dal, arg));
                }

            }
        }
    }
}

3.2 创建DataList类,继承自默认的DataList类并实现IPostBackEventHandler接口:

[ToolboxData(@"<{0}:DataList runat='server'></{0}:DataList>")]
[ParseChildren(true)]
[PersistChildren(false)]
public class DataList : System.Web.UI.WebControls.DataList, IPostBackEventHandler
{
}

3.3 定义DataList属性,保存分页设置信息:

public int RecordCount
{
    get
    {
        object o = ViewState["RecordCount"];

        return o == null ? 0 : Convert.ToInt32(o);
    }
    set
    {
        ViewState["RecordCount"] = value;
    }
}

public virtual int PageIndex
{
    get
    {
        object o = ViewState["PageIndex"];

        return o == null ? 0 : Convert.ToInt32(o);
    }
    set
    {
        ViewState["PageIndex"] = value;
    }
}

[DefaultValue(10)]
public virtual int PageSize
{
    get
    {
        object o = ViewState["PageSize"];

        return o == null ? 10 : Convert.ToInt32(o);
    }
    set
    {
        ViewState["PageSize"] = value;
    }
}

private PagerSettings _settings;

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public PagerSettings PagerSettings
{
    get
    {
        if (_settings == null)
            _settings = new PagerSettings();
        if (base.IsTrackingViewState)
            ((IStateManager)_settings).TrackViewState();

        return this._settings;
    }
}

public bool EnablePaging
{
    get
    {
        object o = ViewState["EnablePaging"];

        return o == null ? false : Convert.ToBoolean(o);
    }
    set
    {
        ViewState["EnablePaging"] = value;
    }
}

3.4 创建自定义事件类,保存新页码:

public class DataListPageEventArgs : EventArgs
{
    public int NewPageIndex
    {
        get;
        set;
    }
}

3.5 在DataList类中创建事件和辅助方法:

public event EventHandler<DataListPageEventArgs> PageIndexChanging;
protected virtual void OnPageIndexChanging(object sender, DataListPageEventArgs e)
{
    if (PageIndexChanging != null)
    {
        PageIndexChanging(sender, e);
    }
}

3.6 编写RenderContent方法,该方法是实现了分页的核心操作,通过实例化DataListPager类的实例,将触发回发操作的链接加入到当前DataList中:

protected override void RenderContents(HtmlTextWriter writer)
{
    this.RenderBeginTag(writer);

    if (EnablePaging)
    {
        Table table = new Table();
        TableRow row = new TableRow();
        table.Rows.Add(row);
        TableCell cell = new TableCell();

        row.RenderBeginTag(writer);
        cell.RenderBeginTag(writer);

        base.RenderContents(writer);

        cell.RenderEndTag(writer);
        row.RenderEndTag(writer);

        TableRow rowpager = new TableRow();

        DataListPager pager = new DataListPager(PagerSettings, PageIndex, RecordCount, PageSize, this);

        rowpager.Cells.Add(pager);

        rowpager.RenderBeginTag(writer);

        pager.RenderControl(writer);

        rowpager.RenderEndTag(writer);

    }
    else
    {
        base.RenderContents(writer);
    }

    this.RenderEndTag(writer);
}

3.7 编写IPostEventHandler接口中RaisePostBackEvent方法的实现,判断当前事件参数并触发换页事件:

public void RaisePostBackEvent(string eventArgument)
{
    string[] args = eventArgument.Split(Constant.ARGUMENT_SPLITTER);

    if (args == null || args.Length < 1)
        return;

    string name = args[0];

    if (name == Constant.PAGE_ARGUMENT)
    {
        int index = 0;
        string argvalue = args[1];
        bool isInt = int.TryParse(argvalue, out index);

        if (isInt)
        {
            DataListPageEventArgs arg = new DataListPageEventArgs();
            arg.NewPageIndex = index;

            OnPageIndexChanging(this, arg);
        }
    }
}

3.8 重写SaveViewState和LoadViewState方法定义保存和读取视图状态方法:

protected override object SaveViewState()
{
    object o = base.SaveViewState();
    object osetting = ((IStateManager)PagerSettings).SaveViewState();
    Pair p = new Pair(o, osetting);

    return p;
}

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

3.9 在网站中创建测试页,声明并定义自定义DataList:

<cc:DataList ID="dalData" runat="server" EnablePaging="true" PageSize="10" OnPageIndexChanging="dalData_PageIndexChanging" PagerSettings-FirstPageText="????">        
    <ItemTemplate>
    <table class="data" cellpadding="0" cellspacing="1" border="0">        
        <tr>
            <td><asp:Label ID="lblId" runat="server" Text='<%#Eval("Id") %>'></asp:Label></td>
            <td><asp:Label ID="lblName" runat="server" Text='<%#Eval("Name") %>'></asp:Label></td>
        </tr>
        </table>
    </ItemTemplate>
</cc:DataList>

3.10 在后置代码中编写BindData方法加载数据,并且在页面不是回发时调用此方法显示数据:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
        this.BindData();
}

private void BindData()
{
    DataTable table = new DataTable();

    DataColumn col = new DataColumn("Id");
    table.Columns.Add(col);
    col = new DataColumn("Name");
    table.Columns.Add(col);

    for (int i = dalData.PageIndex * dalData.PageSize; i < dalData.PageIndex * dalData.PageSize+dalData.PageSize; i++)
    {
        DataRow row = table.NewRow();
        row[0] = i;
        row[1] = "人员 " + i;

        table.Rows.Add(row);
    }

    dalData.DataSource = table;
    dalData.RecordCount = 30;
    dalData.DataBind();
}
3.11 编写DataList的切换页事件,将新的页码索引赋值给DataList并执行数据绑定:
protected void dalData_PageIndexChanging(object sender, DataListPageEventArgs e)
{
    dalData.PageIndex = e.NewPageIndex;
    this.BindData();
}

3.12 在浏览器中预览效果:

clip_image001

4. 总结

在本次任务中,通过为LinkButton加入了JavaScript脚本使得在客户端点击时可以引起回发操作,这是通过ClientScriptManager类的GetPostBackClientHyperlink方法实现的。引起提交后,为了能够在服务器端处理回发事件,在自定义DataList控件中实现了IPostBackEventHandler接口并在实现方法中调用了自定义页切换事件,使开发人员能够根据新页码进行数据绑定。可以看到,现在DataList和GridView都已经实现分页了,但是从某种意义上来说,这个解决方案还不够优雅(经常被Java程序员拿来说事的一个词-_-!!),您可以自行加以改良。


ASP.NET自定义控件系列文章

前言

第一天 简单的星级控件

第二天 带有自定义样式的星级控件

第三天 使用控件状态的星级控件

第四天 折叠面板自定义控件

第五天 可以评分的星级控件

第六天 可以绑定数据源的星级控件

第七天 开发具有丰富特性的列表控件

第八天 显示多个条目星级评分的数据绑定控件

第九天 自定义GridView

第十天 实现分页功能的DataList


全部源码下载

本系列文章PDF版本下载