在分页控件中,寻找适当builder类的操作由一个类型安全的集合完成。
public class adaptercollection:dictionarybase
{
private string getkey(type key)
{
return key.fullname;
}
public adaptercollection() {}
publicvoid add(type key,adapterbuilder value)
{
dictionary.add(getkey(key),value);
}
publicbool contains(type key)
{
return dictionary.contains(getkey(key));
}
publicvoid remove(type key)
{
dictionary.remove(getkey(key));
}
public adapterbuilder this[type key]
{
get{return (adapterbuilder)dictionary[getkey(key)];}
set{dictionary[getkey(key)]=value;}
}
}
adaptercollection依赖于datasource类型,datasource通过boundcontrol_databound巧妙地引 入。这里使用的索引键是type.fullname方法,确保了每一种类型索引键的唯一性,同时这也把保证每一种类型只有一个builder的责任赋予了 adaptercollection。将builder查找加入boundcontrol_databound方法,结果如下:
public adaptercollection adapters
{
get{return _adapters;}
}
private bool hasparentcontrolcalleddatabinding
{
get{return _builder != null;}
}
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);
}
boundcontrol_databound方法利用hasparentcontrolcalleddatabinding检查是否已经创建了builder,如果是,则不再执行寻找适当builder的操作。adapters表的初始化在构造函数中完成:
public pager()
{
selectedpager=new system.web.ui.webcontrols.style();
unselectedpager = new system.web.ui.webcontrols.style();
_adapters = new adaptercollection();
_adapters.add(typeof(datatable),new datatableadapterbuilder());
_adapters.add(typeof(dataview),new dataviewadapterbuilder());
}
最后一个要实现的方法是bindparent,用来处理和返回数据。
private void bindparent()
{
_datasource.getsetmethod().invoke(boundcontrol,
new object[]{_builder.adapter.getpageddata(startrow,resultstoshow*currentpage)});
}
这个方法很简单,因为数据处理实际上是由adapter完成的。这一过程结束后,我们还要用一次reflection api,不过这一次是设置表现控件的datasource属性。 三、界面设计
至此为止,分页控件的核心功能已经差不多实现,不过如果缺少适当的表现方式,分页控件不会很有用。
为了有效地将表现方式与程序逻辑分离,最好的办法莫过于使用模板,或者说得更具体一点,使用itemplate接口。实际上,微软清楚地了解模板的强大 功能,几乎每一个地方都用到了模板,甚至页面解析器本身也不例外。遗憾的是,模板并不象有些人认为的那样是一个简单的概念,需要花些时间才能真正掌握它的 精髓,好在这方面的资料比较多,所以这里就不再赘述了。返回来看分页控件,它有四个按钮:首页,前一页,后一页,末页,当然另外还有各个页面的编号。四个 导航按钮选自imagebutton类,而不是linkbutton类,从专业的web设计角度来看,图形按钮显然要比单调的链接更有用一些。
public imagebutton firstbutton{get {return first;}}
public imagebutton lastbutton{get {return last;}}
public imagebutton previousbutton{get {return previous;}}
public imagebutton nextbutton{get {return next;}}
页面编号是动态构造的,这是因为它们依赖于数据源中记录数量的多少、每个页面显示的记录数量。页面编号将加入到一个panel,web设计者可以通过 panel来指定要在哪里显示页面编号。有关创建页面编号的过程稍后再详细讨论,现在我们需要为分页控件提供一个模板,使得用户能够定制分页控件的外观。
[template container(typeof(layoutcontainer))]
public itemplate layout
{
get{return (_layout;}
set{_layout =value;}
}
public class layoutcontainer:control,inamingcontainer
{
public layoutcontainer()
{this.id = "page";}
}
layoutcontainer类为模板提供了一个容器。一般而言,在模板容器中加入一个定制id总是不会错的,它将避免处理事件和进行页面调用时出现的问题。下面的uml图描述了分页控件的表现机制。

创建模板的第一步是在aspx页面中定义布局:
<layout>
<asp:imagebutton id="first" runat="server" imageurl="play2l_dis.gif"
alternatetext="首页"></asp:imagebutton>
<asp:imagebutton id="previous" runat="server" imageurl="play2l.gif"
alternatetext="上一页"></asp:imagebutton>
<asp:imagebutton id="next" runat="server" imageurl="play2.gif"
alternatetext="下一页"></asp:imagebutton>
<asp:imagebutton id="last" runat="server" imageurl="play2_dis.gif"
alternatetext="末页"></asp:imagebutton>
<asp:panel id="pager" runat="server"></asp:panel>
</layout>
这个布局例子不包含任何格式元素,例如表格等,实际应用当然可以(而且应该)加入格式元素,请参见稍后的更多说明。
itemplate接口只提供了一个方法instantiatein,它解析模板并绑定容器。
private void instantiatetemplate()
{
_container = new layoutcontainer();
layout.instantiatein(_container);
first = (imagebutton)_container.findcontrol("first");
previous = (imagebutton)_container.findcontrol("previous");
next = (imagebutton)_container.findcontrol("next");
last = (imagebutton)_container.findcontrol("last");
holder = (panel)_container.findcontrol("pager");
this.first.click += new system.web.ui.imageclickeventhandler(this.first_click);
this.last.click += new system.web.ui.imageclickeventhandler(this.last_click);
this.next.click += new system.web.ui.imageclickeventhandler(this.next_click);
this.previous.click += new system.web.ui.imageclickeventhandler(this.previous_click);
}
控件的instatiatetemplate方法要做的第一件事情是实例化模板,即调用layout.instantiatein (_container)。容器其实也是一种控件,用法也和其他控件相似。instantiatetemplate方法利用这一特点寻找四个导航按钮,以 及用来容纳页面编号的panel。导航按钮通过它们的id找到,这是对分页控件的一点小小的限制:导航按钮必须有规定的id,分别是first、 previous、next、last,另外,panel的id必须是pager,否则就会找不到。遗憾的是,就我们选定的表现机制而言,这似乎是较好的 处理方式了;但可以相信的是,只要提供适当的说明文档,这一小小限制不会带来什么问题。另外一种可选择使用的办法是:让每一个按钮从 imagebutton类继承,从而也就定义了一个新的类型;由于每一个按钮是一种不同的类型,在容器中可以实现一个递归搜索来寻找各种特定的按钮,从而 不必再用到按钮的id属性。
找到四个按钮之后,再把适当的事件句柄绑定到这些按钮。在这里必须做一个重要的决定,即何时调用 instantiatetemplate。一般地,这类方法应当在createchildcontrols方法中调用,因为 createchildcontrols方法的主要用途就是这一类创建子控件的任务。由于分页控件永远不会修改其子控件,所以它不需要 createchildcontrols提供的功能来根据某些事件修改显示状态。显示子控件的速度总是越快越好,因此调用 instantiatetemplate方法的比较理想的位置是在oninit事件中。
protected override void oninit(eventargs e)
{
_boundcontrol = parent.findcontrol(bindtocontrol);
boundcontrol.databinding += new eventhandler(boundcontrol_databound);
instantiatetemplate();
controls.add(_container);
base.oninit();
}
oninit方法除了调用instantiatetemplate方法,它的另一个重要任务是将容器加入分页控件。如果不将容器加入到分页器的控件集合,由于render方法永远不会被调用,所以模板就不可能显示出来。
模板还可以用编程的方式通过实现itemplate接口定义,这一特性除了可作为提高灵活性的措施之外,还可以提供一个默认的模板,以便在用户没有通过aspx页面提供模板时使用。
文章整理:站长天空 网址:http://www.z6688.com/
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




