动态载入数据的无刷新TreeView控件(5)

    今天讨论一下TreeView控件的交互问题。包括鼠标对TreeNode的选取(单选&多选)、Checked;键盘对TreeNode的选取(单选&多选)、Checked;通过代码和控件交互三种方式。最后提供一个现阶段完成版本的演示示例供大家测试。

    使用封装好的代码来操作TreeView的UI,是我们 上次主要讲述的内容。要实现交互,最重要的就是管理键盘和鼠标的事件。我原来讲过,关于TreeNode的UI外观,我们在TreeNodeBase那个类中进行处理,而把TreeNode的事件处理放在TreeNode类中来处理,这样的设计是为了提供一个清晰的编程结构。下面我们先看一下TreeNode类的定义和TreeNode.Render方法的代码:
ExpandedBlockStart.gif ContractedBlock.gif < script  language ="javascript" > dot.gif
InBlock.gif
function TreeNode(text, action, icon, subtree)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif    
this.Extends(TreeNodeBase, text, action, icon, subtree);
InBlock.gif   
InBlock.gif    this.m_UserData = [];
InBlock.gif   
InBlock.gif    this.m_Id = __GlobalTreeCache__.NewId();
InBlock.gif    __GlobalTreeCache__[
this.m_Id] = this;
InBlock.gif
InBlock.gif    
this.toString = function()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return '[class TreeNode]';
ExpandedSubBlockEnd.gif    }
;     
ExpandedSubBlockEnd.gif}

InBlock.gif
InBlock.gifTreeNode.prototype.Render 
= function(doc)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif    
var tr = this.base.Render.Call(this, doc);
InBlock.gif
InBlock.gif    tr.OpIcon.onclick 
= TreeNode.__OpIconOnClick;
InBlock.gif    
if ( tr.CheckBox )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{  
InBlock.gif        tr.CheckBox.onclick 
= TreeNode.__CheckBoxOnClick;
ExpandedSubBlockEnd.gif    }
 
InBlock.gif
InBlock.gif    
var tdContent = tr.Content;
InBlock.gif    tdContent.onmousedown 
= TreeNode.__ContentOnMouseDown;
InBlock.gif    tdContent.onmouseover 
= TreeNode.__ContentOnMouseOver;
InBlock.gif    tdContent.onmouseout 
= TreeNode.__ContentOnMosueOut;
InBlock.gif    tdContent.onmousemove 
= TreeNode.__ContentOnMouseMove;
InBlock.gif
InBlock.gif    
return tr;   
ExpandedBlockEnd.gif}
;
None.gif
</ script >


    当然,为了能灵活的使用鼠标和TreeViee交互,我们需要处理大部分的鼠标事件。包括click、mousedown、mouseover、mouseout和mousemove。同时在处理这些鼠标事件时,很多时候还需要和键盘配合来操作,比如:Shift+Click的区段选取,Ctrl+Click的check方式选取等。

    在TreeNode的Render方法中,处理节点展开和收缩的事件是比较简单的,因为那只是一个开/关状态的转换。在TreeNode上做Check操作时需要注意,为了让控件的脚本类(TreeNode的实例)和DHTML类之间属性值同步,我们需要完全控制Checkbox的状态的变化,并且为了避免后面我们使用键盘来Check节点时出错,我们还必须保证Checkbox始终不能获得焦点。

None.gif  input.onfocus  =   function (){ FindParentElement( this , 'TD').focus(); };None.gif

    // 总是把焦点置于Checkbox的Parent元素上

    由于我们已经实现了一套对UI更新的机制,就是统一使用ApplyUIChange()方法来负责。所以除了mousedown事件外,其它的事件处理函数都非常的简单,只需要设置一下控件属性,然后调用ApplyUIChange()就行了,比如:__CheckBoxOnClick(),它的实现就非常的简单清晰。

None.gif  TreeNode.__CheckBoxOnClick  =   function ()
None.gif {
None.gif    
var  elmtNode  =  FindParentElement( this , 'TR');
None.gif    
if  ( elmtNode  &&  elmtNode.Comment  ==  'TreeNode' )
None.gif    {
None.gif        
var  objNode  =  elmtNode.Object;
None.gif        
if  ( objNode )
None.gif        {
None.gif            objNode.SetChecked(
this .checked);
None.gif        }
None.gif    }   
None.gif };


    但是由于mousedown事件需要和键盘配合,并且本身它自己就承担着很多的交互功能,所以处理起来比较麻烦。而其中最麻烦的就是按住Shift键再Click的区段TreeNode选取功能。

None.gif if  ( evt.shiftKey  &&   ! evt.ctrlKey )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
if ( innerCache.m_Selecteds.m_Count == 0 )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif         objNode.SetSelected(
true);
InBlock.gif         innerCache.m_LastSelected 
= objNode;
ExpandedSubBlockEnd.gif    }

InBlock.gif    var startNode 
= innerCache.m_LastSelected;
InBlock.gif    var endNode 
= objNode; 
InBlock.gif    var posStart 
= GetAbsoluteLocation(startNode.m_Element).absoluteTop;
InBlock.gif    var posEnd 
= GetAbsoluteLocation(endNode.m_Element).absoluteTop;
InBlock.gif    innerCache.UnselectAll();
InBlock.gif    
if ( startNode != endNode )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{  
InBlock.gif         
if ( posStart > posEnd )
ExpandedSubBlockStart.gifContractedSubBlock.gif         
dot.gif{
InBlock.gif                  var tmp 
= startNode;
InBlock.gif                  startNode 
= endNode;
InBlock.gif                  endNode 
= tmp;
ExpandedSubBlockEnd.gif         }

InBlock.gif         var curNode 
= startNode;
InBlock.gif         
do
ExpandedSubBlockStart.gifContractedSubBlock.gif         
dot.gif{
InBlock.gif                  curNode.SetSelected(
true);
InBlock.gif                  curNode 
= curNode.GetNextRowNode();
ExpandedSubBlockEnd.gif         }

InBlock.gif         
while(curNode != endNode);
InBlock.gif         endNode.SetSelected(
true); 
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif         endNode.SetSelected(
true);
ExpandedSubBlockEnd.gif    }
   
ExpandedBlockEnd.gif}

    这个功能首先需要判断,当前事件是不是mousedown并且同时键盘Shift被按下了。上面第一个if就是做这个判断的,当确认了使这个事件条件后,我们需要判断当前的TreeView上是否存在最近(一种被选中时的循序的状态)被Selected的节点,这个节点将被用作区段选取的起点,鼠标mousedown(width shift)的节点将会是区段选取的终点。innerCache.m_Selecteds.m_Count == 0说明没有起点,那么我们就置当前被点下的点为起点(这是一个最近点),同时完成本次选取操作。如果在mouse down width shift key的时候,TreeView上有超过一个节点已被Selected,那么我们就取出起点(最近点)和终点,并开始计算起点和终点的位置关系,谁在上谁在下?然后把这两个节点整理为起点始终是在上面的节点,终点始终是在下面的节点(在屏幕上的相对位置),这样是为了使用同样的代码就能把两个方向的Selected工作都做了。然后清除TreeView上所有已选中的节点,从起点开始往终点Selected就行了:

None.gif   do
None.gif {
None.gif     curNode.SetSelected(
true );
None.gif     curNode 
=  curNode.GetNextRowNode();
None.gif }
None.gif 
while (curNode  !=  endNode);


    简单吧,可是这个curNode.GetNextRowNode()又不是很简单emembarrassed.gif,在处理键盘事件中我们再来详细说它。那么我们在键盘上需要处理那些操作呢?看下面的代码,我们处理:Up、Down、+、-、Space和Esc这几个按键。

ExpandedBlockStart.gif ContractedBlock.gif < script  language ="javascript" > dot.gif
InBlock.gifTree.__ContainerOnKeyDown 
= function()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif     
var evt = this.document.parentWindow.event;
InBlock.gif     
var keyCode = evt.keyCode;
InBlock.gif     
var innerCache = null;
InBlock.gif     
if ( evt.srcElement.Comment == 'Tree' )
ExpandedSubBlockStart.gifContractedSubBlock.gif     
dot.gif{
InBlock.gif         innerCache 
= evt.srcElement.Object.m_InnerCache;
ExpandedSubBlockEnd.gif     }

InBlock.gif     
else
ExpandedSubBlockStart.gifContractedSubBlock.gif     
dot.gif{
InBlock.gif         
var elmtNode = FindParentElement(evt.srcElement, 'TR');
InBlock.gif         innerCache 
= elmtNode.Object.m_Tree.m_InnerCache; 
ExpandedSubBlockEnd.gif     }

InBlock.gif     
if ( innerCache.m_Selecteds.m_Count == 0 )
ExpandedSubBlockStart.gifContractedSubBlock.gif     
dot.gif{
InBlock.gif         
return;
ExpandedSubBlockEnd.gif     }

InBlock.gif     
var currentNode = innerCache.m_LastSelected;
InBlock.gif     
switch(keyCode)
ExpandedSubBlockStart.gifContractedSubBlock.gif     
dot.gif{
InBlock.gif         
case 38 : // Up
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             
var previousNode = currentNode.GetPreviousRowNode();
InBlock.gif             
if ( previousNode )
ExpandedSubBlockStart.gifContractedSubBlock.gif             
dot.gif{
InBlock.gif                 
if ( !evt.shiftKey )
ExpandedSubBlockStart.gifContractedSubBlock.gif                 
dot.gif{
InBlock.gif                     
if ( innerCache.m_Selecteds.m_Count == 1 )
ExpandedSubBlockStart.gifContractedSubBlock.gif                     
dot.gif{  
InBlock.gif                          currentNode.SetSelected(
false);
ExpandedSubBlockEnd.gif                     }

InBlock.gif                     
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                     
dot.gif{
InBlock.gif                          innerCache.UnselectAll(); 
ExpandedSubBlockEnd.gif                     }
    
ExpandedSubBlockEnd.gif                 }

InBlock.gif                 previousNode.SetSelected(
true);
InBlock.gif                 innerCache.m_LastSelected 
= previousNode;
InBlock.gif                 evt.cancelBubble 
= true
InBlock.gif                 evt.returnValue 
= null
ExpandedSubBlockEnd.gif             }

InBlock.gif             
break;
ExpandedSubBlockEnd.gif         }

InBlock.gif         
case 40 : // Down
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             
var nextNode = currentNode.GetNextRowNode();
InBlock.gif             
if ( nextNode )
ExpandedSubBlockStart.gifContractedSubBlock.gif             
dot.gif{
InBlock.gif                 
if ( !evt.shiftKey )
ExpandedSubBlockStart.gifContractedSubBlock.gif                 
dot.gif{
InBlock.gif                     
if ( innerCache.m_Selecteds.m_Count == 1 )
ExpandedSubBlockStart.gifContractedSubBlock.gif                     
dot.gif{  
InBlock.gif                          currentNode.SetSelected(
false);
ExpandedSubBlockEnd.gif                     }

InBlock.gif                     
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                     
dot.gif{
InBlock.gif                          innerCache.UnselectAll(); 
ExpandedSubBlockEnd.gif                     }
 
ExpandedSubBlockEnd.gif                 }

InBlock.gif                 nextNode.SetSelected(
true);
InBlock.gif                 innerCache.m_LastSelected 
= nextNode;
InBlock.gif                 evt.cancelBubble 
= true
InBlock.gif                 evt.returnValue 
= null
ExpandedSubBlockEnd.gif           }

InBlock.gif             
break;
ExpandedSubBlockEnd.gif         }

InBlock.gif         
case 107 : 
InBlock.gif         
case 187 : // +
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             
if ( innerCache.m_Selecteds.m_Count == 1 )
ExpandedSubBlockStart.gifContractedSubBlock.gif             
dot.gif{
InBlock.gif                  
var activeNode = innerCache.m_Selecteds.Items(0);
InBlock.gif                  activeNode.Expand(); 
ExpandedSubBlockEnd.gif             }

InBlock.gif             
break;
ExpandedSubBlockEnd.gif         }

InBlock.gif         
case 109 : 
InBlock.gif         
case 189 : // -
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             
if ( innerCache.m_Selecteds.m_Count == 1 )
ExpandedSubBlockStart.gifContractedSubBlock.gif             
dot.gif{
InBlock.gif                  
var activeNode = innerCache.m_Selecteds.Items(0);
InBlock.gif                  activeNode.Collapse(); 
ExpandedSubBlockEnd.gif             }

InBlock.gif             
break;
ExpandedSubBlockEnd.gif         }

InBlock.gif         
case 32 : // Space
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             
if ( innerCache.m_Selecteds.m_Count > 0 )
ExpandedSubBlockStart.gifContractedSubBlock.gif             
dot.gif{
InBlock.gif                  
var bSelected = true
InBlock.gif                  
var selects = innerCache.m_Selecteds;
InBlock.gif                  
if ( selects.m_InnerArray[0].Attributes('HasCheckBox') ) 
ExpandedSubBlockStart.gifContractedSubBlock.gif                  
dot.gif
InBlock.gif                      
for ( var i=0 ; i < selects.m_Count ; ++i )
ExpandedSubBlockStart.gifContractedSubBlock.gif                      
dot.gif{
InBlock.gif                           bSelected 
&= selects.m_InnerArray[i].m_Checked;
ExpandedSubBlockEnd.gif                      }

InBlock.gif                      bSelected 
= !bSelected;
InBlock.gif                      
for ( var i=0 ; i < selects.m_Count ; ++i )
ExpandedSubBlockStart.gifContractedSubBlock.gif                      
dot.gif{
InBlock.gif                           selects.m_InnerArray[i].SetChecked(bSelected);
ExpandedSubBlockEnd.gif                      }

InBlock.gif                      evt.cancelBubble 
= true
InBlock.gif                      evt.returnValue 
= null
ExpandedSubBlockEnd.gif                  }

ExpandedSubBlockEnd.gif              }
 
InBlock.gif              
break;
ExpandedSubBlockEnd.gif         }

InBlock.gif         
case 27 : // Esc
ExpandedSubBlockStart.gifContractedSubBlock.gif
         dot.gif{
InBlock.gif             innerCache.UnselectAll();
InBlock.gif             
break;
ExpandedSubBlockEnd.gif         }

ExpandedSubBlockEnd.gif     }

ExpandedBlockEnd.gif}
;
None.gif
</ script >

   上面的代码,switch前是为了取到被操作的TreeNode对象。+、-、Space和Esc都很简单,从代码中一眼就看明白了,麻烦的就是Up和Down这两个操作。其实在这里希望实现的操作都是WinControl的TreeView中支持的,只是被移到Web的控件上而已。Up和Down操作就是Selected最近那个节点的相邻节点,或上面的或下面的。如果我们在一个层次上来找一个节点的上一个和下一个,那是非常简单的index-1、index+1就行了,可是在树这样的层次结构中,寻找一个它的看起来的上一个或下一个展开的节点,就比较郁闷了。两个方法:

None.gif     var  previousNode  =  currentNode.GetPreviousRowNode();
None.gif   
var  nextNode  =  currentNode.GetNextRowNode();None.gif

    要说明白它们是干嘛的,都比较难。这么说吧,当这个TreeView展现我们面前时,我们暂时忽略节点之间的层级关系,把它们的节点看成向List的条目一样的结构,GetPreviousRowNode(),就是取上一行的Node,GetNextRowNode()就是取当前节点下一行的节点。

ExpandedBlockStart.gif ContractedBlock.gif < script  language ="javascript" > dot.gif
InBlock.gif TreeNodeBase.prototype.GetPreviousRowNode 
= function()
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif    
if ( this.IsFirstNode() )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
if ( this.m_TreeLevel == 0 )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return;
ExpandedSubBlockEnd.gif        }

InBlock.gif        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return this.m_Tree.m_ParentNode;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
var node = this.m_Tree.m_Nodes[this.m_Tree.IndexOf(this)-1];
InBlock.gif        
if ( node.m_ChildTree )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return node.m_ChildTree.GetLastExpandedNode();
ExpandedSubBlockEnd.gif        }

InBlock.gif        
return node;
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif }
;
InBlock.gif
InBlock.gif TreeNodeBase.prototype.GetNextRowNode 
= function()
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif    
if ( this.m_ChildTree && this.m_IsChildExpanded )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
if ( this.m_ChildTree.m_Count > 0 )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif
InBlock.gif            
return this.m_ChildTree.m_Nodes[0];
ExpandedSubBlockEnd.gif        }

InBlock.gif        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return null;
ExpandedSubBlockEnd.gif        }
  
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
if ( this.IsLastNode() )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return this.m_Tree.GetFirstExpandedNode();
ExpandedSubBlockEnd.gif        }

InBlock.gif        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return this.m_Tree.m_Nodes[this.m_Tree.IndexOf(this)+1];
ExpandedSubBlockEnd.gif        }
 
ExpandedSubBlockEnd.gif    }
    
ExpandedBlockEnd.gif }
;
None.gif
</ script >

    弄个图来配合代码看可能比较容易理解些emsmile.gif
  TreeView-4.png

    嗯,其它的问题先玩玩demo再继续讨论吧emsmile.gif
  


    to be continued . . .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值