TreeView漫游者—封装递归操作的复杂性(原创翻译)

  
最近在封装TreeView的时候发现了一篇十分优秀的文章,在这里给大家翻译出来共享一下,^_^。

原文标题:TreeViewWalker - Simplifying Recursion
原文地址:http://www.codeproject.com/cs/miscctrl/TreeViewWalker.asp
源码地址:http://www.codeproject.com/cs/miscctrl/TreeViewWalker/TreeViewWalker_src.zip


       
介绍

使用TreeView控件的时候,经常要用到递归操作(一个方法自己调用自己)。一些开发者花了很多时间来实现或维护递归方法。在本文中奉上的TreeviewWalker类是被创建用来帮助大家从商业逻辑中抽象出递归方法的;让我们可以创建一个简单易用的TreeView漫游程序模型。
   
背景知识

TreeViewWalker类使用了两个开发者必须知道的感念:兄弟节点(Sibling nodes)和后代节点(Descendant nodes)。如果你还不熟悉这些感念,下面的解释将为你阐明它们。

兄弟节点(Sibling nodes)

节点是在相同节点集合(TreeNodeCollection)里的所有其他节点的兄弟。在文章上面的截屏图片里,"Lunch" 和"Dinner" 是兄弟。"Ham Sandwich" 和"Risotto" 不是, 因为他们属于不同的节点结合。

后代节点(Descendant nodes)

一个后代节点是在另一个节点的节点集合里的节点,或者是哪个节点的孩子们中的一个,或者是孩子们中的某一个的孩子,等等。在上面的截屏图象中,"Apple Sauce" 是"Meals"、 "Lunch"和"Food"的后代. "Bread and Butter" 并不是"Lunch"的后代。

代码使用

TreeView漫游者使用相当简单。你创建一个类的实例,指定要操纵的TreeView,绑定ProcessNode事件,然后再调用ProcessTree方法即可。例如:

1 None.gif TreeViewWalker treeViewWalker  =   new  TreeViewWalker(  this .treeView );
2 None.giftreeViewWalker.ProcessNode  +=   new  ProcessNodeEventHandler( treeViewWalker_ProcessNode );
3 None.giftreeViewWalker.ProcessTree();

TreeViewWalker遇到的每个节点都会触发ProcessNode事件。所有节点以自上而下的方式导航,当一个节点的所有后代节点都访问过以后,将继续为该节点的下一个兄弟节点触发ProcessNode事件。为了更具体地解释它的用法,请看文章顶部的Demo程序的屏幕截图。在这个图中的情况下,ProcessNode事件将最先被这些节点顺序触发: "Meals", "Lunch", "Food", "Ham Sandwich", "Apple Sauce", "Drinks", "Iced Tea", "Dinner", 等等。
使用这个类的最后一步就是一创建一个绑定到ProcessNode事件的方法。下面是一个这样方法的实例,它用来反转(toggle)树中每个TreeNode的Checked属性。

1 None.gif private   void  treeViewWalker_ProcessNode(  object  sender, ProcessNodeEventArgs e )
2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
3InBlock.gif    e.Node.Checked = ! e.Node.Checked;
4ExpandedBlockEnd.gif}

5 None.gif
6 None.gif

   
ProcessNodeEventArgs
事件类型暴露了一些你在操作树节点的时候能够用到的属性。
 
Node
- 返回被处理的树节点.
ProcessDescendants
- 获取/设置一个值,用来决定ProcessNode是否会被当前树节点的所有子节点触发。默认值为true。如果StopProcessing属性被设置成true,这个属性将被忽略。
ProcessSiblings
- 获取/设置一个值,用来决定ProcessNode是否会被当前树节点的所有未处理过的兄弟节点触发。默认值为true。如果StopProcessing属性被设置成true,这个属性将被忽略。
StopProcessing
- 获取/设置一个值,用来决定ProcessNode是否会被当前树节点的没被操作过的剩余节点触发。如果这个属性被设置为true,ProcessDescendant属性和ProcessSiblings属性都将被忽略。

假如你确认不再需要让TreeView漫游者遍历节点的后代。在这种情况下你可以设置ProcessDescendants属性为false即可,参考下面的例子:

 1 None.gif private   void  treeViewWalker_ProcessNode_HighlightFoodNodes( object  sender, ProcessNodeEventArgs e )
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    if( e.Node.FullPath.IndexOf( "Food" ) > -1 )
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 5InBlock.gif        e.Node.BackColor = Color.LightGreen;
 6ExpandedSubBlockEnd.gif    }

 7InBlock.gif    else if( e.Node.Text == "Drinks" || e.Node.Text == "Dessert" )
 8ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 9InBlock.gif        // 不需要处理Drinks或Dessert分支的任何节点。
10InBlock.gif        // 所以告诉漫游者跳过它们。
11InBlock.gif        e.ProcessDescendants = false;
12ExpandedSubBlockEnd.gif    }

13ExpandedBlockEnd.gif}

14 None.gif
15 None.gif

如果不再需要TreeViewWalker遍历节点的兄弟节点,则设置ProcessSiblings属性为false即可,参考下面的例子:

 1 None.gif private   void  treeViewWalker_ProcessNode_HighlightSecondNodeInEachNodeIsland( object  sender, ProcessNodeEventArgs e)
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    if( e.Node.Index == 1 )
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 5InBlock.gif        e.Node.BackColor = Color.LightGreen;
 6InBlock.gif        // 每次分支里的第二个节点被高亮显示,不需要再
 7InBlock.gif        // 处理相同分支下面的其他节点,所有告诉漫游者
 8InBlock.gif        // 不要再为兄弟节点触发ProcessNode事件。
 9InBlock.gif        e.ProcessSiblings = false;
10ExpandedSubBlockEnd.gif    }

11ExpandedBlockEnd.gif}

让TreeViewWalker完全停止遍历树,仅需要设置StopProcessing属性为true即可,就象:

 1 None.gif private   void  treeViewWalker_ProcessNode_HighlightAsparagusNode(  object  sender, ProcessNodeEventArgs e )
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    if( e.Node.Text == "Asparagus" )
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 5InBlock.gif        e.Node.BackColor = Color.LightGreen;
 6InBlock.gif
 7InBlock.gif        // 如果"Asparagus" 节点已经发现,就告诉漫游者停止操作。
 8InBlock.gif        e.StopProcessing = true;
 9ExpandedSubBlockEnd.gif    }

10ExpandedBlockEnd.gif}

如果你想处理树中的某个特殊的节点分支,那么可以调用TreeView漫游者的ProcessBranch方法,并且传递分支的根节点给它。Demo程序在文章开头示范过。


     
高级应用

到目前为止,我只演示了如何在简单的情况下使用TreeViewWalker。现在你已经知道了如果了为什么和如果使用它了,接下来让我们看一个更复杂的例子。这个实例演示了在把树节点按需要的方式载入的时候,如何使用TreeViewWalker。它向用户提供了一个文本框和四个可以在树节点里导航的按纽来实现在树中搜索包含文本框内容的节点。树里的节点即是用户机器的目录集合。
就象上面的截屏图象一样,上面有四个导航按纽;等价于第一个,上一个,下一个,和最后一个。上一个和下一个按纽分别是用来搜索当前TreeView里选中节点的上一个和下一个节点。因为节点是在需要的时候加载的,且搜索逻辑必须遍历树上的每一个逻辑节点,所以动态加载节点的过程必须发生在ProcessNode事件绑定的方法中。从另一个角度来说,由于TreeView不总是包含所有节点,所以节点必须在执行查询的时候动态加载。

变量

每个按纽对应一个TreeView漫游者。还有两个辅助的变量用来帮助执行查询操作,如下面:

1 None.gif // 每个搜索按纽都有一个漫游者。
2 None.gif // 它们都是在InitializeAdvancedDemo()方法里配置的。
3 None.gif private  TreeViewWalker tvWalkerFirst;
4 None.gif private  TreeViewWalker tvWalkerPrev;
5 None.gif private  TreeViewWalker tvWalkerNext;
6 None.gif private  TreeViewWalker tvWalkerLast;
7 None.gif // 用来辅助搜索的两个变量
8 None.gif private  TreeNode matchingNode;
9 None.gif private   bool  processedSelectedNode;

找到第一个匹配节点

让我们来看看当用户点击"First"按纽的时候发生了什么。一发现节点名字里包含要搜索的字符串,就将节点选中且通知TreeView漫游者停止继续遍历。如果没有匹配的节点,则必须检查后代节点。不管怎样,因为节点是在需要的时候才加载的,所以节点的子节点可能还没有加载。如果是这样的话,TreeView漫游者将出发ProcessNode事件来加载子节点。

 1 None.gif private   void  btnFirst_Click( object  sender, System.EventArgs e)
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    this.tvWalkerFirst.ProcessTree();
 4ExpandedBlockEnd.gif}

 5 None.gif
 6 None.gif private   void  tvWalkerFirst_ProcessNode( object  sender, ProcessNodeEventArgs e)
 7 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 8InBlock.gif    //一发现匹配的节点,节点就会被选中,漫游者也将停止漫游。
 9InBlock.gif    ifthis.ContainsSearchText( e.Node ) )
10ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
11InBlock.gif        this.treeAdvanced.SelectedNode = e.Node;
12InBlock.gif        e.StopProcessing = true;
13ExpandedSubBlockEnd.gif    }

14InBlock.gif    else ifthis.HasDummyNode( e.Node ) )
15InBlock.gif        this.LoadDirectoryNodes( e.Node );
16ExpandedBlockEnd.gif}

17 None.gif

找到上一个匹配节点

当用户点击"Previous"按纽的时候,需要更多的逻辑操作。基本的想法是我们允许为被选中节点之前的所有节点触发ProcessNode事件。最后一个匹配的节点就是我们要找的"previous"节点。(相对于被选中的节点)

 1 None.gif private   void  btnPrev_Click( object  sender, System.EventArgs e)
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    this.matchingNode = null;            
 4InBlock.gif    this.tvWalkerPrev.ProcessTree();
 5ExpandedBlockEnd.gif}

 6 None.gif
 7 None.gif private   void  tvWalkerPrev_ProcessNode( object  sender,ProcessNodeEventArgs e)
 8 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 9InBlock.gif    //我们最近发现的匹配节点就是"previous"节点,并且将会被选中。
10InBlock.gif    if( e.Node == this.treeAdvanced.SelectedNode )
11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
12InBlock.gif        ifthis.matchingNode != null )
13InBlock.gif            this.treeAdvanced.SelectedNode = this.matchingNode;
14InBlock.gif        e.StopProcessing = true;
15InBlock.gif        return;
16ExpandedSubBlockEnd.gif    }

17InBlock.gif    
18InBlock.gif    ifthis.ContainsSearchText( e.Node ) )
19InBlock.gif        this.matchingNode = e.Node;
20InBlock.gif
21InBlock.gif    ifthis.HasDummyNode( e.Node ) )
22InBlock.gif        this.LoadDirectoryNodes( e.Node );
23ExpandedBlockEnd.gif}

找到下一个匹配节点

搜索"next"节点使用跟搜索"previous"节点相反的逻辑。当从被选中的节点往后搜索到匹配的节点的时候就停止遍历。

 1 None.gif private   void  btnNext_Click( object  sender, System.EventArgs e)
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    this.processedSelectedNode = false;
 4InBlock.gif    this.tvWalkerNext.ProcessTree();
 5ExpandedBlockEnd.gif}

 6 None.gif
 7 None.gif private   void  tvWalkerNext_ProcessNode( object  sender, 
 8 None.gif                                      ProcessNodeEventArgs e)
 9 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
10InBlock.gif    if( e.Node == this.treeAdvanced.SelectedNode )
11InBlock.gif        this.processedSelectedNode = true;                
12InBlock.gif    
13InBlock.gif    ifthis.processedSelectedNode               &&
14InBlock.gif        e.Node != this.treeAdvanced.SelectedNode &&
15InBlock.gif        this.ContainsSearchText( e.Node ) )
16ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
17InBlock.gif        this.treeAdvanced.SelectedNode = e.Node;
18InBlock.gif        e.StopProcessing = true;
19InBlock.gif        return;
20ExpandedSubBlockEnd.gif    }

21InBlock.gif    
22InBlock.gif    ifthis.HasDummyNode( e.Node ) )
23InBlock.gif        this.LoadDirectoryNodes( e.Node );
24ExpandedBlockEnd.gif}

找到最后一个匹配节点

搜索"last"节点包括搜索整个树,然后选折最后一个匹配的节点。这个操作是最花费时间的,因为在操作结束之前,所有的节点都必须被加载一遍。

 1 None.gif private   void  btnLast_Click( object  sender, System.EventArgs e)
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    this.Cursor = Cursors.WaitCursor;
 4InBlock.gif
 5InBlock.gif    this.matchingNode = null;
 6InBlock.gif    this.tvWalkerLast.ProcessTree();
 7InBlock.gif    ifthis.matchingNode != null )
 8ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 9InBlock.gif        // 'matchingNode'变量指向最后匹配的节点
10InBlock.gif        this.treeAdvanced.SelectedNode = this.matchingNode;
11ExpandedSubBlockEnd.gif    }

12InBlock.gif
13InBlock.gif    this.Cursor = Cursors.Default;
14ExpandedBlockEnd.gif}

15 None.gif
16 None.gif private   void  tvWalkerLast_ProcessNode( object  sender, ProcessNodeEventArgs e)
17 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
18InBlock.gif    ifthis.ContainsSearchText( e.Node ) )
19InBlock.gif        this.matchingNode = e.Node;
20InBlock.gif    
21InBlock.gif    ifthis.HasDummyNode( e.Node ) )
22InBlock.gif        this.LoadDirectoryNodes( e.Node );
23ExpandedBlockEnd.gif}

虽然上述方法未必是在TreeView搜索的最有效方式,但无疑是最简单。使用TreeView漫游者简化了程序模型,并且可以很自然把不同的搜索程序分离到隔离的方法中。希望这个例子示范出了TreeView漫游者的灵活性和能力,能够给大家一些指点,以便在更复杂的情况下使用它。

关键的地方

如果你对TreeView漫游者是如何工作的感兴趣,这个方法就是他的核心:

 1 None.gif private   bool  WalkNodes( TreeNode node )
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    // 触发ProcessNode事件.
 4InBlock.gif    ProcessNodeEventArgs args = ProcessNodeEventArgs.CreateInstance( node );
 5InBlock.gif    this.OnProcessNode( args );
 6InBlock.gif
 7InBlock.gif    // 缓存ProcessSiblings的值,因为ProcessNodeEventArgs是单态的。
 8InBlock.gif    bool processSiblings = args.ProcessSiblings;
 9InBlock.gif
10InBlock.gif    if( args.StopProcessing )
11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
12InBlock.gif        this.stopProcessing = true;
13ExpandedSubBlockEnd.gif    }

14InBlock.gif    else if( args.ProcessDescendants )
15ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
16InBlock.gif        foreach( TreeNode childNode in node.Nodes )
17InBlock.gif            if! this.WalkNodes( childNode ) || 
18InBlock.gif                  this.stopProcessing )
19InBlock.gif                break;
20ExpandedSubBlockEnd.gif    }

21InBlock.gif
22InBlock.gif    return processSiblings;
23ExpandedBlockEnd.gif}

历史

略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值