// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using Core.Common.Base; using Core.Common.Controls.Forms; using Core.Common.Controls.TreeView.Properties; using log4net; using BaseResources = Core.Common.Base.Properties.Resources; namespace Core.Common.Controls.TreeView { public partial class TreeViewControl : UserControl { public event EventHandler TreeNodeDoubleClick; public event EventHandler NodeUpdated; // TODO; Way to explicit! public event EventHandler NodeDataDeleted; // TODO; Way to explicit! public event EventHandler SelectedNodeChanged; private static readonly ILog Log = LogManager.GetLogger(typeof(TreeViewControl)); private readonly ICollection treeNodeInfos = new HashSet(); private readonly Dictionary tagTypeTreeNodeInfoLookup = new Dictionary(); private readonly int maximumTextLength = 259; private readonly Dictionary treeNodeObserverLookup = new Dictionary(); private object data; private int dropAtLocation; private Point lastDragOverPoint; private PlaceholderLocation lastPlaceholderLocation; private TreeNode nodeDropTarget; private TreeNode lastPlaceholderNode; private Graphics placeHolderGraphics; public TreeViewControl() { InitializeComponent(); treeView.ImageList = new ImageList { ColorDepth = ColorDepth.Depth32Bit }; treeView.StateImageList = new ImageList(); treeView.StateImageList.Images.Add(CreateCheckBoxGlyph(CheckBoxState.UncheckedNormal)); treeView.StateImageList.Images.Add(CreateCheckBoxGlyph(CheckBoxState.CheckedNormal)); treeView.DrawMode = TreeViewDrawMode.Normal; treeView.AllowDrop = true; treeView.LabelEdit = true; treeView.HideSelection = false; treeView.BeforeLabelEdit += TreeViewBeforeLabelEdit; treeView.AfterLabelEdit += TreeViewAfterLabelEdit; treeView.AfterCheck += TreeViewAfterCheck; treeView.KeyDown += TreeViewKeyDown; treeView.MouseClick += TreeViewMouseClick; treeView.DoubleClick += TreeViewDoubleClick; treeView.DragDrop += TreeViewDragDrop; treeView.DragOver += TreeViewDragOver; treeView.ItemDrag += TreeViewItemDrag; treeView.DragLeave += TreeViewDragLeave; treeView.AfterSelect += TreeViewAfterSelect; } /// /// Gets or sets the data to render in the tree view. /// public object Data { get { return data; } set { RemoveAllNodes(); data = value; if (data == null) { return; } AddRootNode(); treeView.SelectedNode = treeView.Nodes.Count > 0 ? treeView.Nodes[0] : null; } } /// /// This method registers the provided . /// /// The to register. public void RegisterTreeNodeInfo(TreeNodeInfo treeNodeInfo) { treeNodeInfos.Add(treeNodeInfo); tagTypeTreeNodeInfoLookup[treeNodeInfo.TagType] = treeNodeInfo; } public bool CanRenameNodeForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); return treeNode != null && CanRename(treeNode); } public void StartRenameForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); if (treeNode != null) { treeNode.BeginEdit(); } } public bool CanRemoveNodeForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); return treeNode != null && CanRemove(treeNode); } public void RemoveNodeForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); if (treeNode != null) { Remove(treeNode); } } public bool CanExpandOrCollapseAllNodesForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); return treeNode != null && treeNode.Nodes.OfType().Any(); } public void CollapseAllNodesForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); if (treeNode != null) { CollapseAll(treeNode); } } public void ExpandAllNodesForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); if (treeNode != null) { ExpandAll(treeNode); } } public void SelectNodeForData(object dataObject) { treeView.SelectedNode = GetNodeByTag(dataObject); } public string GetPathForData(object dataObject) { var treeNode = GetNodeByTag(dataObject); return treeNode != null ? treeNode.FullPath : ""; } /// /// This method searches all nodes in the tree view for a node with a matching tag. /// /// The node data to search the corresponding for. /// The corresponding the provided node data or null if not found. public TreeNode GetNodeByTag(object nodeData) { var node = treeView.Nodes.Count > 0 ? GetNodeByTag(treeView.Nodes[0], nodeData) : null; return node; } private bool CanRename(TreeNode treeNode) { var treeNodeInfo = GetTreeNodeInfoForData(treeNode.Tag); var parentTag = treeNode.Parent != null ? treeNode.Parent.Tag : null; return treeNodeInfo.CanRename != null && treeNodeInfo.CanRename(treeNode.Tag, parentTag); } private bool CanRemove(TreeNode treeNode) { var treeNodeInfo = GetTreeNodeInfoForData(treeNode.Tag); return treeNodeInfo.CanRemove != null && treeNodeInfo.CanRemove(treeNode.Tag, treeNode.Parent != null ? treeNode.Parent.Tag : null); } private void Remove(TreeNode treeNode) { if (!CanRemove(treeNode)) { MessageBox.Show(Resources.TreeView_DeleteNodeData_The_selected_item_cannot_be_removed, BaseResources.Confirm, MessageBoxButtons.OK); return; } var message = string.Format(Resources.TreeView_DeleteNodeData_Are_you_sure_you_want_to_delete_the_following_item_0_, treeNode.Text); if (MessageBox.Show(message, BaseResources.Confirm, MessageBoxButtons.OKCancel) != DialogResult.OK) { return; } var treeNodeInfo = GetTreeNodeInfoForData(treeNode.Tag); if (treeNodeInfo.OnNodeRemoved != null) { treeNodeInfo.OnNodeRemoved(treeNode.Tag, treeNode.Parent != null ? treeNode.Parent.Tag : null); } OnNodeDataDeleted(treeNode); } private static void CollapseAll(TreeNode treeNode) { treeNode.Collapse(); foreach (var childNode in treeNode.Nodes.OfType()) { CollapseAll(childNode); } } private static void ExpandAll(TreeNode treeNode) { treeNode.Expand(); foreach (var childNode in treeNode.Nodes.OfType()) { ExpandAll(childNode); } } private Image CreateCheckBoxGlyph(CheckBoxState state) { var result = new Bitmap(16, 16); using (var g = Graphics.FromImage(result)) { Size glyphSize = CheckBoxRenderer.GetGlyphSize(g, state); CheckBoxRenderer.DrawCheckBox(g, new Point((result.Width - glyphSize.Width)/2, (result.Height - glyphSize.Height)/2), state); } return result; } private static TreeNode GetNodeByTag(TreeNode rootNode, object tag) { if (Equals(rootNode.Tag, tag)) { return rootNode; } return rootNode.Nodes .OfType() .Select(n => GetNodeByTag(n, tag)) .FirstOrDefault(node => node != null); } /// /// This method updates the provided . /// /// The to update. /// Thrown when no corresponding can be found for the provided . private void UpdateNode(TreeNode treeNode) { var treeNodeInfo = GetTreeNodeInfoForData(treeNode.Tag); if (treeNodeInfo == null) { throw new InvalidOperationException("No tree node info registered"); } // First of all refresh the child nodes as the other logic below might depend on the presence of child nodes RefreshChildNodes(treeNode, treeNodeInfo); if (treeNodeInfo.Text != null) { var text = treeNodeInfo.Text(treeNode.Tag); // Having very big strings causes rendering problems in the tree view treeNode.Text = text.Length > maximumTextLength ? text.Substring(0, maximumTextLength) : text; } else { treeNode.Text = ""; } treeNode.ForeColor = treeNodeInfo.ForeColor != null ? treeNodeInfo.ForeColor(treeNode.Tag) : Color.FromKnownColor(KnownColor.ControlText); SetTreeNodeImageKey(treeNode, treeNodeInfo); if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(treeNode.Tag) && treeNodeInfo.IsChecked != null) { if (treeNode.Checked != treeNodeInfo.IsChecked(treeNode.Tag)) { treeView.AfterCheck -= TreeViewAfterCheck; treeNode.Checked = !treeNode.Checked; treeView.AfterCheck += TreeViewAfterCheck; } treeNode.StateImageIndex = treeNode.Checked ? 1 : 0; } OnNodeUpdated(treeNode); } private void SetTreeNodeImageKey(TreeNode treeNode, TreeNodeInfo treeNodeInfo) { if (treeNodeInfo.Image != null) { var image = treeNodeInfo.Image(treeNode.Tag); var imageCollection = treeView.ImageList.Images; var imageKey = GetImageHash(image); if (imageCollection.ContainsKey(imageKey)) { treeNode.ImageKey = imageKey; treeNode.SelectedImageKey = imageKey; } else { treeNode.ImageKey = imageKey; treeNode.SelectedImageKey = imageKey; imageCollection.Add(imageKey, image); } } } private string GetImageHash(Image image) { var stream = new MemoryStream(); image.Save(stream, image.RawFormat); MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); byte[] hash = md5.ComputeHash(stream.ToArray()); return Encoding.UTF8.GetString(hash); } private void AddRootNode() { var rootNode = new TreeNode { Tag = data }; UpdateNode(rootNode); if (rootNode.Nodes.Count > 0) { rootNode.Expand(); } treeView.Nodes.Add(rootNode); treeNodeObserverLookup.Add(rootNode, new TreeNodeObserver(rootNode, this)); } private TreeNode CreateTreeNode(TreeNode parentNode, object nodeData) { var newNode = new TreeNode { Tag = nodeData }; if (treeView.CheckBoxes) { newNode.Checked = parentNode.Checked; } UpdateNode(newNode); treeNodeObserverLookup.Add(newNode, new TreeNodeObserver(newNode, this)); return newNode; } private void RemoveAllNodes() { foreach (var treeNode in treeNodeObserverLookup.Keys) { treeNodeObserverLookup[treeNode].Dispose(); } treeNodeObserverLookup.Clear(); treeView.Nodes.Clear(); } private void RemoveTreeNodeFromLookupRecursively(TreeNode treeNode) { treeNodeObserverLookup[treeNode].Dispose(); treeNodeObserverLookup.Remove(treeNode); foreach (var childNode in treeNode.Nodes.OfType()) { RemoveTreeNodeFromLookupRecursively(childNode); } } /// /// This method tries to return a object corresponding to the provided data. /// /// The data to find the corresponding for. /// The for the provided data or null if no corresponding was found. private TreeNodeInfo GetTreeNodeInfoForData(object item) { if (item == null) { return null; } TreeNodeInfo treeNodeInfo; // Try to find an exact match tagTypeTreeNodeInfoLookup.TryGetValue(item.GetType(), out treeNodeInfo); // Try to match based on class hierarchy return treeNodeInfo ?? tagTypeTreeNodeInfoLookup.FirstOrDefault(kvp => kvp.Key.IsInstanceOfType(item)).Value; } private void RefreshChildNodes(TreeNode treeNode, TreeNodeInfo treeNodeInfo) { var newTreeNodes = new Dictionary(); var outdatedTreeNodes = treeNode.Nodes.OfType().ToList(); var currentTreeNodesPerTag = treeNode.Nodes.OfType().ToList().ToDictionary(ctn => ctn.Tag, ctn => ctn); var newChildNodeObjects = treeNodeInfo.ChildNodeObjects != null ? treeNodeInfo.ChildNodeObjects(treeNode.Tag) : new object[0]; // Create a list of outdated tree nodes and new tree nodes for (var i = 0; i < newChildNodeObjects.Length; i++) { if (currentTreeNodesPerTag.ContainsKey(newChildNodeObjects[i])) { // Remove any node from the list of outdated nodes that should remain part of the node collection outdatedTreeNodes.Remove(currentTreeNodesPerTag[newChildNodeObjects[i]]); } else { // If there's no existing node yet, create a new one and add it to the list of new nodes newTreeNodes.Add(i, CreateTreeNode(treeNode, newChildNodeObjects[i])); } } // Remove any outdated nodes foreach (var removedNode in outdatedTreeNodes) { treeNode.Nodes.Remove(removedNode); RemoveTreeNodeFromLookupRecursively(removedNode); } // Insert any new nodes foreach (var node in newTreeNodes) { treeNode.Nodes.Insert(node.Key, node.Value); } // If relevant, set selection to the last of the added nodes var lastAddedNodeToSetSelectionTo = newTreeNodes.Values.LastOrDefault(node => { var dataObject = node.Tag; var info = GetTreeNodeInfoForData(dataObject); return info.EnsureVisibleOnCreate != null && info.EnsureVisibleOnCreate(dataObject); }); if (lastAddedNodeToSetSelectionTo != null) { lastAddedNodeToSetSelectionTo.EnsureVisible(); treeView.SelectedNode = lastAddedNodeToSetSelectionTo; } } # region Nested types private class TreeNodeObserver : IDisposable, IObserver { private readonly TreeNode treeNode; private readonly TreeViewControl treeViewControl; public TreeNodeObserver(TreeNode treeNode, TreeViewControl treeViewControl) { this.treeNode = treeNode; this.treeViewControl = treeViewControl; var observable = treeNode.Tag as IObservable; if (observable != null) { observable.Attach(this); } } public void Dispose() { var observable = treeNode.Tag as IObservable; if (observable != null) { observable.Detach(this); } } public void UpdateObserver() { treeViewControl.UpdateNode(treeNode); } } # endregion # region TreeView event handling private void TreeViewBeforeLabelEdit(object sender, NodeLabelEditEventArgs e) { if (!CanRename(e.Node)) { e.CancelEdit = true; } } private void TreeViewAfterLabelEdit(object sender, NodeLabelEditEventArgs e) { // Check Label for null as this indicates the node edit was cancelled if (e.Label == null) { return; } var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag); if (treeNodeInfo.OnNodeRenamed != null) { treeNodeInfo.OnNodeRenamed(e.Node.Tag, e.Label); } } private void TreeViewAfterCheck(object sender, TreeViewEventArgs e) { var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag); if (treeNodeInfo.OnNodeChecked != null) { treeNodeInfo.OnNodeChecked(e.Node.Tag, e.Node.Parent != null ? e.Node.Parent.Tag : null); } } private void TreeViewKeyDown(object sender, KeyEventArgs keyEventArgs) { var selectedNode = treeView.SelectedNode; if (selectedNode == null) { return; } switch (keyEventArgs.KeyData) { case Keys.F5: // Refresh the selected node in the tree view { if (treeView.SelectedNode != null) { UpdateNode(treeView.SelectedNode); } break; } case Keys.F2: // Start editing the label of the selected node { selectedNode.BeginEdit(); break; } case Keys.Apps: // If implemented, show the context menu of the selected node { var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag); // Update the context menu (relevant in case of keyboard navigation in the tree view) selectedNode.ContextMenuStrip = treeNodeInfo.ContextMenuStrip != null ? treeNodeInfo.ContextMenuStrip(selectedNode.Tag, selectedNode.Parent != null ? selectedNode.Parent.Tag : null, this) : null; if (treeView.ContextMenu != null && selectedNode.ContextMenuStrip != null) { var location = selectedNode.Bounds.Location; location.Offset(0, selectedNode.Bounds.Height); selectedNode.ContextMenuStrip.Show(location); } break; } case Keys.Enter: // Perform the same action as on double click { OnTreeNodeDoubleClick(); break; } case Keys.Delete: // Try to delete the selected node { Remove(selectedNode); break; } case Keys.Space: // If applicable, change the checked state of the selected node { var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag); if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(selectedNode.Tag)) { selectedNode.Checked = !selectedNode.Checked; } break; } } if (keyEventArgs.KeyData == (Keys.Control | Keys.Shift | Keys.Right)) // Expand all tree nodes { ExpandAll(selectedNode); selectedNode.EnsureVisible(); } if (keyEventArgs.KeyData == (Keys.Control | Keys.Shift | Keys.Left)) // Collapse all tree nodes { CollapseAll(selectedNode); selectedNode.EnsureVisible(); } } private void TreeViewMouseClick(object sender, MouseEventArgs e) { var point = treeView.PointToClient(Cursor.Position); var clickedNode = treeView.GetNodeAt(point); if (clickedNode == null) { return; } var treeNodeInfo = GetTreeNodeInfoForData(clickedNode.Tag); if ((e.Button & MouseButtons.Right) != 0) { treeView.SelectedNode = clickedNode; // Update the context menu clickedNode.ContextMenuStrip = treeNodeInfo.ContextMenuStrip != null ? treeNodeInfo.ContextMenuStrip(clickedNode.Tag, clickedNode.Parent != null ? clickedNode.Parent.Tag : null, this) : null; return; } var isOnCheckBox = IsOnCheckBox(point); if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(clickedNode.Tag) && isOnCheckBox) { clickedNode.Checked = !clickedNode.Checked; } } private bool IsOnCheckBox(Point point) { return treeView.HitTest(point).Location.ToString() == "StateImage"; } private void TreeViewDoubleClick(object sender, EventArgs e) { OnTreeNodeDoubleClick(); } private void OnTreeNodeDoubleClick() { if (TreeNodeDoubleClick != null) { TreeNodeDoubleClick(treeView.SelectedNode, EventArgs.Empty); } } private void TreeViewDragDrop(object sender, DragEventArgs e) { ClearPlaceHolders(); Point point = treeView.PointToClient(new Point(e.X, e.Y)); var nodeOver = treeView.GetNodeAt(point); var nodeDragging = e.Data.GetData(typeof(TreeNode)) as TreeNode; if (nodeOver == null || nodeDragging == null) { if (nodeOver != null) { e.Effect = DragDropEffects.All; } return; } // Handle dragged items which were originally higher up in the tree under the same parent (all indices shift by one) if (e.Effect.Equals(DragDropEffects.Move) && nodeDragging.Parent == nodeDropTarget && nodeOver.Index > nodeDragging.Index) { if (dropAtLocation > 0) { dropAtLocation--; } } // Ensure the drop location is never < 0 if (dropAtLocation < 0) { dropAtLocation = 0; } var treeNodeInfo = GetTreeNodeInfoForData(nodeDropTarget.Tag); try { var previousParentNode = nodeDragging.Parent; previousParentNode.Nodes.Remove(nodeDragging); nodeDropTarget.Nodes.Insert(dropAtLocation, nodeDragging); // Ensure the dragged node is visible afterwards nodeDragging.EnsureVisible(); // Restore any lost selection treeView.SelectedNode = nodeDragging; if (treeNodeInfo.OnDrop != null) { treeNodeInfo.OnDrop(nodeDragging, previousParentNode, ToDragOperation(e.Effect), dropAtLocation); } } catch (Exception ex) { Log.Error(string.Format(Resources.TreeView_TreeViewDragDrop_Error_during_drag_drop_0_, ex.Message)); } } private void TreeViewDragOver(object sender, DragEventArgs e) { if (lastDragOverPoint.X == e.X && lastDragOverPoint.Y == e.Y) { return; } lastDragOverPoint = new Point(e.X, e.Y); var point = treeView.PointToClient(lastDragOverPoint); var nodeOver = treeView.GetNodeAt(point); var nodeDragging = e.Data.GetData(typeof(TreeNode)) as TreeNode; if (nodeOver == null || nodeDragging == null || nodeOver == nodeDragging || IsChildOf(nodeOver, nodeDragging)) { ClearPlaceHolders(); return; } ScrollIntoView(point, nodeOver, sender); PlaceholderLocation placeholderLocation = GetPlaceHoldersLocation(nodeDragging, nodeOver, e); if (null == nodeDropTarget) { return; } var treeNodeInfo = GetTreeNodeInfoForData(nodeDropTarget.Tag); DragOperations allowedOperations = treeNodeInfo.CanDrop != null ? treeNodeInfo.CanDrop(nodeDragging, nodeDropTarget, ToDragOperation(e.AllowedEffect)) : DragOperations.None; e.Effect = ToDragDropEffects(allowedOperations); if (PlaceholderLocation.None == placeholderLocation) { return; } // Determine whether ot not the node can be dropped based on the allowed operations. // A node can also be a valid drop traget if it is the root item (nodeDragging.Parent == null). var dragOperations = treeNodeInfo.CanDrop != null ? treeNodeInfo.CanDrop(nodeDragging, nodeDropTarget, allowedOperations) : DragOperations.None; if (DragOperations.None != dragOperations) { DrawPlaceholder(nodeOver, placeholderLocation); } else { ClearPlaceHolders(); e.Effect = DragDropEffects.None; } } private void TreeViewItemDrag(object sender, ItemDragEventArgs e) { // gather allowed effects for the current item. var sourceNode = (TreeNode) e.Item; var treeNodeInfo = GetTreeNodeInfoForData(sourceNode.Tag); DragOperations dragOperation = treeNodeInfo.CanDrag != null ? treeNodeInfo.CanDrag(sourceNode.Tag, sourceNode) : DragOperations.None; DragDropEffects effects = ToDragDropEffects(dragOperation); if (effects == DragDropEffects.None) { return; } // store both treenode and tag of treenode in dataobject // to be dragged. IDataObject dataObject = new DataObject(); dataObject.SetData(typeof(TreeNode), sourceNode); if (sourceNode.Tag != null) { dataObject.SetData(sourceNode.Tag.GetType(), sourceNode.Tag); } treeView.DoDragDrop(dataObject, effects); } private void TreeViewDragLeave(object sender, EventArgs e) { ClearPlaceHolders(); } private void TreeViewAfterSelect(object sender, TreeViewEventArgs e) { if (SelectedNodeChanged != null) { SelectedNodeChanged(treeView.SelectedNode, EventArgs.Empty); } } private void DrawPlaceholder(TreeNode node, PlaceholderLocation location) { if (lastPlaceholderNode == node && lastPlaceholderLocation == location) { return; } ClearPlaceHolders(); lastPlaceholderNode = node; lastPlaceholderLocation = location; placeHolderGraphics = treeView.CreateGraphics(); node.DrawPlaceHolder(location, treeView.CreateGraphics()); } private void ClearPlaceHolders() { if (placeHolderGraphics != null) { lastPlaceholderNode = null; treeView.Refresh(); placeHolderGraphics.Dispose(); placeHolderGraphics = null; } } private DragOperations ToDragOperation(DragDropEffects dragDropEffects) { return (DragOperations) Enum.Parse(typeof(DragOperations), dragDropEffects.ToString()); } private bool IsChildOf(TreeNode childNode, TreeNode node) { while (childNode != null && childNode.Parent != null) { if (childNode.Parent.Equals(node)) { return true; } childNode = childNode.Parent; // Walk up the tree } return false; } private static void ScrollIntoView(Point point, TreeNode nodeOver, object sender) { var treeView = sender as DoubleBufferedTreeView; if (treeView == null) { return; } int delta = treeView.Height - point.Y; if ((delta < treeView.Height/2) && (delta > 0)) { var nextVisibleNode = nodeOver.NextVisibleNode; if (nextVisibleNode != null) { nextVisibleNode.EnsureVisible(); } } if ((delta > treeView.Height/2) && (delta < treeView.Height)) { var previousVisibleNode = nodeOver.PrevVisibleNode; if (previousVisibleNode != null) { previousVisibleNode.EnsureVisible(); } } } private PlaceholderLocation GetPlaceHoldersLocation(TreeNode nodeDragging, TreeNode nodeOver, DragEventArgs e) { var loc = PlaceholderLocation.None; int offsetY = treeView.PointToClient(Cursor.Position).Y - nodeOver.Bounds.Top; if (offsetY < nodeOver.Bounds.Height/3 && nodeDragging.NextNode != nodeOver) { if (nodeOver.Parent != null) { var treeNodeInfo = GetTreeNodeInfoForData(nodeOver.Parent.Tag); if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging, nodeOver)) { nodeDropTarget = nodeOver.Parent; dropAtLocation = nodeOver.Parent == null ? 0 : nodeOver.Parent.Nodes.IndexOf(nodeOver); loc = PlaceholderLocation.Top; } else { nodeDropTarget = nodeOver; dropAtLocation = 0; loc = PlaceholderLocation.Middle; } } else { nodeDropTarget = nodeOver; dropAtLocation = 0; loc = PlaceholderLocation.Middle; } } else if ((nodeOver.Parent != null) && (offsetY > nodeOver.Bounds.Height - nodeOver.Bounds.Height/3) && nodeDragging.PrevNode != nodeOver) { var treeNodeInfo = GetTreeNodeInfoForData(nodeOver.Parent.Tag); if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging, nodeOver)) { nodeDropTarget = nodeOver.Parent; dropAtLocation = nodeOver.Parent == null ? 0 : nodeOver.Parent.Nodes.IndexOf(nodeOver) + 1; loc = PlaceholderLocation.Bottom; } else { nodeDropTarget = nodeOver; dropAtLocation = 0; loc = PlaceholderLocation.Middle; } } else if (nodeDragging != nodeOver && offsetY < nodeOver.Bounds.Height - nodeOver.Bounds.Height/4 && offsetY > nodeOver.Bounds.Height/4) { nodeDropTarget = nodeOver; dropAtLocation = 0; loc = PlaceholderLocation.Middle; } if (loc == PlaceholderLocation.None || (loc == PlaceholderLocation.Middle && nodeDropTarget == nodeDragging.Parent)) { ClearPlaceHolders(); e.Effect = DragDropEffects.None; } return loc; } private DragDropEffects ToDragDropEffects(DragOperations operation) { return (DragDropEffects) Enum.Parse(typeof(DragDropEffects), operation.ToString()); } # endregion # region Event handling private void OnNodeUpdated(TreeNode treeNode) { if (NodeUpdated != null) { NodeUpdated(treeNode, EventArgs.Empty); } } private void OnNodeDataDeleted(TreeNode node) { if (NodeDataDeleted != null) { NodeDataDeleted(this, new TreeNodeDataDeletedEventArgs(node.Tag)); } } # endregion } }