65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (51投票s)

2005年9月14日

CPOL

10分钟阅读

viewsIcon

1198464

downloadIcon

11447

一个跨浏览器的容器控件,用于数据网格,以冻结标题行并在水平滚动数据网格时同步标题。

Screenshot - Firefox

测试 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 设计器

  1. DLL 压缩文件 下载并解压缩到您的 Web 项目根目录。
    bin/ScrollingGrid.dll
    ScrollingGrid.js
  2. 通过浏览到 bin/ScrollingGrid.dllScrollingGrid 控件添加到您的工具箱。
  3. 在页面上创建一个 ScrollingGrid 控件。
  4. DataGrid 拖到 ScrollingGrid 控件中。
  5. 如果您的页面不在 Web 项目的根目录下,您需要指定 ScriptPath 属性。例如,ScriptPath="../"

Visual Studio HTML 视图

  1. DLL 压缩文件 下载并解压缩到您的 Web 项目根目录。
    bin/ScrollingGrid.dll
    ScrollingGrid.js

    现在,在您的 Web 项目中为 ScrollingGrid.dll 添加一个引用。或者,您可以将 ScrollingGrid.cs 添加到您的项目中(包含在 源 ZIP 中),而不是使用 ScrollingGrid.dll(在这种情况下,您应该删除它)。

  2. 在您的 .aspx 页面中
    1. 注册 TagPrefix
      <%@ Register TagPrefix=avg Assembly=ScrollingGrid 
                                 Namespace=AvgControls %>
    2. 在您的 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>
    3. 如果您的页面不在 Web 项目的根目录下,您需要指定 ScriptPath 属性。例如,ScriptPath="../"

ScrollingGrid 注意事项

  • WidthHeightScriptPathCssClass 属性都是可选的,并具有默认值。
  • Width 值可以是像素或百分比。
  • Height 值必须是像素(不是百分比),并对应于包含数据行的 DIV 的高度。由于标题和分页符已移出此 DIV,您的总高度将略大。
  • ScrollingGrid 仅期望 DataGrid 作为子控件。
  • 如果您的 DataGrid 包含一个底部分页符,它将被自动冻结在内容行下方。
  • 如果您的 ScrollingGrid 未包含在 Web 窗体中,您应该在 HEAD 标签中引用 JavaScript 文件。
    <script language=JavaScript src="ScrollingGrid.js"></script>
  • 某些属性在运行时无法更改,因为 ScrollingGridOnInit 方法中创建控件结构(似乎是唯一可以保留 DataGrid 回发功能的方法)。例如,DataGridShowHeaderScrollingGridScrollingEnabled 属性。
  • 如果在 Internet Explorer 中未设置 DataGrid 中图片的 width 属性,图片可能会被裁剪。
  • 如果您使用 Visual Studio .NET 设计器,您可以将此控件添加到您的工具箱。然后,只需将其拖到页面上,然后将您的 DataGrid 拖到 ScrollingGrid 中。

DataGrid 注意事项

关于您的 DataGrid 控件,有几点需要注意。

  • 如果您未设置 DataGridCellPadding 属性,ScrollingGrid 控件将分配一个 2 的值。默认值 -1 会在 Firefox 中导致问题。
  • ScrollingGrid 控件会自动为您的 DataGrid 控件分配 GridLines=None;否则,Firefox 将忽略您的 CellSpacing 值。这是 Firefox 中 DataGrid 的一个常见问题。
  • ScrollingGrid 控件会自动将您的 DataGridBorderWidth=0 属性设置为 0;否则,Firefox 将无法准确匹配列。如果您需要显示 DataGrid 表格的边框,我建议将 CellSpacing 属性设置为边框的宽度(并且必须具有 GridLines=None)。然后,设置您的 ScrollingGridBackColor 属性(这将显示为边框颜色)。

ScrollingGrid 类

摘要

DataGrid 的跨浏览器容器控件,用于在水平和垂直滚动时冻结其标题和底部分页符。

语法

public class ScrollingGrid : System.Web.UI.WebControls.Panel

成员 (不包括继承的)

FirefoxBorderWorkaround

如果 DataGrid 上的 GridLinesBorderWidth 属性不应被设置以获得最佳的 Firefox 结果,则设置为 false

  • 类型:Boolean
  • 默认值:True

FooterWidthReduction

获取/设置要缩减页脚的像素宽度

  • 类型:Int32
  • 默认值:0

HeaderWidthReduction

获取/设置要缩减标题的像素宽度,例如,17 = 滚动条宽度(如果您不希望标题延伸到滚动条顶部)

  • 类型:Int32
  • 默认值:0

OnInit(EventArgs)

在子 DataGrid 控件之前和之后创建控件。

  • 返回类型:void

溢出

内容 DIV overflow 样式设置。可以是:auto、scroll、hidden。

  • 类型:String
  • 默认值:scroll

RenderBeginTag(HtmlTextWriter)

输出控件容器 TABLE 的开头。

  • 返回类型:void

RenderEndTag(HtmlTextWriter)

输出控件容器 TABLE 的结尾。

  • 返回类型:void

ScriptPath

获取/设置 ScrollingGrid.js 的位置

  • 类型:String
  • 默认值

ScrollingEnabled

设置为 false 以正常显示 DataGrid(即,没有任何滚动或冻结标题等)。

  • 类型:Boolean
  • 默认值:True

SetStartScrollPosFromPostack()

从回发设置内容 DIV 的起始滚动位置。

  • 返回类型:void

StartScrollPos

获取/设置内容 DIV 的起始滚动位置。

  • 类型:Point
  • 默认值:new Point(0, 0)

继承的属性

只有这些继承的属性对 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 树中出现“#textchildNode。因此,在某些情况下,TBODYTABLE 的第一个 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 一起使用。这是浏览器的行为。
  • 要禁用滚动行为,必须在控件的服务器标签(而不是代码隐藏)中指定 ScrollingGridScrollingEnabled 属性。在运行时设置此项不起作用。这是在 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 的路径。
© . All rights reserved.