#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.Collections.Generic;
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
List arrNodesToDeselect = new List(); //mxd
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
List arrNodesToDeselect = new List(); //mxd
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
List arrNodesToDeselect = new List(); //mxd
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
List arrNodesToDeselect = new List(); //mxd
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)
{
using(Pen p = new Pen(SelectionBackColor, 1)) g.DrawRectangle(p, rect);
}
}
else
{
if (tn.BackColor != SelectionBackColor)
{
using (Pen p = new Pen(BackColor, 1))
{
g.DrawRectangle(p, 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
}