.NET平台下带权限控制的TreeView控件节点生成算法
(http://dev.csdn.net/article/53/53647.shtm)
一、引言
在应用系统开发中,TreeView是一种使用频率很高的控件。它的主要特点是能够比较清晰地实现分类、导航、浏览等功能。因而,它的使用方法与编程技巧也一直受到技术人员的关注。随着应用需求的变化,在很多情况下我们需要实现数据显示的权限控制,即用户看到的数据是经过过滤的,或是连续值,或是一些离散的值。就TreeView而言,原先可能显示出来的是完整的具有严格父子关系得节点集,而经权限过滤后所要显示的节点可能会变得离散,不再有完整的继承关系。本文针对这一问题,通过对已有实现方法进行分析,提出改进算法。所附示例程序进一步解释了算法设计思想。
二、三种常见生成方式的简单分析
如文[2,3]所述,TreeView的生成基本上有三种方式:
1. 界面设计时在TreeView设计器或者代码中直接填充TreeView节点。
这种方式通过拖放控件的方式生成树,应用范围窄,是一种非编程方式;
2. 从XML文件中建立树形结构。
这种方式通过XML文件(串)生成树,从形式上来说,这种方式是比较直观的。因为XML本身就是一棵“树”,在.NET 平台下TreeView的自动生成代码中,TreeView的实际内容也是由XML表示的。此外,基于XML文件生成树对异构环境下的分布式应用具有重要意义。事实上,利用XML作为通用数据传递格式已得到普遍认可;
3. 从数据库中得到数据(在.NET中,我们可以理解为一个数据集),建立树形结构。
这种方式通过父子关系递归生成树,是最容易理解的一种编程实现方式。一般是自顶向下递归生成,得到广泛应用。
这里,我们不妨举一个实际的例子来说明一下,假设我们有这样的数据集(可以看作是一个公司的部门列表):
TagValue |
ContentValue |
ParentID |
G01 |
行销部 |
|
G02 |
顾问部 |
|
G03 |
研发部 |
|
G04 |
测试部 |
|
GS01 |
行销一部 |
G01 |
GS02 |
行销二部 |
G01 |
GS03 |
行销三部 |
G01 |
GSL01 |
行销一部北京办 |
GS01 |
GSL02 |
行销一部上海办 |
GS01 |
GS04 |
顾问一部 |
G02 |
GS05 |
顾问二部 |
G02 |
GS06 |
研发一部 |
G03 |
GS07 |
研发二部 |
G03 |
GS08 |
测试一部 |
G04 |
GS09 |
测试二部 |
G04 |
GSL03 |
研发一部杭州分部 |
GS06 |
GSL04 |
研发一部西安分部 |
GS06 |
表1 示例数据集
其中,TagValue是节点的实际值,ContentValue是用户界面上节点显示的值或者说标签值,ParentID是节点的父节点的TagValue。若节点为根节点,一般设ParentID为空或等于本身的TagValue。
默认情况下,我们可以按照下面的算法把所有的节点装配成一棵树,
算法1:通过父子关系递归生成树基本算法 l Step 0:数据准备,给定数据集。 l Step 1:给定待增加子节点的节点(初始时一般为根节点),记作CurNode,以及待增加节点的ParentID值(初始时为根节点的ParentID),记作CurParentID; l Step 2:在数据集中查找具有指定ParentID值的所有节点,得到节点集objArr[], |
最终可以得到下图所示的TreeView:
图1 TreeView效果图
这种方法的缺陷在于"由父节点及子节点"的遍历顺序意味着每个子节点的父节点必须存在,否则将搜索不到,即可能出现断层现象。在很多实际应用中,我们发现这种实现方式不能完全奏效,最典型的情况就是当需要对众节点所表征的实际值(比如机构列表,人员列表,资源列表等)进行权限控制时,这时往往从数据库中筛选出来的数据集中节点会出现断层现象。比如我们假设设定权限时给定数据如表2,即把第一行“行销部”去掉(注:权限过滤操作已超出本文讨论的范围,这里假定数据集已准好),则运用算法1生成的TreeView如图2所示。
TagValue |
ContentValue |
ParentID |
G02 |
顾问部 |
|
G03 |
研发部 |
|
G04 |
测试部 |
|
GS01 |
行销一部 |
G01 |
GS02 |
行销二部 |
G01 |
GS03 |
行销三部 |
G01 |
GSL01 |
行销一部北京办 |
GS01 |
GSL02 |
行销一部上海办 |
GS01 |
GS04 |
顾问一部 |
G02 |
GS05 |
顾问二部 |
G02 |
GS06 |
研发一部 |
G03 |
GS07 |
研发二部 |
G03 |
GS08 |
测试一部 |
G04 |
GS09 |
测试二部 |
G04 |
GSL03 |
研发一部杭州分部 |
GS06 |
GSL04 |
研发一部西安分部 |
GS06 |
表2 给定数据集
图2 TreeView效果图
可以看到,这里产生了节点遗漏现象。一般来说,我们可以从两方面入手去解决问题,一方面可以修正数据集,另一方面则可以修改生成树算法。显然直接修正数据集是很复杂的,且会带来效率问题。而单方面修改生成树算法也是不是很好(即把遗漏的节点直接插到根节点下),因为这时会出现父辈和晚辈同级的现象。
三、通过深度编号递归生成树算法
回顾到已有的一些方法(文[1~5]),其中基于节点深度生成树的方法给我们一些启发,我们在构造数据集时可以增加深度字段,但这里的深度不是简单的层级号,是一个扩展了的概念,具体地说其实是一个深度编号,它与父辈编号存在一定的对应关系。比如表1所示的数据集可以作如下编号:
TagValue |
ContentValue |
ParentID |
DepthID |
G01 |
行销部 |
|
a001 |
G02 |
顾问部 |
|
a002 |
G03 |
研发部 |
|
a003 |
G04 |
测试部 |
|
a004 |
GS01 |
行销一部 |
G01 |
a001001 |
GS02 |
行销二部 |
G01 |
a001002 |
GS03 |
行销三部 |
G01 |
a001003 |
GSL01 |
行销一部北京办 |
GS01 |
a001001001 |
GSL02 |
行销一部上海办 |
GS01 |
a001001002 |
GS04 |
顾问一部 |
G02 |
a002001 |
GS05 |
顾问二部 |
G02 |
a002002 |
GS06 |
研发一部 |
G03 |
a003001 |
GS07 |
研发二部 |
G03 |
a003002 |
GS08 |
测试一部 |
G04 |
a004001 |
GS09 |
测试二部 |
G04 |
a004002 |
GSL03 |
研发一部杭州分部 |
GS06 |
a003001001 |
GSL04 |
研发一部西安分部 |
GS06 |
a003001002 |
表3 带深度编号的数据集
其中,DepthID即是节点的深度编号。生成深度编号的过程其实也不复杂,首先我们可以制定编号的规则,比如层级编号的前缀、编码长度、起始值等。当给某个节点编号时,只要找到所在层级的最大编号,然后增1。具体实现过程这里不再细述。
于是,我们很自然地想到借鉴算法1的思想设计基于深度编号的生成树程序。这时,我们可以根据当前节点的深度编号寻找其后代节点集,但要给出一个最大跨度(可以理解为最高级与最低级间的间隔级数),因为不可能无限制地找下去。这种方法可以部分程度上弥补"由父节点及子节点"的遍历的缺陷,因为当出现断层时会沿着编号继续往后找。但是还是会可能漏掉,比如我们给定数据集(把“研发一部”过滤掉):
TagValue |
ContentValue |
ParentID |
DepthID |
G01 |
行销部 |
|
a001 |
G02 |
顾问部 |
|
a002 |
G03 |
研发部 |
|
a003 |
G04 |
测试部 |
|
a004 |
GS01 |
行销一部 |
G01 |
a001001 |
GS02 |
行销二部 |
G01 |
a001002 |
GS03 |
行销三部 |
G01 |
a001003 |
GSL01 |
行销一部北京办 |
GS01 |
a001001001 |
GSL02 |
行销一部上海办 |
GS01 |
a001001002 |
GS04 |
顾问一部 |
G02 |
a002001 |
GS05 |
顾问二部 |
G02 |
a002002 |
GS07 |
研发二部 |
G03 |
a003002 |
GS08 |
测试一部 |
G04 |
a004001 |
GS09 |
测试二部 |
G04 |
a004002 |
GSL03 |
研发一部杭州分部 |
GS06 |
a003001001 |
GSL04 |
研发一部西安分部 |
GS06 |
a003001002 |
表4 给定数据集
在生成树过程中,当从“研发部”(a003)往下找子节点时,找到的应该是“研发二部”(a003002),因为它是最近的节点。而下面的顺序就是沿着“研发二部”再往下找,显然不可能找到“研发一部杭州分部”和“研发一部西安分部”,因为编号规则不一样,这样生成的树同样会漏掉节点。
我们提出一种新的算法,即打破传统的遍历顺序,采用由底向上的遍历顺序%