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

Tree Check List ASP.NET Web 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (11投票s)

2006 年 4 月 21 日

CPOL

14分钟阅读

viewsIcon

104592

downloadIcon

416

支持单个或类别选择、自定义样式和搜索功能的 Tree Web 控件。

引言

TreeCheckList Web 控件简单而有效,可用于显示、搜索和选择分层数据,支持单独选择或按类别选择。树的外观高度可定制,因为所有内容都通过 CSS 样式支持。该控件完全跨浏览器兼容,并且易于实现。数据元素可以单独加载,也可以通过将 XML 文档分配给控件来加载。客户端 JavaScript 对象类支持所有 UI 交互,并提供搜索功能,以帮助在较大的数据集***找到特定元素。

我开发此控件是因为我想要一种轻松选择数据类别的方法,同时还能在类别中单独选择项目,并仍保持类别的选择状态。这通常以前通过多个列表框来处理,因为大多数树控件都不提供各级别父子节点之间的选择关系。我不想过度复杂化设计,也不想 hack 标准的树控件,所以将此控件组合起来以实现这种特定的选择类型。

演示快速入门

要运行演示,只需下载 zip 文件并按照以下步骤操作

  1. 创建一个名为 TreeCheck 的新 Web 应用程序。
  2. 将演示文件解压到 TreeCheck 目录(如果需要,请包含到项目中)。
  3. 从本地 Web 访问 TreeDemo.aspx 文件(例如,https:///TreeCheck/TreeDemo.aspx)。

演示提供了两个树形列表的示例,展示了如何使用 XML 数据源和 CSS 来创建显示。所有源代码均包含在演示中。

树设计架构

TreeCheckList 的布局非常简单,HTML 输出只是嵌套的 div 元素来表示树结构的级别。树控件本身是一个 div 元素,每个节点都是一个 div 元素,用“p”或“c”表示,表明它是父节点还是子节点。树的 HTML 输出结构如下所示:

<div id="treeList" style="margin:0; padding:0;">
<div id="pNode1_0"><input id="pNode1_0_chk" type="checkbox"/>
<span>First Level Node 0</span>
<div id="i_pNode1_0" style="display:none; visibility:hidden;">
<div id="cNode2_0"><span>Second Level Node 0</span></div>
<div id="cNode2_1"><span>Second Level Node 1</span></div>
</div>
</div>
<div id="pNode1_1"><input id="pNode1_1_chk" type="checkbox"/>
<span>First Level Node 1</span>
<div id="i_pNode1_1" style="display:none; visibility:hidden;">
<div id="cNode3_0"><span>Second Level Node 0</span></div>
<div id="pNode3_1"><input id="pNode3_1_chk" type="checkbox"/>
<span>Second Level Node 1</span>
<div id="i_pNode3_1" style="display:none; visibility:hidden;">
<div id="cNode4_0"><span>Third Level Node 0</span></div>
</div>
</div>
<div id="cNode3_2"><span>Second Level Node 2</span></div>
</div>
</div>
<div id="pNode1_2"><input id="pNode1_2_chk" type="checkbox"/>
<span>First Level Node 2</span>
<div id="i_pNode1_2" style="display:none; visibility:hidden;">
<div id="cNode5_0"><span>Second Level Node 0</span></div>
</div>
</div>
</div>

注意:在当前版本的代码中,已从 ID 中删除了“Node”一词,以减小树的 HTML 大小。)

请注意,在每个 pNode 中,都有一个 div 元素“i_pNode”。这是子节点的组索引元素,根据用户展开或折叠的树级别,该元素可以隐藏或可见。每个节点的文本由 span 元素表示,所有父节点都包含一个复选框输入元素。

由于树仅仅是 divspan 元素的集合,因此所有元素都可以轻松地通过样式进行控制。大多数样式信息由 JavaScript 代码应用,以减少每个树的下载文本量。如演示所示,每个树都可以有自己的样式,因此单个页面上的多个树可以看起来完全不同。

服务器代码概述

树控件的基类是 TreeCheckListTreeCheckList.cs)。这是渲染树的 div 元素结构并创建客户端 JavaScript 对象引用以及所有 CSS 和图像参数的代码。TreeCheckList 类的公共属性用于设置所有显示参数和样式。

TreeListCssClass

主树 div 元素的 CSS 类。

TreeListCssStyle

主树 div 元素的 CSS 样式。

TreeLevelCssClass

每个树级别的 CSS 类前缀(附加级别编号)。

TreeLevelClassOn

如果使用 TreeLevelCssClass,则为 true。默认值为 true

   

ImageCollapseURL

展开的父节点(例如,减号)的图像 URL。

ImageExpandURL

折叠的父节点的图像 URL(例如,加号)。

ImageJoinURL

子节点的图像 URL。

ImageTeeURL

无子节点的一级节点的图像 URL。

ImageTopCollapseURL

展开的一级顶层父节点的图像 URL。

ImageTopExpandURL

折叠的一级顶层父节点的图像 URL。

ImageTopNoExpandURL

一级顶层子节点的图像 URL。

   

ExpandTreeList

如果树最初显示为展开状态,则为 true。默认值为 false

NodeImagePosition

TreeCheckListImagePosition 枚举设置,用于定位节点图像。

IndentOn

目前未实现。

   

ParentNodeLabelCssClass

普通父节点标签的 CSS 类。

ParentNodeLabelCssStyle

普通父节点标签的 CSS 样式。

ParentNodeSelectedCssClass

选定父节点标签的 CSS 类。

ParentNodeMouseOverNormalCssClass

鼠标悬停在普通父节点标签上的 CSS 类。

ParentNodeMouseOverSelectedCssClass

鼠标悬停在选定父节点标签上的 CSS 类。

ParentNodeSearchMouseOverNormalCssClass

鼠标悬停在搜索结果普通父节点标签上的 CSS 类。

ParentNodeSearchMouseOverSelectedCssClass

鼠标悬停在搜索结果选定父节点标签上的 CSS 类。

ParentNodeSearchNormalCssClass

搜索结果普通父节点标签的 CSS 类。

ParentNodeSearchSelectedCssClass

搜索结果选定父节点标签的 CSS 类。

   

ChildLeftIndent

指定子级别缩进的边距(以像素为单位)。

ChildNodeLabelCssClass

普通子节点标签的 CSS 类。

ChildNodeLabelCssStyle

普通子节点标签的 CSS 样式。

ChildNodeSelectedCssClass

选定子节点标签的 CSS 类。

ChildNodeMouseOverNormalCssClass

鼠标悬停在普通子节点标签上的 CSS 类。

ChildNodeMouseOverSelectedCssClass

鼠标悬停在选定子节点标签上的 CSS 类。

ChildNodeSearchMouseOverNormalCssClass

鼠标悬停在搜索结果普通子节点标签上的 CSS 类。

ChildNodeSearchMouseOverSelectedCssClass

鼠标悬停在搜索结果选定子节点标签上的 CSS 类。

ChildNodeSearchNormalCssClass

搜索结果普通子节点标签的 CSS 类。

ChildNodeSearchSelectedCssClass

搜索结果选定子节点标签的 CSS 类。

以下属性函数允许在节点级别进行自定义鼠标处理。如果需要,可以使用 MouseClick 函数为每个节点提供回发事件。

ParentNodeMouseClickFunction

父节点鼠标单击时执行的脚本函数命令。

ParentNodeMouseOutFunction

父节点鼠标移出时执行的脚本函数命令。

ParentNodeMouseOverFunction

父节点鼠标悬停时执行的脚本函数命令。

ChildNodeMouseClickFunction

子节点鼠标单击时执行的脚本函数命令。

ChildNodeMouseOutFunction

子节点鼠标移出时执行的脚本函数命令。

ChildNodeMouseOverFunction

子节点鼠标悬停时执行的脚本函数命令。

要将数据分配给树控件,目前有三种公共方法:

public int AddNode(string pID, string pLabel, string pParent, string imageUrl)

此方法用于一次添加一个节点。pID 参数表示唯一的节点 ID;pLabel 是显示的文本;父节点 ID 由 pParent 指示,如果节点位于第一级,则为空字符串;imageUrl 参数指示要为此节点显示的图像。返回值是节点位置,如果节点 ID 不唯一,则返回 -1。

public void AddNodes(DataTable Nodes, string FieldID, 
                     string FieldLabel, string pParent)

此方法使用 DataTable 填充具有单个父节点的节点组。FieldIDFieldLabel 参数指定 DataTable 中包含这些值的列的名称。pParent 参数指示父节点的节点 ID。此方法尚不支持节点的图像,因为我将其用于特定实现,但可以轻松添加一个列来容纳图像 URL。

public int LoadXml(XmlDocument xmlDoc, string attributeName)

此方法很可能将是最常用且最简单的数据填充树的方式。作为 XmlDocument 传入的 XML 文件可以表示整个树及其所有相关数据。attributeName 参数用于指示 XML 中表示节点显示文本的每个级别的属性。因此,可以使用相同的 XML 文件来表示数据的两种不同视图。使用此方法加载树时,每个节点的 ID 会根据位置自动分配。

TreeCheckList 类内部,树的节点由 ArrayList 表示。ArrayList _nodeIDs 包含每个节点的唯一标识符。另一个 ArrayList_nodeParent,包含父子关系。其他 ArrayList 包含每个节点的显示文本和图像。渲染树时,会遍历 ArrayList 来构建 div 元素。此渲染的主要功能在递归的 WriteNode 方法中完成。

protected int WriteNode(HtmlTextWriter output, string nodeID, object node,
                        int level, int pos, bool bHasChildren, string pDivID)
{
    int nChildCount = 1;
    string divID = this.ClientID + "_";
    divID += (bHasChildren) ? "p" : "c";
    divID += level.ToString() + "_" + pos.ToString();
    output.Write("<div");
    if (_childLeftIndent != 0)
    {
        string indent = "margin-left: " + (_childLeftIndent*(level-1)).ToString() + "px; ";
        output.WriteAttribute("style", indent);
    }
    if (bHasChildren)
    {
        output.WriteAttribute("id", divID);
        output.WriteAttribute("value", nodeID);
        if (_useTreeLevelClass && (_treeLevelCssClass != ""))
            output.WriteAttribute("class", _treeLevelCssClass + level.ToString());
        output.Write(">");
        if ((level==1) && (pos==0))
            RenderExpandImage(output, divID, _imageTopExpand);
        else
            RenderExpandImage(output, divID, _imageExpand);
        if (_imagePosition == TreeCheckListImagePosition.ImageBeforeCheck)
            RenderNodeImage(output, (string)_nodeImages[pos]);
        RenderCheckBox(output, divID);
    }
    else
    {
        output.WriteAttribute("id", divID);
        output.WriteAttribute("value", nodeID);
        if (_useTreeLevelClass && (_treeLevelCssClass != ""))
            output.WriteAttribute("class", _treeLevelCssClass + level.ToString());
        output.Write(">");
        if (level==1)
        {
            string img = (pos==0) ? _imageTopTee : _imageTee;
            if (img != "")
            {
                output.Write("<img");
                output.WriteAttribute("align", "absmiddle");
                output.WriteAttribute("src", img);
                output.Write("/>");
            }
        }
        else if (_imageJoin != "")
        {
            output.Write("<img");
            output.WriteAttribute("align", "absmiddle");
            output.WriteAttribute("src", _imageJoin);
            output.WriteAttribute("border", "0");
            output.Write("/>");
        }
    }
    if (_imagePosition == TreeCheckListImagePosition.ImageAfterCheck)
        RenderNodeImage(output, (string)_nodeImages[pos]);
    output.Write("<span");
    output.WriteAttribute("id", divID + "_lbl");
    if (bHasChildren)
    {
        if (_pnodeLabelStyle != "")
            output.WriteAttribute("style", _pnodeLabelStyle);
        if (_pnodeMouseOverFn != "")
            output.WriteAttribute("onmouseover", _pnodeMouseOverFn);
        if (_pnodeMouseOutFn != "")
            output.WriteAttribute("onmouseout", _pnodeMouseOutFn);
        if (_pnodeMouseClickFn != "")
            output.WriteAttribute("onclick", _pnodeMouseClickFn);
    }
    else
    {
        if (_cnodeLabelStyle != "")
            output.WriteAttribute("style", _cnodeLabelStyle);
        if (_cnodeMouseOverFn != "")
            output.WriteAttribute("onmouseover", _cnodeMouseOverFn);
        if (_cnodeMouseOutFn != "")
            output.WriteAttribute("onmouseout", _cnodeMouseOutFn);
        if (_cnodeMouseClickFn != "")
            output.WriteAttribute("onclick", _cnodeMouseClickFn);
    }
    output.Write(">");
    output.Write((string)node);
    output.Write("</span>");
    if (_imagePosition == TreeCheckListImagePosition.ImageAfterLabel)
        RenderNodeImage(output, (string)_nodeImages[pos]);
    if (bHasChildren)
    {
        output.Write("<div");
        output.WriteAttribute("id", divID + "_i");
        if (!_expandTreeList)
            output.WriteAttribute("style", "display:none; visibility:hidden;");
        output.Write(">");
        for (int i=pos+1; i<_nodeIDs.Count; i++)
        {
            if ((string)_nodeParent[i] == nodeID)
            {
                nChildCount += WriteNode(output, (string)_nodeIDs[i], 
                  _nodeLabels[i], level+1, i, 
                  _nodeParent.Contains(_nodeIDs[i]), divID);
            }
        }
        output.Write("</div>");
    }

    output.Write("</div>");
    return nChildCount;
}

TreeCheckList 类还在 PreRender 方法中写入必要的 JavaScript 脚本块,并创建特定于树的客户端对象类。所有 CSS 类设置都将传递给子对象类。

protected string ClientInitializeScript()
{
    string sId = this.ClientID;
    string sConstruct = string.Format(
        "var tree_obj_{0} = new CheckTree('{0}', '{1}', '{2}', '{3}', " + 
        "'{4}', '{5}', '{6}', '{7}', '{8}', '{9}', '{10}', '{11}', " + 
        "'{12}', '{13}', '{14}', '{15}', '{16}', '{17}', '{18}', '{19}', '{20}');"
        , sId
        , _imageTopExpand
        , _imageTopCollapse
        , _imageExpand
        , _imageCollapse
        , _pnodeLabelClass
        , _pnodeSelectedClass
        , _pnodeNormalOverClass
        , _pnodeSelectOverClass
        , _pnodeSearchNormalClass
        , _pnodeSearchSelectClass
        , _pnodeSearchNormalOver
        , _pnodeSearchSelectOver
        , _cnodeLabelClass
        , _cnodeSelectedClass
        , _cnodeOverNormalClass
        , _cnodeOverSelectClass
        , _cnodeSearchNormalClass
        , _cnodeSearchSelectClass
        , _cnodeSearchNormalOver
        , _cnodeSearchSelectOver
        );
    return string.Format(GetScriptTemplate(), sConstruct);
}

使用 TreeCheckList 类

在我的演示代码中,我创建了两个树控件的示例。我在其他 WebControl 对象中使用 TreeCheckList 控件类来提供更多隔离和抽象,并且还展示了如何创建嵌入了树的自定义、可重用控件。

TreeListControl1.ascxTreeListControl2.ascx 是嵌入 TreeCheckList 控件的两个用户控件。检查这些控件的基本 HTML 会发现它们是非常简单的容器。

<%@ Control Language="c#" AutoEventWireup="false" 
   Codebehind="TreeListControl2.ascx.cs" Inherits="TreeCheck.TreeListControl2" 
   TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<div id="Tree2">
<div id="TreeList2">
<div id="TreeCtrl2">
<div id="Tree2Top">
<div id="Tree2Header"></div>
<div class="treeSearch2" id="TreeSearch2" runat="server"></div>
</div>
<div id="TreeArea2" runat="server" class="TreeArea2">
</div>
<div class="treeInfo2" id="TreeInfo2" runat="server"></div>
</div>
</div>
</div>

代码隐藏的简单性显而易见,这里唯一发生的事情是添加了树控件,并且将一些控件定位在这个外部控件中以提供搜索界面。

private void Page_Load(object sender, System.EventArgs e)
{
    /* Create a new tree and set properties */
    TreeCheckList tcl = new TreeCheckList();
    tcl.ImageTopExpandURL = "tree_arrow_expand2.gif";
    tcl.ImageTopCollapseURL = "tree_arrow_collapse2.gif";
    tcl.ImageExpandURL = "tree_arrow_expand2.gif";
    tcl.ImageCollapseURL = "tree_arrow_collapse2.gif";
    tcl.ImageJoinURL = "tree_join.gif";
    tcl.ImageTopNoExpandURL = "";
    tcl.ImageTeeURL = "";

    tcl.NodeImagePosition = TreeCheckListImagePosition.ImageAfterCheck;
    tcl.TreeListCssClass = "T2_tree";
    tcl.TreeLevelCssClass = "T2_level";
    tcl.ParentNodeLabelCssClass = "T2_treeParentLabel";
    tcl.ParentNodeSelectedCssClass = "T2_treeParentSel";
    tcl.ParentNodeMouseOverNormalCssClass = "T2_treeParentNormalOver";
    tcl.ParentNodeMouseOverSelectedCssClass = "T2_treeParentSelectOver";
    tcl.ParentNodeSearchNormalCssClass = "T2_treeParentSearchNormal";
    tcl.ParentNodeSearchSelectedCssClass = "T2_treeParentSearchSelect";
    tcl.ParentNodeSearchMouseOverNormalCssClass = "T2_treeParentSearchNormalOver";
    tcl.ParentNodeSearchMouseOverSelectedCssClass = "T2_treeParentSearchSelectOver";
    tcl.ChildNodeLabelCssClass = "T2_treeChildLabel";
    tcl.ChildNodeSelectedCssClass = "T2_treeChildSel";
    tcl.ChildNodeMouseOverNormalCssClass = "T2_treeChildMouseOverNormal";
    tcl.ChildNodeMouseOverSelectedCssClass = "T2_treeChildMouseOverSelect";
    tcl.ChildNodeSearchNormalCssClass = "T2_treeChildSearchNormal";
    tcl.ChildNodeSearchSelectedCssClass = "T2_treeChildSearchSelect";
    tcl.ChildNodeSearchMouseOverNormalCssClass = "T2_treeChildSearchNormalOver";
    tcl.ChildNodeSearchMouseOverSelectedCssClass = "T2_treeChildSearchSelectOver";
    tcl.ChildLeftIndent = 0;

    /* Load the data into the tree from XML file */
    tcl.LoadXml(xmlDoc, xmlAttribute);
    /* Add the tree to this control */
    string msg = "";
    try
    {
        TreeArea2.Controls.Add(tcl);
    }
    catch(Exception ex)
    {
        msg = ex.Message;
    }

    // This is specific code added to provide a display change on tree
    // selection in the javascript for this example. It is not needed 
    // when using this control outside of this demo.
    string jsTemp = 
      "<script language="'javascript'"> tmpSelectionDisplayDivs.put('" + 
      tcl.ClientID + "', 'TreeSelections2'); </script>";
    Page.RegisterClientScriptBlock("TreeListC2", jsTemp);

    /* Setup search buttons and textbox and add to control */
    TextBox txtSearch = new TextBox();
    txtSearch.ID = tcl.ClientID + "txtSearch";
    txtSearch.CssClass = "T2_searchText";
    txtSearch.EnableViewState = false;
    txtSearch.MaxLength = 50;
    txtSearch.AutoPostBack = false;
    TreeSearch2.Controls.Add(txtSearch);

    string btnImageGo = "btn_small_search.gif";
    string btnImageHv = "btn_small_search_hvr.gif";
    System.Web.UI.WebControls.Image btnImage = 
                 new System.Web.UI.WebControls.Image();
    btnImage.CssClass = "searchButton";
    btnImage.ImageUrl = btnImageGo;
    btnImage.ImageAlign = ImageAlign.AbsMiddle;
    btnImage.Attributes.Add("onmouseover", "this.src='" + 
             btnImageHv + "'; style.cursor='pointer'; ");
    btnImage.Attributes.Add("onmouseout", "this.src='" + 
             btnImageGo + "'; style.cursor='default'; ");
    HtmlAnchor btnSearch = new HtmlAnchor();
    btnSearch.ID = tcl.ClientID + "btnSearch";
    btnSearch.Controls.Add(btnImage);
    TreeSearch2.Controls.Add(btnSearch);

    string imgNext = "btn_rightarrow_search.gif";
    string imgNextHvr = "btn_rightarrow_search.gif";
    System.Web.UI.WebControls.Image btnImgNext = 
                  new System.Web.UI.WebControls.Image();
    btnImgNext.CssClass = "searchNext";
    btnImgNext.ImageUrl = imgNext;
    btnImgNext.ToolTip = "Find Next";
    btnImgNext.ImageAlign = ImageAlign.AbsMiddle;
    btnImgNext.Attributes.Add("onmouseover", "this.src='" + 
                              imgNextHvr + "'; style.cursor='pointer'; ");
    btnImgNext.Attributes.Add("onmouseout", "this.src='" + 
                              imgNext + "'; style.cursor='default'; ");
    HtmlAnchor btnNext = new HtmlAnchor();
    btnNext.ID = tcl.ClientID + "btnNext";
    btnNext.Attributes.CssStyle.Add("visibility", "hidden");
    btnNext.Controls.Add(btnImgNext);
    TreeSearch2.Controls.Add(btnNext);

    /* Assign javascript search function events to buttons */
    btnSearch.Attributes.Add("onclick", 
      "event.cancelBubble = true; variableSearch(event, '" + 
      tcl.ClientID + "', '" + txtSearch.ClientID + "', '" + 
      btnSearch.ClientID + "', '" + btnNext.ClientID + "', '" + 
      TreeInfo2.ClientID + "');");
    btnNext.Attributes.Add("onclick", 
      "event.cancelBubble = true; variableNext(event, '" + 
      tcl.ClientID + "', '" + txtSearch.ClientID + "', '" + 
      btnSearch.ClientID + "', '" + btnNext.ClientID + 
      "', '" + TreeInfo2.ClientID + "');");
}

Page_Load 方法中唯一值得注意的最后两行是。这些是用于对客户端上的树控件数据执行搜索的 JavaScript 函数。函数 variableSearchvariableNext 包含在 JavaScript 文件 TreeSearch.js 中,该文件已添加到主 TreeDemo.aspx 文件的 head 部分。TreeSearch.js 文件也可以在 TreeListControl1TreeListControl2 代码中注册。

客户端 JavaScript 代码和 CSS 概述

重要提示:此演示包含对 browser_detect.jsutility.js 的引用。这些是我在每个使用 JavaScript 的项目中始终包含的全局文件。如果将 TreeCheck.js 文件传输到您的项目中,请不要忘记将这些文件一起带入并将它们包含在页面 head 中,或在您的代码中注册它们。

一旦 TreeCheckList 控件被渲染,它将在客户端浏览器中显示,此时 CheckTree JavaScript 对象类将接管。CheckTree 类支持各种功能,用于操作树的显示、捕获选择更改以及提供搜索树内容的功能。当树首次实例化时,CheckTree 会向每个节点添加事件侦听器,以提供鼠标和复选框单击处理。

处理父复选框单击

var chks = this.obj.getElementsByTagName("input");
for (var i=0; i<chks.length; i++)
{
    chks[i].checked = false;
    if (this.IsParentNode(chks[i].id))
    {
        if (chks[i].attachEvent)
        {
            chks[i].attachEvent('onclick', 
                    function(e) { eval("NodeChecked(e,'" + id + "',true);") });
            chks[i].attachEvent('onmouseover', 
                    function(e) { eval("ChkMouseOver(e,'" + id + "');") });
            chks[i].attachEvent('onmouseout', 
                    function(e) { eval("ChkMouseOut(e,'" + id + "');") });
        }
        else if (chks[i].addEventListener)
        {
            chks[i].addEventListener('click', 
                    function(e) { eval("NodeChecked(e,'" + id + "',true);") }, false); 
            chks[i].addEventListener('mouseover', 
                    function(e) { eval("ChkMouseOver(e,'" + id + "');") }, false); 
            chks[i].addEventListener('mouseout', 
                    function(e) { eval("ChkMouseOut(e,'" + id + "');") }, false); 
        } 
    }
}

当树实例化时,CheckTree 对象会被添加到全局哈希表中,以便每页可以使用多个树。

TREECHECK_Trees.put(this.id, this);

当树上的选择发生更改时,CheckTree 函数会处理事件并将节点更新为所需的 CSS 类分配。这种 CSS 类分配是在单个函数 SetNodeClass 中完成的。

function CheckTree_SetNodeClass(nodeID, bMouseOver)
{
    var nodeObj = getObject(nodeID);
    if (!nodeObj)
        return;

    if (this.IsParentNode(nodeID))
    {
        if (this.IsNodeSelected(nodeID) || this.HasSelections(nodeID))
        {
            if (this.searchID == nodeID)
                this.SetNodeTextClass(nodeID, 
                  (bMouseOver) ? this.parentSearchSelectedOverCss : 
                   this.parentSearchSelectedCss);
            else
                this.SetNodeTextClass(nodeID, (bMouseOver) ? 
                  this.parentSelectedOverCss : this.parentSelectedCss);
        }
        else
        {
            if (this.searchID == nodeID)
                this.SetNodeTextClass(nodeID, 
                  (bMouseOver) ? this.parentSearchNormalOverCss : 
                   this.parentSearchNormalCss);
            else
                this.SetNodeTextClass(nodeID, (bMouseOver) ? 
                  this.parentNormalOverCss : this.parentNormalCss);
        }
    }
    else if (this.IsChildNode(nodeID))
    {
        if (this.IsNodeSelected(nodeID))
        {
            if (this.searchID == nodeID)
                this.SetNodeTextClass(nodeID, 
                  (bMouseOver) ? this.childSearchSelectedOverCss : 
                   this.childSearchSelectedCss);
            else
                this.SetNodeTextClass(nodeID, (bMouseOver) ? 
                  this.childSelectedOverCss : this.childSelectedCss);
        }
        else
        {
            if (this.searchID == nodeID)
                this.SetNodeTextClass(nodeID, 
                  (bMouseOver) ? this.childSearchNormalOverCss : 
                   this.childSearchNormalCss);
            else
                this.SetNodeTextClass(nodeID, (bMouseOver) ? 
                  this.childNormalOverCss : this.childNormalCss);
        }
    }
}

当单击父复选框或选择单个子项时,会遍历父子链,并更新所有相关节点以反映新选择。每个选定的节点都捕获在 nodesSelected 哈希表中,该哈希表也可用于将更新的信息提供回服务器。

CheckTree JavaScript 对象类提供基本的搜索功能,用于在树***定位节点文本,然后将找到的节点滚动到视图中。为了操作这些搜索功能并向用户提供反馈,您可能需要添加其他 JavaScript 函数来调用搜索功能。我在 TreeSearch.js 中提供了一组函数,它们可以做到这一点。函数 variableSearch 启动初始搜索,并使用超时调用为用户提供良好的等待光标,然后在搜索完成后提供一些反馈。函数 variableNext 对继续到下一个符合搜索标准的项目的搜索执行相同的任务。这些函数被分配给 TreeListControl1TreeListControl2 类中的按钮。

JavaScript 代码的一个重要功能是当复选框被选中或取消选中时触发的事件函数。由于大型树中的父节点可能有数百个子节点,因此在更新所有这些节点时可能会有显著的用户等待时间。我希望向用户表明树正在忙碌,因此我将 NodeChecked 函数分为两部分。函数的第一部分验证参数并设置选定节点的***。然后,函数禁用父节点并调用 setTimeout 来处理其余的子节点。此 setTimeout 调用可确保显示等待光标,并且节点对用户可见且处于活动状态。

function CheckTree_NodeChecked(nodeID, bUpdate)
{
    if (!this.IsParentNode(nodeID))
        return;

    var nodeObj = getObject(nodeID);
    if (!nodeObj)
        return;
    var inodeID = nodeID + "_i";
    var inodeObj = getObject(inodeID);
    if (!inodeObj)
    {
        alert('no object: ' + inodeID);
        return;
    }
    var chkObj = getObject(nodeID + "_chk");
    if (chkObj)
    {
        this.SetNode(nodeID, chkObj.checked);
        cursor_wait();
        nodeObj.disabled = true;
        setTimeout("CheckTree_NodeChecked_OnTimer('" + this.id + "', '" + 
                   nodeID + "', " + chkObj.checked + ", " + bUpdate + ")", 5);
    }
}

当计时器触发时,将调用 NodeChecked 函数的第二部分。该函数的一部分实际上遍历所有子节点和父节点的级别,设置选择***。函数结束时,它会恢复父节点的启用状态并清除等待光标。

注意:如果您的树已知只包含少量数据,则可以将 NodeChecked 函数合并到一个函数中,而无需 setTimeout。这将提供更快的选择和更好的用户交互。

此演示的所有样式都包含在文件 TreeCheck.css 中。我试图使设置相对简单,但又有足够的差异来创建两种不同的树形显示。文件顶部部分的设置用于 TreeDemo 页面的布局。然后是各个树的设置。查看 Tree2 的设置,您可以看到此树使用树级别类为树中的每个子级别提供特定样式。在服务器代码中,树级别类指定为“T2_level”。然后将级别编号附加到类名,在 CSS 文件中,您必须提供每个类级别 - T2_level1、T2_level2 等。使用这些特定的级别类,您可以提供自定义边距、字体等,这些都可以极大地改变树的外观。

CSS 文件中提供了树节点每个状态的文本标签类。这些不同的状态可以像一种样式一样简单,也可以随着每个节点状态而发生显著变化。在演示中,Tree2 的子节点在被选中时,文本颜色会从蓝色变为黄色,背景颜色也会改变。

.T2_treeChildLabel
{
    color: Blue;
    font-weight: normal;
    margin-left: 5px;
}
.T2_treeChildSel
{
    color: Yellow;
    font-weight: normal;
    background-color: #8F8FAF;
    margin-left: 5px;
    padding-left: 2px;
    padding-right: 2px;
}

当鼠标悬停在子节点上时,请注意左边距如何稍微增加以产生“浮动”效果。

.T2_treeChildMouseOverNormal
{
    color: #303082;
    font-weight: normal;
    cursor: pointer;
    background-color: #ACDEFF;
    margin-left: 8px;
}

此外,当子节点是搜索结果时,会添加一个边框,以给用户一个非常好的视觉线索,表明该项目的位置。

.T2_treeChildSearchNormalOver{
    background-color: #EFEFBF;
    color: Blue;
    font-weight: normal;
    border: solid 1px #8F8FAF;
    margin-left: 8px;
    padding-left: 2px;
    padding-right: 2px;
    cursor: pointer;
}

将无限组合的样式应用于树,可以为原本普通的控件带来一些令人印象深刻且不同的外观。

限制和未来增强

最重要需要承认的限制是选择返回服务器的方式。我对该控件的个人实现使用特定的 AJAX 调用回服务器,以便在不进行回发的情况下更新页面上的其他控件。我不想用大量仅适用于我需求的 AJAX 代码来充斥此示例,因此我将 JavaScript SelectionsChanged 函数留给任何类型的用途。换句话说,此版本的控件中没有回发处理。您必须实现一些东西来满足您的需求。我在演示中展示了如何使用树控件的选择更新页面上的另一个文本控件。由于每个节点还分配了值,因此可以检索这些值并将其发送回服务器,或通过 JavaScript 更新页面。我希望将来某个时候添加一个回发事件处理程序,但由于我实际上不需要这个,所以它不是高优先级。如果您决定实现一些东西,请分享。

此外,我没有为此控件添加视图状态。我没有理由来回保存完整的状态,因为我不想将页面回发到服务器,并且通过 AJAX 进行更新。可以使用 JavaScript 函数 LoadSelectionState 来恢复树的状态。此函数接受一个 nodeID 数组,并从此数组恢复树的选择状态。如果您在树中包含回发,请使用此函数通过在服务器代码中注册 JavaScript 调用来恢复选择。我将致力于在某个时候添加视图状态。

有一件事我必须道歉,那就是 JavaScript 中缺乏注释。我保证我将及时提供更多关于参数和代码的信息。:)

致谢

我想感谢 Mike Ellison 的 DropDownCheckList 控件(DropDownCheckList.aspx)。在查看了他的控件后,我产生了 TreeCheckList 的想法。Mike 的控件和代码是一流的,我尽力模仿这种开发类型。

© . All rights reserved.