Index: Core/Common/src/Core.Common.Controls.TreeView/Core.Common.Controls.TreeView.csproj
===================================================================
diff -u -r2750ecd900b9e34ba54bb96d233c464b82d7684c -r18b56bf2778081b817fed8e78925e955e5ffc523
--- Core/Common/src/Core.Common.Controls.TreeView/Core.Common.Controls.TreeView.csproj (.../Core.Common.Controls.TreeView.csproj) (revision 2750ecd900b9e34ba54bb96d233c464b82d7684c)
+++ Core/Common/src/Core.Common.Controls.TreeView/Core.Common.Controls.TreeView.csproj (.../Core.Common.Controls.TreeView.csproj) (revision 18b56bf2778081b817fed8e78925e955e5ffc523)
@@ -57,6 +57,12 @@
Component
+
+ UserControl
+
+
+ TreeViewControl.cs
+
@@ -91,6 +97,9 @@
TreeView.cs
+
+ TreeViewControl.cs
+
Index: Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.Designer.cs
===================================================================
diff -u
--- Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.Designer.cs (revision 0)
+++ Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.Designer.cs (revision 18b56bf2778081b817fed8e78925e955e5ffc523)
@@ -0,0 +1,57 @@
+namespace Core.Common.Controls.TreeView
+{
+ partial class TreeViewControl
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.treeView = new System.Windows.Forms.TreeView();
+ this.SuspendLayout();
+ //
+ // treeView
+ //
+ this.treeView.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.treeView.Location = new System.Drawing.Point(0, 0);
+ this.treeView.Name = "treeView";
+ this.treeView.Size = new System.Drawing.Size(338, 370);
+ this.treeView.TabIndex = 0;
+ //
+ // TreeViewControl
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.Controls.Add(this.treeView);
+ this.Name = "TreeViewControl";
+ this.Size = new System.Drawing.Size(338, 370);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TreeView treeView;
+ }
+}
Index: Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.cs
===================================================================
diff -u
--- Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.cs (revision 0)
+++ Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.cs (revision 18b56bf2778081b817fed8e78925e955e5ffc523)
@@ -0,0 +1,881 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Windows.Forms;
+using Core.Common.Base;
+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
+ {
+ 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;
+
+ private static readonly ILog Log = LogManager.GetLogger(typeof(TreeViewControl));
+
+ public event EventHandler TreeNodeDoubleClick;
+ public event EventHandler NodeUpdated; // TODO; Way to explicit!
+ public event EventHandler NodeDataDeleted; // TODO; Way to explicit!
+
+ public TreeViewControl()
+ {
+ InitializeComponent();
+
+ treeView.ImageList = new ImageList
+ {
+ ColorDepth = ColorDepth.Depth32Bit
+ };
+
+ 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;
+ }
+
+ ///
+ /// 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;
+ }
+ }
+
+ public IEnumerable TreeNodeInfos
+ {
+ get
+ {
+ return treeNodeInfos;
+ }
+ }
+
+ ///
+ /// This method registers the provided .
+ ///
+ /// The to register.
+ public void RegisterTreeNodeInfo(TreeNodeInfo treeNodeInfo)
+ {
+ treeNodeInfos.Add(treeNodeInfo);
+ tagTypeTreeNodeInfoLookup[treeNodeInfo.TagType] = treeNodeInfo;
+ }
+
+ public void DeleteNode(TreeNode selectedNode, TreeNodeInfo treeNodeInfo)
+ {
+ var message = string.Format(Resources.TreeView_DeleteNodeData_Are_you_sure_you_want_to_delete_the_following_item_0_, selectedNode.Text);
+ if (MessageBox.Show(message, BaseResources.Confirm, MessageBoxButtons.OKCancel) != DialogResult.OK)
+ {
+ return;
+ }
+
+ if (treeNodeInfo.OnNodeRemoved != null)
+ {
+ treeNodeInfo.OnNodeRemoved(selectedNode.Tag, selectedNode.Parent != null ? selectedNode.Parent.Tag : null);
+ }
+
+ OnNodeDataDeleted(selectedNode);
+ }
+
+ public void CollapseAll(TreeNode node)
+ {
+ node.Collapse();
+
+ foreach (var childNode in node.Nodes.OfType())
+ {
+ CollapseAll(childNode);
+ }
+ }
+
+ public void ExpandAll(TreeNode node)
+ {
+ node.Expand();
+
+ foreach (var childNode in node.Nodes.OfType())
+ {
+ ExpandAll(childNode);
+ }
+ }
+
+ ///
+ /// This method searches all nodes in the 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)
+ {
+ return treeView.Nodes.Count > 0 ? GetNodeByTag(treeView.Nodes[0], nodeData) : null;
+ }
+
+ 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 System.IO.MemoryStream();
+ image.Save(stream, image.RawFormat);
+ MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
+ byte[] hash = md5.ComputeHash(stream.ToArray());
+ return System.Text.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 TreeView event handling
+
+ private void TreeViewBeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
+ {
+ var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag);
+ if (treeNodeInfo.CanRename == null || !treeNodeInfo.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);
+ }
+ }
+
+ 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, treeNodeInfo)
+ : 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: // If allowed, delete the selected node
+ {
+ var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag);
+
+ if (treeNodeInfo.CanRemove == null || !treeNodeInfo.CanRemove(selectedNode.Tag, selectedNode.Parent != null ? selectedNode.Parent.Tag : null))
+ {
+ MessageBox.Show(Resources.TreeView_DeleteNodeData_The_selected_item_cannot_be_removed, BaseResources.Confirm, MessageBoxButtons.OK);
+ break;
+ }
+
+ DeleteNode(selectedNode, treeNodeInfo);
+
+ 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, treeNodeInfo)
+ : 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 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 TreeView;
+ 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
+
+ # 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
+ }
+}
Index: Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.resx
===================================================================
diff -u
--- Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.resx (revision 0)
+++ Core/Common/src/Core.Common.Controls.TreeView/TreeViewControl.resx (revision 18b56bf2778081b817fed8e78925e955e5ffc523)
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file