通用分页控件c#源码

 

关键词 downmoon,c#,分页      

     通用ASP.NET数据分页控件

  对于几乎所有的数据表现Web应用来说,组织好数据的显示方式、避免给用户带来混乱的感觉就是最主 要的目标之一。每个页面显示20条记录当然是可以接受的,但每页显示10000条记录就很容易给用户带来不便了。将数据分成多个页面显示,即对数据进行分 页,是解决此类问题的最常见的办法。

  一、慨述

  ASP.NET本身只提供了一个支持数据分页的控件,即 DataGrid分页控件,不过它比较适合Intranet环境使用,对于Internet环境来说,DataGrid分页控件提供的功能似乎不足以构造 出灵活的Web应用。其中一个原因是,DataGrid控件对Web设计者放置分页控件的位置和分页控件的外观都有限制,例如,DataGrid控件不允 许垂直放置分页控件。另一个能够发挥分页技术优势的控件是Repeater,Web开发者可以利用Repeater控件快速配置数据的显示方式,但分页功 能却需要开发者自己实现。数据源在不断地变化,数据表现方式也千差万别,如果针对这些不断变动的条件分别定制分页控件,显然太浪费时间了,构造一个不限于 特定表现控件的通用分页控件将极大地有利于节省时间。

  一个优秀的通用数据控件不仅提供常规的分页功能,而且还要能够:
  ⑴ 提供“首页”、“上一页”、“下一页”、“末页”分页导航按钮。

   ⑵ 根据数据显示情况调整自身的状态,即具有数据敏感性。如果分页控件被设置成每页显示10个记录,但实际上只有9个记录,那么分页控件不应该显示出来;在数 据分成多页显示的情况下,第一个页面的“首页”、“上一页”按钮不应显示出来,最后一个页面的“下一页”、“末页”按钮也不应该显示出来。

  ⑶ 不能依赖于特定的数据显示控件。

  ⑷ 具有适应各种现有、将有数据源的能力。

  ⑸ 应当能够方便地配置显示方式,轻松地集成到各种应用之中。

  ⑹ 当分页就绪时,提醒其他控件。

  ⑺ 即使是缺乏经验的Web设计者,也要能够毫无困难地使用。

  ⑻ 提供有关分页信息的属性数据。

  目前市场上已经有一些提供上述功能的商业性控件,不过都价格不菲。对于许多开发者来说,自己构造一个通用的分页控件是最理想的选择。

  图一显示了本文通用分页控件的运行界面,其中用于显示的控件是一个Repeater控件。分页控件由两类部件构成:四个导航按钮,一组页面编号链接。



用户可以方便地改换显示控件、改变分页控件本身的外观。例如,在图一中,和分页控件协作的显示控件可发换成一个DataGrid控件,页面编号链接和四个导航按钮分两行显示。

ASP.NET 支持创建定制Web控件的三种方式:用户控件,复合控件,自定义控件。第三种控件即自定义控件的名称很容易引起误解。实际上,所有这三种控件都应该算是自 定义控件。复合控件和微软所谓的自定义控件的不同之处在于,前者要用到CreateChildControls()方法, CreateChildControls()方法允许控件根据某些事件重新绘制自身。对于本文的通用分页器,我们将使用复合控件。

  下面的UML序列图概括了通用分页控件的一般机制。




虽然我们的目标是让通用分页控件不依赖于表现数据的控件,但很显然,总得有某种方法让分页控件访问数据。每一个从Control类继承的控件都提供一个 DataBinding事件。我们把分页器本身注册成DataBinding事件的监听器,分页器就可以获知数据的情况并修改数据。由于所有从 Control类继承的控件都有这个DataBinding事件,所以分页器控件达到了不依赖于特定数据表现控件的目标——换句话说,分页器控件可以绑定 到所有从Control类派生的控件,即它能够绑定到几乎所有的Web控件。

 

二、核心功能

  当表现控件触发DataBinding事件,分页控件就可以获取DataSource属性。遗憾的是,微软没有提供所 有数据绑定类实现的接口,诸如IdataSourceProvider之类,而且并非所有从Control或WebControl类继承的控件都有一个 DataSource属性,因此向上定型成Control类没有意义,唯一可行的办法是通过Reflection API直接操作DataSoruce属性。在讨论事件句柄方法之前,应该指出的是,为了注册事件句柄,首先必须获得一个表现控件的引用。分页控件显露了一 个简单的字符串属性BindToControl:

public   string  BindToControl
{
get
{
if (_bindcontrol == null)
throw new NullReferenceException("在使用分页控件之前,请先通过设置BindToControl属性绑定到一个控件。");
return _bindcontrol;}

set{_bindcontrol=value;}
}
 



   这个方法非常重要,所以最好能够抛出一个含义更明确的信息,而不是抛出标准的NullReferenceException异常。在分页控件的 OnInit方法中,我们解析了对表现控件的引用。本例应当用OnInit事件句柄(而不是构造函数)来确保JIT编译的aspx页面已经设置了 BindToControl。

protected   override   void  OnInit(EventArgs e)
{
_boundcontrol 
= Parent.FindControl(BindToControl);
BoundControl.DataBinding 
+= new EventHandler(BoundControl_DataBound);
base.OnInit(E);
...
}
 
 



   搜索表现控件的操作通过搜索分页控件的Parent控件完成,在这里,Parent就是页面本身。按照这种方式使用Parent比较危险,举例来说,如 果分页控件嵌入到了另一个控件之中,例如嵌入到了Table控件之中,则Parent引用实际上将是一个对Table控件的引用。由于 FindControl方法只搜索当前的控件集合,除非表现控件就在该集合之中,否则不可能搜索到。一种比较安全的方法是递归地搜索各个控件集合,直至找 到目标控件为止。
  找到BoundControl之后,我们将分页控件注册成为DataBinding事件的监听器。由于分页控件要操作数 据源,所以该事件句柄应当是调用链中的最后一个,这一点很重要。不过,只要表现控件在OnInit事件句柄中注册DataBinding的事件句柄(默认 行为),分页控件操作数据源时就不会出现问题。

  DataBound事件句柄负责获取表现控件的DataSource属性。

private   void  BoundControl_DataBound( object  sender,System.EventArgs e)
{
if (HasParentControlCalledDataBinding) return;
Type type 
= sender.GetType();
_datasource 
= type.GetProperty("DataSource");
if (_datasource == null)
throw new NotSupportedException("分页控件要求表现控件必需包含一个DataSource。");
object data = _datasource.GetGetMethod().Invoke(sender,null);
_builder 
= Adapters[data.GetType()];
if (_builder == null)
throw new NullReferenceException("没有安装适当的适配器来处理下面的数据源类型:"+data.GetType());
_builder.Source 
= data;

ApplyDataSensitivityRules();
BindParent();
RaiseEvent(DataUpdate,
this);
}
 




   在DataBound中,我们尝试通过Reflection API获得DataSource属性,然后返回实际数据源的一个引用。现在虽然已经获知了数据源,但分页控件还必须知道如何操作该数据源。为了让分页控件 不依赖于特定的表现控件,问题复杂了很多。不过,如果让分页控件依赖于特定的数据源,那就背离了设计一个灵活的分页控件的目标。我们要通过一个接插式的体 系结构来确保分页控件能够处理各种数据源,无论是.NET提供的数据源,还是自定义的数据源。

  为了提供一个健壮的、可伸缩的接插式体系结构,我们将利用[GoF] Builder模式构造出一个解决方案。





IDataSourceAdapter接口定义了分页控件操作数据所需的最基本的元素,相当于“插头”。

publicinterface IDataSourceAdapter
{
int TotalCount{get;}
object GetPagedData(int start,int end);
}
 



   TotalCount属性返回在处理数据之前数据源所包含元素的总数,而GetPagedData方法返回原始数据的一个子集,例如:假设数据源是一个 包含20个元素的数组,分页控件将数据显示成每页10个元素,则第一页的元素子集是数组元素0-9,第二页的元素子集是数组元素10-19。 DataViewAdapter提供了一个DataView类型的插头:


internal   class  DataViewAdapter:IDataSourceAdapter
{
private DataView _view;

internal DataViewAdapter(DataView view)
{
_view 
= view;
}

public int TotalCount
{
get{return (_view == null? 0 : _view.Table.Rows.Count;}
}

public object GetPagedData(int start, int end)
{
DataTable table 
= _view.Table.Clone();

for (int i = start;i<=end && i<= TotalCount;i++)
{
table.ImportRow(_view[i
-1].Row);
}

return table;
}

}
 



   DataViewAdapter实现了IDataSourceAdapter的GetPagedData方法,该GetPagedData克隆原始的 DataTable,将原始DataTable中的数据导入到新的DataTable。该类的可见性有意地设置成internal,目的是为了向Web开 发者隐藏实现细节,进而通过Builder类提供一个更简单的接口。

public   abstract   class  AdapterBuilder
{
private object _source;

private void CheckForNull()
{
if (_source == nullthrow new NullReferenceException("必须提供一个合法的数据源");
}

public virtual object Source
{
get
{
CheckForNull();
return _source;}

set
{
_source 
= value;
CheckForNull();
}

}

public abstract IDataSourceAdapter Adapter{get;}
}


   AdapterBuilder抽象类为IdataSourceAdapter类型提供了一个更容易管理的接口,由于提高了抽象程度,我们不必再直接使用 IdataSourceAdapter,同时AdapterBuilder还提供了在分页数据之前执行预处理的指令。另外,该Builder还使得实际的 实现类,例如DataViewAdapter,对分页控件的用户透明:

public   class  DataTableAdapterBuilder:AdapterBuilder
{
private DataViewAdapter _adapter;

private DataViewAdapter ViewAdapter
{
get
{
if (_adapter == null)
{
DataTable table 
= (DataTable)Source;
_adapter 
= new DataViewAdapter(table.DefaultView);
}

return _adapter;
}

}

public override IDataSourceAdapter Adapter
{
get
{
return ViewAdapter;
}

}

}

public   class  DataViewAdapterBuilder:AdapterBuilder
{
private DataViewAdapter _adapter;

private DataViewAdapter ViewAdapter
{
get
// 延迟实例化
if (_adapter == null)
{
_adapter 
= new DataViewAdapter((DataView)Source);
}

return _adapter;
}

}

public override IDataSourceAdapter Adapter
{
get{return ViewAdapter;}
}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值