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

ASP.NET TreeView 控件与客户端浏览器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (323投票s)

2005年6月18日

10分钟阅读

viewsIcon

1913934

downloadIcon

13940

演示如何从客户端脚本中使用 Microsoft ASP.NET TreeView 控件。

引言

本文演示了如何将 Microsoft 的免费 TreeView 控件与 C# 和 ASP.NET 结合使用,以及如何拦截客户端浏览器上的服务器端事件,通过在客户端处理主要事件来减少回发次数并提高应用程序性能。

在离开本文之前,请提供评分/投票

这是作者们获得他们免费分享给所有人的作品的任何形式认可的唯一方式。看到那些帮助了超过 100,000 人的文章,却只有不到 200 人投票或评分,真是令人悲哀。

背景

Web 开发中常用的一个控件是 TreeView。开发者可以选择使用 DHTML “自己动手”,购买第三方控件,或者使用 Microsoft 为 ASP.NET 提供的免费 TreeView。本文重点介绍如何利用 Microsoft 提供的免费 TreeView 控件。

使用 Microsoft 免费控件的一个问题是缺乏支持,以及该控件旨在利用 ASP.NET 的服务器端事件架构。经验丰富的 Web 开发者力求尽可能减少或消除服务器往返(通常称为回发),以提高最终用户的满意度和响应时间。

本文将演示如何以拦截客户端上典型服务器端事件的方式使用 Microsoft 的 TreeView 控件,而不会丢失功能。本文中的示例是基本的,演示了如何为树视图的每个节点存储“元数据”,这些元数据可以在客户端获取和使用。

本文使用的控件是 Microsoft 的免费下载,控件的概述包含在他们的在线文档中。

什么是 Microsoft TreeView?

树视图提供数据和元数据的面向对象的分层或父/子关系视图。树视图最常见的例子是 Windows 资源管理器的目录结构,其中磁盘驱动器包含文件夹,文件夹包含子文件夹,文件夹包含文件。

MS TreeView 控件公开了几个可用于动态构建树的类。您将使用的两个主要类是 TreeViewTreeNode 类。TreeView 类提供了将容纳 1 到 N 个 TreeNode 的容器。树节点表示父/子结构中的单个项。下面是这两个类的特点简短列表

树视图/节点特性
TreeView 控件是显示父/子关系的容器。
TreeView 控件包含 1 – N 个树节点。
树节点表示父/子关系中的一个元素。
树节点可以是其他树节点的父节点和/或子节点。
具有子节点的树节点可以展开以显示其子节点。
具有子节点的树节点可以折叠以隐藏其子节点。
树节点具有一个名为 NodeData 的属性,可用于存储元数据。
树节点可以显示图像(想想 Windows 资源管理器,其中显示了关闭或打开的文件夹图像)。
树节点可以是指向其他网页或网站的超链接。

NodeData 属性是什么?

TreeNode 类提供了一个名为 NodeData 的属性。这个属性是存储您可能需要在客户端或服务器上访问的元数据的好地方。

问题是该属性的底层类型是字符串。那么如何在该属性中存储多条元数据呢?我发现最简单的方法是创建一个由键值对组成的分隔列表,这些键值对可以在客户端或服务器上访问。我通常使用分号 (;) 作为主要分隔符,并用等号 (=) 分隔键和值。例如,我会将元数据存储在树节点的 NodeData 属性中,如下所示

TreeNode tn = new TreeNode();

tn.Text = "Root Parent Node";

tn.NodeData = "Id=1000;Name=Mike Elliott;Article=ASP.NET " + 
               "Tree View Control & the Client's Browser";

您可以通过访问元素的 nodeData 属性来获取客户端上 NodeData 属性的信息。这将允许您获取用户选择的树节点的详细信息,并根据需要显示或提交信息。这一单一功能可以减少大部分由于需要与最终用户选择的节点相关的元数据而导致的回发或往返(这将在本文后面演示)。

您可以选择使用 XML 而不是键值对,但出于性能考虑,我更喜欢使用键值对,因为它们存储在数组中。

准备构建示例

在深入示例之前,我想提供一些设置信息,以防您想跟着做。

第一步是下载 Microsoft 的免费控件套件。该控件套件不仅包含 TreeView;但是,本文中我不会介绍 Tab 控件。从本文前面列出的 URL 下载后,您必须按照下载中获得的 readme 文件中的说明,构建或运行 build.bat 文件以生成 Microsoft.Web.UI.WebControls 程序集。

请做好准备,随免费下载分发的 .bat 文件有一个 bug,可能会(大多数情况下)阻止 .dll 的生成,并且没有给出任何警告或异常消息。

用记事本打开 build.bat 文件,找到以下行

csc.exe /out:build\Microsoft.Web.UI.WebControls.dll @IEWebControls.rsp

除非您将 csc.exe 编译工具放在主路径中,或者将可执行文件复制到 systemsystem32 目录中,否则 .bat 文件将找不到编译工具并静默失败。

为了纠正这个问题,修改 .bat 文件,使其包含 csc.exe 编译工具的完整路径,如下所示(您的机器上位置和版本可能不同)

C:\Windows\Microsoft.NET\Framework\v1.14322\csc.exe 
    /out:build\Microsoft.Web.UI.WebControls.dll @IEWebControls.rsp

这应该可以纠正构建问题,并允许生成 Microsoft.Web.UI.WebControls.dll,正如下载中的 readme 文件所指示的那样。按照 readme 文件中详细的安装说明完成安装。

正确安装控件库后,创建一个新的 ASP.NET Web 应用程序,并添加对新创建的 Microsoft.Web.UI.WebControls.dll 的引用。接下来,将一个名为 TVExample.aspx 的 Web 窗体添加到新的 Web 项目中。现在您就可以复制和粘贴文章中描述的代码了。

客户端事件

我将演示如何在客户端拦截以下事件,而不是允许服务器端回发。

事件 描述
onSelectedIndexChange 当最终用户点击树视图中的节点时触发的事件。
onExpand 当最终用户展开节点时触发的事件。
onCollapse 当最终用户折叠节点时触发的事件。
onDblClick 当最终用户双击节点时触发的事件。
onContextMenu

当最终用户右键点击树视图中的节点时触发的事件。

您将不得不抑制典型的右键上下文菜单的显示,并用您的功能替换它。

这些是您需要拦截的最常见事件,以防止固有的服务器端事件或回发发生。这应该会极大地提高最终用户的体验并缩短响应时间。它还将通过将大部分工作推送到客户端机器来减少 Web 服务器的负载。

以下示例将显示被拦截的事件,如何从树节点的 NodeData 属性(在客户端)获取元数据,以及如何从 NodeData 属性解析键值对信息。

开始编码

在深入代码之前,我想强调一点:ASP.NET 通过 IAttributeAccessor 接口提供对大多数 HTML 和 JavaScript 属性的访问。HtmlControlListItemUserControlWebControl 类型包含此接口的内在实现。此接口允许以编程方式访问服务器控件标签中定义的任何属性。这是一个很棒的功能,允许您通过将事件指向自己的 JavaScript 函数来拦截服务器端事件,正如您将在本文后面看到的那样。

我们将用于拦截通常是服务器端事件的 HTML 和 JavaScript 代码非常直观且应该是不言自明的,但这里有一些您应该注意的要点

  • 我们在 HTML 页面中注册 Microsoft.Web.UI.WebControls。这使我们能够声明性地创建树视图
    @Register TagPrefix=”iewc” 
        Namespace=”Microsoft.Web.UI.WebControls” 
        Assembly=”Microsoft.Web.UI.WebControls”
  • 对于我们在客户端拦截的每个事件,都会调用一个 JavaScript 函数
       // Intercepts the index changed event on the client.
       //  
       function TVIndexChanged()
       {
           ChangeText( 'node changed' );
       }
       
       // Intercepts the expand event on the client.
       //    
       function TVNodeExpand()
       {
           ChangeText( 'onexpand' );
       }
       
       // Intercepts the collapse event on the client.
       //
       function TVNodeCollapse()
       {
           ChangeText( 'oncollapse' );
       }
       
       // Intercepts the double-click event on the client.
       //    
       function TVDoubleClick()
       {
           ChangeText( 'dblclick' );
       }
       
       // Intercepts the right-click event on the client. The selected 
       // tree node is changed to the node over which the end user has 
       // right-clicked so that, that node's meta-data can be obtained.
       //
       function TVRightClick()
       {
           // Get a handle to the tree view.
           var tree = GetTreeHandle();
           var treenode;
       
           if ( null == tree || undefined == tree )
               return;
       
           // Get the selected tree node.
           treenode = tree.getTreeNode( event.treeNodeIndex );  
       
           if ( null == treenode || undefined == treenode )
               return;
       
           // Cause the tree node that was right-clicked on to become the 
           // selected node.  
           tree.selectedNodeIndex = event.treeNodeIndex;   
          
           ChangeText( 'oncontextmenu' );
       }
  • 有一个 JavaScript 函数允许我们获取 TreeView 控件本身的句柄
       // Gets a handle to the TreeView.
       //
       function GetTreeHandle()
       {
           var tree;
           var treeName = 'tvControl';
          
           // Get a handle to the TreeView.
           tree = document.getElementById( treeName );
       
           if ( null == tree || undefined == tree )
               return null;
       
           return tree;
       }
  • 有一个 JavaScript 函数允许我们获取最终用户选择的树节点对象的句柄
       // Gets a handle to the TreeView's selected node.
       //
       function GetSelectedNode()
       {
           var tree = GetTreeHandle();
           var treeNode;
       
           if ( null == tree || undefined == tree )
               return null;
       
           treeNode = tree.getTreeNode( tree.selectedNodeIndex );  
        
           if ( null == treeNode || undefined == treeNode )
               return null;
       
           return treeNode;
       }
  • 有一个 JavaScript 函数用于检索 NodeData 属性的信息并返回传入键的值。这是最重要的函数之一,它允许您在客户端检索元数据并根据需要使用它
       // Gets the value of the searchKey from the NodeData 
       // of a TreeNode.
       //
       function GetKeyValue( searchKey )
       {   
           // Get a handle to the selected TreeNode.
           var treenode = GetSelectedNode();
         
           // Validate the node handle.
           if ( null == treenode || undefined == treenode )
               return null;
       
           // Get the node's NodeData property's value.
           var nodeDataAry = treenode.getAttribute( 'nodeData' );
       
           if ( null == nodeDataAry || undefined == nodeDataAry )
               return null;
        
           nodeDataAry = nodeDataAry.split( ';' );
       
           if ( null == nodeDataAry || undefined == nodeDataAry || 
             0 >= nodeDataAry.length )
               return null;
        
           var count = 0;
           var returnValue = null;
       
           while ( count < nodeDataAry.length )
           {
               var workingItem = nodeDataAry[ count ];
        
               if ( 0 >= workingItem.length )
               {
                   count++;
                   continue;
               }
         
               // Split the string into its key value pairs.
               var kv = workingItem.split( '=' );
       
               if ( 1 >= kv.length )
               {
                   count++;
                   continue;
               }
         
               var key = kv[ 0 ];
               var kValue = kv[ 1 ];
       
               if ( key != searchKey )
               {
                   count++;
                   continue;
               }
       
               returnValue = kValue;
               break;
           }       
        
           return returnValue;
       }

完整的 TVExample.aspx 代码集

<%@ Page language="c#" Codebehind="TVExample.aspx.cs" 
    AutoEventWireup="false" 
    Inherits="Mike.Elliott.Articles.TreeView.TVExample" %>
<%@ Register TagPrefix="iewc" 
    Namespace="Microsoft.Web.UI.WebControls" 
    Assembly="Microsoft.Web.UI.WebControls" %>

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 

  <html>
     <head>
       <title>Tree View Example</title>
       <meta name="GENERATOR" 
           Content="Microsoft Visual Studio .NET 7.1">
       <meta name="CODE_LANGUAGE" Content="C#">
       <meta name=vs_defaultClientScript content="JavaScript">
       <meta name=vs_targetSchema 
         content="http://schemas.microsoft.com/intellisense/ie5">
     </head>
   <body MS_POSITIONING="GridLayout">
   
   <script language="javascript">
   // Intercepts the index changed event on the client.
   //  
   function TVIndexChanged()
   {
       ChangeText( 'node changed' );
   }
   
   // Intercepts the expand event on the client.
   //    
   function TVNodeExpand()
   {
       ChangeText( 'onexpand' );
   }
   
   // Intercepts the collapse event on the client.
   //
   function TVNodeCollapse()
   {
       ChangeText( 'oncollapse' );
   }
   
   // Intercepts the double-click event on the client.
   //    
   function TVDoubleClick()
   {
       ChangeText( 'dblclick' );
   }
   
   // Intercepts the right-click event 
   // on the client. The selected 
   // tree node is changed to the node 
   // over which the end user has 
   // right-clicked so that, that node's 
   // meta-data can be obtained.
   //
   function TVRightClick()
   {
       // Get a handle to the tree view. 
       var tree = GetTreeHandle();
       var treenode;
   
       if ( null == tree || undefined == tree )
           return;
   
       // Get the selected node.
       treenode = tree.getTreeNode( event.treeNodeIndex );  
   
       if ( null == treenode || undefined == treenode )
           return;
   
       // Cause the tree node that was 
       // right-clicked on to become the 
       // selected node.  
       tree.selectedNodeIndex = event.treeNodeIndex;   
      
       ChangeText( 'oncontextmenu' );
   }
        
   // Simply changes the information 
   // in the display text boxes to 
   // demonstrate how to obtain meta-data 
   // from the selected node's 
   // NodeData property on the client.
   //
   function ChangeText( eventName )
   {
       var treeNode = GetSelectedNode();
       
       if ( null == treeNode || undefined == treeNode )
       {
           return;
       }     
       
       var nodeData = 
          treeNode.getAttribute( 'nodeData' ).split( ';' );          
           
       var id = GetKeyValue( 'SomeId' );
       var name = GetKeyValue( 'Name' );
                            
       document.getElementById( 'txtEvent' ).value = 
                                            eventName;
       document.getElementById( 'txtId' ).value = id;
       document.getElementById( 'txtName' ).value = name;
   }
   
   // Gets the value of the searchKey 
   // from the NodeData of a TreeNode.
   //
   function GetKeyValue( searchKey )
   {   
       // Get a handle to the selected TreeNode.
       var treenode = GetSelectedNode();
     
       // Validate the node handle.
       if ( null == treenode || undefined == treenode )
           return null;
   
       // Get the node's NodeData property's value.
       var nodeDataAry = treenode.getAttribute( 'nodeData' );
   
       if ( null == nodeDataAry || undefined == nodeDataAry )
           return null;
    
       nodeDataAry = nodeDataAry.split( ';' );
   
       if ( null == nodeDataAry || undefined == nodeDataAry || 
         0 >= nodeDataAry.length )
           return null;
    
       var count = 0;
       var returnValue = null;
   
       while ( count < nodeDataAry.length )
       {
           var workingItem = nodeDataAry[ count ];
    
           if ( 0 >= workingItem.length )
           {
               count++;
               continue;
           }
     
           // Split the string into its key value pairs.
           var kv = workingItem.split( '=' );
   
           if ( 1 >= kv.length )
           {
               count++;
               continue;
           }
     
           var key = kv[ 0 ];
           var kValue = kv[ 1 ];
   
           if ( key != searchKey )
           {
               count++;
               continue;
           }
   
           returnValue = kValue;
           break;
       }       
    
       return returnValue;
   }
    
   // Gets a handle to the TreeView.
   //
   function GetTreeHandle()
   {
       var tree;
       var treeName = 'tvControl';
      
       // Get a handle to the TreeView.
       tree = document.getElementById( treeName );
   
       if ( null == tree || undefined == tree )
           return null;
   
       return tree;
   }     
   
   // Gets a handle to the TreeView's selected node.
   //
   function GetSelectedNode()
   {
       var tree = GetTreeHandle();
       var treeNode;
   
       if ( null == tree || undefined == tree )
           return null;
   
       treeNode = tree.getTreeNode( tree.selectedNodeIndex );  
    
       if ( null == treeNode || undefined == treeNode )
           return null;
   
       return treeNode;
   }   
   </script>
   
   <form id="frmTVExample" method="post" runat="server">
       <table width="100%" align="center" border="0">
           <tr><td>
             <iewc:treeview id="tvControl" runat="server" 
                 SystemImagesPath=
                       "/webctrl_client/1_0/treeimages/" 
                 EnableViewState="False">
             </iewc:treeview>
           </td></tr>
           <tr>
           <td><input type="text" id="txtId" name="txtId">
           </td></tr>
           <tr>
           <td><input type="text" id="txtName" name="txtName">
           </td></tr>
           <tr>
           <td><INPUT type="text" id="txtEvent" name="txtEvent">
           </td> </tr>
       </table>
   </form>
   
   </body>
   </html>

TVExample.aspx.cs 或代码后台(此部分不包含 ASP.NET 为您创建的 OnInitInitializeComponent 方法,因此请勿删除这些方法或其内容。它也不包含所有 using 指令,因此请勿删除为您创建的 using 指令,但您应该按照示例所示添加 using Microsoft.Web.UI.WebControls 指令)

   using Microsoft.Web.UI.WebControls;
   
   namespace Mike.Elliott.Articles.TreeView
   {
       public class TVExample : System.Web.UI.Page
       {
           protected Microsoft.Web.UI.WebControls.TreeView tvControl;
   
           private void Page_Load( object sender, System.EventArgs e )
           {
               // Create the root tree node.
               TreeNode root = new TreeNode();
               root.Text = "Root Parent Node";
               root.NodeData = "SomeId=1000;Name=Mike Elliott";
   
               // Create a child node.
               TreeNode tn = new TreeNode();
               tn.Text = "Child 1 of Root Parent";
               tn.NodeData = "SomeId=1001;Name=Play For Sport, Inc.";
   
               // Add the child to the root node.
               root.Nodes.Add( tn );
   
               // Create another child node.
               tn = new TreeNode();
               tn.Text = "Child 2 or Root Parent";
               tn.NodeData = "SomeId=1002;Name=Chip Oxendine";
    
               // Create a grandchild node and add it to its parent.
               TreeNode cn = new TreeNode();
               cn.Text = "Grandchild of Root Parent";
               cn.NodeData = "SomeId=1003;Name=Mike Elliott";
               tn.Nodes.Add( cn );
   
               // Add the child to the root node.
               root.Nodes.Add( tn );
               root.Expanded = true;
   
               // Add all the nodes to the tree view.
               this.tvControl.Nodes.Add( root );
   
               this.OverRideServerEvents();
           }
   
           private void OverRideServerEvents()
           {
               // Create and wire up the javascript event handlers.
               //
               string clickHandler = "TVIndexChanged();";
               this.tvControl.Attributes.Add( "onselectedindexchange", 
                                                         clickHandler );
   
               clickHandler = "TVNodeExpand();";
               this.tvControl.Attributes.Add( "onexpand", 
                                              clickHandler );
   
               clickHandler = "TVNodeCollapse();";
               this.tvControl.Attributes.Add( "oncollapse", 
                                              clickHandler );
   
               clickHandler = "TVDoubleClick();";
               this.tvControl.Attributes.Add( "ondblclick", 
                                              clickHandler );
   
               clickHandler = "TVRightClick();";
               this.tvControl.Attributes.Add( "oncontextmenu", 
                                                  clickHandler );
           }
       }
   }

Page_Load 回调、事件或方法是在页面加载时调用的方法。在此方法中,我们只是创建树节点,提供节点的文本,以键/值对分隔字符串的形式为节点的 NodeData 属性提供元数据,将节点添加到其父节点,然后最终将根节点添加到树视图中。

接下来,Page_Load 方法将连接 JavaScript 拦截函数的工作委托给 OverRideServerEvents 方法。正如本文前面提到的,ASP.NET 提供了一种机制,允许您向服务器控件添加属性。此方法只是为我们要在客户端拦截的每个事件向 TreeView 控件添加属性。我们添加属性,这是树视图服务器端事件的正式名称,然后将事件重定向到我们的 JavaScript 函数,就是这样。

当您触发任何事件时,这些事件会被我们的 JavaScript 函数拦截,目标树节点的元数据被解析并显示在文本框中,并且还会显示被拦截事件的名称,以便您清楚地看到发生了什么。

发生了什么?

步骤 描述
1 页面已加载。
2 树视图已创建。
3 已创建树节点。
4 树节点的 TestNodeData 属性已设置。
5 树节点已添加到其父节点的 Node 集合中。
6 根树节点已添加到树视图的 Node 集合中。

尽管本文没有涉及,但您可以通过存储每个树节点的状态(展开/折叠)来完全管理客户端上的树视图状态。这是您需要做的事情,以完全将树视图的功能推送到客户端。否则,一旦您导致回发,您将丢失哪些节点展开、哪些节点折叠以及哪些节点被选择的状态。这是一个稍微复杂一些的主题,我将在后续文章中介绍。

摘要

将繁重的服务器端事件移至客户端,以简化使用免费 MS TreeView 控件的 ASP.NET 应用程序是非常可行的。它非常直接,并且这些概念可以推广到 ASP.NET 中包含的大多数内在服务器控件。

历史

  • 版本 1.0.0.0
© . All rights reserved.