列表和分页器之间的对话
[引言]
很少有应用不涉及到表现集合数据. 表现集合数据就需要用到“列表(名单、一览表)”. 例如显示全体会员的名单. 当一次显示的数据过多时, 如果将所有数据完全列出, 将给用户造成阅读上的困难. 这个时候, 就需要将数据分页列出. 给用户以可驾驭数据的信心. 设身处地的为用户考虑, 是程序员、设计人员应有的禀性.
以下文叙述了作者在应用开发过程中对“数据集合分页”的感受、见解、或技巧。可供诸位参考。
[问题由来]
表现集合数据我们有很多选择. 在ASP.NET范畴, 一个核心的概念就是”绑”. 即把承载数据的集合对象实例通过一个简单的"DataBind()"方法绑定到列表控件上. 这一过程甚至可以不用实际地书写一行代码实现. 实际过程无非是通过向数据库查询得到数据集合. 之后任意选择一个”数据绑定控件”, 设置DataSource属性. 调用DataBind()方法就万事大吉了.
在我们可以选择的数据绑定控件中, DataGrid似乎独树一帜. 甚至有一些人准备终生奋斗奉献与它. 而且
DataGrid是可以自动分页的. 作者也不得不感叹: DataGrid真是太强大了.
但是在使用DataGrid的最初岁月, 作者却对DataGrid一度没有好感. 原因也正是: DataGrid太强大了. 功能太多了. 正所谓有所长必有所短. 应用毕竟有不方便的地方. 而且其分页的原理建立在对性能要求不高的环境下. 而且在一定程度上, DataGrid无法满足我们对输出的特别需要.
很多情形, 我们仅要求可显示集合数据. 并且可以分页. 并不需要诸如auto create columns, sorting, editable, delete等功能. 那么一些较轻量级的, 样式定制性较强的数据绑定控件就进入了我们的选择范围. Repeater就成了比较理想的一个选择. 而且自行编写分页的程序有时候是非常必要的.
这样, 问题就摆出来了: 如何实现分页. 以及如何合理安排分页的逻辑. 本文标题是列表与分页器之间的对话. 既是对话, 必是两个主体之间. 作者将数据表示与分页控制分别设计成了两个对象. 如何组织两个主体之间的调用关系以及消息机制, 将是本文讨论的主要内容.
[设计思想]
为什么要将分页器与列表设计为两个对象? 设计为对象, 必然要求设计其与容器通讯的接口. 往往回增加设计上的工作量. 但是长远看来, 分页器自己的表示逻辑也可能异常复杂. 有一定的内聚性. 又在一定程度上有重用性(将分页器应用到不同的列表上).
分页器(Paginator)其实名不副实, 名字叫分页器, 但其实并不对数据做任何操作. 操作数据是列表自己的事情. 分页器所作的仅仅是根据当前分页的数据决定以何种方式显示页. 以及在用户作出操作后向列表报告应显示的位置.
让我们仔细分析分页器:
1. 分页器需要知道以下数据: 总记录数目, 整页容量(每页容纳的记录数), 当前显示的页次.
2. 分页器必须在用户发出操作请求(换页)时以一定机制通知列表
3. 必须可以显示当前页次
分页的数据是由列表控制的. 在未通知分页器之前, 分页器对此一无所知. 另外, 列表必须可以对分页器的分页命令作出响应. 另外再把新的状态通知分页器. 这就是列表和分页器之间的对话.
我来详细解释一下. 由最初状态开始: 列表拿到数据. 并且程序员设置列表每页显示15行. 列表计算出的需要绑定的数据一共为81条. 这样需要分成81/15=5余6. 需要分成6页. 其中最后一页不满. 这样列表通知分页器: 我这拿到81条数据, 每页显示15条. 一共需要6页. (通过写property). 之后分页器开始初始化. 它顺序的输出 1 2 3 4 5 6数字. 每个数字都可以用鼠标单击. 并且让1下面加一条线, 表示当前显示的是第一页. 这样完成一次页面发送过程.
当用户想直接跳到第4页. 于是他单击了4. 于是"4 clicked"这个消息必须以一定过程发回到后端. 后端是分页器首先拿到4这个数据. 分页器将这个数据以事件的形式发出.
列表可以直接捕获分页器发出的事件. 比方说这个事件名字为 PageChange. 这样, 列表知道了现在必须马上换到第四页. 于是开始对数据进行处理. 得到第四页(从46-60)给自己绑定. 之后再次设置分页器状态. 将当前页次设置为4. 这样分页器将输出的4数字下加一条线. 于是用户看到. 数据发生了变化(前进了3页), 同时分页器重新指示了当前页次.
[实施]
设计列表:
必须实现以下相关公共API(C#):
public int RecordCount // 总记录数
public int PageCount // 总页数
public int PageSize // 页容量
public int CurrentPage // 当前页次
私有方法: 实施分页计算
private void PageMe()
设计分页器:
须实现如下公共API:
public int PageCount // 总页数
public int PointedPage // 当前指示页
public event PageChange()
(PageChange事件必须包含一个整型数据. 做为通知列表切换页次的数据.
如此设计. 各对象分工明确, 逻辑明晰. 而且接口比较标准, 适合移植与重用.并且你可以将分页器放置在前后左右上下任意的地方. 只要让列表控件可以找到他就可以.
[应扩展的特性]
1. 分页器不能光傻乎乎地输出全部页次, 如果页次超过一定数目, 应以一种比较友好的方式显示. 比如类似GOOGLE显示搜索结果分页的模式.
2. 可是设置一个分页的临界点. 比如说超过40条数据才分页. 否则在一页内显示. 你一定不难想想当用户移动到第二页时发现只有一条数据的迷茫神色.
3. 分页器可以显示上一页, 下一页, 第一页, 最后一页等方便用户前进后退. 总之扩展功能变得很方便.
4. 分页器可以做很多样式. 用公共属性来控制他.
以上述除第四项外, 均在作者的项目中实现. 并且作者发现, 要做到非常灵活和健壮, 还必须注意很多问题. 有机会将源代码公布出来与大家共勉.
关于列表项分页的机理, 即可以直接使用DataGrid分页的方法, 又可以自行处理数据. 作者是应用了服务器短数据缓存来减轻数据库的负担. 有机会写文出来大家批判.