// ***************************************************************************** // // Copyright 2004, Coder's Lab // All rights reserved. The software and associated documentation // supplied hereunder are the proprietary information of Coder's Lab // and are supplied subject to licence terms. // // // You can use this control freely in your projects, but let me know if you // are using it so I can add you to a list of references. // // Email: ludwig.stuyck@coders-lab.be // Home page: http://www.coders-lab.be // // History // 18/07/2004 // - Control creation // 24/07/2004 // - Implemented rubberband selection; also combination keys work: // ctrl, shift, ctrl+shift // 25/08/2004 // - Rubberband selection temporary removed due to scrolling problems. // - Renamed TreeViewSelectionMode property to SelectionMode. // - Renamed SelectionModes enumeration to TreeViewSelectionMode. // - Added MultiSelectSameParent selection mode. // - Added keyboard functionality. // - Enhanced selection drawing. // - Added SelectionBackColor property. // 02/09/2004 // - When shift/ctrl was pressed, treeview scrolled to last selected // node. Fixed. // - Moved TreeViewSelectionMode outside the TreeView class. // - BeforeSelect was fired multiple times, AfterSelect was never // fired. Fixed. // - Collapsing/Expanding node changed selection. This does not happen // anymore, except if a node that has selected descendants is // collapsed; then all descendants are unselected and the collapsed // node becomes selected. // - If in the BeforeSelect event, e.Cancel is set to true, then node // will not be selected // - SHIFT selection sometimes didn’t behave correctly. Fixed. // 04/09/2004 // - SelectedNodes is no longer an array of tree nodes, but a // SelectedNodesCollection // - In the AfterSelect event, the SelectedNodes contained two tree // nodes; the old one and the new one. Fixed. // 05/09/2004 // - Added Home, End, PgUp and PgDwn keys functionality // 08/10/2004 // - SelectedNodeCollection renamed to NodeCollection // - Fixes by GKM // // 18/8/2005 // - Added events BeforeDeselect and AfterDeselect // 09/5/2007 // - Added an InvokeRequired check to Flashnode() // 16/5/2007 // - Gave the document a consistant format // - Created a new event 'SelectionsChanged' // // ***************************************************************************** using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms; using System.Runtime.InteropServices; using CaeGlobals; using System.Collections.Generic; namespace UserControls { #region TreeViewSelectionMode enumeration /// /// Selection mode for the treeview. /// /// /// The Selection mode determines how treeview nodes can be selected. /// public enum TreeViewSelectionMode { /// /// Only one node can be selected at a time. /// SingleSelect, /// /// Multiple nodes can be selected at the same time without restriction. /// MultiSelect, /// /// Multiple nodes that belong to the same root branch can be selected at the same time. /// MultiSelectSameRootBranch, /// /// Multiple nodes that belong to the same level can be selected at the same time. /// MultiSelectSameLevel, /// /// Multiple nodes that belong to the same level and same root branch can be selected at the same time. /// MultiSelectSameLevelAndRootBranch, /// /// Only nodes that belong to the same direct parent can be selected at the same time. /// MultiSelectSameParent } #endregion #region Delegates /// /// Delegate used for tree node events. /// public delegate void TreeNodeEventHandler(TreeNode tn); #endregion /// /// The TreeView control is a regular treeview with multi-selection capability. /// [ToolboxItem(true)] public class CodersLabTreeView : TreeView { #region Matej private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44; private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45; private const int TVS_EX_DOUBLEBUFFER = 0x0004; [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); protected override void OnHandleCreated(EventArgs e) { SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER); base.OnHandleCreated(e); } protected override void WndProc(ref Message m) { // 0200 512 WM_MOUSEFIRST // 0200 512 WM_MOUSEMOVE // 0201 513 WM_LBUTTONDOWN // 0202 514 WM_LBUTTONUP // 0203 515 WM_LBUTTONDBLCLK // 0204 516 WM_RBUTTONDOWN // 0205 517 WM_RBUTTONUP // 0206 518 WM_RBUTTONDBLCLK // 0207 519 WM_MBUTTONDOWN // 0208 520 WM_MBUTTONUP // 0209 521 WM_MBUTTONDBLCLK // 0209 521 WM_MOUSELAST // 020a 522 WM_MOUSEWHEEL // // 0100 256 WM_KEYDOWN // 0100 256 WM_KEYFIRST // 0101 257 WM_KEYUP // 0102 258 WM_CHAR // 0103 259 WM_DEADCHAR // 0104 260 WM_SYSKEYDOWN // 0105 261 WM_SYSKEYUP // 0106 262 WM_SYSCHAR // 0107 263 WM_SYSDEADCHAR // 0108 264 WM_KEYLAST // 0109 265 WM_UNICHAR // // 0114 276 WM_HSCROLL // 0115 277 WM_VSCROLL // 020a 522 WM_MOUSEWHEEL // // Eat left and right mouse clicks and buttons // try { if (_disableMouse && ((m.Msg >= 512 && m.Msg <= 522) || (m.Msg >= 512 && m.Msg <= 522))) { // Eat message } else { // Scroll events if (m.Msg == 277 || m.Msg == 276) OnBeforeScroll(); // base.WndProc(ref m); // if (m.Msg == 277 || m.Msg == 276) OnAfterScroll(); } } catch (Exception ex) { MessageBoxes.ShowError(ex.Message); } } // Scrolling private const int ScrollBarHorizontal = 0x0; private const int ScrollBarVertical = 0x1; [DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)] public static extern int GetScrollPos(int hWnd, int nBar); private Point ScrollPosition() { return new Point( GetScrollPos((int)this.Handle, ScrollBarHorizontal), GetScrollPos((int)this.Handle, ScrollBarVertical)); } // Variables private Color _highlightForeErrorColor = Color.Red; private Color _focusedSelectionBackColor = SystemColors.Highlight; private Color _unfocusedSelectionBackColor = SystemColors.Control; private bool _changeHighlightOnFocusLost = true; private bool _disableMouse = false; private Color _mouseOverColor = Color.FromArgb((int)(SystemColors.Highlight.R + (255 - SystemColors.Highlight.R) * 0.8), (int)(SystemColors.Highlight.G + (255 - SystemColors.Highlight.G) * 0.8), (int)(SystemColors.Highlight.B + (255 - SystemColors.Highlight.B) * 0.8)); private TreeNode _prevMouseOverNode; private Color[] _prevMouseOverNodeColors; // Properties public Color HighlightForeErrorColor { get { return _highlightForeErrorColor; } set { _highlightForeErrorColor = value; } } public Color MouseOverColor { get { return _mouseOverColor; } set { _mouseOverColor = value; } } public bool DisableMouse { get { return _disableMouse; } set { _disableMouse = value; } } public bool ChangeHighlightOnFocusLost { get { return _changeHighlightOnFocusLost; } set { _changeHighlightOnFocusLost = value; } } public TreeNode MouseOverNode { get { return _prevMouseOverNode; } } // Events public event Action MouseOverNodeChangedEvent; // Methods public void SetNodeForeColor(TreeNode node, Color color) { if (htblSelectedNodesOrigColors.TryGetValue(node.GetHashCode(), out Color[] originalColors)) { // Node is selected htblSelectedNodesOrigColors.Remove(node.GetHashCode()); originalColors[1] = color; htblSelectedNodesOrigColors.Add(node.GetHashCode(), originalColors); // If the selected node got error, change its back color if (originalColors[1] == _highlightForeErrorColor) node.BackColor = _highlightForeErrorColor; else node.BackColor = _selectionBackColor; } else { node.ForeColor = color; } } protected override void OnMouseMove(MouseEventArgs e) { TreeNode tn = this.GetNodeAt(e.X, e.Y); // if (tn != null && !IsNodeSelected(tn) && IsClickOnNode(tn, e)) { if (tn != _prevMouseOverNode) { ClearMouseOverSelection(false); // _prevMouseOverNodeColors = new Color[] { tn.BackColor, tn.ForeColor }; tn.BackColor = _mouseOverColor; // _prevMouseOverNode = tn; // MouseOverNodeChangedEvent?.Invoke(this); } } else ClearMouseOverSelection(); // base.OnMouseMove(e); } private void ClearMouseOverSelection(bool invokeEvent = true) { if (_prevMouseOverNode != null && !IsNodeSelected(_prevMouseOverNode)) { _prevMouseOverNode.BackColor = _prevMouseOverNodeColors[0]; _prevMouseOverNode.ForeColor = _prevMouseOverNodeColors[1]; // MouseOverNodeChangedEvent?.Invoke(this); } // _prevMouseOverNode = null; _prevMouseOverNodeColors = null; } #endregion public event TreeViewEventHandler AfterDeselect; public event TreeViewEventHandler BeforeDeselect; public event EventHandler SelectionsChanged; protected void OnAfterDeselect(TreeNode tn) { if (AfterDeselect != null) { AfterDeselect(this, new TreeViewEventArgs(tn)); } } protected void OnBeforeDeselect(TreeNode tn) { if (BeforeDeselect != null) { BeforeDeselect(this, new TreeViewEventArgs(tn)); } } protected void OnSelectionsChanged() { if (blnSelectionChanged) if (SelectionsChanged != null) { SelectionsChanged(this, new EventArgs()); } } #region Private variables /// /// Required designer variable. /// private System.ComponentModel.Container components = null; /// /// Used to make sure that SelectedNode can only be used from within this class. /// private bool blnInternalCall = false; /// /// Hashtable that contains all selected nodes. /// private OrderedDictionary htblSelectedNodes = new OrderedDictionary(); //private Hashtable htblSelectedNodes = new Hashtable(); /// /// Track whether the total SelectedNodes changed across multiple operations /// for SelectionsChanged event /// private bool blnSelectionChanged = false; /// /// Dictionary to preserve Node's original colors (colors can be set on the TreeView, or individual nodes) /// (GKM) /// private Dictionary htblSelectedNodesOrigColors = new Dictionary(); /// /// Keeps track of node that has to be pu in edit mode. /// private TreeNode tnNodeToStartEditOn = null; /// /// Remembers whether mouse click on a node was single or double click. /// private bool blnWasDoubleClick = false; /// /// Keeps track of most recent selected node. /// private TreeNode tnMostRecentSelectedNode = null; /// /// Keeps track of the selection mirror point; this is the last selected node without SHIFT key pressed. /// It is used as the mirror node during SHIFT selection. /// private TreeNode tnSelectionMirrorPoint = null; /// /// Keeps track of the number of mouse clicks. /// private int intMouseClicks = 0; /// /// Selection mode. /// private TreeViewSelectionMode selectionMode = TreeViewSelectionMode.SingleSelect; /// /// Backcolor for selected nodes. /// private Color _selectionBackColor = SystemColors.Highlight; /// /// Keeps track whether a node click has been handled by the mouse down event. This is almost always the /// case, except when a selected node has been clicked again. Then, it will not be handled in the mouse /// down event because we might want to drag the node and if that's the case, node should not go in edit /// mode. /// private bool blnNodeProcessedOnMouseDown = false; /// /// Holds node that needs to be flashed. /// private TreeNode tnToFlash = null; /// /// Keeps track of the first selected node when selection has begun with the keyboard. /// private TreeNode tnKeysStartNode = null; #endregion #region SelectedNode, SelectionMode, SelectionBackColor, SelectedNodes + events /// /// This property is for internal use only. Use SelectedNodes instead. /// public new TreeNode SelectedNode { get { if (!blnInternalCall) { if (SelectedNodes.Count <= 0) return null; else return SelectedNodes[0]; throw new NotSupportedException("Use SelectedNodes instead of SelectedNode."); } else { return base.SelectedNode; } } set { if (!blnInternalCall) { SelectedNodes.Clear(); if (value != null) SelectedNodes.Add(value); return; throw new NotSupportedException("Use SelectedNodes instead of SelectedNode."); } else { base.SelectedNode = value; } } } /// /// Gets/sets selection mode. /// public TreeViewSelectionMode SelectionMode { get { return selectionMode; } set { selectionMode = value; } } /// /// Gets/sets backcolor for selected nodes. /// public Color SelectionBackColor { get { return _selectionBackColor; } set { _selectionBackColor = value; _focusedSelectionBackColor = value; } } /// /// Gets selected nodes. /// public NodesCollection SelectedNodes { get { // Create a SelectedNodesCollection to return, and add event handlers to catch actions on it NodesCollection selectedNodesCollection = new NodesCollection(); foreach (TreeNode tn in htblSelectedNodes.Values) { selectedNodesCollection.Add(tn); } selectedNodesCollection.TreeNodeAdded += new TreeNodeEventHandler(SelectedNodes_TreeNodeAdded); selectedNodesCollection.TreeNodeInserted += new TreeNodeEventHandler(SelectedNodes_TreeNodeInserted); selectedNodesCollection.TreeNodeRemoved += new TreeNodeEventHandler(SelectedNodes_TreeNodeRemoved); selectedNodesCollection.SelectedNodesCleared += new EventHandler(SelectedNodes_SelectedNodesCleared); return selectedNodesCollection; } } /// /// Occurs when a tree node is added to the SelectedNodes collection. /// /// Tree node that was added. private void SelectedNodes_TreeNodeAdded(TreeNode tn) { blnSelectionChanged = false; SelectNode(tn, true, TreeViewAction.Unknown); //ProcessNodeRange(null, tn, new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0), Keys.None, TreeViewAction.ByKeyboard, false); OnSelectionsChanged(); } /// /// Occurs when a tree node is inserted to the SelectedNodes collection. /// /// tree node that was inserted. private void SelectedNodes_TreeNodeInserted(TreeNode tn) { blnSelectionChanged = false; SelectNode(tn, true, TreeViewAction.Unknown); OnSelectionsChanged(); } /// /// Occurs when a tree node is removed from the SelectedNodes collection. /// /// Tree node that was removed. private void SelectedNodes_TreeNodeRemoved(TreeNode tn) { blnSelectionChanged = false; SelectNode(tn, false, TreeViewAction.Unknown); OnSelectionsChanged(); } /// /// Occurs when the SelectedNodes collection was cleared. /// /// /// private void SelectedNodes_SelectedNodesCleared(object sender, EventArgs e) { blnSelectionChanged = false; UnselectAllNodes(TreeViewAction.Unknown); OnSelectionsChanged(); tnMostRecentSelectedNode = null; } #endregion #region Node selection methods /// /// Unselects all selected nodes. /// /// Specifies the action that caused the selection change. private void UnselectAllNodes(TreeViewAction tva) { UnselectAllNodesExceptNode(null, tva); } /// /// Unselects all selected nodes that don't belong to the specified level. /// /// Node level. /// Specifies the action that caused the selection change. private void UnselectAllNodesNotBelongingToLevel(int level, TreeViewAction tva) { // First, build list of nodes that need to be unselected ArrayList arrNodesToDeselect = new ArrayList(); foreach (TreeNode selectedTreeNode in htblSelectedNodes.Values) { if (GetNodeLevel(selectedTreeNode) != level) { arrNodesToDeselect.Add(selectedTreeNode); } } // Do the actual unselect foreach (TreeNode tnToDeselect in arrNodesToDeselect) { SelectNode(tnToDeselect, false, tva); } } /// /// Unselects all selected nodes that don't belong directly to the specified parent. /// /// Parent node. /// Specifies the action that caused the selection change. private void UnselectAllNodesNotBelongingDirectlyToParent(TreeNode parent, TreeViewAction tva) { // First, build list of nodes that need to be unselected ArrayList arrNodesToDeselect = new ArrayList(); foreach (TreeNode selectedTreeNode in htblSelectedNodes.Values) { if (selectedTreeNode.Parent != parent) { arrNodesToDeselect.Add(selectedTreeNode); } } // Do the actual unselect foreach (TreeNode tnToDeselect in arrNodesToDeselect) { SelectNode(tnToDeselect, false, tva); } } /// /// Unselects all selected nodes that don't belong directly or indirectly to the specified parent. /// /// Parent node. /// Specifies the action that caused the selection change. private void UnselectAllNodesNotBelongingToParent(TreeNode parent, TreeViewAction tva) { // First, build list of nodes that need to be unselected ArrayList arrNodesToDeselect = new ArrayList(); foreach (TreeNode selectedTreeNode in htblSelectedNodes.Values) { if (!IsChildOf(selectedTreeNode, parent)) { arrNodesToDeselect.Add(selectedTreeNode); } } // Do the actual unselect foreach (TreeNode tnToDeselect in arrNodesToDeselect) { SelectNode(tnToDeselect, false, tva); } } /// /// Unselects all selected nodes, except for the specified node which should not be touched. /// /// Node not to touch. /// Specifies the action that caused the selection change. private void UnselectAllNodesExceptNode(TreeNode nodeKeepSelected, TreeViewAction tva) { // First, build list of nodes that need to be unselected ArrayList arrNodesToDeselect = new ArrayList(); foreach (TreeNode selectedTreeNode in htblSelectedNodes.Values) { if (nodeKeepSelected == null) { arrNodesToDeselect.Add(selectedTreeNode); } else if ((nodeKeepSelected != null) && (selectedTreeNode != nodeKeepSelected)) { arrNodesToDeselect.Add(selectedTreeNode); } } // Do the actual unselect foreach (TreeNode tnToDeselect in arrNodesToDeselect) { SelectNode(tnToDeselect, false, tva); } } /// /// occurs when a node is about to be selected. /// /// TreeViewCancelEventArgs. protected override void OnBeforeSelect(TreeViewCancelEventArgs e) { // We don't want the base TreeView to handle the selection, because it can only handle single selection. // Instead, we'll handle the selection ourselves by keeping track of the selected nodes and drawing the // selection ourselves. e.Cancel = true; } /// /// Determines whether the specified node is selected or not. /// /// Node to check. /// True if specified node is selected, false if not. private bool IsNodeSelected(TreeNode tn) { if (tn != null) return htblSelectedNodes.ContainsKey(tn.GetHashCode()); return false; } private void PreserveNodeColors(TreeNode tn) { if (tn == null) return; // ClearMouseOverSelection(); // if (!htblSelectedNodesOrigColors.ContainsKey(tn.GetHashCode())) { htblSelectedNodesOrigColors.Add(tn.GetHashCode(), new Color[] { tn.BackColor, tn.ForeColor }); } } /// /// (Un)selects the specified node. /// /// Node to (un)select. /// True to select node, false to unselect node. /// Specifies the action that caused the selection change. /// True if node was selected, false if not. private bool SelectNode(TreeNode tn, bool select, TreeViewAction tva) { bool blnSelected = false; // if (tn == null) return false; // if (select) { // Only try to select node if it was not already selected if (!IsNodeSelected(tn)) { // Check if node selection is cancelled TreeViewCancelEventArgs tvcea = new TreeViewCancelEventArgs(tn, false, tva); base.OnBeforeSelect(tvcea); // This node selection was cancelled! if (tvcea.Cancel) return false; // PreserveNodeColors(tn); HighlightNode(tn); // htblSelectedNodes.Add(tn.GetHashCode(), tn); blnSelected = true; blnSelectionChanged = true; // tn.EnsureVisible(); // base.OnAfterSelect(new TreeViewEventArgs(tn, tva)); } // tnMostRecentSelectedNode = tn; } else { // Only unselect node if it was selected if (IsNodeSelected(tn)) { OnBeforeDeselect(tn); // if (htblSelectedNodesOrigColors.TryGetValue(tn.GetHashCode(), out Color[] originalColors)) { htblSelectedNodes.Remove(tn.GetHashCode()); blnSelectionChanged = true; htblSelectedNodesOrigColors.Remove(tn.GetHashCode()); // tn.BackColor = originalColors[0]; tn.ForeColor = originalColors[1]; } // OnAfterDeselect(tn); } } return blnSelected; } /// /// Selects nodes within the specified range. /// /// Start node. /// End Node. /// Specifies the action that caused the selection change. private void SelectNodesInsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva) { try { if (startNode == null && endNode == null) return; if (startNode == null && endNode != null) startNode = endNode; if (startNode != null && endNode == null) endNode = startNode; // Calculate start node and end node TreeNode firstNode = null; TreeNode lastNode = null; if (startNode.Bounds.Y < endNode.Bounds.Y) { firstNode = startNode; lastNode = endNode; } else { firstNode = endNode; lastNode = startNode; } // Select each node in range SelectNode(firstNode, true, tva); TreeNode tnTemp = firstNode; while (tnTemp != null && tnTemp != lastNode) { tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { SelectNode(tnTemp, true, tva); } } SelectNode(lastNode, true, tva); } catch { int error = 0; } } /// /// Unselects nodes outside the specified range. /// /// Start node. /// End node. /// Specifies the action that caused the selection change. private void UnselectNodesOutsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva) { if (startNode == null && endNode == null) return; if (startNode == null && endNode != null) startNode = endNode; if (startNode != null && endNode == null) endNode = startNode; // Calculate start node and end node TreeNode firstNode = null; TreeNode lastNode = null; if (startNode.Bounds.Y < endNode.Bounds.Y) { firstNode = startNode; lastNode = endNode; } else { firstNode = endNode; lastNode = startNode; } // Unselect each node outside range TreeNode tnTemp = firstNode; while (tnTemp != null) { tnTemp = tnTemp.PrevVisibleNode; if (tnTemp != null) { SelectNode(tnTemp, false, tva); } } tnTemp = lastNode; while (tnTemp != null) { tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { SelectNode(tnTemp, false, tva); } } } /// /// Recursively unselect node. /// /// Node to recursively unselect. /// Specifies the action that caused the selection change. private bool UnselectNodesRecursively(TreeNode tn, TreeViewAction tva) { bool wasSelected = IsNodeSelected(tn); SelectNode(tn, false, tva); // foreach (TreeNode child in tn.Nodes) { wasSelected |= UnselectNodesRecursively(child, tva); } // return wasSelected; } #endregion #region Helper methods /// /// Determines whether a mouse click was inside the node bounds or outside the node bounds.. /// /// TreeNode to check. /// MouseEventArgs. /// True is mouse was clicked inside the node bounds, false if it was clicked ouside the node bounds. private bool IsClickOnNode(TreeNode tn, MouseEventArgs e) { if (tn == null) return false; // GKM // Determine the rightmost position we'll process clicks (so that the click has to be on the node's bounds, // like the .NET treeview int rightMostX = tn.Bounds.X + tn.Bounds.Width; return (tn != null && e.X < rightMostX); // GKM } /// /// Determines whether the mouse is inside the node bounds or outside the node bounds. /// /// TreeNode to check. /// MouseEventArgs. /// True is mouse is inside the node bounds, false if it is ouside the node bounds. private bool IsMouseOverNode(TreeNode tn, MouseEventArgs e) { if (tn == null) return false; // int rightMostX = tn.Bounds.X + tn.Bounds.Width; return (tn != null && e.X < rightMostX); } /// /// Gets level of specified node. /// /// Node. /// Level of node. public int GetNodeLevel(TreeNode node) { int level = 0; while ((node = node.Parent) != null) level++; return level; } /// /// Determines whether the specified node is a child (indirect or direct) of the specified parent. /// /// Node to check. /// Parent node. /// True if specified node is a direct or indirect child of parent node, false if not. private bool IsChildOf(TreeNode child, TreeNode parent) { bool blnChild = false; TreeNode tnTemp = child; while (tnTemp != null) { if (tnTemp == parent) { blnChild = true; break; } else { tnTemp = tnTemp.Parent; } } return blnChild; } /// /// Gets root parent of specified node. /// /// Node. /// Root parent of specified node. public TreeNode GetRootParent(TreeNode child) { TreeNode tnParent = child; while (tnParent.Parent != null) { tnParent = tnParent.Parent; } return tnParent; } /// /// Gets number of visible nodes. /// /// Number of visible nodes. private int GetNumberOfVisibleNodes() { int intCounter = 0; TreeNode tnTemp = this.Nodes[0]; while (tnTemp != null) { if (tnTemp.IsVisible) { intCounter++; } tnTemp = tnTemp.NextVisibleNode; } return intCounter; } /// /// Gets last visible node. /// /// Last visible node. private TreeNode GetLastVisibleNode() { TreeNode tnTemp = this.Nodes[0]; while (tnTemp.NextVisibleNode != null) { tnTemp = tnTemp.NextVisibleNode; } return tnTemp; } /// /// Gets next tree node(s), starting from the specified node and direction. /// /// Node to start from. /// True to go down, false to go up. /// Number of nodes to go down or up. /// Next node. private TreeNode GetNextTreeNode(TreeNode start, bool down, int intNumber) { int intCounter = 0; TreeNode tnTemp = start; while (intCounter < intNumber) { if (down) { if (tnTemp.NextVisibleNode != null) tnTemp = tnTemp.NextVisibleNode; else break; } else { if (tnTemp.PrevVisibleNode != null) tnTemp = tnTemp.PrevVisibleNode; else break; } intCounter++; } return tnTemp; } /// /// makes focus rectangle visible or hides it. /// /// Node to make focus rectangle (in)visible for. /// True to make focus rectangle visible, false to hide it. private void SetFocusToNode(TreeNode tn, bool visible) { Graphics g = this.CreateGraphics(); Rectangle rect = new Rectangle(tn.Bounds.X, tn.Bounds.Y, tn.Bounds.Width, tn.Bounds.Height); if (visible) { this.Invalidate(rect, false); Update(); if (tn.BackColor != _selectionBackColor) { g.DrawRectangle(new Pen(new SolidBrush(_selectionBackColor), 1), rect); } } else { if (tn.BackColor != _selectionBackColor) { g.DrawRectangle(new Pen(new SolidBrush(BackColor), 1), tnMostRecentSelectedNode.Bounds.X, tnMostRecentSelectedNode.Bounds.Y, tnMostRecentSelectedNode.Bounds.Width, tnMostRecentSelectedNode.Bounds.Height); } this.Invalidate(rect, false); Update(); } } #endregion #region Dispose /// /// Clean up any resources being used. /// protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } #endregion #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() { components = new System.ComponentModel.Container(); } #endregion #region OnMouseUp, OnMouseDown /// /// Occurs when mouse button is up after a click. /// /// protected override void OnMouseUp(MouseEventArgs e) { #if DEBUG try { #endif if (!this.blnNodeProcessedOnMouseDown) { TreeNode tn = this.GetNodeAt(e.X, e.Y); // Mouse click has not been handled by the mouse down event, so do it here. This is the case when // a selected node was clicked again; in that case we handle that click here because in case the // user is dragging the node, we should not put it in edit mode. if (IsClickOnNode(tn, e)) { this.ProcessNodeRange(this.tnMostRecentSelectedNode, tn, e, Control.ModifierKeys, TreeViewAction.ByMouse, true); } } this.blnNodeProcessedOnMouseDown = false; base.OnMouseUp(e); #if DEBUG } catch (Exception ex) { // GKM - Untrapped exceptions were killing me for debugging purposes. // It probably shouldn't be here permanently, but it was causing real trouble for me. MessageBoxes.ShowError("CodersLabTreeView: " + ex.ToString()); } #endif } private bool IsPlusMinusClicked(TreeNode tn, MouseEventArgs e) { Point offset = ScrollPosition(); int intNodeLevel = GetNodeLevel(tn); bool blnPlusMinusClicked = false; if (e.X + offset.X < 20 + (intNodeLevel * 20)) { blnPlusMinusClicked = true; blnNodeProcessedOnMouseDown = true; } return blnPlusMinusClicked; } /// /// Occurs when mouse is down. /// /// protected override void OnMouseDown(MouseEventArgs e) { Focus(); tnKeysStartNode = null; // Store number of mouse clicks in OnMouseDown event, because here we also get e.Clicks = 2 when an item was doubleclicked // in OnMouseUp we seem to get always e.Clicks = 1, also when item is doubleclicked intMouseClicks = e.Clicks; TreeNode tn = this.GetNodeAt(e.X, e.Y); if (tn == null) { base.OnMouseDown(e); return; } // Preserve colors here, because if you do it later then it will already have selected colors // Don't know why...! PreserveNodeColors(tn); // If +/- was clicked, we should not process the node. if (!IsPlusMinusClicked(tn, e)) { // If mouse down on a node that is already selected, then we should process this node in the mouse up event, because we // might want to drag it and it should not be put in edit mode. // Also, only process node if click was in node's bounds. if ((tn != null) && (IsClickOnNode(tn, e)) && (!IsNodeSelected(tn))) { // Flash node. In case the node selection is cancelled by the user, this gives the effect that it // was selected and unselected again. tnToFlash = tn; // Changed 14. 11. 2018 to prevent creation of threads - Flash not working //System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(FlashNode)); //t.Start(); blnNodeProcessedOnMouseDown = true; //System.Diagnostics.Debug.WriteLine("*** " + tn.BackColor); ProcessNodeRange(tnMostRecentSelectedNode, tn, e, Control.ModifierKeys, TreeViewAction.ByMouse, true); } } base.OnMouseDown(e); } protected override void OnMouseLeave(EventArgs e) { ClearMouseOverSelection(); // base.OnMouseLeave(e); } #endregion #region FlashNode, StartEdit /// /// Flashes node. /// private void FlashNode() { if (this.InvokeRequired) { this.Invoke(new MethodInvoker(delegate { FlashNode(); })); return; } TreeNode tn = tnToFlash; // Only flash node is it's not yet selected if (!IsNodeSelected(tn)) { tn.BackColor = _selectionBackColor; tn.ForeColor = this.BackColor; this.Invalidate(); this.Refresh(); Application.DoEvents(); System.Threading.Thread.Sleep(200); } // If node is not selected yet, restore default colors to end flashing if (!IsNodeSelected(tn)) { tn.BackColor = BackColor; tn.ForeColor = this.ForeColor; } } /// /// Starts edit on a node. /// private void StartEdit() { System.Threading.Thread.Sleep(200); if (!blnWasDoubleClick) { blnInternalCall = true; SelectedNode = tnNodeToStartEditOn; blnInternalCall = false; tnNodeToStartEditOn.BeginEdit(); } else { blnWasDoubleClick = false; } } #endregion #region ProcessNodeRange /// /// Processes a node range. /// /// Start node of range. /// End node of range. /// MouseEventArgs. /// Keys. /// TreeViewAction. /// True if node can go to edit mode, false if not. private void ProcessNodeRange(TreeNode startNode, TreeNode endNode, MouseEventArgs e, Keys keys, TreeViewAction tva, bool allowStartEdit) { blnSelectionChanged = false; // prepare for OnSelectionsChanged if (e.Button == MouseButtons.Left) { blnWasDoubleClick = (intMouseClicks == 2); TreeNode tnTemp = null; int intNodeLevelStart; if (((keys & Keys.Control) == 0) && ((keys & Keys.Shift) == 0)) { // CTRL and SHIFT not held down tnSelectionMirrorPoint = endNode; int intNumberOfSelectedNodes = SelectedNodes.Count; // If it was a double click, select node and suspend further processing if (blnWasDoubleClick) { base.OnMouseDown(e); return; } if (!IsPlusMinusClicked(endNode, e)) { bool blnNodeWasSelected = false; if (IsNodeSelected(endNode)) blnNodeWasSelected = true; UnselectAllNodesExceptNode(endNode, tva); SelectNode(endNode, true, tva); if ((blnNodeWasSelected) && (LabelEdit) && (allowStartEdit) && (!blnWasDoubleClick) && (intNumberOfSelectedNodes <= 1)) { // Node should be put in edit mode tnNodeToStartEditOn = endNode; System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(StartEdit)); t.Start(); } } } else if (((keys & Keys.Control) != 0) && ((keys & Keys.Shift) == 0)) { // CTRL held down tnSelectionMirrorPoint = null; if (!IsNodeSelected(endNode)) { switch (selectionMode) { case TreeViewSelectionMode.SingleSelect: UnselectAllNodesExceptNode(endNode, tva); break; case TreeViewSelectionMode.MultiSelectSameRootBranch: TreeNode tnAbsoluteParent2 = GetRootParent(endNode); UnselectAllNodesNotBelongingToParent(tnAbsoluteParent2, tva); break; case TreeViewSelectionMode.MultiSelectSameLevel: UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva); break; case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch: TreeNode tnAbsoluteParent = GetRootParent(endNode); UnselectAllNodesNotBelongingToParent(tnAbsoluteParent, tva); UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva); break; case TreeViewSelectionMode.MultiSelectSameParent: TreeNode tnParent = endNode.Parent; UnselectAllNodesNotBelongingDirectlyToParent(tnParent, tva); break; } SelectNode(endNode, true, tva); } else { SelectNode(endNode, false, tva); } } else if (((keys & Keys.Control) == 0) && ((keys & Keys.Shift) != 0)) { // SHIFT pressed if (tnSelectionMirrorPoint == null) { tnSelectionMirrorPoint = startNode; } switch (selectionMode) { case TreeViewSelectionMode.SingleSelect: UnselectAllNodesExceptNode(endNode, tva); SelectNode(endNode, true, tva); break; case TreeViewSelectionMode.MultiSelectSameRootBranch: TreeNode tnAbsoluteParentStartNode = GetRootParent(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { TreeNode tnAbsoluteParent = GetRootParent(tnTemp); if (tnAbsoluteParent == tnAbsoluteParentStartNode) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStartNode, tva); UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva); break; case TreeViewSelectionMode.MultiSelectSameLevel: intNodeLevelStart = GetNodeLevel(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { int intNodeLevel = GetNodeLevel(tnTemp); if (intNodeLevel == intNodeLevelStart) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva); UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva); break; case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch: TreeNode tnAbsoluteParentStart = GetRootParent(startNode); intNodeLevelStart = GetNodeLevel(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { int intNodeLevel = GetNodeLevel(tnTemp); TreeNode tnAbsoluteParent = GetRootParent(tnTemp); if ((intNodeLevel == intNodeLevelStart) && (tnAbsoluteParent == tnAbsoluteParentStart)) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStart, tva); UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva); UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva); break; case TreeViewSelectionMode.MultiSelect: SelectNodesInsideRange(tnSelectionMirrorPoint, endNode, tva); UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva); break; case TreeViewSelectionMode.MultiSelectSameParent: TreeNode tnParentStartNode = startNode.Parent; tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { TreeNode tnParent = tnTemp.Parent; if (tnParent == tnParentStartNode) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingDirectlyToParent(tnParentStartNode, tva); UnselectNodesOutsideRange(tnSelectionMirrorPoint, endNode, tva); break; } } else if (((keys & Keys.Control) != 0) && ((keys & Keys.Shift) != 0)) { // SHIFT AND CTRL pressed switch (selectionMode) { case TreeViewSelectionMode.SingleSelect: UnselectAllNodesExceptNode(endNode, tva); SelectNode(endNode, true, tva); break; case TreeViewSelectionMode.MultiSelectSameRootBranch: TreeNode tnAbsoluteParentStartNode = GetRootParent(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { TreeNode tnAbsoluteParent = GetRootParent(tnTemp); if (tnAbsoluteParent == tnAbsoluteParentStartNode) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStartNode, tva); break; case TreeViewSelectionMode.MultiSelectSameLevel: intNodeLevelStart = GetNodeLevel(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { int intNodeLevel = GetNodeLevel(tnTemp); if (intNodeLevel == intNodeLevelStart) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva); break; case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch: TreeNode tnAbsoluteParentStart = GetRootParent(startNode); intNodeLevelStart = GetNodeLevel(startNode); tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { int intNodeLevel = GetNodeLevel(tnTemp); TreeNode tnAbsoluteParent = GetRootParent(tnTemp); if ((intNodeLevel == intNodeLevelStart) && (tnAbsoluteParent == tnAbsoluteParentStart)) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStart, tva); UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva); break; case TreeViewSelectionMode.MultiSelect: tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { SelectNode(tnTemp, true, tva); } } break; case TreeViewSelectionMode.MultiSelectSameParent: TreeNode tnParentStartNode = startNode.Parent; tnTemp = startNode; // Check each visible node from startNode to endNode and select it if needed while ((tnTemp != null) && (tnTemp != endNode)) { if (startNode.Bounds.Y > endNode.Bounds.Y) tnTemp = tnTemp.PrevVisibleNode; else tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { TreeNode tnParent = tnTemp.Parent; if (tnParent == tnParentStartNode) { SelectNode(tnTemp, true, tva); } } } UnselectAllNodesNotBelongingDirectlyToParent(tnParentStartNode, tva); break; } } } else if (e.Button == MouseButtons.Right) { // if right mouse button clicked, clear selection and select right-clicked node if (!IsNodeSelected(endNode)) { UnselectAllNodes(tva); SelectNode(endNode, true, tva); } } OnSelectionsChanged(); } #endregion #region OnBeforeLabelEdit /// /// Occurs before node goes into edit mode. /// /// protected override void OnBeforeLabelEdit(NodeLabelEditEventArgs e) { blnSelectionChanged = false; // prepare for OnSelectionsChanged // Make sure that it's the only selected node SelectNode(e.Node, true, TreeViewAction.ByMouse); UnselectAllNodesExceptNode(e.Node, TreeViewAction.ByMouse); OnSelectionsChanged(); base.OnBeforeLabelEdit(e); } #endregion #region OnKeyDown /// /// occurs when a key is down. /// /// protected override void OnKeyDown(KeyEventArgs e) { if (tnMostRecentSelectedNode == null) { base.OnKeyDown(e); return; } Keys kMod = Keys.None; switch (e.Modifiers) { case Keys.Shift: case Keys.Control: case Keys.Control | Keys.Shift: kMod = Keys.Shift; if (tnKeysStartNode == null) tnKeysStartNode = tnMostRecentSelectedNode; break; default: tnKeysStartNode = null; break; } int intNumber = 0; TreeNode tnNewlySelectedNodeWithKeys = null; switch (e.KeyCode) { case Keys.Down: tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.NextVisibleNode; break; case Keys.Up: tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.PrevVisibleNode; break; case Keys.Left: if (tnMostRecentSelectedNode.IsExpanded) tnMostRecentSelectedNode.Collapse(); else tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.Parent; break; case Keys.Right: if (!tnMostRecentSelectedNode.IsExpanded) tnMostRecentSelectedNode.Expand(); else if (tnMostRecentSelectedNode.Nodes != null) tnNewlySelectedNodeWithKeys = tnMostRecentSelectedNode.Nodes[0]; break; case Keys.Home: tnNewlySelectedNodeWithKeys = this.Nodes[0]; break; case Keys.End: tnNewlySelectedNodeWithKeys = GetLastVisibleNode(); break; case Keys.PageDown: intNumber = GetNumberOfVisibleNodes(); tnNewlySelectedNodeWithKeys = GetNextTreeNode(tnMostRecentSelectedNode, true, intNumber); break; case Keys.PageUp: intNumber = GetNumberOfVisibleNodes(); tnNewlySelectedNodeWithKeys = GetNextTreeNode(tnMostRecentSelectedNode, false, intNumber); break; default: base.OnKeyDown(e); // GKM return; } if ((tnNewlySelectedNodeWithKeys != null)) { SetFocusToNode(tnMostRecentSelectedNode, false); ProcessNodeRange(tnKeysStartNode, tnNewlySelectedNodeWithKeys, new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0), kMod, TreeViewAction.ByKeyboard, false); tnMostRecentSelectedNode = tnNewlySelectedNodeWithKeys; SetFocusToNode(tnMostRecentSelectedNode, true); } // Ensure visibility if (tnMostRecentSelectedNode != null) { TreeNode tnToMakeVisible = null; switch (e.KeyCode) { case Keys.Down: case Keys.Right: tnToMakeVisible = tnMostRecentSelectedNode; // GetNextTreeNode(tnMostRecentSelectedNode, true, 1); break; case Keys.Up: case Keys.Left: tnToMakeVisible = tnMostRecentSelectedNode; // GetNextTreeNode(tnMostRecentSelectedNode, false, 1); break; case Keys.Home: case Keys.End: tnToMakeVisible = tnMostRecentSelectedNode; break; case Keys.PageDown: tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, true, intNumber - 2); break; case Keys.PageUp: tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, false, intNumber - 2); break; } if (tnToMakeVisible != null) tnToMakeVisible.EnsureVisible(); } base.OnKeyDown(e); } #endregion #region OnAfterCollapse protected override void OnBeforeCollapse(TreeViewCancelEventArgs e) { if (intMouseClicks == 1) BeginUpdate(); base.OnBeforeCollapse(e); } /// /// Occurs after a node is collapsed. /// /// protected override void OnAfterCollapse(TreeViewEventArgs e) { blnSelectionChanged = false; // If more nodes are selected // All child nodes should be deselected bool blnChildSelected = false; foreach (TreeNode tn in e.Node.Nodes) { blnChildSelected |= UnselectNodesRecursively(tn, TreeViewAction.Collapse); } // if (blnChildSelected) { SelectNode(e.Node, true, TreeViewAction.Collapse); } // OnSelectionsChanged(); // if (intMouseClicks == 1) EndUpdate(); base.OnAfterCollapse(e); } protected override void OnBeforeExpand(TreeViewCancelEventArgs e) { if (intMouseClicks == 1) BeginUpdate(); base.OnBeforeExpand(e); } protected override void OnAfterExpand(TreeViewEventArgs e) { if (intMouseClicks == 1) EndUpdate(); base.OnAfterExpand(e); } private void OnBeforeScroll() { BeginUpdate(); } private void OnAfterScroll() { EndUpdate(); Application.DoEvents(); } #endregion #region OnItemDrag /// /// Occurs when an item is being dragged. /// /// protected override void OnItemDrag(ItemDragEventArgs e) { e = new ItemDragEventArgs(MouseButtons.Left, this.SelectedNodes); base.OnItemDrag(e); } #endregion #region OnGot/OnLost Focus protected override void OnGotFocus(EventArgs e) { if (_changeHighlightOnFocusLost) { foreach (TreeNode node in SelectedNodes) HighlightNode(node); } // base.OnGotFocus(e); } protected override void OnLostFocus(EventArgs e) { if (_changeHighlightOnFocusLost) { foreach (TreeNode node in SelectedNodes) HighlightNode(node); } // base.OnLostFocus(e); } private void HighlightNode(TreeNode node) { if (Focused || !_changeHighlightOnFocusLost) { if (node.ForeColor == _highlightForeErrorColor) { node.BackColor = _highlightForeErrorColor; node.ForeColor = BackColor; } else { node.BackColor = _selectionBackColor; node.ForeColor = BackColor; } } // Unfocused else { Color[] originalColors = (Color[])htblSelectedNodesOrigColors[node.GetHashCode()]; if (originalColors != null) { if (node.ForeColor == _highlightForeErrorColor) { node.BackColor = _unfocusedSelectionBackColor; node.ForeColor = _highlightForeErrorColor; } else { node.BackColor = _unfocusedSelectionBackColor; node.ForeColor = originalColors[1]; } } } } #endregion } #region SelectedNodesCollection /// /// Collection of selected nodes. /// public class NodesCollection : CollectionBase { #region Events /// /// Event fired when a tree node has been added to the collection. /// internal event TreeNodeEventHandler TreeNodeAdded; /// /// Event fired when a tree node has been removed to the collection. /// internal event TreeNodeEventHandler TreeNodeRemoved; /// /// Event fired when a tree node has been inserted to the collection. /// internal event TreeNodeEventHandler TreeNodeInserted; /// /// Event fired the collection has been cleared. /// internal event EventHandler SelectedNodesCleared; #endregion #region CollectionBase members /// /// Gets tree node at specified index. /// public TreeNode this[int index] { get { return ((TreeNode)List[index]); } } /// /// Adds a tree node to the collection. /// /// Tree node to add. /// The position into which the new element was inserted. public int Add(TreeNode treeNode) { if (TreeNodeAdded != null) TreeNodeAdded(treeNode); return List.Add(treeNode); } /// /// Inserts a tree node at specified index. /// /// The position into which the new element has to be inserted. /// Tree node to insert. public void Insert(int index, TreeNode treeNode) { if (TreeNodeInserted != null) TreeNodeInserted(treeNode); List.Add(treeNode); } /// /// Removed a tree node from the collection. /// /// Tree node to remove. public void Remove(TreeNode treeNode) { if (List.Contains(treeNode)) { if (TreeNodeRemoved != null) TreeNodeRemoved(treeNode); List.Remove(treeNode); } } /// /// Determines whether treenode belongs to the collection. /// /// Tree node to check. /// True if tree node belongs to the collection, false if not. public bool Contains(TreeNode treeNode) { return List.Contains(treeNode); } /// /// Gets index of tree node in the collection. /// /// Tree node to get index of. /// Index of tree node in the collection. public int IndexOf(TreeNode treeNode) { return List.IndexOf(treeNode); } #endregion #region OnClear /// /// Occurs when collection is being cleared. /// protected override void OnClear() { if (SelectedNodesCleared != null) SelectedNodesCleared(this, EventArgs.Empty); base.OnClear(); } #endregion } #endregion }