執行托放操作
定義了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文檔一一對應的,沒有了它 ,這樣的直接使用可能是不明智的,例如假設篩選器匹配下面的結構:
<contact> <email /> <city /> <country /> </contact> 這樣的約束意味著Xpath 篩選器將contacts.xml的層次作為一個簡單的子元素列表看待
[0] <contacts> [0] <contact="Alex"> [1] <contact="Rebekah"> [2] <contact="Justin"> 然而,treeview 將相同的文檔看作一個節點的層次列表
[0] <contacts> [0] <contact="Alex"> [0] <email> [1] <city> [2] <country> [1] <contact="Rebekah"> 只要聯系點從不和另一個聯系點嵌套,你就能保持treeview 和 xml文檔保持同步而沒有必要求助于篩選器,例如 如果你想交換"Alex"和"Rebekah"聯系點入口,你可以很容易的這么做: 指令: 移除 node[0], child[0];在node[0], child[0]之后重新插入它 treeview: 移除叫做"Alex"的"contact"節點,在叫做"Rebekah" 的"contact"節點之后從新插入它 xml文檔:移除叫做"Alex"的"contact"節點,在叫做"Rebekah" 的"contact"節點之后從新插入 但是嵌套的contacts,相同的指令會引起TreeView表示和xml文檔表示對不準。例如 假設你試圖移動在下面treeview表示中嵌套的"Rebekah": [0] <contacts> [0] <contact="Alex"> [0] <contact="Rebekah"> [1] <contact="Justin"> 在用不同方法表現節點的xml文檔中 [0] <contacts> [0] <contact="Alex"> [0] <contact="Rebekah"> [1] <contact="Justin"> 一個對treeview 表現真正有意義的指令沒有必要和xml文檔執行相通的工作: 指令:Remove node[0], child[0], child[0] treeview: Remove "contact" node called "Rebekah" xml文檔:從一個叫做“ALex”的節點上錯誤的移動了“Email”節點 我們可以借助一個篩選器,篩選器應該能夠用離散的實體區分contacts,而不是通過簡單的樹節點的路徑進行區分。這樣你就沒有必要在擔心如何contact "Rebekah"放到它的父節點”alex”內部的正確位置了,因此你就可以保證自己的安全設置 假設一個用戶決定要拖動其中一個contact,下一步就是對用戶操作的內容給予反饋,一個DragEnter檢測操作被拖動的項目是一個treeview 節點,然后記錄發生的拖拉操作。對于一個想要執行它自己的應用程序來說這個控制又很大的用處。因此變量drag_drop_active作為DragDropActive的屬性直接公開 [C#] private void XmlTreeView_DragEnter(object sender, System.Windows.Forms.DragEventArgs e) { // Allow the user to drag tree nodes within a // tree if (e.Data.GetDataPresent( "System.Windows.Forms.TreeNode", true )) { e.Effect = DragDropEffects.Move; drag_drop_active = true; } else e.Effect = DragDropEffects.None; }
[VB] Private Sub XmlTreeView_DragEnter( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.DragEventArgs) _ Handles MyBase.DragEnter ' Allow the user to drag tree nodes within a tree If e.Data.GetDataPresent( _ "System.Windows.Forms.TreeNode", True) Then e.Effect = DragDropEffects.Move drag_drop_active = True Else e.Effect = DragDropEffects.None End If End Sub 當用戶拖動文件夾時DragOver被不斷的調用 Private Sub XmlTreeView_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles MyBase.DragOver ' Fired continuously while a tree node is dragged. We need to override this to ' provide appropriate feedback If e.Data.GetDataPresent("System.Windows.Forms.TreeNode", True) Then ' Determine which node we are dragging over Dim pt As Point = Me.PointToClient(New Point(e.X, e.Y)) Dim drop_node As TreeNode = Me.GetNodeAt(pt) ' If it's the same as the one we last dragged over, take no further action If drop_node Is last_drop_node Then Return End If ' Otherwise highlight the node as a potential drop target Me.SelectedNode = drop_node last_drop_node = drop_node ' If the drop node and drag node are the same, indicate that the drag is ' disallowed and take no further action (as per Explorer) If drag_node Is drop_node Then e.Effect = DragDropEffects.None Return End If If right_mouse_drag Then ' Right mouse drag-and-drop operations constitute intra-folder ' rearrangements which provide continuous graphical feedback ' We need to cache the drop node's parent, since it will ' be inaccessible if we remove it from the tree Dim drop_parent As TreeNode = drop_node.Parent ' Check if it's at the same level as the node being dragged If drag_node.Parent Is drop_parent Then ' Temporarily remove the drop node's siblings from the tree; then add ' them back in a different order Dim siblings(drop_parent.Nodes.Count) As System.Windows.Forms.TreeNode Dim count As Integer = siblings.Length - 1 Dim item As Integer For item = 0 To count - 1 siblings(item) = drop_parent.Nodes(0) drop_parent.Nodes(0).Remove() Next For item = 0 To count - 1 If siblings(item) Is drop_node Then drop_parent.Nodes.Add(drag_node) Else If siblings(item) Is drag_node Then drop_parent.Nodes.Add(drop_node) Else drop_parent.Nodes.Add(siblings(item)) End If End If Next ' Highlight the new node last_drop_node = drag_node e.Effect = DragDropEffects.Move Me.SelectedNode = drag_node Else e.Effect = DragDropEffects.None End If Else ' If the user is left-button dragging, disallow (pointless) attempts ' to drag a node into its parent's folder (as per Explorer) If drag_node.Parent Is drop_node Then e.Effect = DragDropEffects.None Else e.Effect = DragDropEffects.Move End If End If End If End Sub 出于執行效率的原因,代碼首先檢查自從上次調用dragover 以后被拖動的文件是否發生了變化,如果發生了變化,代碼接著判斷處理中的拖動類型。以前我必須允許用戶最后可以重新排序和設置層次,我在這里選擇類似windows的行為(只要它被定義),在其他的地方使用我的方案。因此讓用戶使用左鍵復制或者移動文件夾是很不自然的,我們應該讓用戶使用右鍵進行處理文件夾的操作。然而這樣做會產生一個小問題,因為這兩個拖動將會用不同的方法進行處理:左鍵的拖動直到拖動結束時,而右鍵拖動將不斷的反饋狀態,即使不斷的拖動,直到用戶松開鼠標的按鍵前,文檔不發生物理位置上的改變 既然這樣,代碼檢查被拖動的節點是否是節點的兄弟節點,如果是的話,父節點的所有子節點被從樹中分離出來,然后進行拖放操作交換節點位置,然后再把這些子節點添加回去。結果是:釋放操作完成時,底層數據源根據當前的可視化表達方式進行更新,隱藏的底層數據和數據的可視化表達就可以保持同步。更好的處理方法是:不斷的顯示更新操作,因此用戶可以立刻得到關于拖動的反饋,xml文檔只需在拖動完成時更新一次。鼠標左鍵的拖放操作不需要特殊的代碼,drag/drop API 可以適當的處理反饋. 用戶通過松開鼠標鍵來完成拖放操作, 參考下面的代碼列表3 Listing 3. XMLTreeView_DragDrop and Helper Methods: The XMLTreeView_DragDrop and its helper swapXmlDocumentNodes methods provide logic to decide where a node belongs among its siblings Private Sub XmlTreeView_DragDrop(ByVal sender As Object, ByVal e As _ System.Windows.Forms.DragEventArgs) Handles MyBase.DragDrop ' Cancel drag/drop drag_drop_active = False ' Check that we are dropping nodes within the same tree view If e.Data.GetDataPresent("System.Windows.Forms.TreeNode", True) = False Then Return End If ' If it's a right-mouse drag-and-drop operation, the tree view will already ' show the updated hierarchy; so it's just a matter of updating the xml ' document to match the tree view If right_mouse_drag Then swapXmlDocumentNodes() drag_node = Nothing last_drop_node = drag_node xpath_remove_query = "" Else ' Determine which node we are dropping onto Dim pt As Point = Me.PointToClient(New Point(e.X, e.Y)) Dim drop_node As TreeNode = Me.GetNodeAt(pt) ' Do nothing if the drag and drop target are the same node If drag_node Is drop_node Then Return End If If drop_node Is Nothing Then ' Don't allow the user to drag nodes off the tree. Though the tree view ' wouldn't complain, any attempt to create an xml document with 2 roots ' would cause problems Return End If ' Add the new node where it was dropped drag_node.Remove() drop_node.Nodes.Add(drag_node) ' And update the xml document to match the new tree view hierarchy swapXmlDocumentNodes() drag_node = Nothing last_drop_node = drag_node xpath_remove_query = "" End If End Sub Private Sub swapXmlDocumentNodes() ' This method updates the xml document bound to the tree view so that the two node ' hierarchies are the same; it determines appropriate xpath queries to remove and ' reinsert the node in question by comparing the tree view's structure before and ' after the drag/drop operation took place Dim node As System.Xml.XmlNode node = xml_document.DocumentElement.SelectSingleNode(xpath_remove_query) node.ParentNode.RemoveChild(node) ' Create a query to determine where the node should be reinserted Dim xpath_insert_query As String = buildXPathQuery(drag_node) ' We are only interested in the parent portion of the insert query xpath_insert_query = xpath_insert_query.Substring(0, xpath_insert_query.LastIndexOf("/")) Dim insert_parent As System.Xml.XmlNode = xml_document.DocumentElement.SelectSingleNode(xpath_insert_query) If drag_node.Parent.Nodes.Count = 1 Then ' Special case: if as a result of the drag/drop operation some parent without ' previous children gained a child, just add the child to the parent. insert_parent.AppendChild(node) Else ' Otherwise we need to insert the child at its appropriate position; XmlNode ' does not have an Index property, so we need to do this by hand Dim child As Integer For child = 0 To insert_parent.ChildNodes.Count - 1 If child = drag_node.Index Then insert_parent.InsertBefore(node, insert_parent.ChildNodes(child)) Return End If Next ' If we've reached here, the node to be reinserted must be the last child insert_parent.AppendChild(node) End If End Sub 那樣的話,一些簡單的代碼就可以完成在文檔中移除樹節點和它相關的文件夾,還可以通過創建適當的Xpath 查詢來在新的位置上重新插入文件夾。需要特別指出的是:當用戶在一個沒有子節點的文件夾下面插入一個文件夾時,只能通過創建一個新的子節點來實現。
|