ScrollingGrid:跨浏览器冻结头部双向滚动数据网格






4.75/5 (51投票s)
一个跨浏览器的容器控件,用于数据网格,以冻结标题行并在水平滚动数据网格时同步标题。
测试 ScrollingGrid
的在线演示:在线演示^ (支持 Internet Explorer, Firefox 1.0+, Netscape 7+)。
引言
此控件提供了一个跨浏览器的解决方案,用于解决大型 DataGrid
的常见问题:即在滚动数据时保持标题行冻结在数据上方。此控件支持双向滚动——即,标题行随着您水平滚动而左右滑动。
有一段时间以来,存在一个仅限 IE 的解决方案,它使标题行像一个层一样工作。但它有一个小问题是,在滚动时,下拉列表会浮在标题行的上方。一个主要问题是它仅限 IE :-)。
与类似控件相比的优势
- 跨浏览器兼容:Mozilla Firefox 1+, Internet Explorer 5+, Netscape 7+
<select>
元素不会浮在标题行之上。这是其他解决方案常见的抱怨。- 上次滚动位置在回发时提交,您可以使用它来设置起始滚动位置。
- 您仍然可以使用 ASP.NET
DataGrid
控件,因此您不会在 Visual Studio 的 HTML 编辑器中丢失 Intellisense(对于网格列等)。
缺点
- 它只冻结标题行和底部分页符**行**。它不冻结列(类似仅限 IE 的控件可以)。
其他要点
- 实现简单
- 支持同一页面上的多个滚动网格
- 尚不支持 Opera 浏览器(仍不支持)
- 如果禁用了 JavaScript,则整个原始
DataGrid
将在主DIV
中滚动(即,标题不会冻结)。
使用 ScrollingGrid
Visual Studio 设计器
- 将 DLL 压缩文件 下载并解压缩到您的 Web 项目根目录。
bin/ScrollingGrid.dll ScrollingGrid.js
- 通过浏览到 bin/ScrollingGrid.dll 将
ScrollingGrid
控件添加到您的工具箱。 - 在页面上创建一个
ScrollingGrid
控件。 - 将
DataGrid
拖到ScrollingGrid
控件中。 - 如果您的页面不在 Web 项目的根目录下,您需要指定
ScriptPath
属性。例如,ScriptPath="../"
。
Visual Studio HTML 视图
- 将 DLL 压缩文件 下载并解压缩到您的 Web 项目根目录。
bin/ScrollingGrid.dll ScrollingGrid.js
现在,在您的 Web 项目中为 ScrollingGrid.dll 添加一个引用。或者,您可以将 ScrollingGrid.cs 添加到您的项目中(包含在 源 ZIP 中),而不是使用 ScrollingGrid.dll(在这种情况下,您应该删除它)。
- 在您的 .aspx 页面中
- 注册
TagPrefix
<%@ Register TagPrefix=avg Assembly=ScrollingGrid Namespace=AvgControls %>
- 在您的 Web 窗体中,将您的
DataGrid
控件用ScrollingGrid
控件包围,如下所示<form runat=server> <avg:ScrollingGrid runat=server ID=sg1 Width=450 Height=240 CssClass=sgTbl> <asp:DataGrid runat=server ID=Grid2 CellPadding=5 CellSpacing=1 AutoGenerateColumns=True AllowSorting=True AllowPaging=True PageSize=35 OnPageIndexChanged=Grid2_PageIndexChanged AllowCustomPaging=True> <HeaderStyle BackColor=red ForeColor=white Font-Bold=True /> <ItemStyle BackColor=#fefefe /> <AlternatingItemStyle BackColor=#eeeeee /> <PagerStyle BackColor=silver ForeColor=White Mode=NumericPages /> </asp:DataGrid> </avg:ScrollingGrid> </form>
- 如果您的页面不在 Web 项目的根目录下,您需要指定
ScriptPath
属性。例如,ScriptPath="../"
。
- 注册
ScrollingGrid 注意事项
Width
、Height
、ScriptPath
和CssClass
属性都是可选的,并具有默认值。Width
值可以是像素或百分比。Height
值必须是像素(不是百分比),并对应于包含数据行的DIV
的高度。由于标题和分页符已移出此DIV
,您的总高度将略大。ScrollingGrid
仅期望DataGrid
作为子控件。- 如果您的
DataGrid
包含一个底部分页符,它将被自动冻结在内容行下方。 - 如果您的
ScrollingGrid
未包含在 Web 窗体中,您应该在HEAD
标签中引用 JavaScript 文件。<script language=JavaScript src="ScrollingGrid.js"></script>
- 某些属性在运行时无法更改,因为
ScrollingGrid
在OnInit
方法中创建控件结构(似乎是唯一可以保留DataGrid
回发功能的方法)。例如,DataGrid
的ShowHeader
和ScrollingGrid
的ScrollingEnabled
属性。 - 如果在 Internet Explorer 中未设置
DataGrid
中图片的width
属性,图片可能会被裁剪。 - 如果您使用 Visual Studio .NET 设计器,您可以将此控件添加到您的工具箱。然后,只需将其拖到页面上,然后将您的
DataGrid
拖到ScrollingGrid
中。
DataGrid 注意事项
关于您的 DataGrid
控件,有几点需要注意。
- 如果您未设置
DataGrid
的CellPadding
属性,ScrollingGrid
控件将分配一个2
的值。默认值-1
会在 Firefox 中导致问题。 ScrollingGrid
控件会自动为您的DataGrid
控件分配GridLines=None
;否则,Firefox 将忽略您的CellSpacing
值。这是 Firefox 中DataGrid
的一个常见问题。ScrollingGrid
控件会自动将您的DataGrid
的BorderWidth=0
属性设置为0
;否则,Firefox 将无法准确匹配列。如果您需要显示DataGrid
表格的边框,我建议将CellSpacing
属性设置为边框的宽度(并且必须具有GridLines=None
)。然后,设置您的ScrollingGrid
的BackColor
属性(这将显示为边框颜色)。
ScrollingGrid 类
摘要
DataGrid
的跨浏览器容器控件,用于在水平和垂直滚动时冻结其标题和底部分页符。
语法
public class ScrollingGrid : System.Web.UI.WebControls.Panel
成员 (不包括继承的)
| FirefoxBorderWorkaround | 如果
|
| FooterWidthReduction | 获取/设置要缩减页脚的像素宽度
|
| HeaderWidthReduction | 获取/设置要缩减标题的像素宽度,例如,17 = 滚动条宽度(如果您不希望标题延伸到滚动条顶部)
|
| OnInit(EventArgs) | 在子
|
| 溢出 | 内容
|
| RenderBeginTag(HtmlTextWriter) | 输出控件容器
|
| RenderEndTag(HtmlTextWriter) | 输出控件容器
|
| ScriptPath | 获取/设置 ScrollingGrid.js 的位置
|
| ScrollingEnabled | 设置为
|
| SetStartScrollPosFromPostack() | 从回发设置内容
|
| StartScrollPos | 获取/设置内容
|
继承的属性
只有这些继承的属性对 HTML 输出有影响。
BackColor
CssClass
高度
宽度
工作原理
最初的想法是在几年前的一次工作面试中提出的。想法是标题实际上可以是一个完全独立的表格,其列宽与内容表格精确匹配。两个表格都位于各自的 DIV
中。标题 DIV
会隐藏溢出。内容 DIV
会滚动。当用户滚动内容 DIV
时,标题 DIV
会自动滚动到相同的水平滚动值。当用户向下滚动内容 DIV
时,标题会保持可见。
然而,手动设置列宽并不实际。创建一个新的网格控件吸引力有限,因为大多数开发人员习惯于 DataGrid
控件的功能。大约一年前,我开始着手将它渲染到 DataGrid
控件周围的想法。但一个问题是,DataGrid
没有提供一种方法只获取标题 HTML。但是访问浏览器 DOM 中的标题行很简单。因此,当页面在浏览器中加载时,脚本会简单地将标题 TR
重新分配给一个新的表格。
但是移动标题行并不能保持原始列宽。所以,它们需要被动态匹配。一旦完成,它看起来几乎就像原始表格,数据行带有滚动条,以及一个“冻结”的标题行,该标题行在数据滚动时左右滑动。
至于 ASP.NET 控件,ScrollingGrid
类继承自 Panel
控件,以便与 VS.NET 设计器兼容。然而,HTML 输出是完全自定义的,因此 Panel
的大多数继承属性都没有影响。ScrollingGrid
期望一个 DataGrid
子控件,并在 DataGrid
周围添加其 HTML。我没有继承 DataGrid
类的主要原因是因为在 HTML 模式下编码 DataGrid
时会丢失 VS.NET Intellisense(如果您在 HTML 模式下编码 ASP.NET 页面,这会非常令人沮丧)。
代码
此控件的大部分功能都在浏览器中控件的 JavaScript 初始化中。DataGrid
的标题行和底部分页符行被移动到它们各自的占位符表格。然后,通过增加较窄列的宽度来同步标题和内容的列宽。
列宽通常受表格总宽度的影响。在 Internet Explorer 中,您可以通过设置 tableEl.style.tableLayout = "fixed"
来改变这种行为。然而,在 Firefox 中,这似乎没有效果,因此您需要确保表格有足够的空间来扩展。这可以通过将外部占位符表格的宽度设置为非常大(即 10000)来实现。
以下是 initScrollingGrid()
JavaScript 函数的摘录(用于将标题 TR
元素移动到占位符表格)。
var tblHdr = document.getElementById(scrollingGridID + "$tblHdr");
var tblDataGrid = document.getElementById(gridID);
var tblPager = document.getElementById(scrollingGridID + "$tblPager");
// get header table's first row
var tbodyEl = tblHdr.childNodes[firstChildElIndex(tblHdr, "TBODY")];
var trEl = tbodyEl.childNodes[firstChildElIndex(tbodyEl, "TR")];
// get datagrid table's first row
var tbodyEl2 = tblDataGrid.childNodes[firstChildElIndex(tblDataGrid, "TBODY")];
var trEl2 = tbodyEl2.childNodes[firstChildElIndex(tbodyEl2, "TR")];
// delete empty TR on placeholder table
tbodyEl.removeChild(trEl);
// move the header row from datagrid table to our placeholder table
tbodyEl.appendChild(trEl2);
firstChildElIndex
函数是 Firefox 的一个必要步骤,以应对一个恼人的行为——即空格会导致 DOM 树中出现“#text
” childNode
。因此,在某些情况下,TBODY
是 TABLE
的第一个 childNode
,在其他情况下是第二个 childNode
(取决于 <table>
和 <tr>
之间是否有空格)。
以下是 SetWidths
JavaScript 函数的摘录。
for (var i=0; i<widths.length; i++)
{
if (widths[i]+"" == "undefined")
continue;
// TD element for the header row
var tdHdr = trEl.childNodes[i];
// TD element for the content row
var tdContent = trEl2.childNodes[i];
var widthAdjustment = 0;
if (!document.all)
{
// FF: subtract cellpadding
widthAdjustment = -2 * parseInt(tblGrid.getAttribute("cellpadding"));
}
// Update either the header cell or content cell
// (not both, otherwise FF stuffs up)
if (tdHdr.offsetWidth != widths[i])
// update header column width
tdHdr.style.width = widths[i] + widthAdjustment;
if (tdContent.offsetWidth != widths[i])
// update content column width
tdContent.style.width = widths[i] + widthAdjustment;
}
widths
数组是在之前的循环中填充的,包含每列的正确宽度。因此,这里会将适当的表格单元格调整到它们的新宽度。
为了同步标题和内容,这个简单的 JavaScript 函数处理内容 DIV
上的滚动事件。
// content scroll event handler (matches the header row
// with the horizontal scroll position of content)
function updateScroll(divObj, scrollingGridID)
{
if (document.getElementById(scrollingGridID + "$divHdr") != null)
document.getElementById(scrollingGridID + "$divHdr").scrollLeft =
divObj.scrollLeft;
// save scroll position to hidden input
document.getElementById(scrollingGridID + "$hdnScrollPos").value =
divObj.scrollLeft + "-" + divObj.scrollTop;
}
即使标题 DIV
不显示滚动条,其 scrollLeft
属性仍然会改变其内容的滚动位置。
问题
- 在 Firefox (1.5 之前的版本) 中,如果您拖动以选择内容表格中的文本并导致内容
DIV
滚动,则不会触发滚动事件处理程序,因此标题不会同步。 - 在 Firefox (1.5 之前的版本) 中,鼠标滚轮无法与
DIV
一起使用。这是浏览器的行为。 - 要禁用滚动行为,必须在控件的服务器标签(而不是代码隐藏)中指定
ScrollingGrid
的ScrollingEnabled
属性。在运行时设置此项不起作用。这是在OnInit
方法中设置所有控件的一个副作用(这是为了避免DataGrid
回发事件的问题)。我必须尝试其他一些方法来解决这个限制。
关注点
- IE 与 FF 的渲染行为非常不同。开发可变宽度功能对于 Firefox 来说要容易得多,Firefox 会按预期渲染表格,就像指定百分比宽度一样。然而,IE 的反应完全不同。因为外部表格内的
DIV
包含大量内容(尽管溢出被裁剪),IE 会使外部表格非常宽以适应内容。解决方案是使用table-layout:fixed
,它告诉 IE 监听指定的表格宽度(并裁剪任何宽内容)。然后我使用脚本更新DIV
的宽度与TD
相同。 - 大量数据似乎没有问题。当有数千行时,标题行同步的速度不会那么快。浏览器会占用大量内存和 CPU。但据我所知,这并不比单独使用
DataGrid
多。 - 开发与 Firefox + IE 配合工作的 DHTML 是一项挑战,主要是因为 Firefox DOM 会创建
#text
节点,即使 HTML 标签之间只有空格。 - 如果您想裁剪列中的文本(而不是让它换行),您需要创建一个
TemplateColumn
并对 CSS 和DIV
进行一些特殊处理。Firefox 在这方面得分更高,因为您可以实际高亮显示文本,它会在裁剪区域内滑动,而 IE 只会裁剪它。以下是实现此目的的TemplateColumn
。<asp:TemplateColumn Visible=True HeaderText=ShipName> <ItemTemplate> <div style="overflow:hidden; text-overflow:clip; width:70px;"> <nobr><%# DataBinder.Eval(Container.DataItem, "ShipName") %></nobr> </div> </ItemTemplate> </asp:TemplateColumn>
结论
开发跨浏览器 DHTML 可能是一个真正的挑战,但据我看来,Firefox 是一个伟大的浏览器,它持续的受欢迎程度值得付出额外的努力。这无疑是一次关于浏览器渲染行为以及开发自定义控件的宝贵课程。如果您觉得此控件有用,请随时在下方留下反馈。
更新
2006 年 8 月 - 对控件进行了重大改进。重写了本文。
- 现在支持可变宽度(即百分比)。
- 在回发时提交上次滚动位置,可选择使用新的
StartScrollPos
属性将其用作起始滚动位置。 - 用于在浏览器调整大小时缩放高度的实用函数。
- 添加了一个属性来指定 ScrollingGrid.js 的路径。