#region ================== Copyright // ***************************************************************************** // // 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' // // ***************************************************************************** #endregion #region ================== Namespaces using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; #endregion namespace CodeImp.DoomBuilder.GZBuilder.Controls { #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 MultiSelectTreeview : TreeView { 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 Container components; /// /// Used to make sure that SelectedNode can only be used from within this class. /// private bool blnInternalCall; /// /// Hashtable that contains all selected nodes. /// private Hashtable htblSelectedNodes = new Hashtable(); /// /// Track whether the total SelectedNodes changed across multiple operations /// for SelectionsChanged event /// private bool blnSelectionChanged; /// /// Hashtable to preserve Node's original colors (colors can be set on the TreeView, or individual nodes) /// (GKM) /// private Hashtable htblSelectedNodesOrigColors = new Hashtable(); /// /// Keeps track of node that has to be pu in edit mode. /// private TreeNode tnNodeToStartEditOn; /// /// Remembers whether mouse click on a node was single or double click. /// private bool blnWasDoubleClick; /// /// Keeps track of most recent selected node. /// private TreeNode tnMostRecentSelectedNode; /// /// 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; /// /// Keeps track of the number of mouse clicks. /// private int intMouseClicks; /// /// 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; /// /// Holds node that needs to be flashed. /// private TreeNode tnToFlash; /// /// Keeps track of the first selected node when selection has begun with the keyboard. /// private TreeNode tnKeysStartNode; #endregion #region SelectedNode, SelectionMode, SelectionBackColor, SelectedNodes + events /// /// This property is for internal use only. Use SelectedNodes instead. /// public new TreeNode SelectedNode { get { if (!blnInternalCall) { throw new NotSupportedException("Use SelectedNodes instead of SelectedNode."); } else { return base.SelectedNode; } } set { if (!blnInternalCall) { 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; } } /// /// 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(); } #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; if (htblSelectedNodesOrigColors.ContainsKey(tn.GetHashCode())) { // Color[] color = (Color[])htblSelectedNodesOrigColors[tn.GetHashCode()]; // color[0]=tn.BackColor; // color[1]=tn.ForeColor; } else { 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); if (tvcea.Cancel) { // This node selection was cancelled! return false; } PreserveNodeColors(tn); tn.BackColor = SelectionBackColor; // GKM moved from above tn.ForeColor = BackColor; // GKM moved from above htblSelectedNodes.Add(tn.GetHashCode(), tn); blnSelected = true; blnSelectionChanged = true; base.OnAfterSelect(new TreeViewEventArgs(tn, tva)); } tnMostRecentSelectedNode = tn; } else { // Only unselect node if it was selected if (IsNodeSelected(tn)) { OnBeforeDeselect(tn); Color[] originalColors = (Color[])this.htblSelectedNodesOrigColors[tn.GetHashCode()]; if (originalColors != null) { htblSelectedNodes.Remove(tn.GetHashCode()); blnSelectionChanged = true; htblSelectedNodesOrigColors.Remove(tn.GetHashCode()); // GKM - Restore original node colors tn.BackColor = originalColors[0]; // GKM - was BackColor; tn.ForeColor = originalColors[1]; // GKM - was ForeColor; } 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) { // 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 != lastNode) { tnTemp = tnTemp.NextVisibleNode; if (tnTemp != null) { SelectNode(tnTemp, true, tva); } } SelectNode(lastNode, true, tva); } /// /// 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) { // 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 void UnselectNodesRecursively(TreeNode tn, TreeViewAction tva) { SelectNode(tn, false, tva); foreach (TreeNode child in tn.Nodes) { UnselectNodesRecursively(child, tva); } } #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 static 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 } /// /// 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 static 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 static 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(); DoubleBuffered = true; //mxd SetStyle(ControlStyles.OptimizedDoubleBuffer, true); //mxd } #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. MessageBox.Show(this, ex.ToString()); } #endif } private bool IsPlusMinusClicked(TreeNode tn, MouseEventArgs e) { int intNodeLevel = GetNodeLevel(tn); bool blnPlusMinusClicked = e.X < 20 + (intNodeLevel * 20); return blnPlusMinusClicked; } /// /// Occurs when mouse is down. /// /// protected override void OnMouseDown(MouseEventArgs e) { 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) 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; System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(FlashNode)); t.Start(); blnNodeProcessedOnMouseDown = true; ProcessNodeRange(tnMostRecentSelectedNode, tn, e, Control.ModifierKeys, TreeViewAction.ByMouse, true); } } base.OnMouseDown(e); } #endregion #region FlashNode, StartEdit /// /// Flashes node. /// private void FlashNode() { if (this.InvokeRequired) { this.Invoke(new MethodInvoker(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; 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 = IsNodeSelected(endNode); 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) { 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 = GetNextTreeNode(tnMostRecentSelectedNode, true, 5); break; case Keys.Up: case Keys.Left: tnToMakeVisible = GetNextTreeNode(tnMostRecentSelectedNode, false, 5); 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 /// /// Occurs after a node is collapsed. /// /// protected override void OnAfterCollapse(TreeViewEventArgs e) { blnSelectionChanged = false; // All child nodes should be deselected bool blnChildSelected = false; foreach (TreeNode tn in e.Node.Nodes) { if (IsNodeSelected(tn)) { blnChildSelected = true; } UnselectNodesRecursively(tn, TreeViewAction.Collapse); } if (blnChildSelected) { SelectNode(e.Node, true, TreeViewAction.Collapse); } OnSelectionsChanged(); base.OnAfterCollapse(e); } #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 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 (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 }