.NET平台下带权限控制的TreeView控件节点生成算法
点击次数:29 次 发布日期:2008-11-26 23:44:42 作者:源代码网
|
源代码网推荐 源代码网推荐在应用系统开发中,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:数据准备,给定数据集。 源代码网推荐一般来说数据集遵循这样的格式,即(TagValue,ContentValue,ParentID); 源代码网推荐 源代码网推荐l Step 1:给定待增加子节点的节点(初始时一般为根节点),记作CurNode,以及待增加节点的ParentID值(初始时为根节点的ParentID),记作CurParentID; 源代码网推荐 源代码网推荐l Step 2:在数据集中查找具有指定ParentID值的所有节点,得到节点集objArr[], 源代码网推荐if (objArr == null) 源代码网推荐 return; 源代码网推荐else 源代码网推荐{ 源代码网推荐 //遍历节点集 源代码网推荐 for(int i=0; i<objArr.Length;i++) 源代码网推荐 { 源代码网推荐 将objArr[i]添加为CurNode的子节点,同时递归(即将objArr[i]作为CurNode,objArr[i]的TagValue作为CurParentID,goto Step 1); 源代码网推荐 } 源代码网推荐} 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐最终可以得到下图所示的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),因为它是最近的节点。而下面的顺序就是沿着“研发二部”再往下找,显然不可能找到“研发一部杭州分部”和“研发一部西安分部”,因为编号规则不一样,这样生成的树同样会漏掉节点。 源代码网推荐 源代码网推荐我们提出一种新的算法,即打破传统的遍历顺序,采用由底向上的遍历顺序。形象地说,传统的方法是通过一个既有根节点或父节点来不断衍生新的子节点(如图3(a)所示),而新的算法是通过不断聚集节点,形成子树集,最后汇成一棵树(如图3(b)所示)。 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐图3 TreeView节点生成流程示意图 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐算法2:由底向上按层次(深度)遍历法生成树算法 源代码网推荐 源代码网推荐l Step 0:数据准备,给定数据集(TagValue,ContentValue,DepthID), TagValue是节点的实际值,ContentValue是节点显示的值或者说标签值,DepthID是节点的深度编号。若节点为根节点,一般其DepthID长度为最短。给定最大深度iMaxDepLen和最小深度iMinDepLen。给定用于存储当前子树的Hashtable; 源代码网推荐 源代码网推荐l Step 1:给定当前遍历的层级长度iCurDepLen,初始设为iMaxDepLen; 源代码网推荐 源代码网推荐l Step 2:在数据集中根据给定iCurDepLen查找满足条件的层级,得到该层级的节点集objArr[], 源代码网推荐if (objArr == null) 源代码网推荐 return; 源代码网推荐else 源代码网推荐{ 源代码网推荐 //遍历节点集 源代码网推荐 for(int i=0; i<objArr.Length;i++) 源代码网推荐 { 源代码网推荐 Step 2.1 查找objArr[i]的父节点,若无父节点,直接加入,goto Step 2.2;若有父节点,先查找父节点是否已在Hashtable中。若有,将其从Hashtable中移出并记为tempParNode;否则生成新节点tempParNode;goto Step 2.3; 源代码网推荐 Step 2.2 若当前节点objArr[i]不在Hashtable中,在Hashtable中添加objArr[i];continue; 源代码网推荐 Step 2.3 若当前节点objArr[i]不在Hashtable中,根据objArr[i]生成节点tempNode;否则,将其从Hashtable中移出,并记为tempNode;将tempNode插到tempParNode中,并将存入Hashtable。 源代码网推荐 } 源代码网推荐} 源代码网推荐 源代码网推荐l Step 3:若当前层级iCurDepLen大于最小层级iMinDepLen,则继续回溯,将iCurDepLen减1并作为当前iCurDepLen,goto Step 2;否则goto Step 4. 源代码网推荐 源代码网推荐l Step 4:在得到用Hashtable存储的节点表后(实际上是一子树表),遍历Hashtable,将各棵子树插入TreeView. 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐在该算法中,我们一开始便计算好数据集中节点深度编号的最小长度和最大长度,目的是为了不盲目搜索。但如果数据集中每一层级的深度编号是固定长的,则可以更简化搜索过程。存放临时子树的Hashtable的键值是当前子树根节点的Tag值,这样的好处是查找相当方便,不需要在TreeView中遍历一个个节点。所以,每次处理上一层级的节点,只需看其父节点在不在Hashtable中,若在将其插入子树,否则增加Hashtable项。 源代码网推荐 源代码网推荐附录示例程序实现了这一算法,这里介绍一下关键的几个函数。 源代码网推荐 源代码网推荐函数形式及其参数解释 源代码网推荐 功能 源代码网推荐 源代码网推荐PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex) 源代码网推荐 源代码网推荐1. objTreeView是最终要生成的TreeView; 源代码网推荐 源代码网推荐2. dsSource是给定数据集; 源代码网推荐 源代码网推荐3. strTreeCaption指定TreeView根节点的名称; 源代码网推荐 源代码网推荐4. iTagIndex是数据集中TagValue字段的列号; 源代码网推荐 源代码网推荐5. iContentIndex是数据集中ContentValue字段的列号; 源代码网推荐 源代码网推荐6. iDepthIndex是数据集中DepthID字段的列号; 源代码网推荐 1. 采用层次(深度)遍历法生成树主调函数; 源代码网推荐 源代码网推荐2. 调用CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode) 源代码网推荐 源代码网推荐CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode) 源代码网推荐 源代码网推荐1. dsSource,iTagIndex,iContentIndex,iDepthIndex同上; 源代码网推荐 源代码网推荐2. iCurDepLen指当前层级深度编号长度; 源代码网推荐 源代码网推荐3. iMinDepLen指最小深度即最顶层深度编号长度; 源代码网推荐 源代码网推荐4. objArrNode指用于存放中间子树的Hashtable 源代码网推荐 1. 从底往上聚集节点; 源代码网推荐 源代码网推荐2. 调用 LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex) 源代码网推荐 源代码网推荐LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex) 源代码网推荐 源代码网推荐1. dsSource,iTagIndex,iContentIndex,iDepthIndex同上; 源代码网推荐 源代码网推荐2. strSubDepth指当前节点的深度编号(因为是递归查找) 源代码网推荐 1. 查找最近的上控层级,因为有可能父节点层级不存在。 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐此时若给定数据集(我们把“研发部”和“行销一部”过滤掉), 源代码网推荐 源代码网推荐TagValue 源代码网推荐 ContentValue 源代码网推荐 ParentID 源代码网推荐 DepthID 源代码网推荐 源代码网推荐G01 源代码网推荐 行销部 源代码网推荐 源代码网推荐 a001 源代码网推荐 源代码网推荐G02 源代码网推荐 顾问部 源代码网推荐 源代码网推荐 a002 源代码网推荐 源代码网推荐G04 源代码网推荐 测试部 源代码网推荐 源代码网推荐 a004 源代码网推荐 源代码网推荐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 源代码网推荐 源代码网推荐 源代码网推荐表5 给定数据集 源代码网推荐 源代码网推荐则生成树如下图所示, 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐图4 TreeView效果图 源代码网推荐 源代码网推荐这正是我们需要的结果。 源代码网推荐 源代码网推荐当然,有时为了结构的需要,我们还会采取所谓“中立”的方法。比如对于本文所提的TreeView控件节点生成问题,如果不想再写算法去生成深度编号,那么我们还可以通过给数据集增加标志位的方法,即用标志位来标识数据是否已被筛选。在运用传统算法生成树后,再检查一下是否有未被筛选的数据,若有则查找其祖辈节点,将其插入祖辈节点。不过这里的“查找祖辈节点”是在TreeView上进行的,当节点很多时其效率肯定没有直接在数据集上搜索高。 源代码网推荐 源代码网推荐另外,深度编号的引入不仅会给生成树带来方便,还可以让权限设置更灵活。具体到我们的示例来说,一般如果我们要把某些部门过滤掉,那么会把这些部门一个一个挑出来,我们称之为“离散值设置方式”。而当系统结构庞大时,我们更希望挑选一个区间,比如把一个部门及其下控的n级过滤掉,这是一个“连续值设置方式”,这时包含层级概念的深度编号可以很好地解决这个问题。实际的系统开发中,我们也发现采用这种方式是切实可行的。 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐四、其他TreeView生成方式 源代码网推荐 源代码网推荐前面提到TreeView还可以通过XML文件(串)生成。这种方式实现的关键是构造出一个类似于TreeView的XML文档或字符串出来。其基本思想应该与前面讨论的算法是相似的,只是在程序实现上稍微复杂一些(其中,XML节点的索引可以基于文档对象模型(DOM)来做)。另外还要注意的是,有很多的第三方TreeView控件,他们所支持的XML文档的格式是不尽相同的。限于篇幅,本文不详细讨论具体实现过程。 源代码网推荐 源代码网推荐五、小结 源代码网推荐 源代码网推荐 本文主要讨论了.NET平台下TreeView控件节点生成程序设计,结合已有方法和实际需求,对设计方法进行了研究,给出了比较完整的解决方法。 源代码网推荐 源代码网推荐在树的具体应用中,除了生成树之外,节点的增、删、改、查甚至节点的升级和降级都是很常见的。本质上说,这些操作所涉及的是与业务相关的数据库操作,所以在采用“由底向上按层次(深度)遍历法”生成的TreeView中,这些操作的实现与传统方法是一致的,额外的操作无非是添加或修改深度编号。当然,实际需求是变化多端的,相应算法的设计与分析也是无止境的。 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐参考文献(Reference): 源代码网推荐 源代码网推荐[1] Zane Thomas. DataViewTree for Windows Forms,http://www.abderaware.com/WhitePapers/ datatreeview.htm 源代码网推荐 源代码网推荐[2] 李洪根. 树形结构在开发中的应用, http://www.microsoft.com/china/community/Column/ 21.mspx 源代码网推荐 源代码网推荐[3] 李洪根. .NET平台下Web树形结构程序设计, http://www.microsoft.com/china/community/ Column/30.mspx 源代码网推荐 源代码网推荐[4] Don Schlichting. Populating the TreeView Control from a Database, http://www.15seconds. com/issue/030827.htm 源代码网推荐 源代码网推荐[5] HOW TO: Populate a TreeView Control from a Dataset in Visual Basic .NET, http://support. microsoft.com/?kbid=320755 源代码网推荐 源代码网推荐[6] Scott Mitchell. Displaying XML Data in the Internet Explorer TreeView Control,http://aspnet. 4guysfromrolla.com/articles/051403-1.aspx 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐------------- 源代码网推荐source code: 源代码网推荐 源代码网推荐using System; 源代码网推荐using System.Data; 源代码网推荐using System.Windows.Forms; 源代码网推荐using System.Collections; 源代码网推荐 源代码网推荐 源代码网推荐namespace PopTreeView 源代码网推荐{ 源代码网推荐 /// <summary> 源代码网推荐 /// TreeOperator 的摘要说明。 源代码网推荐 /// </summary> 源代码网推荐 public class TreeOperator 源代码网推荐 { 源代码网推荐 public TreeOperator() 源代码网推荐 { 源代码网推荐 // 源代码网推荐 // TODO: 在此处添加构造函数逻辑 源代码网推荐 // 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 /// <summary> 源代码网推荐 /// 采用层次(深度)遍历法生成树 源代码网推荐 /// </summary> 源代码网推荐 /// <param name="objTreeView">目标树</param> 源代码网推荐 /// <param name="dsSource">数据集</param> 源代码网推荐 /// <param name="strTreeCaption">树显示名</param> 源代码网推荐 /// <param name="iTagIndex">值索引</param> 源代码网推荐 /// <param name="iContentIndex">内容索引</param> 源代码网推荐 /// <param name="iDepthIndex">层次索引</param> 源代码网推荐 public static void PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex) 源代码网推荐 { 源代码网推荐 //从底层开始遍历,开辟一个HashTable(以Tag值为关键字),存放当前计算的节点 源代码网推荐 objTreeView.Nodes.Clear(); 源代码网推荐 int iMaxLen = GetMaxDepthLen(dsSource,iDepthIndex); 源代码网推荐 int iMinLen = GetTopDepthLen(dsSource,iDepthIndex); 源代码网推荐 Hashtable objArrNode = new Hashtable(); 源代码网推荐 CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iMaxLen,iMinLen,ref objArrNode); 源代码网推荐 源代码网推荐 TreeNode objRootNode = new TreeNode(strTreeCaption); 源代码网推荐 源代码网推荐 //在得到节点表后,插入树 源代码网推荐 foreach(object objNode in objArrNode.Values) 源代码网推荐 { 源代码网推荐 TreeNode objNewNode = new TreeNode(); 源代码网推荐 objNewNode = (TreeNode)objNode; 源代码网推荐 objRootNode.Nodes.Add(objNewNode); 源代码网推荐 } 源代码网推荐 源代码网推荐 objTreeView.Nodes.Add(objRootNode); 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 /// <summary> 源代码网推荐 /// 从底往上聚集节点 源代码网推荐 /// </summary> 源代码网推荐 /// <param name="dsSource"></param> 源代码网推荐 /// <param name="iTagIndex"></param> 源代码网推荐 /// <param name="iContentIndex"></param> 源代码网推荐 /// <param name="iDepthIndex"></param> 源代码网推荐 /// <param name="iCurDepLen"></param> 源代码网推荐 /// <param name="iMinDepLen"></param> 源代码网推荐 /// <param name="objArrNode"></param> 源代码网推荐 private static void CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode) 源代码网推荐 { 源代码网推荐 //收集节点 源代码网推荐 System.Data.DataView dv; 源代码网推荐 System.Windows.Forms.TreeNode tempNode; 源代码网推荐 源代码网推荐 //查找给定层节点 源代码网推荐 int i=iCurDepLen; 源代码网推荐 do 源代码网推荐 { 源代码网推荐 dv = new DataView(dsSource.Tables[0]); 源代码网推荐 string strExpr = "LEN(TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+"))="+Convert.ToString(i); 源代码网推荐 dv.RowFilter = strExpr; 源代码网推荐 i--; 源代码网推荐 }while(i>=iMinDepLen && dv.Count<=0); 源代码网推荐 iCurDepLen = i+1; 源代码网推荐 源代码网推荐 #region 逐层回溯,收集节点 源代码网推荐 foreach(System.Data.DataRowView drow in dv) 源代码网推荐 { 源代码网推荐 //查找父节点 源代码网推荐 string[] strArrParentInfo = LookupParentNode(dsSource,iDepthIndex,drow[iDepthIndex].ToString().Trim(),iTagIndex,iContentIndex); 源代码网推荐 string strTagValue = drow[iTagIndex].ToString().Trim(); 源代码网推荐 string strContentValue = drow[iContentIndex].ToString(); 源代码网推荐 源代码网推荐 //若无父节点,直接加入 源代码网推荐 if (strArrParentInfo == null) 源代码网推荐 { 源代码网推荐 //当前节点不在Hashtable中 源代码网推荐 if (objArrNode[strTagValue]==null) 源代码网推荐 { 源代码网推荐 //添加当前节点 源代码网推荐 tempNode = new TreeNode(strContentValue); 源代码网推荐 tempNode.Tag = strTagValue; 源代码网推荐 objArrNode.Add(strTagValue,tempNode); 源代码网推荐 } 源代码网推荐 } 源代码网推荐 else //有父节点,此时先查找父节点是否已在Hashtable中 源代码网推荐 { 源代码网推荐 string strParTagValue = strArrParentInfo[0].Trim(); 源代码网推荐 string strParContentValue = strArrParentInfo[1].Trim(); 源代码网推荐 源代码网推荐 //父节点已在Hashtable中 源代码网推荐 if (objArrNode[strParTagValue]!= null) 源代码网推荐 { 源代码网推荐 //当前节点不在Hashtable中 源代码网推荐 if (objArrNode[strTagValue]==null) 源代码网推荐 { 源代码网推荐 tempNode = new TreeNode(strContentValue); 源代码网推荐 tempNode.Tag = strTagValue; 源代码网推荐 } 源代码网推荐 else 源代码网推荐 { 源代码网推荐 //取出并移除该节点,然后插入父节点 源代码网推荐 tempNode = new TreeNode(); 源代码网推荐 tempNode =(TreeNode)objArrNode[strTagValue]; 源代码网推荐 objArrNode.Remove(strTagValue); 源代码网推荐 } 源代码网推荐 源代码网推荐 //插入到父节点中 源代码网推荐 TreeNode tempParNode = new TreeNode(); 源代码网推荐 tempParNode = (TreeNode)objArrNode[strParTagValue]; 源代码网推荐 tempParNode.Nodes.Add(tempNode); 源代码网推荐 objArrNode[strParTagValue] = tempParNode; 源代码网推荐 } 源代码网推荐 else //父节点不在Hashtable中 源代码网推荐 { 源代码网推荐 //当前节点不在Hashtable中 源代码网推荐 if (objArrNode[strTagValue]==null) 源代码网推荐 { 源代码网推荐 tempNode = new TreeNode(strContentValue); 源代码网推荐 tempNode.Tag = strTagValue; 源代码网推荐 } 源代码网推荐 else 源代码网推荐 { 源代码网推荐 //取出并移除该节点,然后插入父节点 源代码网推荐 tempNode = new TreeNode(); 源代码网推荐 tempNode = (TreeNode)objArrNode[strTagValue]; 源代码网推荐 objArrNode.Remove(strTagValue); 源代码网推荐 } 源代码网推荐 源代码网推荐 //创建父节点并将当前节点插入到父节点中 源代码网推荐 TreeNode tempParNode = new TreeNode(strParContentValue); 源代码网推荐 tempParNode.Tag = strParTagValue; 源代码网推荐 tempParNode.Nodes.Add(tempNode); 源代码网推荐 objArrNode.Add(strParTagValue,tempParNode); 源代码网推荐 } 源代码网推荐 } 源代码网推荐 } 源代码网推荐 #endregion 源代码网推荐 源代码网推荐 //还有未遍历的层 源代码网推荐 if (iCurDepLen>iMinDepLen) 源代码网推荐 { 源代码网推荐 CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iCurDepLen-1,iMinDepLen,ref objArrNode); 源代码网推荐 } 源代码网推荐 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 /// <summary> 源代码网推荐 /// 查找父亲节点 源代码网推荐 /// </summary> 源代码网推荐 /// <param name="dsSource"></param> 源代码网推荐 /// <param name="iDepthIndex"></param> 源代码网推荐 /// <param name="strSubDepth"></param> 源代码网推荐 /// <param name="iTagIndex"></param> 源代码网推荐 /// <param name="iContentIndex"></param> 源代码网推荐 /// <returns>找到返回由Tag值,内容值和深度值组成的字符串数组,否则返回null</returns> 源代码网推荐 private static string[] LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex) 源代码网推荐 { 源代码网推荐 System.Data.DataView dv; 源代码网推荐 int iSubLen = strSubDepth.Length; 源代码网推荐 源代码网推荐 if (iSubLen<=1) 源代码网推荐 { 源代码网推荐 return null; 源代码网推荐 } 源代码网推荐 源代码网推荐 int i=1; 源代码网推荐 do 源代码网推荐 { 源代码网推荐 dv = new DataView(dsSource.Tables[0]); 源代码网推荐 string strExpr ="TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+") = ""+strSubDepth.Substring(0,iSubLen-i)+"""; 源代码网推荐 dv.RowFilter = strExpr; 源代码网推荐 i++; 源代码网推荐 }while(i<iSubLen && dv.Count<=0); 源代码网推荐 源代码网推荐 if (dv.Count<=0) 源代码网推荐 { 源代码网推荐 return null; 源代码网推荐 } 源代码网推荐 else 源代码网推荐 { 源代码网推荐 string[] strArr = {dv[0][iTagIndex].ToString(),dv[0][iContentIndex].ToString(),dv[0][iDepthIndex].ToString()}; 源代码网推荐 return strArr; 源代码网推荐 } 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 /// <summary> 源代码网推荐 /// 得到最大深度值(深度的长度) 源代码网推荐 /// </summary> 源代码网推荐 /// <param name="dsSource">数据集</param> 源代码网推荐 /// <param name="iDepthIndex">深度索引(列号)</param> 源代码网推荐 /// <returns>最大深度值</returns> 源代码网推荐 private static int GetMaxDepthLen(DataSet dsSource,int iDepthIndex) 源代码网推荐 { 源代码网推荐 DataRowCollection objRowCol = dsSource.Tables[0].Rows; 源代码网推荐 int iMax = objRowCol[0][iDepthIndex].ToString().Trim().Length; 源代码网推荐 源代码网推荐 foreach(DataRow objRow in objRowCol) 源代码网推荐 { 源代码网推荐 int iCurlen = objRow[iDepthIndex].ToString().Trim().Length; 源代码网推荐 if (iMax<iCurlen) 源代码网推荐 { 源代码网推荐 iMax = iCurlen; 源代码网推荐 } 源代码网推荐 } 源代码网推荐 源代码网推荐 return iMax; 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 /// <summary> 源代码网推荐 /// 得到最小深度值(深度的长度) 源代码网推荐 /// </summary> 源代码网推荐 /// <param name="dsSource">数据集</param> 源代码网推荐 /// <param name="iDepthIndex">深度索引(列号)</param> 源代码网推荐 /// <returns>最小深度值</returns> 源代码网推荐 private static int GetTopDepthLen(DataSet dsSource,int iDepthIndex) 源代码网推荐 { 源代码网推荐 DataRowCollection objRowCol = dsSource.Tables[0].Rows; 源代码网推荐 int iMin = objRowCol[0][iDepthIndex].ToString().Trim().Length; 源代码网推荐 源代码网推荐 foreach(DataRow objRow in objRowCol) 源代码网推荐 { 源代码网推荐 int iCurlen = objRow[iDepthIndex].ToString().Trim().Length; 源代码网推荐 if (iMin>iCurlen) 源代码网推荐 { 源代码网推荐 iMin = iCurlen; 源代码网推荐 } 源代码网推荐 } 源代码网推荐 源代码网推荐 return iMin; 源代码网推荐 } 源代码网推荐 源代码网推荐 源代码网推荐 } 源代码网推荐} 源代码网推荐 源代码网推荐 源代码网推荐----------- 源代码网推荐memo: 本文最初我想在刊物上发表的,篇幅很长,这里有所删节。欢迎指正、交流! 源代码网推荐 源代码网推荐 源代码网推荐 源代码网推荐 源代码网供稿. |
