#region Imports using DPumpHydr.WinFrmUI.RLT.Extension.Crown; using DPumpHydr.WinFrmUI.RLT.Util; 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; using static DPumpHydr.WinFrmUI.RLT.Helper.CrownHelper; #endregion namespace DPumpHydr.WinFrmUI.RLT.Controls { #region CrownTreeView public class CrownTreeView : CrownScrollView { #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 _nodes; private CrownTreeNode _anchoredNodeStart; private CrownTreeNode _anchoredNodeEnd; private Bitmap _nodeClosed; private Bitmap _nodeClosedHover; private Bitmap _nodeClosedHoverSelected; private Bitmap _nodeOpen; private Bitmap _nodeOpenHover; private Bitmap _nodeOpenHoverSelected; private CrownTreeNode _provisionalNode; private CrownTreeNode _dropNode; private bool _provisionalDragging; private List _dragNodes; private Point _dragPos; #endregion #region Property Region [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ObservableList Nodes { get => _nodes; set { if (_nodes != null) { _nodes.ItemsAdded -= Nodes_ItemsAdded; _nodes.ItemsRemoved -= Nodes_ItemsRemoved; foreach (CrownTreeNode node in _nodes) { UnhookNodeEvents(node); } } _nodes = value; _nodes.ItemsAdded += Nodes_ItemsAdded; _nodes.ItemsRemoved += Nodes_ItemsRemoved; foreach (CrownTreeNode node in _nodes) { HookNodeEvents(node); } UpdateNodes(); } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ObservableCollection SelectedNodes { get; } [Category("Appearance")] [Description("Determines the height of tree nodes.")] [DefaultValue(20)] public int ItemHeight { get => _itemHeight; set { _itemHeight = value; MaxDragChange = _itemHeight; UpdateNodes(); } } [Category("Appearance")] [Description("Determines the amount of horizontal space given by parent node.")] [DefaultValue(20)] public int Indent { get => _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; } [Category("Appearance")] [Description("Determines whether icons are rendered with the tree nodes.")] [DefaultValue(false)] public bool ShowIcons { get; set; } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int VisibleNodeCount { get; private set; } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IComparer TreeViewNodeSorter { get; set; } #endregion #region Constructor Region public CrownTreeView() { Nodes = new ObservableList(); SelectedNodes = new ObservableCollection(); SelectedNodes.CollectionChanged += SelectedNodes_CollectionChanged; MaxDragChange = _itemHeight; LoadIcons(); } #endregion #region Dispose Region protected override void Dispose(bool disposing) { if (!_disposed) { 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; } _disposed = true; } base.Dispose(disposing); } #endregion #region Event Handler Region private void Nodes_ItemsAdded(object sender, ObservableListModified e) { foreach (CrownTreeNode 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 e) { foreach (CrownTreeNode node in e.Items) { node.ParentTree = this; node.IsRoot = true; HookNodeEvents(node); } UpdateNodes(); } private void ChildNodes_ItemsAdded(object sender, ObservableListModified e) { foreach (CrownTreeNode node in e.Items) { HookNodeEvents(node); } UpdateNodes(); } private void ChildNodes_ItemsRemoved(object sender, ObservableListModified e) { foreach (CrownTreeNode node in e.Items) { if (SelectedNodes.Contains(node)) { SelectedNodes.Remove(node); } UnhookNodeEvents(node); } UpdateNodes(); } private void SelectedNodes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { SelectedNodesChanged?.Invoke(this, null); } private void Nodes_TextChanged(object sender, EventArgs e) { UpdateNodes(); } private void Nodes_NodeExpanded(object sender, EventArgs e) { UpdateNodes(); AfterNodeExpand?.Invoke(this, null); } private void Nodes_NodeCollapsed(object sender, EventArgs e) { UpdateNodes(); AfterNodeCollapse?.Invoke(this, null); } protected override void OnMouseMove(MouseEventArgs e) { if (_provisionalDragging) { if (OffsetMousePosition != _dragPos) { StartDrag(); HandleDrag(); return; } } if (IsDragging) { if (_dropNode != null) { Rectangle 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 is MouseButtons.Left or MouseButtons.Right) { foreach (CrownTreeNode 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) { Point 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 (CrownTreeNode node in Nodes) { CheckNodeDoubleClick(node, OffsetMousePosition); } } base.OnMouseDoubleClick(e); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); foreach (CrownTreeNode 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 is not Keys.Down and not Keys.Up and not Keys.Left and not Keys.Right) { return; } if (_anchoredNodeEnd == null) { if (Nodes.Count > 0) { SelectNode(Nodes[0]); } return; } if (e.KeyCode is Keys.Down or 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 is Keys.Left or 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; } Point pos = PointToClient(MousePosition); if (_vScrollBar.Visible) { // Scroll up if (pos.Y < ClientRectangle.Top) { int difference = (pos.Y - ClientRectangle.Top) * -1; if (difference > ItemHeight) { difference = ItemHeight; } _vScrollBar.Value -= difference; } // Scroll down if (pos.Y > ClientRectangle.Bottom) { int difference = pos.Y - ClientRectangle.Bottom; if (difference > ItemHeight) { difference = ItemHeight; } _vScrollBar.Value += difference; } } if (_hScrollBar.Visible) { // Scroll left if (pos.X < ClientRectangle.Left) { int difference = (pos.X - ClientRectangle.Left) * -1; if (difference > ItemHeight) { difference = ItemHeight; } _hScrollBar.Value -= difference; } // Scroll right if (pos.X > ClientRectangle.Right) { int difference = pos.X - ClientRectangle.Right; if (difference > ItemHeight) { difference = ItemHeight; } _hScrollBar.Value += difference; } } } #endregion #region Method Region private void HookNodeEvents(CrownTreeNode node) { node.Nodes.ItemsAdded += ChildNodes_ItemsAdded; node.Nodes.ItemsRemoved += ChildNodes_ItemsRemoved; node.TextChanged += Nodes_TextChanged; node.NodeExpanded += Nodes_NodeExpanded; node.NodeCollapsed += Nodes_NodeCollapsed; foreach (CrownTreeNode childNode in node.Nodes) { HookNodeEvents(childNode); } } private void UnhookNodeEvents(CrownTreeNode node) { node.Nodes.ItemsAdded -= ChildNodes_ItemsAdded; node.Nodes.ItemsRemoved -= ChildNodes_ItemsRemoved; node.TextChanged -= Nodes_TextChanged; node.NodeExpanded -= Nodes_NodeExpanded; node.NodeCollapsed -= Nodes_NodeCollapsed; foreach (CrownTreeNode childNode in node.Nodes) { UnhookNodeEvents(childNode); } } private void UpdateNodes() { if (IsDragging) { return; } ContentSize = new(0, 0); if (Nodes.Count == 0) { return; } int yOffset = 0; bool isOdd = false; int index = 0; CrownTreeNode prevNode = null; for (int i = 0; i <= Nodes.Count - 1; i++) { CrownTreeNode node = Nodes[i]; UpdateNode(node, ref prevNode, 0, ref yOffset, ref isOdd, ref index); } ContentSize = new(ContentSize.Width, yOffset); VisibleNodeCount = index; Invalidate(); } private void UpdateNode(CrownTreeNode node, ref CrownTreeNode 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 (CrownTreeNode childNode in node.Nodes) { UpdateNode(childNode, ref prevNode, indent + Indent, ref yOffset, ref isOdd, ref index); } } } private void UpdateNodeBounds(CrownTreeNode node, int yOffset, int indent) { int expandTop = yOffset + (ItemHeight / 2) - (_expandAreaSize / 2); node.ExpandArea = new(indent + 3, expandTop, _expandAreaSize, _expandAreaSize); int iconTop = yOffset + (ItemHeight / 2) - (_iconSize / 2); if (ShowIcons) { node.IconArea = new(node.ExpandArea.Right + 2, iconTop, _iconSize, _iconSize); } else { node.IconArea = new(node.ExpandArea.Right, iconTop, 0, 0); } using (Graphics g = CreateGraphics()) { int textSize = (int)g.MeasureString(node.Text, Font).Width; node.TextArea = new(node.IconArea.Right + 2, yOffset, textSize + 1, ItemHeight); } node.FullArea = new(indent, yOffset, node.TextArea.Right - indent, ItemHeight); if (ContentSize.Width < node.TextArea.Right + 2) { ContentSize = new(node.TextArea.Right + 2, ContentSize.Height); } } private void LoadIcons() { DisposeIcons(); _nodeClosed = Properties.Resources.node_closed_empty.SetColor(ThemeProvider.Theme.Colors.LightText); _nodeClosedHover = Properties.Resources.node_closed_empty.SetColor(ThemeProvider.Theme.Colors.BlueHighlight); _nodeClosedHoverSelected = Properties.Resources.node_closed_full.SetColor(ThemeProvider.Theme.Colors.LightText); _nodeOpen = Properties.Resources.node_open.SetColor(ThemeProvider.Theme.Colors.LightText); _nodeOpenHover = Properties.Resources.node_open.SetColor(ThemeProvider.Theme.Colors.BlueHighlight); _nodeOpenHoverSelected = Properties.Resources.node_open_empty.SetColor(ThemeProvider.Theme.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 (CrownTreeNode node in Nodes) { CheckNodeHover(node, OffsetMousePosition); } } private void NodeMouseLeave(CrownTreeNode node) { node.ExpandAreaHot = false; foreach (CrownTreeNode childNode in node.Nodes) { NodeMouseLeave(childNode); } Invalidate(); } private void CheckNodeHover(CrownTreeNode node, Point location) { if (IsDragging) { Rectangle rect = GetNodeFullRowArea(node); if (rect.Contains(OffsetMousePosition)) { CrownTreeNode newDropNode = _dragNodes.Contains(node) ? null : node; if (_dropNode != newDropNode) { _dropNode = newDropNode; Invalidate(); } } } else { bool hot = node.ExpandArea.Contains(location); if (node.ExpandAreaHot != hot) { node.ExpandAreaHot = hot; Invalidate(); } } foreach (CrownTreeNode childNode in node.Nodes) { CheckNodeHover(childNode, location); } } private void CheckNodeClick(CrownTreeNode node, Point location, MouseButtons button) { Rectangle 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 (CrownTreeNode childNode in node.Nodes) { CheckNodeClick(childNode, location, button); } } } private void CheckNodeDoubleClick(CrownTreeNode node, Point location) { Rectangle rect = GetNodeFullRowArea(node); if (rect.Contains(location)) { if (!node.ExpandArea.Contains(location)) { node.Expanded = !node.Expanded; } return; } if (node.Expanded) { foreach (CrownTreeNode childNode in node.Nodes) { CheckNodeDoubleClick(childNode, location); } } } public void SelectNode(CrownTreeNode node) { SelectedNodes.Clear(); SelectedNodes.Add(node); _anchoredNodeStart = node; _anchoredNodeEnd = node; Invalidate(); } public void SelectNodes(CrownTreeNode startNode, CrownTreeNode endNode) { List nodes = new(); if (startNode == endNode) { nodes.Add(startNode); } if (startNode.VisibleIndex < endNode.VisibleIndex) { CrownTreeNode node = startNode; nodes.Add(node); while (node != endNode && node != null) { node = node.NextVisibleNode; nodes.Add(node); } } else if (startNode.VisibleIndex > endNode.VisibleIndex) { CrownTreeNode node = startNode; nodes.Add(node); while (node != endNode && node != null) { node = node.PrevVisibleNode; nodes.Add(node); } } SelectNodes(nodes, false); } public void SelectNodes(List nodes, bool updateAnchors = true) { SelectedNodes.Clear(); foreach (CrownTreeNode 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(CrownTreeNode node) { _anchoredNodeEnd = node; SelectNodes(_anchoredNodeStart, _anchoredNodeEnd); } public void ToggleNode(CrownTreeNode 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(CrownTreeNode node) { if (node.ParentNode != null && !node.ParentNode.Expanded) { return new Rectangle(-1, -1, -1, -1); } int width = Math.Max(ContentSize.Width, Viewport.Width); Rectangle rect = new(0, node.FullArea.Top, width, ItemHeight); return rect; } public void EnsureVisible() { if (SelectedNodes.Count == 0) { return; } foreach (CrownTreeNode node in SelectedNodes) { node.EnsureVisible(); } int itemTop = -1; if (!MultiSelect) { itemTop = SelectedNodes[0].FullArea.Top; } else { itemTop = _anchoredNodeEnd.FullArea.Top; } int 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 (CrownTreeNode node in Nodes) { SortChildNodes(node); } } private void SortChildNodes(CrownTreeNode node) { node.Nodes.Sort(TreeViewNodeSorter); foreach (CrownTreeNode childNode in node.Nodes) { SortChildNodes(childNode); } } public CrownTreeNode FindNode(string path) { foreach (CrownTreeNode node in Nodes) { CrownTreeNode compNode = FindNode(node, path); if (compNode != null) { return compNode; } } return null; } private CrownTreeNode FindNode(CrownTreeNode parentNode, string path, bool recursive = true) { if (parentNode.FullPath == path) { return parentNode; } foreach (CrownTreeNode node in parentNode.Nodes) { if (node.FullPath == path) { return node; } if (recursive) { CrownTreeNode compNode = FindNode(node, path); if (compNode != null) { return compNode; } } } return null; } #endregion #region Drag & Drop Region protected override void StartDrag() { if (!AllowMoveNodes) { _provisionalDragging = false; return; } // Create initial list of nodes to drag _dragNodes = new List(); foreach (CrownTreeNode node in SelectedNodes) { _dragNodes.Add(node); } // Clear out any nodes with a parent that is being dragged foreach (CrownTreeNode node in _dragNodes.ToList()) { if (node.ParentNode == null) { continue; } if (_dragNodes.Contains(node.ParentNode)) { _dragNodes.Remove(node); } } _provisionalDragging = false; Cursor = Cursors.SizeAll; base.StartDrag(); } private void HandleDrag() { if (!AllowMoveNodes) { return; } CrownTreeNode 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; } CrownTreeNode dropNode = _dropNode; if (dropNode == null) { StopDrag(); return; } if (ForceDropToParent(dropNode)) { dropNode = dropNode.ParentNode; } if (CanMoveNodes(_dragNodes, dropNode, true)) { List cachedSelectedNodes = SelectedNodes.ToList(); MoveNodes(_dragNodes, dropNode); foreach (CrownTreeNode 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 (CrownTreeNode node in cachedSelectedNodes) { SelectedNodes.Add(node); } } StopDrag(); UpdateNodes(); } protected override void StopDrag() { _dragNodes = null; _dropNode = null; Cursor = Cursors.Default; Invalidate(); base.StopDrag(); } protected virtual bool ForceDropToParent(CrownTreeNode node) { return false; } protected virtual bool CanMoveNodes(List dragNodes, CrownTreeNode dropNode, bool isMoving = false) { if (dropNode == null) { return false; } foreach (CrownTreeNode node in dragNodes) { if (node == dropNode) { if (isMoving) { CrownMessageBox.ShowError($"Cannot move {node.Text}. The destination folder is the same as the source folder.", Application.ProductName); } return false; } if (node.ParentNode != null && node.ParentNode == dropNode) { if (isMoving) { CrownMessageBox.ShowError($"Cannot move {node.Text}. The destination folder is the same as the source folder.", Application.ProductName); } return false; } CrownTreeNode parentNode = dropNode.ParentNode; while (parentNode != null) { if (node == parentNode) { if (isMoving) { CrownMessageBox.ShowError($"Cannot move {node.Text}. The destination folder is a subfolder of the source folder.", Application.ProductName); } return false; } parentNode = parentNode.ParentNode; } } return true; } protected virtual void MoveNodes(List dragNodes, CrownTreeNode dropNode) { } protected virtual void NodesMoved(List nodesMoved) { } #endregion #region Paint Region protected override void OnPaintBackground(PaintEventArgs e) { base.OnPaintBackground(e); LoadIcons(); } protected override void PaintContent(Graphics g) { // Fill body using (SolidBrush b = new(ThemeProvider.Theme.Colors.GreyBackground)) { g.FillRectangle(b, ClientRectangle); } foreach (CrownTreeNode node in Nodes) { DrawNode(node, g); } } private void DrawNode(CrownTreeNode node, Graphics g) { Rectangle rect = GetNodeFullRowArea(node); // 1. Draw background Color bgColor = node.Odd ? ThemeProvider.Theme.Colors.HeaderBackground : ThemeProvider.Theme.Colors.GreyBackground; if (SelectedNodes.Count > 0 && SelectedNodes.Contains(node)) { bgColor = Focused ? ThemeProvider.Theme.Colors.BlueSelection : ThemeProvider.Theme.Colors.GreySelection; } if (IsDragging && _dropNode == node) { bgColor = Focused ? ThemeProvider.Theme.Colors.BlueSelection : ThemeProvider.Theme.Colors.GreySelection; } using (SolidBrush b = new(bgColor)) { g.FillRectangle(b, rect); } // 2. Draw plus/minus icon if (node.Nodes.Count > 0) { Point pos = new(node.ExpandArea.Location.X - 1, node.ExpandArea.Location.Y - 1); Bitmap 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 (ShowIcons && 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 (SolidBrush b = new(ThemeProvider.Theme.Colors.LightText)) { StringFormat stringFormat = new() { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center }; g.DrawString(node.Text, Font, b, node.TextArea, stringFormat); } // 5. Draw child nodes if (node.Expanded) { foreach (CrownTreeNode childNode in node.Nodes) { DrawNode(childNode, g); } } } #endregion } #endregion }