From bd776d50131e76e97f15c1058ce736c38d23b009 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 18 Sep 2015 13:01:09 +0100 Subject: [PATCH] Created DarkTreeView --- DarkUI/Controls/DarkTreeView.cs | 1274 +++++++++++++++++++++ DarkUI/Controls/Items/DarkTreeNode.cs | 254 ++++ DarkUI/DarkUI.csproj | 28 +- DarkUI/Icons/TreeViewIcons.Designer.cs | 103 ++ DarkUI/Icons/TreeViewIcons.resx | 133 +++ DarkUI/Resources/node_closed_empty.png | Bin 0 -> 2849 bytes DarkUI/Resources/node_closed_full.png | Bin 0 -> 2847 bytes DarkUI/Resources/node_open.png | Bin 0 -> 2836 bytes DarkUI/Resources/node_open_empty.png | Bin 0 -> 17741 bytes Example/Example.csproj | 18 + Example/Forms/MainForm.Designer.cs | 55 +- Example/Forms/MainForm.cs | 23 +- Example/Icons.Designer.cs | 93 ++ Example/Icons.resx | 130 +++ Example/Resources/Files_7954.png | Bin 0 -> 195 bytes Example/Resources/folder_16x.png | Bin 0 -> 286 bytes Example/Resources/folder_Closed_16xLG.png | Bin 0 -> 196 bytes 17 files changed, 2095 insertions(+), 16 deletions(-) create mode 100644 DarkUI/Controls/DarkTreeView.cs create mode 100644 DarkUI/Controls/Items/DarkTreeNode.cs create mode 100644 DarkUI/Icons/TreeViewIcons.Designer.cs create mode 100644 DarkUI/Icons/TreeViewIcons.resx create mode 100644 DarkUI/Resources/node_closed_empty.png create mode 100644 DarkUI/Resources/node_closed_full.png create mode 100644 DarkUI/Resources/node_open.png create mode 100644 DarkUI/Resources/node_open_empty.png create mode 100644 Example/Icons.Designer.cs create mode 100644 Example/Icons.resx create mode 100644 Example/Resources/Files_7954.png create mode 100644 Example/Resources/folder_16x.png create mode 100644 Example/Resources/folder_Closed_16xLG.png diff --git a/DarkUI/Controls/DarkTreeView.cs b/DarkUI/Controls/DarkTreeView.cs new file mode 100644 index 0000000..3c8aef5 --- /dev/null +++ b/DarkUI/Controls/DarkTreeView.cs @@ -0,0 +1,1274 @@ +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 _nodes; + private ObservableCollection _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 _dragNodes; + private Timer _dragTimer; + private Point _dragPos; + + #endregion + + #region Property Region + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ObservableList 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 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 TreeViewNodeSorter { get; set; } + + #endregion + + #region Constructor Region + + public DarkTreeView() + { + Nodes = new ObservableList(); + _selectedNodes = new ObservableCollection(); + _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 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 e) + { + foreach (var node in e.Items) + { + node.ParentTree = this; + node.IsRoot = true; + + HookNodeEvents(node); + } + + UpdateNodes(); + } + + private void ChildNodes_ItemsAdded(object sender, ObservableListModified e) + { + foreach (var node in e.Items) + HookNodeEvents(node); + + UpdateNodes(); + } + + private void ChildNodes_ItemsRemoved(object sender, ObservableListModified 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(); + + 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 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(); + 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 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 dragNodes, DarkTreeNode dropNode) + { } + + protected virtual void NodesMoved(List 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 + } +} diff --git a/DarkUI/Controls/Items/DarkTreeNode.cs b/DarkUI/Controls/Items/DarkTreeNode.cs new file mode 100644 index 0000000..ae1b931 --- /dev/null +++ b/DarkUI/Controls/Items/DarkTreeNode.cs @@ -0,0 +1,254 @@ +using System; +using System.Drawing; + +namespace DarkUI +{ + public class DarkTreeNode + { + #region Event Region + + public event EventHandler> ItemsAdded; + public event EventHandler> ItemsRemoved; + + public event EventHandler TextChanged; + public event EventHandler NodeExpanded; + public event EventHandler NodeCollapsed; + + #endregion + + #region Field Region + + private string _text; + private bool _isRoot; + private DarkTreeView _parentTree; + private DarkTreeNode _parentNode; + + private ObservableList _nodes; + + private bool _expanded; + + #endregion + + #region Property Region + + public string Text + { + get { return _text; } + set + { + if (_text == value) + return; + + _text = value; + + OnTextChanged(); + } + } + + public Rectangle ExpandArea { get; set; } + public Rectangle IconArea { get; set; } + public Rectangle TextArea { get; set; } + public Rectangle FullArea { get; set; } + + public bool ExpandAreaHot { get; set; } + + public Bitmap Icon { get; set; } + public Bitmap ExpandedIcon { get; set; } + + public bool Expanded + { + get { return _expanded; } + set + { + if (_expanded == value) + return; + + if (value == true && Nodes.Count == 0) + return; + + _expanded = value; + + if (_expanded) + { + if (NodeExpanded != null) + NodeExpanded(this, null); + } + else + { + if (NodeCollapsed != null) + NodeCollapsed(this, null); + } + } + } + + public ObservableList Nodes + { + get { return _nodes; } + set + { + if (_nodes != null) + { + _nodes.ItemsAdded -= Nodes_ItemsAdded; + _nodes.ItemsRemoved -= Nodes_ItemsRemoved; + } + + _nodes = value; + + _nodes.ItemsAdded += Nodes_ItemsAdded; + _nodes.ItemsRemoved += Nodes_ItemsRemoved; + } + } + + public bool IsRoot + { + get { return _isRoot; } + set { _isRoot = value; } + } + + public DarkTreeView ParentTree + { + get { return _parentTree; } + set + { + if (_parentTree == value) + return; + + _parentTree = value; + + foreach (var node in Nodes) + node.ParentTree = _parentTree; + } + } + + public DarkTreeNode ParentNode + { + get { return _parentNode; } + set { _parentNode = value; } + } + + public bool Odd { get; set; } + + public object NodeType { get; set; } + + public object Tag { get; set; } + + public string FullPath + { + get + { + var parent = ParentNode; + var path = Text; + + while (parent != null) + { + path = string.Format("{0}{1}{2}", parent.Text, "\\", path); + parent = parent.ParentNode; + } + + return path; + } + } + + public DarkTreeNode PrevVisibleNode { get; set; } + + public DarkTreeNode NextVisibleNode { get; set; } + + public int VisibleIndex { get; set; } + + public bool IsNodeAncestor(DarkTreeNode node) + { + var parent = ParentNode; + while (parent != null) + { + if (parent == node) + return true; + + parent = parent.ParentNode; + } + + return false; + } + + #endregion + + #region Constructor Region + + public DarkTreeNode() + { + Nodes = new ObservableList(); + } + + public DarkTreeNode(string text) + : this() + { + Text = text; + } + + #endregion + + #region Method Region + + public void Remove() + { + if (ParentNode != null) + ParentNode.Nodes.Remove(this); + else + ParentTree.Nodes.Remove(this); + } + + public void EnsureVisible() + { + var parent = ParentNode; + + while (parent != null) + { + parent.Expanded = true; + parent = parent.ParentNode; + } + } + + #endregion + + #region Event Handler Region + + private void OnTextChanged() + { + if (ParentTree != null && ParentTree.TreeViewNodeSorter != null) + { + if (ParentNode != null) + ParentNode.Nodes.Sort(ParentTree.TreeViewNodeSorter); + else + ParentTree.Nodes.Sort(ParentTree.TreeViewNodeSorter); + } + + if (TextChanged != null) + TextChanged(this, null); + } + + private void Nodes_ItemsAdded(object sender, ObservableListModified e) + { + foreach (var node in e.Items) + { + node.ParentNode = this; + node.ParentTree = ParentTree; + } + + if (ParentTree != null && ParentTree.TreeViewNodeSorter != null) + Nodes.Sort(ParentTree.TreeViewNodeSorter); + + if (ItemsAdded != null) + ItemsAdded(this, e); + } + + private void Nodes_ItemsRemoved(object sender, ObservableListModified e) + { + if (Nodes.Count == 0) + Expanded = false; + + if (ItemsRemoved != null) + ItemsRemoved(this, e); + } + + #endregion + } +} diff --git a/DarkUI/DarkUI.csproj b/DarkUI/DarkUI.csproj index f4bee5f..de062b4 100644 --- a/DarkUI/DarkUI.csproj +++ b/DarkUI/DarkUI.csproj @@ -43,7 +43,10 @@ - + + Component + + Component @@ -84,6 +87,7 @@ Component + Form @@ -115,6 +119,11 @@ True ScrollIcons.resx + + True + True + TreeViewIcons.resx + @@ -141,6 +150,11 @@ ScrollIcons.Designer.cs DarkUI + + ResXFileCodeGenerator + TreeViewIcons.Designer.cs + DarkUI + @@ -169,6 +183,18 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + ..\Resources\node_closed_empty.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\node_closed_full.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\node_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\node_open_empty.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/DarkUI/Resources/node_closed_empty.png b/DarkUI/Resources/node_closed_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..5da81920fdc43957d5037ef26621caa006ee55be GIT binary patch literal 2849 zcmV++3*PjJP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0000?Nklq@&|NjF3y|o+~^2yoi00000NkvXXu0mjf1cXcr literal 0 HcmV?d00001 diff --git a/DarkUI/Resources/node_closed_full.png b/DarkUI/Resources/node_closed_full.png new file mode 100644 index 0000000000000000000000000000000000000000..21a7062107244f8b06594252de1a56f14721ce6d GIT binary patch literal 2847 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 literal 0 HcmV?d00001 diff --git a/DarkUI/Resources/node_open.png b/DarkUI/Resources/node_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a9addef170c161c2058d06bb56036da1105106cf GIT binary patch literal 2836 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 literal 0 HcmV?d00001 diff --git a/Example/Example.csproj b/Example/Example.csproj index 23408d1..e99a27b 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -45,11 +45,20 @@ MainForm.cs + + True + True + Icons.resx + MainForm.cs + + ResXFileCodeGenerator + Icons.Designer.cs + ResXFileCodeGenerator Resources.Designer.cs @@ -76,6 +85,15 @@ DarkUI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + Resources\Files_7954.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Resources\folder_Closed_16xLG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Resources\folder_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Example/Resources/Files_7954.png b/Example/Resources/Files_7954.png new file mode 100644 index 0000000000000000000000000000000000000000..009dde90556b43b069ad85be333cd64a1b6bc97e GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9F5he4R}c>anMpkS$| zi(^Pd+|f%1Ia>_`S{_dM(qmeeQPINPXIcA!PC{xWt~$(696x6MOgp< literal 0 HcmV?d00001 diff --git a/Example/Resources/folder_16x.png b/Example/Resources/folder_16x.png new file mode 100644 index 0000000000000000000000000000000000000000..bf1195da992ce4da1382f534cf5a48c3fa0c0e7b GIT binary patch literal 286 zcmV+(0pb3MP)rqj=N&;I8MK|GAui<2djmfEz?q0?>jW32rsA`&bOxc;+=>!8Y1t|g1!Bj~vbykhW)art@T5$x_ex0pwwZ)LEM=8Etk>j{YX zv%c@{;8Qg+5V4yLv0sh(0*a!T!SIW9d16zHzV7})Q#G;>aojFJlOO&Th$wP8Z01ao k|9?zGlm-xiN50Sb42+jWo;z}AG5`Po07*qoM6N<$f>f)2RE6w=KAld