Asp.Net 可定制分页用户控件

Asp.Net 可定制分页用户控件

今天研究了以下Asp.Net 可定制分页,用起来很爽,下面给大家介绍下。

借助 Asp.Net 提供的数据绑定控件,我们无需太多的代码,甚至不需要代码,只要在 VS2005 中拖拽几下控件,进行一些属性的设置,便可以实现在Asp时代需要做大量工作才能够实现的分页功能。但在实际的应用中,尤其是在Web站点程序中,我们经常需要更加丰富的用户界面,而类似DataList或者 GridView 这样的数据控件往往不能或者很难满足我们的要求。此时,我们常常求助于 Repeater 控件,这样我们依旧会面临分页及其显示的问题。

本文不是讲述如何进行数据库分页,而将注意力集中在如何实现可定制地 获取页码、获取路径、显示分页链接,并且通过构建一个用户控件来实现代码重用上。如果你是一个初学者,你可以借鉴一下我的实现方式;如果你已经是一位高手,不妨提出设计的不足和改进意见。

本文是以接口的实现方式作为讲述,这是因为我写这篇文章的时候使用的是接口,但我后来又提供了一种更好的使用继承的方式来实现,我提供了两个版本的代码下载,你可以相互对比着参考。

控件组成

为了能迅速提起大家的兴趣,可以先点击这个链接,看看实际的效果:

http://www.tracefact.net/Demo/Pager/Default.aspx

IUrlManager 接口

想一想如果你在设计一个可重用的分页用户控件,你面临的问题是什么?每个人获取页码的方式都不同,例如,你的站点URL可能是类似这样的 Default.aspx?page=1 ,而另外一个站点的URL 是这样的 Default.aspx?p=1。更有一些可能根本不使用 QueryString 来获取页码,它们的URL可能是这样的 Default-1.aspx、Default-2.aspx 等等。获取页码的方式不同,根据页码产生链接地址的方法自然也不相同。按照封装变化的思想,我们应该将这变化的部分取出来,建一个 IUrlManager 接口:

public interface IUrlManager

{

int CurrentPageIndex{ get; } // 当前页码

string GetPageUrl(int pageIndex); // 根据 页码 获取页面路径

}

而实际上,当前页码不应该大于总页数,所以获取当前页CurrentPageIndex属性需要能得知 总页数,而总页数通常是由 记录数 和 分页大小计算得出,这个接口实际上应该是这样:

public interface IUrlManager

{

int CurrentPageIndex{ get; } // 当前页码

string GetPageUrl(int pageIndex); // 根据 页码 获取页面路径

int PageCount { get; } // 总页数

int RecordCount { get; } // 记录总数

int PageSize { get; } // 分页大小

}

要求在 IUrlManager里实现总页数PageCount属性可能有点奇怪,但仔细想一想就明白了:

控件本身需要知道总共有多少页,然后才能判断显示多少链接数。我们的用户控件部分仅仅是进行一系列链接的显示,它仅需要知道 当前页码、总页码、以及链接的URL 就足够了

因为 当前页码 应该小于等于 总页码,所以获取当前页码 CurrentPageIndex 也需要知道 总页码。

而为什么要实现 RecordSize 和 PageSize 属性是一个值得思考的地方:

总页码 是根据 记录数 和 分页大小 算得的,所以对于实现 IUrlManager 接口的类,我们总是需要提供 记录数 和 分页大小,何不简单地提供一个属性来对它们进行访问。而其他的地方(比如某个方法)有可能会需要这两个值,此时我们可以直接将 IUrlManager 作为参数传进去。

问题是:如果这个接口仅仅是用于 分页控件,那么实现 RecordSize 和 PageSize 是不必要的,我们也不应该在控件上设置 RecordCount、PageSize。这里的粒度可能大了。

DefaultUrlManager 类

现在 所有获取页码 及 根据页码获取路径 的逻辑都可以放在实现了这个接口的类中。如果你想使用这个控件,你需要提供一个实现了IUrlManger接口的类。为了使控件立即可用,我在这里提供了一个默认实现,我管它叫做 DefaultUrlManger。它通过Request.QueryString获取页码,并默认以"Page"作为参数。

public class DefaultUrlManager : IUrlManager {

private HttpContext context;

private Regex reg;

private string queryParam; // QueryString 参数名称

private int currentPageIndex; // 当前页

private int pageCount; // 总页数

private int recordCount; // 记录总数

private int pageSize; // 分页大小

// 构造函数

public DefaultUrlManager(int recordCount, int pageSize, string queryParam)

{

if (recordCount < 0)

throw new ArgumentOutOfRangeException("recordCount 应该大于等于 0 !");

if (pageSize <= 0)

throw new ArgumentOutOfRangeException("pageSize 应该大于 0 !");

if (string.IsNullOrEmpty(queryParam))

throw new ArgumentNullException("queryParam 不能为空!");

// 设置私有变量

this.recordCount = recordCount;

this.pageSize = pageSize;

this.queryParam = queryParam;

context = HttpContext.Current;

string pattern = @"(?<r1>[?&]" + queryParam + @"=)[^&]*";

reg = new Regex(pattern, RegexOptions.IgnoreCase);

// 如果记录数为0,至少也显示一页

if (recordCount == 0) {

currentPageIndex = 1;

pageCount = 1;

} else {

// 设置总页数

double recordCount2 = Convert.ToDouble(recordCount);

double pageSize2 = Convert.ToDouble(pageSize);

pageCount = Convert.ToInt32(Math.Ceiling(recordCount2 / pageSize2));

// 设置当前页码

string queryIndex = context.Request.QueryString[queryParam];

if (string.IsNullOrEmpty(queryIndex))

currentPageIndex = 1;

else {

try {

currentPageIndex = Math.Abs(int.Parse(queryIndex));

if (currentPageIndex == 0)

currentPageIndex = 1;

if (currentPageIndex > pageCount)

currentPageIndex = pageCount;

}

catch {

currentPageIndex = 1;

}

}

}

}

// 默认以 "Page" 作为 QueryString 的参数

public DefaultUrlManager(int recordCount, int pageSize) : this(recordCount, pageSize, "Page") { }

// 默认以 10 作为 PageSize

public DefaultUrlManager(int recordCount, string queryParam) : this(recordCount, 10, queryParam) { }

// 默认以 "Page" 作为 QueryString 的参数,以 10 作为分页大小

public DefaultUrlManager(int recordCount) : this(recordCount, 10) { }

public string GetPageUrl(int pageIndex) {

string pageUrl = context.Request.RawUrl;

// 如果找到匹配,也就是URL中含有类似 ?page=3 或者 &page=4 这样的字符串

// 则对后面的数值进行替换

if (reg.IsMatch(pageUrl)) {

pageUrl = reg.Replace(pageUrl, "${r1}" + pageIndex.ToString());

} else {

string queryString = context.Request.Url.Query;

if (string.IsNullOrEmpty(queryString))

pageUrl += "?" + queryParam + "=" + pageIndex.ToString();

else

pageUrl += "&" + queryParam + "=" + pageIndex.ToString();

}

return pageUrl;

}

public int CurrentPageIndex

{

get { return currentPageIndex; }

}

public int PageCount

{

get { return pageCount; }

}

public int RecordCount

{

get { return recordCount; }

}

public int PageSize

{

get { return pageSize; }

}

}

Pager.ascx 文件

由于我们的链接是动态生成的,所以我们大部分的代码都会存在于 Pager.ascx.cs 中,在Pager.ascx 文件中,我们只提供一个装链接的容器:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Pager.ascx.cs" Inherits="CustomPager" %>

<asp:Panel runat="server" CssClass="Pager"></asp:Panel>

Pager.ascx.cs 文件

我们对于页面的显示的主要实现都放置在了这个文件中,我在文件中做了大量注释,这里就不再文字描述了:

using System;

// ... 略

public partial class CustomPager : System.Web.UI.UserControl

{

private int currentPage; // 当前页的页码

private int previousPageCount = 5; // 当前页之前可以显示的最多条目数,大于此条目的将被隐藏

private int afterPageCount = 4; // 当前页之后可以显示的最多条目数,大于此条目的将被隐藏

private int pageCount; // 总页数

private bool showPrevious = false; // 是否显示 上一页、第一页 的链接

private bool showNext = false; // 是否显示 下一页、最末页 的链接

private int startPage; // 显示的第一页 的 页码

private int endPage; // 显示的最末页 的 页码

private IUrlManager urlManager; // 设置接口

public int PreviousPageCount

{

get { return previousPageCount; }

set { previousPageCount = value; }

}

public int AfterPageCount

{

get { return afterPageCount; }

set { afterPageCount = value; }

}

public IUrlManager UrlManager

{

get { return urlManager; }

set { urlManager = value; }

}

// 设置 pnPager Div 的 CssClass 属性,通过它来为控件定义样式

public string CssClass {

set { pnPager.CssClass = value; }

get { return pnPager.CssClass; }

}

private void RenderPager()

{

if (urlManager == null)

throw new ArgumentNullException("IUrlManager 不能为 Null");

// 获取当前页

currentPage = urlManager.CurrentPageIndex;

// 获取总页数

pageCount = urlManager.PageCount;

SetStartPage();

SetEndPage();

HyperLink link;

// 循环打印链接

for (int i = startPage; i <= endPage; i++)

{

if (showPrevious) // 如果需要显示前一页、第一页链接

AddPreviousLink(urlManager);

link = new HyperLink();

if (i == currentPage)

link.CssClass = "CurrentPage";

link.Text = i.ToString();

link.NavigateUrl = urlManager.GetPageUrl(i);

pnPager.Controls.Add(link);

if (i == endPage && showNext) // 如果需要显示 下一页、最末页 链接

AddNextLink(urlManager);

}

Label lbCount = new Label();

lbCount.Text = String.Format(" ( 第<b>{0}</b>页/共<b>{1}</b>页 )", currentPage, pageCount);

pnPager.Controls.Add(lbCount);

}

// 添加“第一页”,“上一页”的连接

private void AddPreviousLink(IUrlManager urlManager)

{

HyperLink first = new HyperLink();

first.CssClass = "PagerIcon";

first.Text = "&lt;&lt;";

first.ToolTip = "第一页";

first.NavigateUrl = urlManager.GetPageUrl(1);

pnPager.Controls.Add(first);

HyperLink previous = new HyperLink();

previous.CssClass = "PagerIcon";

previous.Text = "&lt;";

previous.ToolTip = "上一页";

previous.NavigateUrl = urlManager.GetPageUrl(currentPage - 1);

pnPager.Controls.Add(previous);

showPrevious = false; // 只显示一次

}

// 添加 “下一页”、“最末页” 的链接

private void AddNextLink(IUrlManager urlManager)

{

HyperLink next = new HyperLink();

next.CssClass = "PagerIcon";

next.Text = "&gt;";

next.ToolTip = "下一页";

next.NavigateUrl = urlManager.GetPageUrl(currentPage + 1);

pnPager.Controls.Add(next);

HyperLink last = new HyperLink();

last.CssClass = "PagerIcon";

last.Text = "&gt;&gt;";

last.ToolTip = "最末页";

last.NavigateUrl = urlManager.GetPageUrl(pageCount);

pnPager.Controls.Add(last);

showNext = false; // 可有可无,程序会跳出循环

}

// 根据当前页,当前页之前可以显示的页数,算得从第几页开始进行显示

private void SetStartPage()

{

// 如果当前页小于 它前面所可以显示的条目数,那么显示第一页就是实际的第一页

if (currentPage <= previousPageCount)

{

startPage = 1;

} else // 这种情况下 currentPage 前面总是能显示完,要根据后面的长短确定是不是前面应该多显示

{

if(currentPage > previousPageCount+1)

showPrevious = true;

int linkLength = (pageCount - currentPage + 1) + previousPageCount;

int startPage = currentPage - previousPageCount;

while (linkLength < previousPageCount + afterPageCount + 1 && startPage > 1)

{

linkLength++;

startPage--;

}

this.startPage = startPage;

}

}

// 根据CurrentPage、总页数、当前页之后长度 算得显示的最末页是 第几页

private void SetEndPage()

{

// 如果当前页加上它之后可以显示的页数 大于 总页数,那么显示的最末页就是实际的最末页

if (currentPage + afterPageCount >= pageCount)

{

endPage = pageCount;

} else // 这种情况下 currentPage后面的总是可以显示完,要根据前面的长短确定是不是后面应该多显示

{

int linkLength = (currentPage - startPage + 1) + afterPageCount;

int endPage = currentPage + afterPageCount;

while (linkLength < previousPageCount + afterPageCount + 1 && endPage < pageCount)

{

linkLength++;

endPage++;

}

if (endPage < pageCount)

showNext = true;

this.endPage = endPage;

}

}

protected void Page_Load(object sender, EventArgs e)

{

RenderPager();

}

}

设置样式

控件没有提供任何的样式控制,对于样式,你唯一能做的就是通过它的CssClass属性来设置由Panel控件生成的Div的Class,然后利用这个Class编写样式表来控制显示。如果有必要,你还可以通过利用PagerIcon这个Css类来控制“上一页”、“下一页”、“第一页”、“最末页”的显示;通过 CurrentPage 这个Css类来控制 当前页 的显示。由此,在所有使用这个控件的页面,你都应该包含有控制控件样式的样式表。

这里,我提供了一个默认的实现(在你不设置控件的CssClass属性的时候,默认为Pager):

.Pager a{

display:block;

border:1px solid #ccc;

float:left;

padding:4px 5px;

text-decoration:none;

text-align:center;

margin:0 1px;

}

.Pager a.CurrentPage{

background:#999;

color:#eee;

}

.Pager span{

position:relative;

top:6px;

}

控件的使用

好了,现在一切都OK了,让我们看看控件如何使用。我们以一种最简单的方式开始,再以一种最复杂的方式开始。

声明式使用

直接拖拽控件到页面上(比如Default.aspx),然后在CodeBehind 中设置一下它的RecordCount值就可以了。aspx页面代码如下:

// ... 略

<%@ Register Src="~/UserControl/CustomPager.ascx" TagName="CustomPager" TagPrefix="uc1" %>

// ... 略

<uc1:CustomPager runat="server" />

代码后置文件的内容如下(片段):

CustomPager1.UrlManager = new DefaultUrlManager(337);

动态创建式使用

我们也可以编写后置代码,然后来动态地使用它。为了看一下它如何配合Repeater控件使用,我们再在页面上拖一个Repeater控件,采用默认的命名Repeater1; GetList()方法返回一个列表,我们将对这个列表进行分页显示(下载 完整代码):

protected void Page_Load(object sender, EventArgs e)

{

if (!IsPostBack) {

// 获取数据

List<DemoObj> list = GetList();

// 动态创建的使用方式

CustomPager pager = (CustomPager)LoadControl(@"~/UserControl/CustomPager.ascx");

// 设置根据Request.QueryString获取页码的参数"P",分页大小 7

DefaultUrlManager manager = new DefaultUrlManager(list.Count, 7, "P");

// 如果你实现了自己的IUrlManager接口,这里可能是这样:

// pager.UrlManager = new YourUrlManger(133);

pager.UrlManager = manager;

pager.CssClass = "GreenStyle"; // 设置颜色

pager.PreviousPageCount = 3; // 设置当前页之前显示的最大链接数

pager.AfterPageCount = 3; // 设置当前也之后可以显示的最大链接数

// 将控件加入到页面上

phHolder.Controls.Add(pager);

int start; // 列表索引的起始位置

int count; // 返回列表的长度

GetPagerParam(manager, out start, out count); // 设置 start, count 参数

// 使用 GetRange() 方法进行分页

Repeater1.DataSource = list.GetRange(start, count);

Repeater1.DataBind();

}

}

注意到这里,由于我们使用了新的CssClass样式,所以你也需要提供基于GreenStyle的样式表,我是这样提供的:

.GreenStyle a{

display:block;

border:1px solid #9cba39;

float:left;

padding:4px 5px;

text-decoration:none;

text-align:center;

margin:0 1px;

color:#9cba39

}

.GreenStyle a.CurrentPage{

background:#C5D985;

color:#fff;

}

.GreenStyle span{

position:relative;

top:6px;

}

.GreenStyle span b{

color:#C33;

}

总结

本文我们实现了Asp.Net中的一个常见的功能,通过一个用户控件来实现数据分页的页面层以达到代码重用的目的。

我们通过实现 IUrlManager 接口来封装了不同情况下根据URL获取页码的方法。在用户控件中我们实现了主要的逻辑,并调用了实现IUrlManager的类的方法。最后,我们还看了如何通过CSS来控制控件的显示。

这个分页用户控件的实现方式还很不完善,因为我使用了接口,所以在 DefaultUrlManager 类中的一些逻辑没有达到代码重用(比如确保当前页位于1和总页数之间 以及 获取分页大小),如果你需要实现自己的 IUrlManager接口,这些代码可能还需要实现一遍。因此,更好的方式可能是使用基类。我已经编写并提供了使用基类的方式来实现的代码,可以作为参考,你也可以自己完善、修改它。

感谢你阅读本文,希望这篇文章能带给你帮助。

欢迎下载:

(1).使用继承实现:

(2).使用接口实现