打造通用ASP.NET数据分页控件(4)

发表于:2007-06-30来源:作者:点击数: 标签:
三、界面设计 至此为止,分页控件的核心功能已经差不多实现,不过如果缺少适当的表现方式,分页控件不会很有用。 为了有效地将表现方式与程序逻辑分离,最好的办法莫过于使用模板,或者说得更具体一点,使用Itemplate接口。实际上,微软清楚地了解模板的强大
     三、界面设计
  
    至此为止,分页控件的核心功能已经差不多实现,不过如果缺少适当的表现方式,分页控件不会很有用。
  
    为了有效地将表现方式与程序逻辑分离,最好的办法莫过于使用模板,或者说得更具体一点,使用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(e);
  }
  
    OnInit方法除了调用InstantiateTemplate方法,它的另一个重要任务是将容器加入分页控件。如果不将容器加入到分页器的控件集合,由于Render方法永远不会被调用,所以模板就不可能显示出来。
  
    模板还可以用编程的方式通过实现Itemplate接口定义,这一特性除了可作为提高灵活性的措施之外,还可以提供一个默认的模板,以便在用户没有通过aspx页面提供模板时使用。
  
  public class DefaultPagerLayout:ITemplate
  {
  private ImageButton Next;
  private ImageButton First;
  private ImageButton Last;
  private ImageButton Previous;
  private Panel Pager;
  
  public DefaultPagerLayout()
  {
  Next = new ImageButton();
  First = new ImageButton();
  Last = new ImageButton();
  Previous = new ImageButton();
  Pager = new Panel();
  
  Next.ID="Next"; Next.AlternateText="下一页";Next.ImageUrl="play2.gif";
  First.ID="First"; First.AlternateText="首页";First.ImageUrl="play2L_dis.gif";
  Last.ID = "Last"; Last.AlternateText ="末页";Last.ImageUrl="play2_dis.gif";
  Previous.ID="Previous"; Previous.AlternateText="上一页";Previous.ImageUrl="play2L.gif";
  Pager.ID="Pager";
  }
  public void InstantiateIn(Control control)
  {
  control.Controls.Clear();
  Table table = new Table();
  table.BorderWidth = Unit.Pixel(0);
  table.CellSpacing= 1;
  table.CellPadding =0;
  TableRow row = new TableRow();
  row.VerticalAlign = VerticalAlign.Top;
  table.Rows.Add(row);
  TableCell cell = new TableCell();
  cell.HorizontalAlign = HorizontalAlign.Right;
  cell.VerticalAlign = VerticalAlign.Middle;
  cell.Controls.Add(First);
  cell.Controls.Add(Previous);
  row.Cells.Add(cell);
  cell = new TableCell();
  cell.HorizontalAlign= HorizontalAlign.Center;
  cell.Controls.Add(Pager);
  row.Cells.Add(cell);
  cell = new TableCell();
  cell.VerticalAlign = VerticalAlign.Middle;
  cell.Controls.Add(Next);
  cell.Controls.Add(Last);
  row.Cells.Add(cell);
  
  control.Controls.Add(table);
  }
  }
  
    DefaultPagerLayout通过编程的方式提供了所有的导航元素,并将它们加入到aspx页面,不过这一次导航元素用标准的HTML表格设置了格式。现在,如果用户没有提供一个表现模板,程序将自动提供一个默认的模板。
  
  [TemplateContainer(typeof(LayoutContainer))]
  public ITemplate Layout
  {
  get{return (_layout == null)? new DefaultPagerLayout():_layout;}
  set{_layout =value;}
  }
  
    下面再来看看生成各个页面编号的过程。分页控件首先需要确定一些属性值,通过这些属性值来确定要生成多少不同的页面编号。
  
  public int CurrentPage
  {
  get
  {
  string cur = (string)ViewState["CurrentPage"];
  return (cur == string.Empty || cur ==null)? 1 : int.Parse(cur);
  }
  set
  {
  ViewState["CurrentPage"] = value.ToString();}
  }
  
  public int PagersToShow
  {
  get{return _results;}
  set{_results = value;}
  }
  
  public int ResultsToShow
  {
  get{return _resultspERPage;}
  set{_resultsperpage = value;}
  }
  
    CurrentPage保存的实际上是页面编号的ViewState中的当前页面,PagersToShow方法定义的属性允许用户指定要显示多少页面,而ResultsToShow定义的属性则允许用户指定每页要显示多少记录,默认值是10。
  
    NumberofPagersToGenerate返回当前应当生成的页面编号的数量。
  
  private int PagerSequence
  {
  get
  {
  return Convert.ToInt32
  (Math.Ceiling((double)CurrentPage/(double)PagersToShow));}
  }
  
  private int NumberOfPagersToGenerate
  {
  get{return PagerSequence*PagersToShow;}
  }
  
  private int TotalPagesToShow
  {
  get{return Convert.ToInt32(Math.Ceiling((double)TotalResults/(double)_resultsperpage));}
  }
  public int TotalResults
  {
  get{return _builder.Adapter.TotalCount;}
  }
  
    TotalPagesToShow方法返回要显示的总页面数量,由用户预设的ResultsToShow属性调整。
  

原文转自:http://www.ltesting.net