ASP.NET的TreeView

 

树是组织信息的强有力工具,软件开发人员手中一定要有一棵称手的“树”。这篇文章以VS2008为例讨论ASP.NET提供的树控件“TreeView”,看看它是否足够强大,能否满足商业应用。

1        首次使用遇到的问题

很简单的,我在页面(WebForm)中添加一个“TreeView”,然后在设计视图里添加了几个静态节点,出于美观的需要,我想把节点用线连接起来,微软果然厚道,通过“自定义行图标”直接设置“LineStyle”为“Solid”,搞定!下面是显示效果:

我和大家一样困惑,为什么实线会断开?然后我开始在网上寻找答案。虽然很多人写了很多关于“TreeView”的技术文章,但我没有找到关于这个问题的讨论。为什么我一开始就遇到的问题这些文章完全没有提及,真想忍不住问一句:“你们真用过TreeView吗?”。没办法,我只有开始了自己的艰难探索之路。花了大半天,终于找到真相。在“.aspx”文件的源视图的顶部,有这样一条语句:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

它是如此的平凡,我一直当它空气。删除这句,然后运行,下面是正常的显示效果:

 

我不明白微软为什么要自动生成这条语句。查了一些资料,有点乱,不能给大家一个说法。总之以后大家养成良好的习惯,每当添加一个新页面(WebForm),随即删除这条语句。

2        动态填充

关于树的一个关键技术是“动态填充”,即在展开节点的时候才创建当前节点的子节点并显示。例如,通过树来显示某家企业的组织机构和机构内的员工(上千条记录),如果采用一次性生成整棵树的方法,势必会产生大量的数据检索操作,累积的时间消耗是不能承受的。改变实现,先生成一级子节点,当用户展开某个节点时再生成当前节点的子节点。让我们看看“TreeView”在这种技术需求上的表现如何。

抽象的思维一下,动态填充的实现思路应该是:单击某个节点左侧的“+”号,页面回发,在服务器端某个“填充”事件被执行,当前节点的子节点被生成。开发中对应的设计过程是:将节点的“PopulateOnDemand”属性设置为True,然后在树的“TreeNodePopulate”事件中编写代码填充当前节点(即被展开节点)。

注意,PopulateOnDemand属性是TreeNode的属性,而不是TreeView的属性。它的意义在于:因为叶子节点不需要填充,所以我们可以去掉叶子节点的“动态填充”特性(即将叶子节点的PopulateOnDemand属性设为False),从而避免一些无谓的处理。以下面的数据源为例:

DeptID     DeptName       ParentDeptID

1              行政部               0

2              人力资源部       0

3              市场部               0

4              技术部               0

5              游戏开发组       4

6              ERP开发组         4

7              核心技术组       4

这是展开前的效果:

 

 

这是完全展开的效果:

 

 

每单击一次“+”号,“TreeNodePopulate”事件都会被执行一次,在执行中根据当前节点的Value属性(对应DeptID)检索节点的子节点并添加到当前节点之下。当页面返回客户端时,就可以看到当前节点的子节点。如果将已填充的节点合上再展开,则不会再次触发“TreeNodePopulate”事件(这样是合理的,但如果能够通过设置某个属性,让开发人员选择是否每次重复填充以满足某些对数据时效性要求很高的需求就更好了)。此时如果点“确定”按钮,等页面再次返回客户端时,树依然是完全展开的,即控件的状态(ViewState)被持续了,这一点很重要。

补充一点,如果将“EnableClientScript”属性设为“False”,则每次点“+”或“-”页面都会回发(还是那句话,如果节点已填充,则不会重复触发“TreeNodePopulate”事件),在服务器端完成折叠与展开的处理。我认为此属性纯属鸡肋,因为Web开发的原则是能在客户端做的事绝不会放到服务器端。至于“PopulateNodesFromClient”属性,我尝试多次,此属性的设置在多种情况下对结果均没有影响,大家可无视。

功能需求升级:在动态填充时要求结合AJAX技术实现部分页更新。AJAX有效的缓解了动态填充时由于页面回发所带来的不良交互体验。可以这样讲,没有AJAX,动态填充几乎没有意义。在页面上分别添加一个ScriptManagerUpdatePanel,然后将TreeView放在UpdatePanel内,大功告成,由TreeView产生的页面回发都将以异步调用的形式“悄悄”的执行。

3        “选择”树

每个节点的左侧会出现一个CheckBox,用户勾选若干节点,然后提交页面,由服务器端对选择的节点进行处理。通过设置TreeViewShowCheckBoxes的属性可以显示CheckBox,除此之外,没有其它支持。但一个显然的技术需求是:勾选父节点,子节点自动选中,此即为CheckBox的级联操作。

解决这个问题首先要清楚由TreeView生成的HTML元素结构,最外层生成一个“div”,每个节点生成一个“table”,如果节点有若干子节点,则紧接一个包含若干“table”的“div”,如此递归生成。对本篇的例子则生成如下结构

<div id=”TreeView1”>

         <table>… </table>  //显示“部门”节点

         <div>

                   <table>… </table>  //显示“行政部”节点

                   <table>… </table>  //显示“人力资源部”节点

     <table>… </table>  //显示“市场部”节点

                   <table>… </table>  //显示“技术部”节点

                   <div>

                             <table>… </table>  //显示“游戏开发组”节点

                             <table>… </table>  //显示“ERP开发组”节点

                                    <table>… </table>  //显示“核心技术组”节点

                   </div>

         </div>

</div>

CheckBox嵌在table元素内,其id按深度优先原则依次为TreeView1n0CheckBoxTreeView1n1CheckBox直至TreeView1n7CheckBox。从这里我们可以看到一个明显的弊端,CheckBoxid线性排列,使我们无法利用id做文章,例如,我想从“ERP开发组”节点的CheckBoxid找到其父节点“技术部”的CheckBoxid是做不到的。Microsoft的这种随意处理应该受到鄙视。对级联操作的实现这位网友给出了一种方法(http://www.cnblogs.com/zhujiahai/archive/2010/03/10/1683066.html),谢谢他的劳动。但这种实现还需要完善,留给有兴趣的朋友。为了增加大家的理解,我再列出其中几句关键的代码:

//获取被点击的CheckBox

obj = window.event.srcElement;

 

//获取包含CheckBoxTable

do {node = node.parentNode;}while (node.tagName != "TABLE")

 

//获取节点的子节点。

if (node.nextSibling != null && node.nextSibling.tagName == "DIV") {…}

 

//获取节点的父节点。

node.parentNode.previousSibling

接下来的问题是如何获取用户选择的节点。首先,如果提交页面,在服务器端通过代码遍历树,通过节点的“Checked”属性获取。但是,在浏览器端如何通过JS脚本获取呢?实现过程如下:

1、  通过TreeViewID获取对应TreeViewHTML元素的最外层DIV

2、  getElementsByTagName获取树内所有的CheckBox

3、  调用CheckBox.nextSibling.innerText获取CheckBox对应的节点的文本(节点的文本内置于Span中,而SpanCheckBox紧邻);

4        “导航”树

树是为导航而生的(所以在VS2008的设计视图中,TreeView放在“导航”栏内),如果TreeView没有为导航提供足够的支持,那它就没有资格称为“树”。每个树节点需要两个信息:导航到的URL(即显示哪个页面),目标窗口(即在哪里显示)。TreeViewTreeNode分别有NavigateUrlTarget属性与之相对。

光说不练,等于没说。按下面步骤,很快就可以体验一把树的导航功能。

1、  新加一个HTML页,添加如下HTML代码:

<frameset cols="30%,70%">

<frame name="tree" src="TreeViewTest.aspx" />

<frame name="content" />

</frameset>

2、  新加一个WebForm,命名为TreeViewTest.aspx

3、  TreeViewTest.aspx的设计视图中加入一个TreeView,添加两个节点,节点1NavigateUrlTarget属性分别为“http://www.sina.com”和“content”,节点2的分别为“http://www.sohu.com”和“content”。

4、  运行HTML页,下面是效果图:

 

当树用于导航时,往往结构相对简单,一般是静态的,例如CSDN论坛左侧的导航树。每个节点不带CheckBox,但每个节点本身是一个链接,点击时,另一侧的区域会显示相应的内容(此即为“导航”)。

功能升级的需求又来了,对每个节点增加显示一个图像,当节点展开或合上时,节点左边的图像会相应的变化(参看CSDN论坛左侧的导航树)。可以分别设置根节点、父节点、叶子节点的图像,但不能根据节点的折叠与展开状态分别设置图像。如果展开或合上节点时页面可以回发,在服务器端可以通过设置TreeNodeImageUrl改变其图像。不过很遗憾,在展开或合上节点时页面是绝对不应该回发的。关于这个问题我没有找到合适的办法,在网上也没有发现相关的讨论,只能留在这里了。

5        实战应用

先根据功能需求总结一下TreeView的表现:

1、  当树用于导航时,出于界面表现的需要,节点左侧需要显示一幅小图片,表示节点的折叠与展开状态(不是“+”与“-”,可参看CSDN论坛左侧的导航树),TreeView不能满足这种需求,也很难通过添加代码达到目的。此时不能采用ASP.NETTreeView

2、  当树用于节点选择时,需要额外编写代码实现CheckBox的级联操作。但网上有成熟的代码实现了这种需求。同时TreeView的动态填充以及与ASP.NETAJAX控件结合都有不错的表现。所以ASP.NETTreeView可以满足这种商业需求。

在我的工作经历中,从来没有使用过TreeView(这也是为什么我不愿意花更多的时间研究TreeView的原因)。事实上,即使对ASP.NET的其它服务器控件(如GridView)的使用也是非常谨慎的,造成这种现象的原因从我对TreeView的讨论可见一斑:Microsoft的技术比较“亲民”(廉价、易上手、快速开发),但在商业应用上有点力不从心。大家对Microsoft技术似乎是选择性使用,拿Web开发来说,选择ASP.NET的基础框架,但在控件使用上却选择第三方的成熟产品。最后一句话总结:从长期的发展考虑,还是应该选择一棵久经市场考验的“树”。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页