• 即使asp:TreeView有几万个节点,也让IE不死的解决方法


     背景介绍:

                          

                        图一                                                      图二

    开发环境是:VS2008(+SP1) 及 ASP.NET 3.5(+SP1) 及 ASP.NET AJAX。

    业务需求是:BI首页上会显示的CCHR的各种图表和统计数据,另外要提供一个选择组织的弹出窗口,在首页打开后,用户可以再根据自己的需要,从自己有权限的组织树弹出窗口中,只选择某些组织,查看其对应的统计数据。比如:A用户有XXX国际控股的及下面所有组织的权限,那他进入BI首页时,默认看到的是XXX国际控股这家公司全部的统计数据,如果他只想看东北的数据,就可以打开选择组织的弹出窗口(如图一),在这棵组织树上只勾选东北这个节点前的CheckBox再提交刷新首页,首页上的统计数据和图表就只有东北的数据了。

    问题现象是:CCHR中XXX国际控股这个客户的组织多达3万多个,加载显示这棵3万多个节点的组织树直接让IE几乎挂掉,要等好几分钟树才能加载完,并且IE一直占用CPU百分之九十多,根本没办法用了。(网上查资料看到,如果是2000个以下的节点数选择TreeView才比较合适,多了肯定不行)

    解决思路是:造一棵貌似完整的组织树,其实是一棵数据量很少的假树来欺骗用户的眼睛。比如:A用户刚进来时只加载前三层节点,用户点击某个节点时,再加载此节点的所有孙子节点。也就是刚进页面时把XXX国际控股XXX国际控股下面的两层子节点加载进来(树默认展开前两层,所以用户只看到XXX国际控股和它的子节点(如图一)),加载三层的原因是要让XXX国际控股的所有子节点前面有"+"号(比如东北前面的加号),用户可以再点击展开,展开时,再只把此节点的所有孙子节点都加载进来,比如用户点击了东北,就加载东北的所有孙子节点(长春区、沈阳区等)进来,每次都加载孙子节点就是为了保证子节点前面有"+"号,可以继续展开,除非没有孙子节点。既然是假树,那它其实也就仅能供用户欣赏了,最后选中了哪些组织就不能从树上去取了,只能根据用户的勾选,在后台从数据源中去判断获取了。(比如:用户在上面第一个图中选了东北,如果我们从树上获取组织,只能得到东北和东北的子组织,孙子组织没有。)

    解决方案:

    数据源是由我们调用CCHR提供的WebService得到一个XML结构的组织树。所以我们用一个asp:XmlDataSource控件作为Treeview的数据源。

    XML结构的组织树数据源示例如下:

    <ReturnInfo>
        
    <Success>true</Success>
        
    <OrganizationData>
           
    <Organization unitid='0' punitid = '' unitcode='BELLE' unitname = 'XXX国际控股' hasauthoriry = '1'>
            
    <Organization unitid=' 12205083-c0a7-4f54-b8e5-8f0643fe3b84' punitid = '0' unitcode=' YG' unitname = '云贵' 
                                  hasauthoriry = '0'>
             
    <Organization unitid=' ab68bcb7-4e10-49b7-8923-527bafa340ec' punitid = '12205083-c0a7-4f54-b8e5-8f0643fe3b84' 
                                  unitcode=' YGYN' unitname = '云南省' hasauthoriry = '0'>
                
    <Organization unitid=' 61dd0f74-d576-4963-988e-ff9cec4b92ee' punitid = 'ab68bcb7-4e10-49b7-8923-527bafa340ec' 
                                     unitcode=' YGYN1' unitname = '总经理室' hasauthoriry = '1'>
                
    </Organization>

              
    </Organization>
            
    </Organization>
          
    </Organization>
        
    </OrganizationData>
    </ReturnInfo>

    下面是前台HTML代码:

    用了UpdatePanel是为了在展开子节点时实现局部刷新。
    OnTreeNodeExpanded展开节点事件中加载此节点的孙子节点。
    OnTreeNodeCheckChanged点选CheckBox事件中,处理哪些节点被选中,定义了一个Session变量,向Session中添加或移除此组织及它的子组织。

    <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
        
    <ContentTemplate>
            
    <asp:TreeView ID="TreeViewOrg" runat="server" OnClick="OnTreeNodeChecked()" ShowCheckBoxes="All"
                OnTreeNodeExpanded
    ="TreeViewOrg_TreeNodeExpanded" 
                  OnTreeNodeCheckChanged="TreeViewOrg_TreeNodeCheckChanged"
                ExpandDepth
    ="1" ShowLines="true">
                
    <DataBindings>
                    
    <asp:TreeNodeBinding DataMember="Organization" SelectAction="None" ValueField="UnitID"
                        TextField
    ="UnitName" TargetField="HasAuthority" />
                
    </DataBindings>
            
    </asp:TreeView>
            
    <asp:XmlDataSource ID="XmlDataSource1" runat="server"></asp:XmlDataSource>
        
    </ContentTemplate>
    </asp:UpdatePanel>

     

    下面是前台JS代码:

    OnTreeNodeChecked() 方法实现了在Treeview上选中某一个节点时自动选中其所有子节点。
    postBackByObject() 方法是为了在点CheckBox时,可以提交到后台去处理。

    <script language='javascript' type='text/javascript'>
        
    function OnTreeNodeChecked() {
            
    var ele =
     event.srcElement;
            
    if (ele.type ==
     'checkbox') {
                
    var childrenDivID =
     ele.id.replace('CheckBox', 'Nodes');
                
    var div =
     document.getElementById(childrenDivID);
                
    if (div == nullreturn
    ;
                
    var checkBoxs =
     div.getElementsByTagName('INPUT');
                
    for (var i = 0; i < checkBoxs.length; i++
    ) {
                    
    if (checkBoxs[i].type ==
     'checkbox')
                        checkBoxs[i].checked 
    =
     ele.checked;
                }
                postBackByObject();
            }
        }

        
    //点击复选框时触发事件

        function postBackByObject() {
            
    var o =
     window.event.srcElement;
            
    if (o.tagName == "INPUT" && o.type == "checkbox"
    ) {
                
    //这里的第一个参数是UpdatePanel ID,因为我使用了MS的ASPAJAX来实现局部刷新

                //如果没有使用MS的ASPAJAX,这里的两个参数都可以为空
                __doPostBack("UpdatePanel1""");
            }
        }
    </script>

     

    后台代码

    第一次进入页面给TreeView绑定前三层组织(注释中有详细说明):

    private void BindOrgTree()
    {
        
    //
    调用CCHR的Web Service取得XML结构组织树要花几秒中时间,出于性能考虑,
        
    //对一个登陆用户,我们只取一次组织树数据放到Session中

        if (Session["OrgTree"== null)
        
    {
            Session[
    "OrgTree"= this
    .GetSecurityOrganizationTree(_loginUser, _dashboardName, _pageName);
        }

        
    string orgXml = Session["OrgTree"].ToString();
        
    if (orgXml != null && orgXml != ""
    )
        
    {
            
    //
    第一次打开页面时,Treeview上只加载前三层的组织,
            
    //所以我们用下面程序从CCHR的XML组织树中只拿出前三层的XML组织树

            string xmlStart = @"<ReturnInfo><Success>true</Success><OrganizationData>";
            
    string xmlEnd = @"</OrganizationData></ReturnInfo>"
    ;
            StringBuilder xmlData 
    = new
     StringBuilder();
            XmlDocument xmldoc 
    = new
     XmlDocument();
            xmldoc.LoadXml(orgXml);
            XmlElement root 
    =
     xmldoc.DocumentElement;
            XmlElement theOrg 
    = (XmlElement)root.SelectSingleNode("/ReturnInfo/OrganizationData"
    );
            XmlNodeList nodelist1 
    =
     theOrg.ChildNodes;

            xmlData.Append(xmlStart);
            
    if (nodelist1.Count > 0
    )
            
    {
                
    for (int i = 0; i < nodelist1.Count; i++
    )
                
    {
                    
    //第一层组织

                    xmlData.Append(GetNewXmlNodeString(nodelist1[i]));
                    XmlNodeList nodelist2 
    =
     nodelist1[i].ChildNodes;
                    
    for (int j = 0; j < nodelist2.Count; j++
    )
                    
    {
                        
    //第二层组织

                        xmlData.Append(GetNewXmlNodeString(nodelist2[j]));
                        XmlNodeList nodelist3 
    =
     nodelist2[j].ChildNodes;
                        
    for (int k = 0; k < nodelist3.Count; k++
    )
                        
    {
                            
    //第三层组织

                            xmlData.Append(GetNewXmlNodeString(nodelist3[k]));
                            xmlData.Append(
    "</Organization>"
    );
                        }

                        xmlData.Append(
    "</Organization>");
                    }

                    xmlData.Append(
    "</Organization>");
                }

            }

            xmlData.Append(xmlEnd);
            
    //把XML字符串赋给XML DataSource控件
            XmlDataSource1.Data = xmlData.ToString();
            XmlDataSource1.EnableCaching 
    = false
    ;
            XmlDataSource1.DataBind();
            XmlDataSource1.XPath 
    = "ReturnInfo/OrganizationData/Organization"
    ;

            
    //把XML DataSource绑定到Treeview上

            TreeViewOrg.DataSource = XmlDataSource1;
            TreeViewOrg.DataBind();
            
    //
    循环TreeView的所有节点,对HasAuthority=0,
            
    //也就是没有某个组织权限的节点,把节点前面的CheckBox隐藏掉

            HiddenNoAuthorityNodeCheckBox(this.TreeViewOrg.Nodes);
        }

    }

     

    上面的方法会调用下面两个公用方法:

    1、得到某个组织节点XML字符串的公用方法:

    private string GetNewXmlNodeString(XmlNode xmlNode)
    {
        
    string xmlNodeString = "<Organization UnitID=\
    ""
                    + xmlNode.Attributes["UnitID"].Value + "\" PUnitID =\""

                    
    + xmlNode.Attributes["PUnitID"].Value + "\" UnitCode=\""
                    
    + xmlNode.Attributes["UnitCode"].Value + "\" UnitName = \""
                    
    + xmlNode.Attributes["UnitName"].Value + "\" HasAuthority = \""
                    
    + xmlNode.Attributes["HasAuthority"].Value + "\">";
        return xmlNodeString;
    }

     

    2、隐藏没有权限节点前面CheckBox的公用方法:

    public void HiddenNoAuthorityNodeCheckBox(TreeNodeCollection treeNodes)
    {
        
    foreach (TreeNode treeNode in
     treeNodes)
        
    {
            _treeNodeCount 
    += 1
    ;
            
    if (treeNode.Target == "0"
    )
            
    {
                treeNode.ShowCheckBox 
    = false
    ;
            }

            HiddenNoAuthorityNodeCheckBox(treeNode.ChildNodes);
        }

    }

     

    在点击某个节点的事件中,给此节点添加孙子节点(添加时,如果父节点是选中的,那添加的孙子节点也要是选中状态)

    protected void TreeViewOrg_TreeNodeExpanded(object sender, TreeNodeEventArgs e)
    {
        
    if (e.Node.ChildNodes.Count > 0
    )
        
    {
            
    for (int i = 0; i < e.Node.ChildNodes.Count; i++
    )
            
    {
                XmlNodeList nodeList 
    =
     GetXmlNodeChildNodeListByValue(e.Node.ChildNodes[i].Value);
                
    if (nodeList != null
    )
                
    {
                    e.Node.ChildNodes[i].ChildNodes.Clear();
                    
    for (int j = 0; j < nodeList.Count; j++
    )
                    
    {
                        TreeNode treeNode 
    = new
     TreeNode();
                        treeNode.Value 
    = nodeList[j].Attributes["UnitID"
    ].Value;
                        treeNode.Text 
    = nodeList[j].Attributes["UnitName"
    ].Value;
                        treeNode.Target 
    = nodeList[j].Attributes["HasAuthority"
    ].Value;
                        treeNode.SelectAction 
    =
     TreeNodeSelectAction.None;
                        
    if
     (e.Node.Checked)
                        
    {
                            treeNode.Checked 
    = true
    ;
                        }

                        e.Node.ChildNodes[i].ChildNodes.Add(treeNode);
                    }

                }

            }

        }

    }

     

    上面的事件中会调用下面这个公用方法:

    1、根据一个组织ID,去CCHR的XML组织树中找到这个XML节点子节点列表的公用方法:

    private XmlNodeList GetXmlNodeChildNodeListByValue(string value)
    {
        
    if (Session["OrgTree"!= null
    )
        
    {
            
    string orgXml = Session["OrgTree"
    ].ToString();

            XmlDocument xmldoc 
    = new
     XmlDocument();
            xmldoc.LoadXml(orgXml);
            XmlElement root 
    =
     xmldoc.DocumentElement;
            XmlElement theOrg 
    =
     (XmlElement)root.SelectSingleNode(
                
    "/ReturnInfo/OrganizationData/Organization/Organization//Organization[@UnitID='" + value + "']"
    );
            
    if (theOrg != null
    )
            
    {
                XmlNodeList nodelist 
    =
     theOrg.ChildNodes;
                
    return
     nodelist;
            }

            
    else
            
    {
                
    return null
    ;
            }

        }

        
    else
        
    {
            
    return null
    ;
        }

    }

     

    在每次单击某个CheckBox的事件中,获取选中的组织,有可能是添加,有可能是移除,最后都存在Session中:

    如果CheckBox是选中,就先把当前点选中的组织先存到Session中,然后再递归循环它下面所有的子组织,也存入Session。反之,如果是没选中,就从Session中全部移除。

    protected void TreeViewOrg_TreeNodeCheckChanged(object sender, TreeNodeEventArgs e)
    {
        
    string checkedNodeValue =
     e.Node.Value;
        XmlNode xmlNode 
    = this
    .GetXmlNodeByValue(checkedNodeValue);
        
    if
     (e.Node.Checked)
        
    {
            
    string xx = xmlNode.Attributes["UnitName"
    ].Value;
            
    if (Session["SelectedOrganizationIDs"!= null
    )
            
    {
                
    if (Session["SelectedOrganizationIDs"].ToString().IndexOf(xmlNode.Attributes["UnitID"].Value + ";"< 0
    )
                
    {
                    Session[
    "SelectedOrganizationIDs"= Session["SelectedOrganizationIDs"
    ].ToString()
                        
    + xmlNode.Attributes["UnitID"].Value + ";"
    ;
                }

            }

            
    else
            
    {
                Session[
    "SelectedOrganizationIDs"= xmlNode.Attributes["UnitID"].Value + ";"
    ;
            }

        }

        
    else
        
    {
            
    string xx = xmlNode.Attributes["UnitName"
    ].Value;
            
    if (Session["SelectedOrganizationIDs"!= null
    )
            
    {
                Session[
    "SelectedOrganizationIDs"= Session["SelectedOrganizationIDs"].ToString().Replace(xmlNode.Attributes["UnitID"].Value + ";"""
    );
            }

        }

        GetSelectedOrganization(xmlNode.ChildNodes, e.Node.Checked);
    }

     

    上面的事件中会调用下面两个公用方法:

    1、根据一个组织ID,去CCHR的XML组织树中找到这个XML节点的公用方法:

    private XmlNode GetXmlNodeByValue(string value)
    {
        
    if (Session["OrgTree"!= null
    )
        
    {
            
    string orgXml = Session["OrgTree"
    ].ToString();

            XmlDocument xmldoc 
    = new
     XmlDocument();
            xmldoc.LoadXml(orgXml);
            XmlElement root 
    =
     xmldoc.DocumentElement;
            XmlNode orgNode 
    = root.SelectSingleNode("/ReturnInfo/OrganizationData//Organization[@UnitID='" + value + "']"
    );
            
    return
     orgNode;
        }

        
    else
        
    {
            
    return null
    ;
        }

    }

     

    2、得到选中的组织的公用方法:

    public void GetSelectedOrganization(XmlNodeList nodeList, bool selected)
    {
        
    foreach (XmlNode xmlNode in
     nodeList)
        
    {
            
    if
     (selected)
            
    {
                
    string xx = xmlNode.Attributes["UnitName"
    ].Value;
                
    if (Session["SelectedOrganizationIDs"!= null
    )
                
    {
                    
    if (Session["SelectedOrganizationIDs"].ToString().IndexOf(xmlNode.Attributes["UnitID"].Value + ";"< 0
    )
                    
    {
                        Session[
    "SelectedOrganizationIDs"= Session["SelectedOrganizationIDs"
    ].ToString()
                            
    + xmlNode.Attributes["UnitID"].Value + ";"
    ;
                    }

                }

                
    else
                
    {
                    Session[
    "SelectedOrganizationIDs"= xmlNode.Attributes["UnitID"].Value + ";"
    ;
                }

            }

            
    else
            
    {
                
    string xx = xmlNode.Attributes["UnitName"
    ].Value;
                
    if (Session["SelectedOrganizationIDs"!= null
    )
                
    {
                    Session[
    "SelectedOrganizationIDs"= Session["SelectedOrganizationIDs"].ToString().Replace(xmlNode.Attributes["UnitID"].Value + ";"""
    );
                }

            }

            GetSelectedOrganization(xmlNode.ChildNodes, selected);
        }

    }

     

    大家如果用更好的解决方法,请不忘跟贴赐教。

  • 相关阅读:
    JavaOne Online Hands-on Labs
    Using DTrace to Profile and Debug A C++ Program
    怎样挑选电线?家装用线越大越好吗?
    ORACLE DTRACE DOC
    内核书
    SQL Server vNext CTP 1.2
    用VS Code打造最佳Markdown编辑器
    opendtrace 开源汇总
    DTrace C++ Mysteries Solved 转
    MYSQL-RJWEB 博客学习
  • 原文地址:https://www.cnblogs.com/songsh96/p/1440697.html
Copyright © 2020-2023  润新知