![]() |
许多使用ASP.NET开发的朋友都会使用到DataList控件,DataList是个非常优秀的控件,我们使用他的时候(与DataGrid比较)主要是利用他的允许分列的功能。但绝大多数在使用的时候对该控件有一点小小的不满意:不能象DataGrid一样支持分页。本文介绍了一种实现DataList分页的方法,实现了PagerDataList控件,既可以象DataGrid一样方便的实现数据的分页,又具有DataList的强大功能;同时也提供一个成熟可用的PagerDataList控件。 问题的提出 .NET DataList 控件在一个重复列表中显示数据项,并且还可以支持选择和编辑项目。可使用模板对 DataList 中列表项的内容和布局进行定义。他的最大的特点是可以分列显示数据,在.NET的开发中经常会使用DataList、Repeater、DataGrid来显示、编辑数据。在使用DataList(而不是用DataGrid)的时候,主要是利用他的允许“分列”的功能。但在使用的时候对该控件有一点的不满意:不能象DataGrid一样支持“分页”。 网上有很多朋友讨论这个问题,提出的解决办法不外乎有两种: 1. 使用数据库实现:在数据库检索的记录中实现分页,如利用MSSQL Server的TSQL语句来实现,如Select Top 100 * from t_tab。 2. 使用DataSet来实现:将从数据库中检索的数据填充到DataSet中时,使用DbDataAdapter.Fill( DataSet dataSet,int startRecord, int maxRecords, string srcTable))方法将数据指定范围的数据填充到DataSet中,来实现分页处理。 以上这两中方法都可以达到对DataList的分页,但存在的问题也是非常明显的: 1. 第一种方法限制了数据库的种类,必须挑选少数支持在SQL语句中实现分页的数据库系统。 2. 第二种方法将DataList的数据源限制为只能是DataSet/DataTable。 3. 这两种方法都限制了数据的格式,将DataList的数据源的使用范围缩小了。如对数组、哈希表及用户自定义的实现System.Collection.IEnumerable接口对象等等都不能支持或很容易的支持。 4. 都不能建立一个通用的分页DataList控件,对不同的数据和应用都需要重新写不同的分页实现程序。 本文介绍了一种扩展DataList控件的简单有效的方法,实现了一个可分页的DataList控件――PagerDataList控件。 总体思路 为了实现PagerDataList,我们可以参考DataGrid,通过比较DataList和DataGrid的不同,仿照DataGrid来实现PagerDataList的各项功能。 实现的总体思路是: 1. 创建继承DataList的控件PagerDataList,保留DataList的全部功能 2. 覆盖DataList的属性DataSource,在新的DataSource中对数据实现分页 3. 实现分页的相关属性 4. 实现分页按钮的显示 5. 实现分页的相关事件 下面分别讲述各个步骤的实现方法和部分源代码。 覆盖DataSource属性 为什么想到要覆盖DataSource属性呢?DataList和DataGrid都是从BaseDataList继承下来的,通过分析DataGrid,我们可以发现两者的区别:DataGrid修改了BaseDataList的DataSource属性,使用的是封装了的PagedDataSource对象。先让我们来看看PagedDataSource到底为何物? PagedDataSource 类封装 DataGrid 控件的属性,这些属性使 DataGrid 可以执行分页。此类主要由控件开发人员使用。此类使用最适用的接口枚举属于当前页的数据。如果基础数据源支持索引访问(如 System.Array 和 System.Collections.IList),则此类使用基础数据源。否则,它使用 System.Collections.IEnumerable。PagedDataSource类的部分成员见下表: PagedDataSource的成员(部分) 公共构造函数 PagedDataSource构造函数 初始化 PagedDataSource 类的新实例。 公共属性 AllowCustomPaging 获取或设置指示是否启用自定义分页的值。 AllowPaging 获取或设置指示是否启用分页的值。 Count 获取要从数据源使用的项数。 CurrentPageIndex 获取或设置当前页的索引。 DataSource 获取或设置数据源。 DataSourceCount 获取数据源中的项数。 FirstIndexInPage 获取页中的第一个索引。 IsCustomPagingEnabled 获取一个值,该值指示是否启用自定义分页。 IsFirstPage 获取一个值,该值指示当前页是否是首页。 IsLastPage 获取一个值,该值指示当前页是否是最后一页。 IsPagingEnabled 获取一个值,该值指示是否启用分页。 IsReadOnly 获取一个值,该值指示数据源是否是只读的。 IsSynchronized 获取一个值,该值指示是否同步对数据源的访问(线程安全)。 PageCount 获取显示数据源中的所有项所需要的总页数 PageSize 获取或设置要在单页上显示的项数。 SyncRoot 获取可用于同步集合访问的对象。 VirtualCount 获取或设置在使用自定义分页时数据源中的实际项数。 公共方法 CopyTo 从 System.Array 中的指定索引位置开始,将数据源中的所有项复制到指定的 System.Array。 Equals(从 Object 继承) 已重载。确定两个 Object 实例是否相等。 GetEnumerator 返回 System.Collections.IEnumerator 实现的对象,该对象包含数据源中的所有项。 GetHashCode(从 Object 继承) 用作特定类型的哈希函数,适合在哈希算法和数据结构(如哈希表)中使用。 GetItemProperties 返回System.ComponentModel.PropertyDescriptorCollection,它表示用于绑定数据的每项的属性。 GetListName 返回列表的名称。此方法不适用于此类。 GetType(从 Object 继承) 获取当前实例的 Type。 ToString(从 Object 继承) 返回表示当前 Object 的 String。 利用PagedDataSource,我们可以方便的实现分页,而不用考虑数据库等等数据源形式。通过覆盖DataList的DataSource属性,这样就可以与原来的DataSource保持相同的接口,又可以实现分页功能。 DataList.DataSource是Object对象,而PagedDataSource.DataSource实现的是System.Collections.IEnumerable接口,因此我们需要调整PagerDataList的DataSource为System.Collections.IEnumerable接口,这样PagerDataList就可以使用许多种数据源了。 覆盖DataSource属性的源代码如下: 程序代码: /// <summary> /// 覆盖,设置分页的数据源 /// </summary> /// <returns></returns> public new System.Collections.IEnumerable DataSource { set { //创建PagedDataSource对象,由该对象来负责数据的分页 System.Web.UI.WebControls.PagedDataSource pgDs = new System.Web.UI.WebControls.PagedDataSource(); //如果页长大于0表示需要分页,否则不分页 pgDs.AllowPaging = this.PageSize>0; //设置分页的页长 pgDs.PageSize = this.PageSize; //设置当前需要显示的页码 pgDs.CurrentPageIndex = this.CurrentPageIndex; //将数据源传递给PagedDataSource pgDs.DataSource = value; //将实现了数据源分页的PagedDataSource作为DataList的数据源,这样就实现了DataList的数据分页显示 base.DataSource = pgDs; //设置总页数 this.PageCount = pgDs.PageCount; //计算当前按钮组的序号 this.PageButtonGroupCount = this.PageCount / this.PageButtonGroupSize; if(this.PageCount % this.PageButtonGroupSize >0) this.PageButtonGroupCount ++; } } ![]() 注意:在这里我们只实现了DataSource属性的set方法,没有实现get方法。代码中的粗体是实现分页的关键。 实现分页按钮的显示 分页按钮的显示相对简单,主要是根据PageMode的设置、当前显示的页码和总页数来确定分页按钮的显示方式、显示的多少,这是通过重写RenderContents方法来实现的。实现分页按钮的源代码大体如下: 程序代码: /// <summary> /// 创建页码按钮 /// </summary> protected virtual void buildPageButton(HtmlTextWriter output) { ………………………. if(this.PageMode == PagerMode.NextPrev) { //前后按钮模式 //显示前一页按钮 if(this.CurrentPageIndex >0 ) { output.AddAttribute(HtmlTextWriterAttribute.Href,this.Page.GetPostBackClientHyperlink(this,this.CurrentPageIndex.ToString())); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write(this.PrevPageText); output.RenderEndTag(); output.Write(" "); } //显示后一页按钮 if( this.CurrentPageIndex < this.PageCount - 1) { output.AddAttribute(HtmlTextWriterAttribute.Href,this.Page.GetPostBackClientHyperlink(this,(this.CurrentPageIndex+2).ToString())); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write(this.NextPageText); output.RenderEndTag(); } } else { //页码按钮模式 //显示前一按钮组 if(this.CurrentPageButtonGroupIndex >0 ) { output.AddAttribute(HtmlTextWriterAttribute.Href,this.Page.GetPostBackClientHyperlink(this,((this.CurrentPageButtonGroupIndex-1) * this.PageButtonGroupSize+1).ToString())); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write("..."); output.RenderEndTag(); output.Write(" "); } //页码 int begin = CurrentPageButtonGroupIndex * this.PageButtonGroupSize+1; int end = this.PageCount; int pgSize = this.PageButtonGroupSize; int cPindex = this.CurrentPageIndex; for( int i=begin,j=0 ; i <= end && j < pgSize;i++,j++) { if(i-1 == cPindex) { output.RenderBeginTag(HtmlTextWriterTag.Span); output.Write(i.ToString()+" "); output.RenderEndTag(); } else { output.AddAttribute(HtmlTextWriterAttribute.Href,this.Page.GetPostBackClientHyperlink(this,i.ToString())); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write(i.ToString()); output.RenderEndTag(); output.Write(" "); } } //显示后一按钮组 if( this.CurrentPageButtonGroupIndex < this.PageButtonGroupCount-1) { //output.AddAttribute(HtmlTextWriterAttribute.Title,"后一组"); output.AddAttribute(HtmlTextWriterAttribute.Href,this.Page.GetPostBackClientHyperlink(this,((this.CurrentPageButtonGroupIndex+1) * this.PageButtonGroupSize+1).ToString())); output.RenderBeginTag(HtmlTextWriterTag.A); output.Write("..."); output.RenderEndTag(); } } ………………………. } ![]() 实现分页的相关事件 对于实现分页的操作,我们只需要实现换页事件PageIndexChanged,在当前显示页码改变的时候触发该事件即可。实现该事件的代码如下: 程序代码: /// <summary> /// 换页事件 /// </summary> [Category("Event"), Description("换页事件"), Browsable(true) ] public event EventHandler PageIndexChanged; 在用户程序中,通过事件委托机制来实现触发PagerDataList控件的PageIndexChanged事件,执行用户需要的特定操作,一般情况下是修改PagerDataList的CurrentPageIndex属性,用来显示新的一页。标准示例代码如下:(PagerDataList控件的名称为PagerDataListCntrl) //事件委托 private void InitializeComponent() { this.PagerDataListCntrl.PageIndexChanged += new System.EventHandler(this.PagerDataListCntrl_PageIndexChanged); this.Load += new System.EventHandler(this.Page_Load); } //实际的事件处理程序 private void PagerDataListCntrl_PageIndexChanged(object sender, System.EventArgs e) { int pageindex = ((DataGridPageChangedEventArgs)e).NewPageIndex; this.PagerDataListCntrl.CurrentPageIndex = pageindex; this.BindList(); } ![]() 总结 通过继承DataList控件,可以保留DataList的强大功能;通过覆盖DataSource属性来实现对数据的分页处理;这样开发的PagerDataList 服务器控件可以象ASP.NET的标准控件一样使用,而不会有其他一些分页处理带来的限制和弊端;对于做ASP.NET开发的朋友会带来有很大的方便。 |