DarkUI.Net5/DarkUI/Controls/DarkTreeView.cs
2015-09-18 13:01:09 +01:00

1275 lines
38 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace DarkUI
{
public class DarkTreeView : DarkScrollView
{
#region Event Region
public event EventHandler SelectedNodesChanged;
public event EventHandler AfterNodeExpand;
public event EventHandler AfterNodeCollapse;
#endregion
#region Field Region
private bool _disposed;
private readonly int _expandAreaSize = 16;
private readonly int _iconSize = 16;
private int _itemHeight = 20;
private int _indent = 20;
private ObservableList<DarkTreeNode> _nodes;
private ObservableCollection<DarkTreeNode> _selectedNodes;
private DarkTreeNode _anchoredNodeStart;
private DarkTreeNode _anchoredNodeEnd;
private Bitmap _nodeClosed;
private Bitmap _nodeClosedHover;
private Bitmap _nodeClosedHoverSelected;
private Bitmap _nodeOpen;
private Bitmap _nodeOpenHover;
private Bitmap _nodeOpenHoverSelected;
private bool _isDragging;
private DarkTreeNode _provisionalNode;
private DarkTreeNode _dropNode;
private bool _provisionalDragging;
private List<DarkTreeNode> _dragNodes;
private Timer _dragTimer;
private Point _dragPos;
#endregion
#region Property Region
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ObservableList<DarkTreeNode> Nodes
{
get { return _nodes; }
set
{
if (_nodes != null)
{
_nodes.ItemsAdded -= Nodes_ItemsAdded;
_nodes.ItemsRemoved -= Nodes_ItemsRemoved;
foreach (var node in _nodes)
UnhookNodeEvents(node);
}
_nodes = value;
_nodes.ItemsAdded += Nodes_ItemsAdded;
_nodes.ItemsRemoved += Nodes_ItemsRemoved;
foreach (var node in _nodes)
HookNodeEvents(node);
UpdateNodes();
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ObservableCollection<DarkTreeNode> SelectedNodes
{
get { return _selectedNodes; }
}
[Category("Appearance")]
[Description("Determines the height of tree nodes.")]
[DefaultValue(20)]
public int ItemHeight
{
get { return _itemHeight; }
set
{
_itemHeight = value;
UpdateNodes();
}
}
[Category("Appearance")]
[Description("Determines the amount of horizontal space given by parent node.")]
[DefaultValue(20)]
public int Indent
{
get { return _indent; }
set
{
_indent = value;
UpdateNodes();
}
}
[Category("Behavior")]
[Description("Determines whether multiple tree nodes can be selected at once.")]
[DefaultValue(false)]
public bool MultiSelect { get; set; }
[Category("Behavior")]
[Description("Determines whether nodes can be moved within this tree view.")]
[DefaultValue(false)]
public bool AllowMoveNodes { get; set; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int VisibleNodeCount { get; private set; }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IComparer<DarkTreeNode> TreeViewNodeSorter { get; set; }
#endregion
#region Constructor Region
public DarkTreeView()
{
Nodes = new ObservableList<DarkTreeNode>();
_selectedNodes = new ObservableCollection<DarkTreeNode>();
_selectedNodes.CollectionChanged += SelectedNodes_CollectionChanged;
_dragTimer = new Timer();
_dragTimer.Interval = 1;
_dragTimer.Tick += DragTimer_Tick;
LoadIcons();
}
#endregion
#region Dispose Region
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Release managed resources
}
// Release unmanaged resources.
// Set large fields to null.
DisposeIcons();
if (SelectedNodesChanged != null)
SelectedNodesChanged = null;
if (AfterNodeExpand != null)
AfterNodeExpand = null;
if (AfterNodeCollapse != null)
AfterNodeExpand = null;
if (_nodes != null)
_nodes.Dispose();
if (_selectedNodes != null)
_selectedNodes.CollectionChanged -= SelectedNodes_CollectionChanged;
// Set disposed flag
_disposed = true;
}
// Call Dispose on your base class.
base.Dispose(disposing);
}
#endregion
#region Event Handler Region
private void Nodes_ItemsAdded(object sender, ObservableListModified<DarkTreeNode> e)
{
foreach (var node in e.Items)
{
node.ParentTree = this;
node.IsRoot = true;
HookNodeEvents(node);
}
if (TreeViewNodeSorter != null)
Nodes.Sort(TreeViewNodeSorter);
UpdateNodes();
}
private void Nodes_ItemsRemoved(object sender, ObservableListModified<DarkTreeNode> e)
{
foreach (var node in e.Items)
{
node.ParentTree = this;
node.IsRoot = true;
HookNodeEvents(node);
}
UpdateNodes();
}
private void ChildNodes_ItemsAdded(object sender, ObservableListModified<DarkTreeNode> e)
{
foreach (var node in e.Items)
HookNodeEvents(node);
UpdateNodes();
}
private void ChildNodes_ItemsRemoved(object sender, ObservableListModified<DarkTreeNode> e)
{
foreach (var node in e.Items)
{
if (SelectedNodes.Contains(node))
SelectedNodes.Remove(node);
UnhookNodeEvents(node);
}
UpdateNodes();
}
private void SelectedNodes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SelectedNodesChanged != null)
SelectedNodesChanged(this, null);
}
private void Nodes_TextChanged(object sender, EventArgs e)
{
UpdateNodes();
}
private void Nodes_NodeExpanded(object sender, EventArgs e)
{
UpdateNodes();
if (AfterNodeExpand != null)
AfterNodeExpand(this, null);
}
private void Nodes_NodeCollapsed(object sender, EventArgs e)
{
UpdateNodes();
if (AfterNodeCollapse != null)
AfterNodeCollapse(this, null);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_provisionalDragging)
{
if (OffsetMousePosition != _dragPos)
{
StartDrag();
HandleDrag();
return;
}
}
if (_isDragging)
{
if (_dropNode != null)
{
var rect = GetNodeFullRowArea(_dropNode);
if (!rect.Contains(OffsetMousePosition))
{
_dropNode = null;
Invalidate();
}
}
}
CheckHover();
if (_isDragging)
{
HandleDrag();
}
base.OnMouseMove(e);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
CheckHover();
base.OnMouseWheel(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
{
foreach (var node in Nodes)
CheckNodeClick(node, OffsetMousePosition, e.Button);
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (_isDragging)
{
HandleDrop();
}
if (_provisionalDragging)
{
if (_provisionalNode != null)
{
var pos = _dragPos;
if (OffsetMousePosition == pos)
SelectNode(_provisionalNode);
}
_provisionalDragging = false;
}
base.OnMouseUp(e);
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
if (ModifierKeys == Keys.Control)
return;
if (e.Button == MouseButtons.Left)
{
foreach (var node in Nodes)
CheckNodeDoubleClick(node, OffsetMousePosition);
}
base.OnMouseDoubleClick(e);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
foreach (var node in Nodes)
NodeMouseLeave(node);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (_isDragging)
return;
if (Nodes.Count == 0)
return;
if (e.KeyCode != Keys.Down && e.KeyCode != Keys.Up && e.KeyCode != Keys.Left && e.KeyCode != Keys.Right)
return;
if (_anchoredNodeEnd == null)
{
if (Nodes.Count > 0)
SelectNode(Nodes[0]);
return;
}
if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
{
if (MultiSelect && ModifierKeys == Keys.Shift)
{
if (e.KeyCode == Keys.Up)
{
if (_anchoredNodeEnd.PrevVisibleNode != null)
{
SelectAnchoredRange(_anchoredNodeEnd.PrevVisibleNode);
EnsureVisible();
}
}
else if (e.KeyCode == Keys.Down)
{
if (_anchoredNodeEnd.NextVisibleNode != null)
{
SelectAnchoredRange(_anchoredNodeEnd.NextVisibleNode);
EnsureVisible();
}
}
}
else
{
if (e.KeyCode == Keys.Up)
{
if (_anchoredNodeEnd.PrevVisibleNode != null)
{
SelectNode(_anchoredNodeEnd.PrevVisibleNode);
EnsureVisible();
}
}
else if (e.KeyCode == Keys.Down)
{
if (_anchoredNodeEnd.NextVisibleNode != null)
{
SelectNode(_anchoredNodeEnd.NextVisibleNode);
EnsureVisible();
}
}
}
}
if (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
{
if (e.KeyCode == Keys.Left)
{
if (_anchoredNodeEnd.Expanded && _anchoredNodeEnd.Nodes.Count > 0)
{
_anchoredNodeEnd.Expanded = false;
}
else
{
if (_anchoredNodeEnd.ParentNode != null)
{
SelectNode(_anchoredNodeEnd.ParentNode);
EnsureVisible();
}
}
}
else if (e.KeyCode == Keys.Right)
{
if (!_anchoredNodeEnd.Expanded)
{
_anchoredNodeEnd.Expanded = true;
}
else
{
if (_anchoredNodeEnd.Nodes.Count > 0)
{
SelectNode(_anchoredNodeEnd.Nodes[0]);
EnsureVisible();
}
}
}
}
}
private void DragTimer_Tick(object sender, EventArgs e)
{
if (!_isDragging)
{
StopDrag();
return;
}
if (MouseButtons != MouseButtons.Left)
{
StopDrag();
return;
}
var pos = PointToClient(MousePosition);
if (_vScrollBar.Visible)
{
// Scroll up
if (pos.Y < ClientRectangle.Top)
{
var difference = (pos.Y - ClientRectangle.Top) * -1;
if (difference > ItemHeight)
difference = ItemHeight;
_vScrollBar.Value = _vScrollBar.Value - difference;
}
// Scroll down
if (pos.Y > ClientRectangle.Bottom)
{
var difference = pos.Y - ClientRectangle.Bottom;
if (difference > ItemHeight)
difference = ItemHeight;
_vScrollBar.Value = _vScrollBar.Value + difference;
}
}
if (_hScrollBar.Visible)
{
// Scroll left
if (pos.X < ClientRectangle.Left)
{
var difference = (pos.X - ClientRectangle.Left) * -1;
if (difference > ItemHeight)
difference = ItemHeight;
_hScrollBar.Value = _hScrollBar.Value - difference;
}
// Scroll right
if (pos.X > ClientRectangle.Right)
{
var difference = pos.X - ClientRectangle.Right;
if (difference > ItemHeight)
difference = ItemHeight;
_hScrollBar.Value = _hScrollBar.Value + difference;
}
}
}
#endregion
#region Method Region
private void HookNodeEvents(DarkTreeNode node)
{
node.Nodes.ItemsAdded += ChildNodes_ItemsAdded;
node.Nodes.ItemsRemoved += ChildNodes_ItemsRemoved;
node.TextChanged += Nodes_TextChanged;
node.NodeExpanded += Nodes_NodeExpanded;
node.NodeCollapsed += Nodes_NodeCollapsed;
foreach (var childNode in node.Nodes)
HookNodeEvents(childNode);
}
private void UnhookNodeEvents(DarkTreeNode node)
{
node.Nodes.ItemsAdded -= ChildNodes_ItemsAdded;
node.Nodes.ItemsRemoved -= ChildNodes_ItemsRemoved;
node.TextChanged -= Nodes_TextChanged;
node.NodeExpanded -= Nodes_NodeExpanded;
node.NodeCollapsed -= Nodes_NodeCollapsed;
foreach (var childNode in node.Nodes)
UnhookNodeEvents(childNode);
}
private void UpdateNodes()
{
if (_isDragging)
return;
if (Nodes.Count == 0)
return;
var yOffset = 0;
var isOdd = false;
var index = 0;
DarkTreeNode prevNode = null;
ContentSize = new Size(0, 0);
for (var i = 0; i <= Nodes.Count - 1; i++)
{
var node = Nodes[i];
UpdateNode(node, ref prevNode, 0, ref yOffset, ref isOdd, ref index);
}
ContentSize = new Size(ContentSize.Width, yOffset);
VisibleNodeCount = index;
Invalidate();
}
private void UpdateNode(DarkTreeNode node, ref DarkTreeNode prevNode, int indent, ref int yOffset,
ref bool isOdd, ref int index)
{
UpdateNodeBounds(node, yOffset, indent);
yOffset += ItemHeight;
node.Odd = isOdd;
isOdd = !isOdd;
node.VisibleIndex = index;
index++;
node.PrevVisibleNode = prevNode;
if (prevNode != null)
prevNode.NextVisibleNode = node;
prevNode = node;
if (node.Expanded)
{
foreach (var childNode in node.Nodes)
UpdateNode(childNode, ref prevNode, indent + Indent, ref yOffset, ref isOdd, ref index);
}
}
private void UpdateNodeBounds(DarkTreeNode node, int yOffset, int indent)
{
var expandTop = yOffset + (ItemHeight / 2) - (_expandAreaSize / 2);
node.ExpandArea = new Rectangle(indent + 3, expandTop, _expandAreaSize, _expandAreaSize);
var iconTop = yOffset + (ItemHeight / 2) - (_iconSize / 2);
node.IconArea = new Rectangle(node.ExpandArea.Right + 2, iconTop, _iconSize, _iconSize);
using (var g = CreateGraphics())
{
var textSize = (int)(g.MeasureString(node.Text, Font).Width);
node.TextArea = new Rectangle(node.IconArea.Right + 2, yOffset, textSize + 1, ItemHeight);
}
node.FullArea = new Rectangle(indent, yOffset, (node.TextArea.Right - indent), ItemHeight);
if (ContentSize.Width < node.TextArea.Right + 2)
ContentSize = new Size(node.TextArea.Right + 2, ContentSize.Height);
}
private void LoadIcons()
{
DisposeIcons();
_nodeClosed = TreeViewIcons.node_closed_empty.SetColor(Colors.LightText);
_nodeClosedHover = TreeViewIcons.node_closed_empty.SetColor(Colors.BlueHighlight);
_nodeClosedHoverSelected = TreeViewIcons.node_closed_full.SetColor(Colors.LightText);
_nodeOpen = TreeViewIcons.node_open.SetColor(Colors.LightText);
_nodeOpenHover = TreeViewIcons.node_open.SetColor(Colors.BlueHighlight);
_nodeOpenHoverSelected = TreeViewIcons.node_open_empty.SetColor(Colors.LightText);
}
private void DisposeIcons()
{
if (_nodeClosed != null)
_nodeClosed.Dispose();
if (_nodeClosedHover != null)
_nodeClosedHover.Dispose();
if (_nodeClosedHoverSelected != null)
_nodeClosedHoverSelected.Dispose();
if (_nodeOpen != null)
_nodeOpen.Dispose();
if (_nodeOpenHover != null)
_nodeOpenHover.Dispose();
if (_nodeOpenHoverSelected != null)
_nodeOpenHoverSelected.Dispose();
}
private void CheckHover()
{
if (!ClientRectangle.Contains(PointToClient(MousePosition)))
{
if (_isDragging)
{
if (_dropNode != null)
{
_dropNode = null;
Invalidate();
}
}
return;
}
foreach (var node in Nodes)
CheckNodeHover(node, OffsetMousePosition);
}
private void NodeMouseLeave(DarkTreeNode node)
{
node.ExpandAreaHot = false;
foreach (var childNode in node.Nodes)
NodeMouseLeave(childNode);
Invalidate();
}
private void CheckNodeHover(DarkTreeNode node, Point location)
{
if (_isDragging)
{
var rect = GetNodeFullRowArea(node);
if (rect.Contains(OffsetMousePosition))
{
var newDropNode = _dragNodes.Contains(node) ? null : node;
if (_dropNode != newDropNode)
{
_dropNode = newDropNode;
Invalidate();
}
}
}
else
{
var hot = node.ExpandArea.Contains(location);
if (node.ExpandAreaHot != hot)
{
node.ExpandAreaHot = hot;
Invalidate();
}
}
foreach (var childNode in node.Nodes)
CheckNodeHover(childNode, location);
}
private void CheckNodeClick(DarkTreeNode node, Point location, MouseButtons button)
{
var rect = GetNodeFullRowArea(node);
if (rect.Contains(location))
{
if (node.ExpandArea.Contains(location))
{
if (button == MouseButtons.Left)
node.Expanded = !node.Expanded;
}
else
{
if (button == MouseButtons.Left)
{
if (MultiSelect && ModifierKeys == Keys.Shift)
{
SelectAnchoredRange(node);
}
else if (MultiSelect && ModifierKeys == Keys.Control)
{
ToggleNode(node);
}
else
{
if (!SelectedNodes.Contains(node))
SelectNode(node);
_dragPos = OffsetMousePosition;
_provisionalDragging = true;
_provisionalNode = node;
}
return;
}
else if (button == MouseButtons.Right)
{
if (MultiSelect && ModifierKeys == Keys.Shift)
return;
if (MultiSelect && ModifierKeys == Keys.Control)
return;
if (!SelectedNodes.Contains(node))
SelectNode(node);
return;
}
}
}
if (node.Expanded)
{
foreach (var childNode in node.Nodes)
CheckNodeClick(childNode, location, button);
}
}
private void CheckNodeDoubleClick(DarkTreeNode node, Point location)
{
var rect = GetNodeFullRowArea(node);
if (rect.Contains(location))
{
if (!node.ExpandArea.Contains(location))
node.Expanded = !node.Expanded;
return;
}
if (node.Expanded)
{
foreach (var childNode in node.Nodes)
CheckNodeDoubleClick(childNode, location);
}
}
public void SelectNode(DarkTreeNode node)
{
_selectedNodes.Clear();
_selectedNodes.Add(node);
_anchoredNodeStart = node;
_anchoredNodeEnd = node;
Invalidate();
}
public void SelectNodes(DarkTreeNode startNode, DarkTreeNode endNode)
{
var nodes = new List<DarkTreeNode>();
if (startNode == endNode)
nodes.Add(startNode);
if (startNode.VisibleIndex < endNode.VisibleIndex)
{
var node = startNode;
nodes.Add(node);
while (node != endNode && node != null)
{
node = node.NextVisibleNode;
nodes.Add(node);
}
}
else if (startNode.VisibleIndex > endNode.VisibleIndex)
{
var node = startNode;
nodes.Add(node);
while (node != endNode && node != null)
{
node = node.PrevVisibleNode;
nodes.Add(node);
}
}
SelectNodes(nodes, false);
}
public void SelectNodes(List<DarkTreeNode> nodes, bool updateAnchors = true)
{
_selectedNodes.Clear();
foreach (var node in nodes)
_selectedNodes.Add(node);
if (updateAnchors && _selectedNodes.Count > 0)
{
_anchoredNodeStart = _selectedNodes[_selectedNodes.Count - 1];
_anchoredNodeEnd = _selectedNodes[_selectedNodes.Count - 1];
}
Invalidate();
}
private void SelectAnchoredRange(DarkTreeNode node)
{
_anchoredNodeEnd = node;
SelectNodes(_anchoredNodeStart, _anchoredNodeEnd);
}
public void ToggleNode(DarkTreeNode node)
{
if (_selectedNodes.Contains(node))
{
_selectedNodes.Remove(node);
// If we just removed both the anchor start AND end then reset them
if (_anchoredNodeStart == node && _anchoredNodeEnd == node)
{
if (_selectedNodes.Count > 0)
{
_anchoredNodeStart = _selectedNodes[0];
_anchoredNodeEnd = _selectedNodes[0];
}
else
{
_anchoredNodeStart = null;
_anchoredNodeEnd = null;
}
}
// If we just removed the anchor start then update it accordingly
if (_anchoredNodeStart == node)
{
if (_anchoredNodeEnd.VisibleIndex < node.VisibleIndex)
_anchoredNodeStart = node.PrevVisibleNode;
else if (_anchoredNodeEnd.VisibleIndex > node.VisibleIndex)
_anchoredNodeStart = node.NextVisibleNode;
else
_anchoredNodeStart = _anchoredNodeEnd;
}
// If we just removed the anchor end then update it accordingly
if (_anchoredNodeEnd == node)
{
if (_anchoredNodeStart.VisibleIndex < node.VisibleIndex)
_anchoredNodeEnd = node.PrevVisibleNode;
else if (_anchoredNodeStart.VisibleIndex > node.VisibleIndex)
_anchoredNodeEnd = node.NextVisibleNode;
else
_anchoredNodeEnd = _anchoredNodeStart;
}
}
else
{
_selectedNodes.Add(node);
_anchoredNodeStart = node;
_anchoredNodeEnd = node;
}
Invalidate();
}
public Rectangle GetNodeFullRowArea(DarkTreeNode node)
{
if (node.ParentNode != null && !node.ParentNode.Expanded)
return new Rectangle(-1, -1, -1, -1);
var width = Math.Max(ContentSize.Width, Viewport.Width);
var rect = new Rectangle(0, node.FullArea.Top, width, ItemHeight);
return rect;
}
public void EnsureVisible()
{
if (SelectedNodes.Count == 0)
return;
foreach (var node in SelectedNodes)
node.EnsureVisible();
var itemTop = -1;
if (!MultiSelect)
itemTop = SelectedNodes[0].FullArea.Top;
else
itemTop = _anchoredNodeEnd.FullArea.Top;
var itemBottom = itemTop + ItemHeight;
if (itemTop < Viewport.Top)
VScrollTo(itemTop);
if (itemBottom > Viewport.Bottom)
VScrollTo((itemBottom - Viewport.Height));
}
public void Sort()
{
if (TreeViewNodeSorter == null)
return;
Nodes.Sort(TreeViewNodeSorter);
foreach (var node in Nodes)
SortChildNodes(node);
}
private void SortChildNodes(DarkTreeNode node)
{
node.Nodes.Sort(TreeViewNodeSorter);
foreach (var childNode in node.Nodes)
SortChildNodes(childNode);
}
public DarkTreeNode FindNode(string path)
{
foreach (var node in Nodes)
{
var compNode = FindNode(node, path);
if (compNode != null)
return compNode;
}
return null;
}
private DarkTreeNode FindNode(DarkTreeNode parentNode, string path, bool recursive = true)
{
if (parentNode.FullPath == path)
return parentNode;
foreach (var node in parentNode.Nodes)
{
if (node.FullPath == path)
return node;
if (recursive)
{
var compNode = FindNode(node, path);
if (compNode != null)
return compNode;
}
}
return null;
}
#endregion
#region Drag & Drop Region
private void StartDrag()
{
if (!AllowMoveNodes)
{
_provisionalDragging = false;
return;
}
// Create initial list of nodes to drag
_dragNodes = new List<DarkTreeNode>();
foreach (var node in SelectedNodes)
_dragNodes.Add(node);
// Clear out any nodes with a parent that is being dragged
foreach (var node in _dragNodes.ToList())
{
if (node.ParentNode == null)
continue;
if (_dragNodes.Contains(node.ParentNode))
_dragNodes.Remove(node);
}
_provisionalDragging = false;
_isDragging = true;
_dragTimer.Start();
Cursor = Cursors.SizeAll;
}
private void HandleDrag()
{
if (!AllowMoveNodes)
return;
var dropNode = _dropNode;
if (dropNode == null)
{
if (Cursor != Cursors.No)
Cursor = Cursors.No;
return;
}
if (ForceDropToParent(dropNode))
dropNode = dropNode.ParentNode;
if (!CanMoveNodes(_dragNodes, dropNode))
{
if (Cursor != Cursors.No)
Cursor = Cursors.No;
return;
}
if (Cursor != Cursors.SizeAll)
Cursor = Cursors.SizeAll;
}
private void HandleDrop()
{
if (!AllowMoveNodes)
return;
var dropNode = _dropNode;
if (dropNode == null)
{
StopDrag();
return;
}
if (ForceDropToParent(dropNode))
dropNode = dropNode.ParentNode;
if (CanMoveNodes(_dragNodes, dropNode, true))
{
var cachedSelectedNodes = SelectedNodes.ToList();
MoveNodes(_dragNodes, dropNode);
foreach (var node in _dragNodes)
{
if (node.ParentNode == null)
Nodes.Remove(node);
else
node.ParentNode.Nodes.Remove(node);
dropNode.Nodes.Add(node);
}
if (TreeViewNodeSorter != null)
dropNode.Nodes.Sort(TreeViewNodeSorter);
dropNode.Expanded = true;
NodesMoved(_dragNodes);
foreach (var node in cachedSelectedNodes)
_selectedNodes.Add(node);
}
StopDrag();
UpdateNodes();
}
private void StopDrag()
{
_isDragging = false;
_dragNodes = null;
_dropNode = null;
_dragTimer.Stop();
Cursor = Cursors.Default;
Invalidate();
}
protected virtual bool ForceDropToParent(DarkTreeNode node)
{
return false;
}
protected virtual bool CanMoveNodes(List<DarkTreeNode> dragNodes, DarkTreeNode dropNode, bool isMoving = false)
{
if (dropNode == null)
return false;
foreach (var node in dragNodes)
{
if (node == dropNode)
{
if (isMoving)
DarkMessageBox.ShowError(String.Format(@"Cannot move {0}. The destination folder is the same as the source folder.", node.Text), Application.ProductName);
return false;
}
if (node.ParentNode != null && node.ParentNode == dropNode)
{
if (isMoving)
DarkMessageBox.ShowError(String.Format(@"Cannot move {0}. The destination folder is the same as the source folder.", node.Text), Application.ProductName);
return false;
}
var parentNode = dropNode.ParentNode;
while (parentNode != null)
{
if (node == parentNode)
{
if (isMoving)
DarkMessageBox.ShowError(String.Format(@"Cannot move {0}. The destination folder is a subfolder of the source folder.", node.Text), Application.ProductName);
return false;
}
parentNode = parentNode.ParentNode;
}
}
return true;
}
protected virtual void MoveNodes(List<DarkTreeNode> dragNodes, DarkTreeNode dropNode)
{ }
protected virtual void NodesMoved(List<DarkTreeNode> nodesMoved)
{ }
#endregion
#region Paint Region
protected override void PaintContent(Graphics g)
{
foreach (var node in Nodes)
{
DrawNode(node, g);
}
}
private void DrawNode(DarkTreeNode node, Graphics g)
{
var rect = GetNodeFullRowArea(node);
// 1. Draw background
var bgColor = node.Odd ? Colors.HeaderBackground : Colors.GreyBackground;
if (SelectedNodes.Count > 0 && SelectedNodes.Contains(node))
bgColor = Focused ? Colors.BlueSelection : Colors.GreySelection;
if (_isDragging && _dropNode == node)
bgColor = Focused ? Colors.BlueSelection : Colors.GreySelection;
using (var b = new SolidBrush(bgColor))
{
g.FillRectangle(b, rect);
}
// 2. Draw plus/minus icon
if (node.Nodes.Count > 0)
{
var pos = new Point(node.ExpandArea.Location.X - 1, node.ExpandArea.Location.Y - 1);
var icon = _nodeOpen;
if (node.Expanded && !node.ExpandAreaHot)
icon = _nodeOpen;
else if (node.Expanded && node.ExpandAreaHot && !SelectedNodes.Contains(node))
icon = _nodeOpenHover;
else if (node.Expanded && node.ExpandAreaHot && SelectedNodes.Contains(node))
icon = _nodeOpenHoverSelected;
else if (!node.Expanded && !node.ExpandAreaHot)
icon = _nodeClosed;
else if (!node.Expanded && node.ExpandAreaHot && !SelectedNodes.Contains(node))
icon = _nodeClosedHover;
else if (!node.Expanded && node.ExpandAreaHot && SelectedNodes.Contains(node))
icon = _nodeClosedHoverSelected;
g.DrawImageUnscaled(icon, pos);
}
// 3. Draw icon
if (node.Icon != null)
{
if (node.Expanded && node.ExpandedIcon != null)
g.DrawImageUnscaled(node.ExpandedIcon, node.IconArea.Location);
else
g.DrawImageUnscaled(node.Icon, node.IconArea.Location);
}
// 4. Draw text
using (var b = new SolidBrush(Colors.LightText))
{
var stringFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
g.DrawString(node.Text, Font, b, node.TextArea, stringFormat);
}
// 5. Draw child nodes
if (node.Expanded)
{
foreach (var childNode in node.Nodes)
DrawNode(childNode, g);
}
}
#endregion
}
}