几乎在做每一个Web项目开发的时候都会用到列表显示数据,但在HTML里并没有提供类似于ListView这样的功能,因表头不能固定在表格的顶部,它会随着滚动条一起拉动,对于比较长的表格来说会很不方便。其实一直以来都很想做这样一个东西,直到这将项目所逼,才真正动手来完成。对于固定表头的方案,在网上搜索了一下也有不少,基本上都是通过CSS来控制,但效果并不是特别理想。后来偶然在CodeProject上找到一篇文章(抱歉地址找不到了),给了我很大的启发,于是着手用JavaScript实现了这样的需求。

除了表头的固定,在其它三边上是否也可以固定呢?固定了有没有用呢?在我开始设计的时候就问了自己这样的问题。我很快就得出了肯定的答案,why not! 既然大局已定,那就要给这个东西取个名字了,当然不能称为ListView了,就偷个懒暂且称为DataGridView吧。因此,本文接下来所提到的DataGridView如果没有特别说明,都是指我的这个东西,而不是WinForm里的那个DataGridView控件。

先来看效果图,点击图片看在线演示。

废话不多说,看下面这个示意图。

Header, Left
Header, Center
Header, Right
Body, Left
Body, Center
Body, Right
Footer, Left
Footer, Center
Footer, Right

要让一个表格四边固定不受滚动条的影响,只用一个表格是很难实现的,并且可能还会受制于浏览器对表格的渲染,因此就需要把表格拆分成3*3的九宫格,分别来处理。纵向的三行分别称为Header、Body和Footer,横向的三列分别称为Left、Center和Right,由它们组合形成9个区块,每个区块的名称为纵横名称的首字母组合,例如中间这个蓝色的区块名称为BC,即BodyCenter。从HTML结构上来说,这9个区块都有一个共同的父元素,也就是DataGridView的根元素,我称它为Canvas。

虽然表格的四边是固定的,但并不代表它们不动。例如Header或Footer虽然不会纵向滚动,但它还是要接受横向的滚动,而Left和Right正好相反。因此不难想像,上图中红色的区块仅做横向滚动,绿色的区块仅做纵向滚动,蓝色的区块要双向滚动,而黑色的区块不进行任何滚动。这样一来,只要把一整个表格拆分成这9块分别放入对应的区块里,并控制好每个单元格的宽度和高度就可以了。接下来的问题就是如何实现滚动了。

滚动本来是一件很简单的事,为什么要单独拿出来说呢?首先,用脚趾头都可以想到,滚动条不可以直接放在任何一个区块上,因为这样会使这个表格看起来很怪异。其次,因为每个区块都是独立的,要让它们同时滚动就必须使用代码来控制。滚动条不能直接设置在Canvas上,因为这样会导致所有区块同时滚动。所以,必须在Canvas里再增加一个用于设置滚动条的区块,称之为ScrollBars(SB不好听),它的大小必须和Canvas一致,相当于在Canvas上盖了一层。那么要让一个HTML元素产生滚动条,除了要设置适当的样式,还需要在元素内部有内容超出边界,因此再增加一个Scoller的区块,参考图中的虚线框。

好了,下面列个表来复习一下一个DataGridView由哪些部件构成。

  • Canvas
    • ScrollBars
      • Scroller
    • HL (Left Header)
    • HC (Center Header)
    • HR (Right Header)
    • BL (Left Body)
    • BC (Center Body)
    • BR (Right Body)
    • FL (Left Footer)
    • FC (Center Footer)
    • FR (Right Footer)

部件有了,接下来就是要控制它们的位置。各个区块的位置如何布局,以及单元格的布局,我想了很久,并且做了三个不同的版本才最终定下来。
第1个版本,区块的布局我用的是Table,区块内部单元格也是用Table,相当于Table套Table。
第2个版本,区块用Div布局,单元格用Table。
第3个版本,区块用Div布局,单元格用Span,完全摆脱了Table。
不用Table的原因很简单,因为我发现Table很难控制,并且恐怕不同的浏览器对Table的不同处理方式导致的显示问题。

既然用Div布局,那它必须设置为绝对定位,至于位置和大小,就必须进行实时计算。算法并不复杂,无非是列宽和行高的组合,因此,必须在生成DataGridView之前,为它指定每一行的宽度和行高。 相对于列宽来说,行高要麻烦一些,因为列数是已知的,而行的数目是不可知的。为了简化计算,目前DataGridView使用统一的行高设置,这样就可以暂时避开这个问题。每个区块的具体计算公式就不解释了,主要说一下和滚动有关的部分。

在这些区块中,最主要的滚动区块就是正中间蓝色的BC,滚动条的大小需要参照它的大小来设置。显然,ScrollBars比BC大,要用大的滚动条控制小窗口的滚动看起来会比较难。我开始的时候就犯了一个错误,通过两者的比例来换算,结果因为浮点计算的差异使得滚动条有偏差,行数越多偏差越大。 后来想到一个更简单更有效的办法,因为ScrollBars和Canvas大小是一样的,所以可以直接把BC的位置映射到ScrollBars里的Scroller上。至于Scroller的大小,并不是BC的大小,而是实际滚动区域的大小,宽度是属于Center部分列的总宽度,而高度就是所有行高度的总和。不过这样还不够,还必须加上Footer的高度和Right的宽度,要不然有一截是滚动不到的。上图中虚框就是Scroller的大小,中间的那条坚线标明了实际宽度的界限,当横向滚动条拉到最右边时,它正好与BC的右边界重叠,这就表示滚动的量刚好。接下来,就是要把ScrollBars的滚动与其它几个区块的滚动关联起来了。只需要处理ScrollBars的onscroll事件,在这个事件的处理函数里,同步几个需要滚动区块的offsetLeft和offsetTop属性即可。

有一点必须要说的是,在计算行高和列宽时,我发现浏览器在处理边框(Border)或者内空(Padding)时,并不是在样式指定的范围内,而是向外扩展的。例如指定了DIV的宽度为50,边框为1,那么实际产生的大小是52个像素。换句话说,样式里所指定的width或height实际上是元素的工作区域大小,而不是实际大小。这点必须在计算时考虑进去,要不然宽度或者高度的计算会有误差,导致显示不正常。

再来说一下网格线的处理,要考虑一个整体性,每一个细节都不能放过。每一个单元格都是一个Span,不过每一个Span在同一个方向上只能是一侧有边框,这样可以避免出现边框重叠,看着很不舒服。对于四周的区块,在靠近中间的一侧必须有边框,这样在滚动时不会与中间BC的单元格在视觉上产生粘连。而BC的单元格有点特殊,如果从整体上来看,可以很形象的归纳为一个汉字“十”,也就是说只有内框没有外框,这样当BC与四周的区块拼接时就不会出现重叠的边框了。但在实际开发时,我发现要实现这个“十”并不简单,原因在于最后一行的下边线。一方面因为在生成行的时候并不知道当前是不是最后一行,另一方面是当行的数量不足以填充BC的高度时,必须要有最后一行的下边框,否则很难看。因此,我把单元格堆成了“土”的形状。不过这样一来,当滚动条到了最底部就会出现重叠的边线了,没关系,只要在滚动的高度上做点文章,这个问题就解决了。在设计Scroller的高度时,减去一个网格线的宽度,那么在滚动时就看不到这最后一条线了,也就不会出现重叠的现象了。

那么,BC的右边最后一条边框呢?当然,也可以这样处理,不过我没有,因为这里要说最后一个问题了。当行数不足以填充BC时,可以留空,这不会影响到视觉效果,但如果列不足以填充就不能留空了,要不然看起来象是分开的两张表了。对于这个问题,我的处理方法是让Right的三个区块紧帖着Center,把空间留到Right的右边,这样看起来就还是一个整体了。

好了,到此为止我把DataGridView的设计思路大概说完了,还有一些细节问题有兴趣的可以参考源代码。因为是匆忙做的东西,还有很多问题没有很好的处理,还有很多设想没有完成。因此,我不想对代码解释太多,因为这只是一个雏形。如果想要深入了解实现细节,请看源代码。

接下来要完成的功能有这些:

  • 列模板以及对Footer的管理。
  • 基于Ajax模式的数据加载支持。
  • ASP.NET控件封装。
  • 样式设置。

将来如果精力够的话,还有这些功能要做:

  • 数据编辑。
  • 行和列的排序。
  • 数据筛选。
  • 其它现在还没想到的……

点击这里看在线演示地址点这里下载源码

哦,还有件事忘记说了,DataGridView在IE8、IE8的兼容模式和Chrome 3.0里可正常使用,如果有人能帮我测试一下IE6就太感谢了,而FireFox不用试我就知道肯定是有问题的了。

posted on 2009-12-25 14:21  一风  阅读(3953)  评论(25编辑  收藏  举报