即使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);
    }

}

 

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

 

posted on 2009-04-21 18:27  Sammy  阅读(2238)  评论(5编辑  收藏  举报

导航