知识分享 - 如何分批分层的加载树节点
金蝶云社区-JohnnyDing
JohnnyDing
2人赞赏了该文章 4,428次浏览 未经作者许可,禁止转载编辑于2016年01月07日 16:38:06

案例背景:
界面上使用了树控件,需显示大量的节点;
如果一次性加载到客户端,显示渲染会非常慢;
因此,需要分层、分批下载,以缓解每次加载的性能压力,提升用户体验。

案例说明:
新增一个动态表单,挂上本插件,并在界面有一个树控件 F_JD_TreeView;
界面初始化时,仅加载第一层节点;
用户点击节点时,才加载其包含的子节点,而且每次最多加载10个;
单层超过10个节点,通过"点击加载更多..."节点,分批下载

案例效果:
图一:在动态表单上,添加一个树控件,并挂上插件(示例代码在下面)

图二:初始仅加载10个一级节点

图三:"点击加载更多...",加载了下一批10个一级节点

图四:点击父节点,加载了其第一批10个子节点

示例代码:
//*********************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using Kingdee.BOS;
using Kingdee.BOS.Util;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.DynamicForm;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.DynamicForm.PlugIn.ControlModel;
using Kingdee.BOS.Core.Metadata;


namespace JDSample.FormPlugIn.DynamicForm
{
///


/// 分批分层加载树节点
///

///
/// 案例背景:
/// 界面上使用树控件,需显示大量的节点;
/// 如果一次性加载到客户端,显示渲染会非常慢;
/// 因此,需要分层、分批下载
///
/// 案例说明:
/// 新增一个动态表单,挂上本插件,并在界面有一个树控件 F_JD_TreeView;
/// 界面初始化时,仅加载第一层节点;
/// 用户点击节点时,才加载其包含的子节点,而且每次最多加载10个;
/// 单层超过10个节点,通过"点击加载更多..."节点,分批下载
///

[Description("分批分层加载树节点")]
public class S160107TreeEdit : AbstractDynamicFormPlugIn
{
///
/// 本地变量,存储需加载到前端的所有节点信息,以及其是否已经加载标志
///

private Dictionary _dctNodes = new Dictionary();

///
/// 本地变量,存储已经被加载过的父节点Id,避免重复搜索其子节点,浪费时间
///

private HashSet _loadedNodeIds = new HashSet();


///
/// 界面初始化结束,触发此事件,通知插件开始加载树节点:在此事件,加载第一层节点
///

///
public override List GetTreeViewData(TreeNodeArgs e)
{
if (e.Key.EqualsIgnoreCase("F_JD_TreeView") == false)
{
// 需加载是其他树控件的节点,略过
return new List();
}


// 加载全部节点信息到内存
this.LoadNodes();


// 记录已经加载过根节点
this._loadedNodeIds.Add("0");


// 展开树控件节点
TreeView tv = this.View.GetControl("F_JD_TreeView");
tv.SetExpanded(true);


// 构造根目录下的第一层节点并返回
List nodes = this.BuildTreeNodes("0");

return nodes;
}


///
/// 用户点击节点时触发此事件:加载更多子节点
///

///
public override void TreeNodeClick(TreeNodeArgs e)
{
if (e.Key.EqualsIgnoreCase("F_JD_TreeView") == false)
{
// 点击的是其他树控件,略过
return;
}


// 判断是否已经加载过此节点的子节点,如果加载过,则不再加载
if (this._loadedNodeIds.Contains(e.NodeId))
{
return;
}


this._loadedNodeIds.Add(e.NodeId);
TreeView tv = this.View.GetControl("F_JD_TreeView");
string parentId = e.NodeId;
if (parentId.StartsWith("more"))
{// 当前点击的节点,是"点击加载更多..."

string[] keys = parentId.Split('|');
parentId = keys[1]; // 第2部分为父节点部分
// "点击加载更多..."节点已经被点击过,不再需要了,移除之
tv.RemoveNode(e.NodeId);
}


// 开始加载更多的子节点
List childNodes = this.BuildTreeNodes(parentId);
if (childNodes.Count > 0)
{
tv.AddNodes(parentId, childNodes);
}
}


///
/// 构建树控件所需要的节点对象
///

/// 父节点Id,为0表示第一层节点
///
private List BuildTreeNodes(string parentId)
{
List nodes = new List();

// 遍历全部节点,找指定节点中,未加载的子节点的子节点
int count = 0;
int index = 0;
foreach (var item in _dctNodes)
{
NodeInfo nodeInfo = item.Value;
if (nodeInfo.Loaded == false
&& nodeInfo.ParentId == parentId)
{
nodeInfo.Loaded = true;

count++;
TreeNode node = new TreeNode()
{
id = nodeInfo.Id,
text = nodeInfo.Caption,
parentid = nodeInfo.ParentId,
};

nodes.Add(node);
}


if (count >= 10)
{// 本次加载超过了10个
// 生成一个特殊的节点(加载更多...),并停止本批加载更多子节点
TreeNode node = new TreeNode()
{
// 需要基于如下需求,生成一个特殊的节点Id
// 1. 需要与其他普通节点进行区分:以more为前缀
// 2. 需要能够提取出父节点Id:包含父节点Id
// 3. 每次产生的特殊节点Id不能重复:需包含当前节点索引
id = string.Format("more|{0}|{1}", parentId, index),
text = "点击加载更多...",
parentid = parentId,
};
nodes.Add(node);

break;
}


index++;
}

return nodes;
}

///
/// 到数据库加载全部节点信息:本示例直接手工构建一批有层次的节点信息
///

private void LoadNodes()
{
// 节点名称,包含其子节点的数量,以提示用户,可以展开查看子节点

// 第一层节点
_dctNodes.Add("1", new NodeInfo() { Id = "1", ParentId = "0", Caption = "江西(100)" });
_dctNodes.Add("2", new NodeInfo() { Id = "2", ParentId = "0", Caption = "广东(3)" });

// 循环添加100个第一层节点
for (int i = 3; i <= 100; i++)
{
_dctNodes.Add(i.ToString(), new NodeInfo()
{ Id = i.ToString(), ParentId = "0", Caption = string.Format("省份{0}(0)", i) });
}

// 第二层节点:
_dctNodes.Add("1.1", new NodeInfo() { Id = "1.1", ParentId = "1", Caption = "南昌(0)" });
_dctNodes.Add("1.2", new NodeInfo() { Id = "1.2", ParentId = "1", Caption = "九江(0)" });
_dctNodes.Add("1.3", new NodeInfo() { Id = "1.3", ParentId = "1", Caption = "赣州(0)" });

// 循环添加100个第二层节点
for (int i = 4; i <= 100; i++)
{
_dctNodes.Add(string.Format("1.{0}", i),
new NodeInfo()
{
Id = string.Format("1.{0}", i),
ParentId = "1",
Caption = string.Format("城市{0}(0)", i)
});
}

_dctNodes.Add("0201", new NodeInfo() { Id = "0201", ParentId = "02", Caption = "广州(0)" });
_dctNodes.Add("0202", new NodeInfo() { Id = "0202", ParentId = "02", Caption = "深圳(2)" });
_dctNodes.Add("0203", new NodeInfo() { Id = "0203", ParentId = "02", Caption = "惠州(0)" });

// 第三层节点:
_dctNodes.Add("020201", new NodeInfo() { Id = "020201", ParentId = "0202", Caption = "南山区" });
_dctNodes.Add("020202", new NodeInfo() { Id = "020202", ParentId = "0202", Caption = "宝安区" });
}
}

///
/// 节点信息对象
///

class NodeInfo
{
///
/// 本节点Id
///

public string Id { get; set; }
///
/// 父节点Id
///

public string ParentId { get; set; }
///
/// 本节点标题
///

public string Caption { get; set; }
///
/// 是否已经加载标志:默认为false,未加载到前端
///

public bool Loaded { get; set; }
}
}