创建可编辑的xml文档(之三)执行拖放操作

执行托放操作

定义了treeview 显示得内容以后,现在你应该准备处理如何四处移动元素了,大多数得开发人员在处理拖放操作时得通用观念都是很相似得,无论使用visual c++ visual basic 或者任何一种.net 语言,所以我一直用下面的四个方法处理这个操作:

MouseDown-----用户选择得内容

DragEnter---用户开始拖动选中得项目

DragOver ---用户拖动选中得项目经过另一个项目

DragDrop---用户在某个地方放下选择得项目

执行这些方法适当得给用户针对可以和不可以处理的得操作分别给予视觉反馈,同时告诉用户他们是怎样被执行的,并且不用管给定的上下文的细节操作,所以就有三个直接的问题需要被考虑:

1. 你如何使treeview 控件中的一个节点和底层xml文档中的节点进行匹配

2. 为了物理节点能够跟随图形进行转换,用户如何操作xml文档

3. 你如何有效地执行大的xml文档。如果这样的转变要不得不加强时,你不想把没有必要的东西绑定到用户界面

清单1

A TreeNode's position maps to an XML node using an XPath query.

Private Sub XmlTreeView_MouseDown(ByVal sender As Object, ByVal e As _
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
' First check whether we've clicked on a node in the tree view; if not,
' just return
Dim pt As New Point(e.X, e.Y)
drag_node = Me.GetNodeAt(pt)

If drag_node Is Nothing Then Return

' Highlight the node and build an xpath query so that we can remove it later
xpath_remove_query = buildXPathQuery(drag_node)
Me.SelectedNode = drag_node

' Decide whether we're going to perform an intra-folder rearrangement (right
' mouse button) or a genuine drag-and-drop (left mouse button);
' we do this in the MouseDown rather than DragEnter method, since by the time
' DragEnter fires, the mouse may well have been dragged to a different node
If e.Button = System.Windows.Forms.MouseButtons.Right Then
right_mouse_drag = True
Else
right_mouse_drag = False
End If
End Sub

Private Function buildXPathQuery(ByVal node As System.Windows.Forms.TreeNode) As String
Dim query As String = ""

Do
query = "*[" & xpath_filter & "][" & (node.Index + 1) & "]/" & query
node = node.Parent
Loop While Not (node Is Nothing)

Return query.Substring(0, query.Length - 1)
End Function

显示了MouseDown 句柄 和它调用的帮助方法buildXPathQuery,首先代码检查一个被选中的节点,接着通过使用事先定义好的筛选, 存储TreeNode (drag_node) 和使它关联到xml文档根节点的Xpath 查询(xpath_remove_query)。 例如下面的查询确定了树的根节点的第二个孩子有五个孩子文件夹,一个文件夹可以用查询"attribute::id." 唯一确定

[attribute::id][2]/[attribute::id][5]
当用户拖动一个节点到另外一个位置时,代码列表1提供了移动treenode 和treenode相关联的xmlNode的足够信息。你也许认为你能够得到相同的效果,而完全没有必要引用筛选,并且简单的指定像“托动文档根节点的第二个孩子到第一个孩子节点内部”这样的事情,但是这里不是你认为的那样,应该是筛选器强迫treeview 的节点层次和xml文档一一对应的,没有了它 ,这样的直接使用可能是不明智的,例如假设筛选器匹配下面的结构:

1<contact>
2<email></email>
3<city></city>
4<country></country>
5</contact>

这样的约束意味着Xpath 筛选器将contacts.xml的层次作为一个简单的子元素列表看待

[0]

  1<contacts>   
  2[0] <contact="alex">   
  3[1] <contact="rebekah">   
  4[2] <contact="justin">   
  5然而,treeview 将相同的文档看作一个节点的层次列表 
  6
  7[0] <contacts>   
  8[0] <contact="alex">   
  9[0] <email>   
 10[1] <city>   
 11[2] <country>   
 12[1] <contact="rebekah">   
 13只要联系点从不和另一个联系点嵌套,你就能保持treeview 和 xml文档保持同步而没有必要求助于筛选器,例如 如果你想交换"Alex"和"Rebekah"联系点入口,你可以很容易的这么做:   
 14指令: 移除 node[0], child[0];在node[0], child[0]之后重新插入它   
 15treeview: 移除叫做"Alex"的"contact"节点,在叫做"Rebekah" 的"contact"节点之后从新插入它   
 16xml文档:移除叫做"Alex"的"contact"节点,在叫做"Rebekah" 的"contact"节点之后从新插入   
 17但是嵌套的contacts,相同的指令会引起TreeView表示和xml文档表示对不准。例如 假设你试图移动在下面treeview表示中嵌套的"Rebekah":   
 18[0] <contacts>   
 19[0] <contact="alex">   
 20[0] <contact="rebekah">   
 21[1] <contact="justin">   
 22在用不同方法表现节点的xml文档中   
 23[0] <contacts>   
 24[0] <contact="alex">   
 25[0] <contact="rebekah">   
 26[1] <contact="justin">   
 27  
 28一个对treeview 表现真正有意义的指令没有必要和xml文档执行相通的工作:   
 29指令:Remove node[0], child[0], child[0]   
 30treeview: Remove "contact" node called "Rebekah"   
 31xml文档:从一个叫做“ALex”的节点上错误的移动了“Email”节点   
 32我们可以借助一个筛选器,筛选器应该能够用离散的实体区分contacts,而不是通过简单的树节点的路径进行区分。这样你就没有必要在担心如何contact "Rebekah"放到它的父节点”alex”内部的正确位置了,因此你就可以保证自己的安全设置   
 33假设一个用户决定要拖动其中一个contact,下一步就是对用户操作的内容给予反馈,一个DragEnter检测操作被拖动的项目是一个treeview 节点,然后记录发生的拖拉操作。对于一个想要执行它自己的应用程序来说这个控制又很大的用处。因此变量drag_drop_active作为DragDropActive的属性直接公开   
 34[C#]   
 35private void XmlTreeView_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)   
 36{   
 37// Allow the user to drag tree nodes within a   
 38// tree   
 39if (e.Data.GetDataPresent(   
 40"System.Windows.Forms.TreeNode", true ))   
 41{   
 42e.Effect = DragDropEffects.Move;   
 43drag_drop_active = true;   
 44}   
 45else   
 46e.Effect = DragDropEffects.None;   
 47}   
 48  
 49
 50
 51[VB]   
 52Private Sub XmlTreeView_DragEnter( _   
 53ByVal sender As Object, _   
 54ByVal e As System.Windows.Forms.DragEventArgs) _   
 55Handles MyBase.DragEnter   
 56' Allow the user to drag tree nodes within a tree   
 57If e.Data.GetDataPresent( _   
 58"System.Windows.Forms.TreeNode", True) Then   
 59e.Effect = DragDropEffects.Move   
 60drag_drop_active = True   
 61Else   
 62e.Effect = DragDropEffects.None   
 63End If   
 64End Sub   
 65当用户拖动文件夹时DragOver被不断的调用   
 66Private Sub XmlTreeView_DragOver(ByVal sender As Object, ByVal e As   
 67System.Windows.Forms.DragEventArgs) Handles MyBase.DragOver   
 68' Fired continuously while a tree node is dragged. We need to override this to   
 69' provide appropriate feedback   
 70If e.Data.GetDataPresent("System.Windows.Forms.TreeNode", True) Then   
 71' Determine which node we are dragging over   
 72Dim pt As Point = Me.PointToClient(New Point(e.X, e.Y))   
 73Dim drop_node As TreeNode = Me.GetNodeAt(pt)   
 74  
 75' If it's the same as the one we last dragged over, take no further action   
 76If drop_node Is last_drop_node Then   
 77Return   
 78End If   
 79  
 80' Otherwise highlight the node as a potential drop target   
 81Me.SelectedNode = drop_node   
 82last_drop_node = drop_node   
 83  
 84' If the drop node and drag node are the same, indicate that the drag is   
 85' disallowed and take no further action (as per Explorer)   
 86If drag_node Is drop_node Then   
 87e.Effect = DragDropEffects.None   
 88Return   
 89End If   
 90  
 91If right_mouse_drag Then   
 92' Right mouse drag-and-drop operations constitute intra-folder   
 93' rearrangements which provide continuous graphical feedback   
 94' We need to cache the drop node's parent, since it will   
 95' be inaccessible if we remove it from the tree   
 96Dim drop_parent As TreeNode = drop_node.Parent   
 97  
 98' Check if it's at the same level as the node being dragged   
 99If drag_node.Parent Is drop_parent Then   
100' Temporarily remove the drop node's siblings from the tree; then add   
101' them back in a different order   
102Dim siblings(drop_parent.Nodes.Count) As System.Windows.Forms.TreeNode   
103Dim count As Integer = siblings.Length - 1   
104  
105Dim item As Integer   
106For item = 0 To count - 1   
107siblings(item) = drop_parent.Nodes(0)   
108drop_parent.Nodes(0).Remove()   
109Next   
110  
111For item = 0 To count - 1   
112If siblings(item) Is drop_node Then   
113drop_parent.Nodes.Add(drag_node)   
114Else   
115If siblings(item) Is drag_node Then   
116drop_parent.Nodes.Add(drop_node)   
117Else   
118drop_parent.Nodes.Add(siblings(item))   
119End If   
120End If   
121Next   
122  
123' Highlight the new node   
124last_drop_node = drag_node   
125e.Effect = DragDropEffects.Move   
126Me.SelectedNode = drag_node   
127Else   
128e.Effect = DragDropEffects.None   
129End If   
130Else   
131' If the user is left-button dragging, disallow (pointless) attempts   
132' to drag a node into its parent's folder (as per Explorer)   
133If drag_node.Parent Is drop_node Then   
134e.Effect = DragDropEffects.None   
135Else   
136e.Effect = DragDropEffects.Move   
137End If   
138End If   
139End If   
140End Sub   
141出于执行效率的原因,代码首先检查自从上次调用dragover 以后被拖动的文件是否发生了变化,如果发生了变化,代码接着判断处理中的拖动类型。以前我必须允许用户最后可以重新排序和设置层次,我在这里选择类似windows的行为(只要它被定义),在其他的地方使用我的方案。因此让用户使用左键复制或者移动文件夹是很不自然的,我们应该让用户使用右键进行处理文件夹的操作。然而这样做会产生一个小问题,因为这两个拖动将会用不同的方法进行处理:左键的拖动直到拖动结束时,而右键拖动将不断的反馈状态,即使不断的拖动,直到用户松开鼠标的按键前,文档不发生物理位置上的改变   
142既然这样,代码检查被拖动的节点是否是节点的兄弟节点,如果是的话,父节点的所有子节点被从树中分离出来,然后进行拖放操作交换节点位置,然后再把这些子节点添加回去。结果是:释放操作完成时,底层数据源根据当前的可视化表达方式进行更新,隐藏的底层数据和数据的可视化表达就可以保持同步。更好的处理方法是:不断的显示更新操作,因此用户可以立刻得到关于拖动的反馈,xml文档只需在拖动完成时更新一次。鼠标左键的拖放操作不需要特殊的代码,drag/drop API 可以适当的处理反馈.   
143用户通过松开鼠标键来完成拖放操作, 参考下面的代码列表3   
144Listing 3. XMLTreeView_DragDrop and Helper Methods:   
145The XMLTreeView_DragDrop and its helper swapXmlDocumentNodes methods provide logic to decide where a node belongs among its siblings   
146Private Sub XmlTreeView_DragDrop(ByVal sender As Object, ByVal e As _   
147System.Windows.Forms.DragEventArgs) Handles MyBase.DragDrop   
148' Cancel drag/drop   
149drag_drop_active = False   
150  
151' Check that we are dropping nodes within the same tree view   
152If e.Data.GetDataPresent("System.Windows.Forms.TreeNode", True) = False Then   
153Return   
154End If   
155' If it's a right-mouse drag-and-drop operation, the tree view will already   
156' show the updated hierarchy; so it's just a matter of updating the xml   
157' document to match the tree view   
158If right_mouse_drag Then   
159swapXmlDocumentNodes()   
160drag_node = Nothing   
161last_drop_node = drag_node   
162xpath_remove_query = ""   
163Else   
164' Determine which node we are dropping onto   
165Dim pt As Point = Me.PointToClient(New Point(e.X, e.Y))   
166Dim drop_node As TreeNode = Me.GetNodeAt(pt)   
167  
168' Do nothing if the drag and drop target are the same node   
169If drag_node Is drop_node Then   
170Return   
171End If   
172If drop_node Is Nothing Then   
173' Don't allow the user to drag nodes off the tree. Though the tree view   
174' wouldn't complain, any attempt to create an xml document with 2 roots   
175' would cause problems   
176Return   
177End If   
178  
179' Add the new node where it was dropped   
180drag_node.Remove()   
181drop_node.Nodes.Add(drag_node)   
182  
183' And update the xml document to match the new tree view hierarchy   
184swapXmlDocumentNodes()   
185drag_node = Nothing   
186last_drop_node = drag_node   
187xpath_remove_query = ""   
188End If   
189End Sub   
190  
191  
192Private Sub swapXmlDocumentNodes()   
193' This method updates the xml document bound to the tree view so that the two node   
194' hierarchies are the same; it determines appropriate xpath queries to remove and   
195' reinsert the node in question by comparing the tree view's structure before and   
196' after the drag/drop operation took place   
197Dim node As System.Xml.XmlNode   
198node = xml_document.DocumentElement.SelectSingleNode(xpath_remove_query)   
199node.ParentNode.RemoveChild(node)   
200  
201' Create a query to determine where the node should be reinserted   
202Dim xpath_insert_query As String = buildXPathQuery(drag_node)   
203  
204' We are only interested in the parent portion of the insert query   
205xpath_insert_query = xpath_insert_query.Substring(0, xpath_insert_query.LastIndexOf("/"))   
206Dim insert_parent As System.Xml.XmlNode = xml_document.DocumentElement.SelectSingleNode(xpath_insert_query)   
207  
208If drag_node.Parent.Nodes.Count = 1 Then   
209' Special case: if as a result of the drag/drop operation some parent without   
210' previous children gained a child, just add the child to the parent.   
211insert_parent.AppendChild(node)   
212Else   
213' Otherwise we need to insert the child at its appropriate position; XmlNode   
214' does not have an Index property, so we need to do this by hand   
215Dim child As Integer   
216For child = 0 To insert_parent.ChildNodes.Count - 1   
217If child = drag_node.Index Then   
218insert_parent.InsertBefore(node, insert_parent.ChildNodes(child))   
219Return   
220End If   
221Next   
222  
223' If we've reached here, the node to be reinserted must be the last child   
224insert_parent.AppendChild(node)   
225End If   
226End Sub   
227那样的话,一些简单的代码就可以完成在文档中移除树节点和它相关的文件夹,还可以通过创建适当的  Xpath 查询来在新的位置上重新插入文件夹。需要特别指出的是:当用户在一个没有子节点的文件夹下面插入一个文件夹时,只能通过创建一个新的子节点来实现。</contact="justin"></contact="rebekah"></contact="alex"></contacts></contact="justin"></contact="rebekah"></contact="alex"></contacts></contact="rebekah"></country></city></email></contact="alex"></contacts></contact="justin"></contact="rebekah"></contact="alex"></contacts>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus