用于显示分层数据的嵌套网格(from msdn)

80酷酷网    80kuku.com

  数据|显示在“前沿技术”的 2003 年 8 月刊,我讨论了如何扩展 ASP.NET DataGrid 服务器控件,以便将多表数据容器(如 DataSet 对象)用作其数据源。 如果 DataSet 包含数对相关表,则只要所显示的表是其中某个关系的父级,该控件就将添加动态创建的按钮列。 当单击此列按钮时,将显示子 DataGrid,并将根据此关系列出选定记录的子行。 总体行为显示在图 1 中,此行为与 Windows® 窗体 DataGrid 控件在类似情形下的工作方式相同。



图 1 父级和子级 DataGrids

图 1 中显示的应用程序是包含两个一起工作的 DataGrid 控件的用户控件。 该用户控件(请参阅 2003 年 8 月的源代码)包含使两个网格保持同步所需的全部逻辑。 父 DataGrid 绑定到 DataSet 并显示父表的内容。 当这一情况发生时,该用户控件确保 DataSet(所显示的表在其中充当父级)内部存在关系。 子 DataGrid 绑定到数据视图,该视图包含仅仅与选定记录相关的子表中的所有记录。 因此,如果您有一个 DataSet,并且它有两个已建立关系的表,那么该用户控件将为您节省时间,因为您不需要针对任何额外的显示机制来编写代码。

那么这种方法有什么问题呢? 如果您仅仅关注基本功能,那么它没有问题。 但是,一些读者已注意到不使用两个物理上分隔的 DataGrid 控件也许会更好。 该用户控件在组成控件的周围构建了一个壁垒,从而使您只能通过映射属性和方法或者通过公开整个内部控件来访问这些组成控件。 从可编程性的观点来看,使用一个 DataGrid 控件来显示分层数据要简单得多。 首先,您不必担心父表的配置问题。 只需使用 DataGrid 控件的标准接口即可。 显示相关数据的任何子网格都可以动态创建,并可以显示在主网络的布局内部。



图 2 嵌入式子 DataGrid

另一方面,需要提醒的是,设计 DataGrid 控件不是为了包含分层数据。 其内部布局最适合显示表格式数据。 DataList 控件可能是一个不错的选择,但它不提供固有的分页支持,并且需要一些代码才能像 DataGrid 一样工作。 当在 Google 上快速搜索“嵌套 DataGrid”时,返回了讨论如何将 DataGrid 嵌入到 DataList 控件的文章的链接,这些文章给了我关于本专栏的一些启示。 这里,我将构造一个从 DataGrid 类继承的自定义控件。 该控件实现一个自定义列类型 (ExpandCommandColumn),并包含显示与被单击的项关联的记录所需的全部逻辑。 展开视图通过嵌入到父级中的子 DataGrid 表示。 图 2 显示了此控件的外观。

构造嵌套网格

只有当数据源是包含表之间的关系的 DataSet 对象时,分层的 DataGrid 控件才有意义。 例如,假定某个 DataSet 具有 Customers 表和 Orders 表,并在 CustomerID 列上建立了这两个表之间的 DataRelation。 只要 DataGrid 包含按钮列,那么当您单击它时就能够为选定的客户创建一个子视图,并将所生成的 DataView 对象绑定到子网格。

由于新控件(在示例代码中称为 NestedGrid)是从 DataGrid 类继承的,因此可以在适合使用 DataGrid 对象的任何情况下使用它。 但是,最后这一句还有待修饰。 通常,当从基类派生控件时,可能存在这样的情况:所派生的控件由于其特定的扩展和附加项而无法替换原始控件。 在本专栏中,我不会花太多的时间来使 NestedGrid 组件向后兼容基 DataGrid 类。 为了简单起见,我假设您始终将它绑定到 DataSet 对象。

关于 NestedGrid 控件,还有其他几个假设,这将在后面的部分逐渐说明。 特别要说明的是,由您负责添加规定每一行的 expanded/collapsed 状态的按钮列。 从理论上来讲,该列可以放在网格中的任何位置。 但是,我在这里假设展开列是网格中的第一列。 (2003 年 8 月刊已讨论,可以适当地修改行为,以便只有当 DataGrid 绑定到具有相关表的 DataSet 时才动态生成该列。)

如果您有过一点使用 DataGrid 控件的经验,会知道尽管它功能极其强大,可自定义性也非常强,但它无法很好地支持布局的变化。 网格布局表示表格式数据 — 按规则连续的若干个大小相等的行。 怎样才能嵌入具有此限制的子网格呢?

这里要提醒的重要一点是,网格是作为标准的 HTML 表呈现的。 一旦单元格形成了规则的表布局,就可以在其中的每个单元格中放入任何内容,其中包括表示子网格的子表(使用 rowspan 标记)。 首先,删除包含命令按钮的单元格以外的其他所有单元格,以修改组成选定行(即用户单击的展开命令按钮所在的行)的单元格的数目。 如果假设展开命令列位于最左侧,这很容易实现。 所有单元格都删除后,可以创建一个横跨若干列(列数必须等于 DataGrid 控件的 Columns 集合中的项数)的全新单元格。

此时,您已拥有了使该行可展开的完全自定义单元格。 可以通过编程的方式在此自定义单元格中填入服务器控件的任意组合。 例如,可以插入这样一个表:最上面一行模拟已删除的单元格的结构(通常是关于父行的信息),最下面一行包含子 DataGrid。 图 2 中的控件是基于此方案创建的。

NestedGrid 类

前面已提到,NestedGrid 类从 System.Web.DataGrid 类继承,并添加了额外几个属性(见图 3)。 此控件还将在需要数据绑定时引发自定义的 UpdateView 事件。 要对分配给 DataSource 属性的对象类型加以严格的控制(并确保它是 DataSet),可以重写 DataSource 属性,如下面的代码所示:

public override object DataSource
{
get {return base.DataSource;}
set {
if (!(value is DataSet)) {
// throw an exception
}
base.DataSource = value;
}
}

当用户单击行按钮以展开记录(某个客户)查看其详细信息时,NestedGrid 控件实例化。 为此,嵌套的网格必须包含一个具有某些特定功能的按钮列。 首先,网格必须提供针对 ItemCommand 事件的处理程序,以便可以处理展开/折叠请求。 处理程序将 ExpandedItemIndex 属性设置为被单击的记录的基于零的索引,并更新网格视图。 那么,什么时候修改被单击的行的布局合适呢?

网格布局创建出来后,ItemDataBound 事件在事件链的底部激发。 ItemDataBound 激发后,数据绑定阶段基本完成,所有单元格都可以显示了。 此后您所看到的布局和数据将不会再发生任何变化。 就是因为这个原因,我决定在处理 ItemDataBound 事件之前实现所有必要的更改。

在深入探讨控件的实现之前,还有几点注意事项需要提出来。 首先,ExpandedItemIndex 属性是基于零的,但它表示的是所单击的行的绝对位置。 此属性与类似的网格属性(如 SelectedItemIndex 和 EditItemIndex)的唯一不同之处在于,它表示的不是基于页的值。 其次,NestedGrid 还在内部实现分页。 要使该控件在成员表的各页之间移动,除了处理 UpdateView 事件并传递绑定数据以外,不必做其他任何工作:

void UpdateView(object sender, EventArgs e) {
BindData();
}
void BindData() {
dataGrid.DataSource = (DataSet) Cache["MyData"];
dataGrid.DataBind();
}

NestedGrid 类具有针对 PageIndexChanged 事件的内置处理程序,如下所示:

void PageIndexChanged(object sender, DataGridPageChangedEventArgs e)
{
CurrentPageIndex = e.NewPageIndex;
SelectedIndex = -1;
EditItemIndex = -1;
ExpandedItemIndex = -1;

if (UpdateView != null)
UpdateView(this, EventArgs.Empty);
}

NestedGrid 控件的结构的关键要素是按钮列。 为简单起见,此版本的控件仅支持单个展开项。 通过将 ExpandedItemIndex 属性从整数更改为数组或集合,可以轻松地扩展此功能。

ExpandCommandColumn 类

可以使用字符串(如“+/-”或“Expand/Collapse”)或位图来呈现展开列。 您可能要对不同的应用程序使用不同的图片。 要实现此功能,最灵活的方法是使用几个像 ExpandText 和 CollapseText 这样的属性。 那么,应当在 NestedGrid 类上定义这些属性吗? 在类似的方案(就地编辑)中,ASP.NET 开发小组创建了一个自定义的 DataGrid 列,并在该列中放入了像 EditText、CancelText 和 UpdateText 这样的属性。 基于此,我创建了自己的 ExpandCommandColumn 类,并在其中放入了几个文本属性,以表示用于展开和折叠视图的 HTML 输出。 下面的代码片段显示了如何将该自定义列与网格集成。

<cc1:NestedDataGrid id="dataGrid" runat="server" ...>
<Columns>
<cc1:ExpandCommandColumn
CollapseText="<img src=http://www.163design.net/a/s/images/collapse.gif>"
ExpandText=

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: