用于标准 SQL 数据的自定义 TreeView
一个 ASP.NET TreeView,兼容标准 DataViews,并完全支持声明式语法。
引言
ASP.NET 1.0 没有提供树形视图控件。我想这是很多人需要的,因为微软在 ASP.NET 2 中引入了这样的控件。然而,MS 的 TreeView
有一个特别之处,让它在处理“标准”数据时使用起来很不方便:这个 TreeView
需要一个实现 IHierarchicalEnumerable
的数据源(例如:XML 数据……)。当你想要将这个 TreeView
与 SQL 数据绑定时,你必须编写一些代码(即,你不能使用声明式语法,这是 ASP.NET 的标志性功能)。这里介绍的 CustomTreeView
提供了从标准 DataView
显示数据的可能性,并且仍然可以受益于 ASP.NET 的声明式语法(通过 NodeTemplate
模板)。
背景
CustomTreeView
是一个模板化数据绑定控件。我不会解释它是如何工作的,因为你可以在 MSDN(或在我之前的文章 NestedRepeater)中找到完整的细节。
使用代码
数据源
我将显示一个名为 GEOGRAPHY 的表的数据。这个表需要主键/外键关系。正是这种关系创建了树形视图渲染的层次结构。在这里,GEO_PARENT 引用 GEO_ID 来创建父子关系。
代码隐藏
GEOGRAPHY 中的数据存储在 DataView
中。在 DataView
中创建了一个 DataRelation
,然后使用 RowFilter
过滤器来选择最顶层的节点。代码如下:
SqlDataAdapter da = new SqlDataAdapter("select * from GEOGRAPHY",
ConfigurationManager.ConnectionStrings[1].ConnectionString);
DataSet ds = new DataSet();
da.Fill(ds, "geo");
ds.Relations.Add("FatherChild", ds.Tables["geo"].Columns["GEO_ID"],
ds.Tables["geo"].Columns["GEO_PARENT"]);
myTree.DataSource = ds.Tables["geo"].DefaultView;
myTree.RowFilter = "GEO_PARENT is null";
myTree.RelationName = "FatherChild";
myTree.DataBind();
请注意,DataRelation
被赋予了一个名称,该名称通过 RelationName
属性提供给 CustomTreeView
。
在 Web 窗体上
在本例中,我将显示一个包含国家、地区、城市……以及一些示例功能(链接和按钮)的树。要在你的 Web 窗体上使用 CustomTreeView
,你必须首先注册程序集:
<%@ Register tagprefix="WCC"
Namespace="WebCustomControls" Assembly="WebControls"%>
如果你使用 Visual Studio,你必须在你的项目中添加一个对“WebControls.dll”的引用,才能使此行正常工作。然后:
<WCC:CustomTreeView id=myTree runat="server">
<NodeTemplate>
<%# (Container.DataItem as DataRow)["GEO_NAME"]%>
<%# String.Format("<a href=\"http://www.google.com/search?q={0}\"
target=\"_blank\">Google</a>",
(Container.DataItem as DataRowView)[1])%>
<asp:ImageButton runat=server ID=BnSend
ImageUrl="~/IMGs/Valid.gif"
CommandArgument=<%# (Container.DataItem as DataRowView)[0] %>
ImageAlign=AbsMiddle
OnCommand=DoValid />
</NodeTemplate>
</WCC:/NodeTemplate>
使用 NodeTemplate
类,你可以为每个节点添加任何控件。此模板将为每个节点实例化。
为了展开/折叠节点,你必须点击“文件夹”图标(见下文示例)。这让你有机会使用节点内容的“Click”事件。
图像
为了简单起见,这里的图像 URL 是硬编码的。所有图像都位于网站根文件夹下的“IMGs”目录中。你可以通过在 /IMGs 文件夹中提供自己的图像(但保留硬编码的名称)来显示你自己的图像。但请确保保留“menu_bar_invisible.gif”图像不变。如果你决定使用自己的图像,请注意所有图像必须尺寸相同。你还可以添加一些额外的属性,以便宿主 Web 窗体能够动态指定 URL。当你在 NodeTemplate
中添加控件时,请确保用于渲染树分支的图像足够大,否则这些图像之间会出现空白,从而“破坏”树形视图,如下所示:
在本例中,图像的高度只有 16 像素,而按钮至少需要 24 像素。在这里,你必须提供自己尺寸更大的图像。
工作原理
有几点需要解释。
m_lstColumnNeedVisualRendering
我们使用此列表来知道在哪里必须显示“menu_bar.gif”以及在哪里必须显示“menu_bar_invisible.gif”。这两个图像用于在视觉上渲染树(树的分支)。困难之处在于,一旦我们到达了节点的最后一个子节点,即使该最后一个子节点也有子节点,也必须没有分隔条。
在上面的示例中,即使“France”下的子节点也属于“Europe”,“Europe”下的垂直分隔条在到达“France”时就停止了。m_lstColumnNeedVisualRendering
是一个布尔值列表,列表中的每个布尔值对应树结构中的一列。如果列表中位置为 i 的元素为 true
,则显示列 i 的分隔条,否则显示一个“不可见的 GIF”。
m_listOpenedNodes
此列表允许我们知道在用户提交包含 CustomTreeView
的表单时,哪些节点被展开了。这样,我们就可以渲染 CustomTreeView
,使其状态与客户端浏览器上的状态完全相同。在生成的 HTML 中,此列表存储在一个隐藏字段中(<input type=hidden...>
)。当用户展开节点时,JavaScript 客户端代码会将该节点添加到列表中(如果用户随后关闭该节点,则会将其移除)。在回发时,此隐藏字段的内容是通过使用 Request.Form
集合读取的。这是因为隐藏字段的视图状态不由 .NET 处理(因为它是在控件上动态添加的)。
JavaScript
在每个节点下方,所有子节点都放置在一个 SPAN
中。如果节点的 ID 为 i,则 SPAN
的 ID 为“Level_i”。节点的“文件夹”图标的 ID 为“Image_i”。当用户点击一个节点时,JavaScript 函数 ExpandNode
将显示/隐藏合适的 SPAN
,并更改文件夹图标以反映更改。
结论
所以输出如下:
在每个节点旁边,都添加了一个超链接,允许用户显示该项目的 Google 页面。还添加了一个 ImageButton
。点击此按钮将提交 Web 窗体。一旦 Click
事件在服务器端被处理,树形视图将在用户浏览器的相同状态下显示。