// 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);
}
}
# 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);
nodeDragging.EnsureVisible();
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
}
}