mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-26 13:51:40 +00:00
1718 lines
49 KiB
C#
Executable file
1718 lines
49 KiB
C#
Executable file
#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.Controls
|
||
{
|
||
#region TreeViewSelectionMode enumeration
|
||
|
||
/// <summary>
|
||
/// Selection mode for the treeview.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The Selection mode determines how treeview nodes can be selected.
|
||
/// </remarks>
|
||
public enum TreeViewSelectionMode
|
||
{
|
||
/// <summary>
|
||
/// Only one node can be selected at a time.
|
||
/// </summary>
|
||
SingleSelect,
|
||
/// <summary>
|
||
/// Multiple nodes can be selected at the same time without restriction.
|
||
/// </summary>
|
||
MultiSelect,
|
||
/// <summary>
|
||
/// Multiple nodes that belong to the same root branch can be selected at the same time.
|
||
/// </summary>
|
||
MultiSelectSameRootBranch,
|
||
/// <summary>
|
||
/// Multiple nodes that belong to the same level can be selected at the same time.
|
||
/// </summary>
|
||
MultiSelectSameLevel,
|
||
/// <summary>
|
||
/// Multiple nodes that belong to the same level and same root branch can be selected at the same time.
|
||
/// </summary>
|
||
MultiSelectSameLevelAndRootBranch,
|
||
/// <summary>
|
||
/// Only nodes that belong to the same direct parent can be selected at the same time.
|
||
/// </summary>
|
||
MultiSelectSameParent
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Delegates
|
||
|
||
/// <summary>
|
||
/// Delegate used for tree node events.
|
||
/// </summary>
|
||
public delegate void TreeNodeEventHandler(TreeNode tn);
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// The TreeView control is a regular treeview with multi-selection capability.
|
||
/// </summary>
|
||
[ToolboxItem(true)]
|
||
public class MultiSelectTreeview : BufferedTreeView
|
||
{
|
||
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
|
||
|
||
/// <summary>
|
||
/// Required designer variable.
|
||
/// </summary>
|
||
private Container components;
|
||
|
||
/// <summary>
|
||
/// Used to make sure that SelectedNode can only be used from within this class.
|
||
/// </summary>
|
||
private bool blnInternalCall;
|
||
|
||
/// <summary>
|
||
/// Hashtable that contains all selected nodes.
|
||
/// </summary>
|
||
private Hashtable htblSelectedNodes = new Hashtable();
|
||
|
||
/// <summary>
|
||
/// Track whether the total SelectedNodes changed across multiple operations
|
||
/// for SelectionsChanged event
|
||
/// </summary>
|
||
private bool blnSelectionChanged;
|
||
|
||
/// <summary>
|
||
/// Hashtable to preserve Node's original colors (colors can be set on the TreeView, or individual nodes)
|
||
/// (GKM)
|
||
/// </summary>
|
||
private Hashtable htblSelectedNodesOrigColors = new Hashtable();
|
||
|
||
/// <summary>
|
||
/// Keeps track of node that has to be pu in edit mode.
|
||
/// </summary>
|
||
private TreeNode tnNodeToStartEditOn;
|
||
|
||
/// <summary>
|
||
/// Remembers whether mouse click on a node was single or double click.
|
||
/// </summary>
|
||
private bool blnWasDoubleClick;
|
||
|
||
/// <summary>
|
||
/// Keeps track of most recent selected node.
|
||
/// </summary>
|
||
private TreeNode tnMostRecentSelectedNode;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
private TreeNode tnSelectionMirrorPoint;
|
||
|
||
/// <summary>
|
||
/// Keeps track of the number of mouse clicks.
|
||
/// </summary>
|
||
private int intMouseClicks;
|
||
|
||
/// <summary>
|
||
/// Selection mode.
|
||
/// </summary>
|
||
private TreeViewSelectionMode selectionMode = TreeViewSelectionMode.SingleSelect;
|
||
|
||
/// <summary>
|
||
/// Backcolor for selected nodes.
|
||
/// </summary>
|
||
private Color selectionBackColor = SystemColors.Highlight;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
private bool blnNodeProcessedOnMouseDown;
|
||
|
||
/// <summary>
|
||
/// Holds node that needs to be flashed.
|
||
/// </summary>
|
||
private TreeNode tnToFlash;
|
||
|
||
/// <summary>
|
||
/// Keeps track of the first selected node when selection has begun with the keyboard.
|
||
/// </summary>
|
||
private TreeNode tnKeysStartNode;
|
||
|
||
#endregion
|
||
|
||
#region SelectedNode, SelectionMode, SelectionBackColor, SelectedNodes + events
|
||
|
||
/// <summary>
|
||
/// This property is for internal use only. Use SelectedNodes instead.
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets/sets selection mode.
|
||
/// </summary>
|
||
public TreeViewSelectionMode SelectionMode
|
||
{
|
||
get
|
||
{
|
||
return selectionMode;
|
||
}
|
||
set
|
||
{
|
||
selectionMode = value;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets/sets backcolor for selected nodes.
|
||
/// </summary>
|
||
public Color SelectionBackColor
|
||
{
|
||
get
|
||
{
|
||
return selectionBackColor;
|
||
}
|
||
set
|
||
{
|
||
selectionBackColor = value;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets selected nodes.
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs when a tree node is added to the SelectedNodes collection.
|
||
/// </summary>
|
||
/// <param name="tn">Tree node that was added.</param>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs when a tree node is inserted to the SelectedNodes collection.
|
||
/// </summary>
|
||
/// <param name="tn">tree node that was inserted.</param>
|
||
private void SelectedNodes_TreeNodeInserted(TreeNode tn)
|
||
{
|
||
blnSelectionChanged = false;
|
||
|
||
SelectNode(tn, true, TreeViewAction.Unknown);
|
||
|
||
OnSelectionsChanged();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs when a tree node is removed from the SelectedNodes collection.
|
||
/// </summary>
|
||
/// <param name="tn">Tree node that was removed.</param>
|
||
private void SelectedNodes_TreeNodeRemoved(TreeNode tn)
|
||
{
|
||
blnSelectionChanged = false;
|
||
|
||
SelectNode(tn, false, TreeViewAction.Unknown);
|
||
|
||
OnSelectionsChanged();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs when the SelectedNodes collection was cleared.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void SelectedNodes_SelectedNodesCleared(object sender, EventArgs e)
|
||
{
|
||
blnSelectionChanged = false;
|
||
|
||
UnselectAllNodes(TreeViewAction.Unknown);
|
||
|
||
OnSelectionsChanged();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Node selection methods
|
||
|
||
/// <summary>
|
||
/// Unselects all selected nodes.
|
||
/// </summary>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectAllNodes(TreeViewAction tva)
|
||
{
|
||
UnselectAllNodesExceptNode(null, tva);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unselects all selected nodes that don't belong to the specified level.
|
||
/// </summary>
|
||
/// <param name="level">Node level.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectAllNodesNotBelongingToLevel(int level, TreeViewAction tva)
|
||
{
|
||
// First, build list of nodes that need to be unselected
|
||
List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unselects all selected nodes that don't belong directly to the specified parent.
|
||
/// </summary>
|
||
/// <param name="parent">Parent node.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectAllNodesNotBelongingDirectlyToParent(TreeNode parent, TreeViewAction tva)
|
||
{
|
||
// First, build list of nodes that need to be unselected
|
||
List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unselects all selected nodes that don't belong directly or indirectly to the specified parent.
|
||
/// </summary>
|
||
/// <param name="parent">Parent node.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectAllNodesNotBelongingToParent(TreeNode parent, TreeViewAction tva)
|
||
{
|
||
// First, build list of nodes that need to be unselected
|
||
List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unselects all selected nodes, except for the specified node which should not be touched.
|
||
/// </summary>
|
||
/// <param name="nodeKeepSelected">Node not to touch.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectAllNodesExceptNode(TreeNode nodeKeepSelected, TreeViewAction tva)
|
||
{
|
||
// First, build list of nodes that need to be unselected
|
||
List<TreeNode> arrNodesToDeselect = new List<TreeNode>(); //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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// occurs when a node is about to be selected.
|
||
/// </summary>
|
||
/// <param name="e">TreeViewCancelEventArgs.</param>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified node is selected or not.
|
||
/// </summary>
|
||
/// <param name="tn">Node to check.</param>
|
||
/// <returns>True if specified node is selected, false if not.</returns>
|
||
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[] { tn.BackColor, tn.ForeColor });
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// (Un)selects the specified node.
|
||
/// </summary>
|
||
/// <param name="tn">Node to (un)select.</param>
|
||
/// <param name="select">True to select node, false to unselect node.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
/// <returns>True if node was selected, false if not.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Selects nodes within the specified range.
|
||
/// </summary>
|
||
/// <param name="startNode">Start node.</param>
|
||
/// <param name="endNode">End Node.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void SelectNodesInsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva)
|
||
{
|
||
// Calculate start node and end node
|
||
TreeNode firstNode, lastNode;
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unselects nodes outside the specified range.
|
||
/// </summary>
|
||
/// <param name="startNode">Start node.</param>
|
||
/// <param name="endNode">End node.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectNodesOutsideRange(TreeNode startNode, TreeNode endNode, TreeViewAction tva)
|
||
{
|
||
// Calculate start node and end node
|
||
TreeNode firstNode, lastNode;
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Recursively unselect node.
|
||
/// </summary>
|
||
/// <param name="tn">Node to recursively unselect.</param>
|
||
/// <param name="tva">Specifies the action that caused the selection change.</param>
|
||
private void UnselectNodesRecursively(TreeNode tn, TreeViewAction tva)
|
||
{
|
||
SelectNode(tn, false, tva);
|
||
foreach(TreeNode child in tn.Nodes)
|
||
{
|
||
UnselectNodesRecursively(child, tva);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Helper methods
|
||
|
||
/// <summary>
|
||
/// Determines whether a mouse click was inside the node bounds or outside the node bounds..
|
||
/// </summary>
|
||
/// <param name="tn">TreeNode to check.</param>
|
||
/// <param name="e">MouseEventArgs.</param>
|
||
/// <returns>True is mouse was clicked inside the node bounds, false if it was clicked ouside the node bounds.</returns>
|
||
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
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets level of specified node.
|
||
/// </summary>
|
||
/// <param name="node">Node.</param>
|
||
/// <returns>Level of node.</returns>
|
||
public int GetNodeLevel(TreeNode node)
|
||
{
|
||
int level = 0;
|
||
while((node = node.Parent) != null)
|
||
level++;
|
||
return level;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified node is a child (indirect or direct) of the specified parent.
|
||
/// </summary>
|
||
/// <param name="child">Node to check.</param>
|
||
/// <param name="parent">Parent node.</param>
|
||
/// <returns>True if specified node is a direct or indirect child of parent node, false if not.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets root parent of specified node.
|
||
/// </summary>
|
||
/// <param name="child">Node.</param>
|
||
/// <returns>Root parent of specified node.</returns>
|
||
public TreeNode GetRootParent(TreeNode child)
|
||
{
|
||
TreeNode tnParent = child;
|
||
|
||
while(tnParent.Parent != null)
|
||
{
|
||
tnParent = tnParent.Parent;
|
||
}
|
||
|
||
return tnParent;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets number of visible nodes.
|
||
/// </summary>
|
||
/// <returns>Number of visible nodes.</returns>
|
||
private int GetNumberOfVisibleNodes()
|
||
{
|
||
int intCounter = 0;
|
||
|
||
TreeNode tnTemp = this.Nodes[0];
|
||
|
||
while(tnTemp != null)
|
||
{
|
||
if(tnTemp.IsVisible)
|
||
{
|
||
intCounter++;
|
||
}
|
||
|
||
tnTemp = tnTemp.NextVisibleNode;
|
||
}
|
||
|
||
return intCounter;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets last visible node.
|
||
/// </summary>
|
||
/// <returns>Last visible node.</returns>
|
||
private TreeNode GetLastVisibleNode()
|
||
{
|
||
TreeNode tnTemp = this.Nodes[0];
|
||
|
||
while(tnTemp.NextVisibleNode != null)
|
||
{
|
||
tnTemp = tnTemp.NextVisibleNode;
|
||
}
|
||
|
||
return tnTemp;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets next tree node(s), starting from the specified node and direction.
|
||
/// </summary>
|
||
/// <param name="start">Node to start from.</param>
|
||
/// <param name="down">True to go down, false to go up.</param>
|
||
/// <param name="intNumber">Number of nodes to go down or up.</param>
|
||
/// <returns>Next node.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// makes focus rectangle visible or hides it.
|
||
/// </summary>
|
||
/// <param name="tn">Node to make focus rectangle (in)visible for.</param>
|
||
/// <param name="visible">True to make focus rectangle visible, false to hide it.</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Clean up any resources being used.
|
||
/// </summary>
|
||
protected override void Dispose(bool disposing)
|
||
{
|
||
if(disposing)
|
||
{
|
||
if(components != null)
|
||
{
|
||
components.Dispose();
|
||
}
|
||
}
|
||
base.Dispose(disposing);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Component Designer generated code
|
||
|
||
/// <summary>
|
||
/// Required method for Designer support - do not modify
|
||
/// the contents of this method with the code editor.
|
||
/// </summary>
|
||
private void InitializeComponent()
|
||
{
|
||
components = new System.ComponentModel.Container();
|
||
DoubleBuffered = true; //mxd
|
||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true); //mxd
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region OnMouseUp, OnMouseDown
|
||
|
||
/// <summary>
|
||
/// Occurs when mouse button is up after a click.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs when mouse is down.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Flashes node.
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Starts edit on a node.
|
||
/// </summary>
|
||
private void StartEdit()
|
||
{
|
||
System.Threading.Thread.Sleep(200);
|
||
if(!blnWasDoubleClick)
|
||
{
|
||
blnInternalCall = true;
|
||
SelectedNode = tnNodeToStartEditOn;
|
||
blnInternalCall = false;
|
||
tnNodeToStartEditOn.BeginEdit();
|
||
}
|
||
else
|
||
{
|
||
blnWasDoubleClick = false;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region ProcessNodeRange
|
||
|
||
/// <summary>
|
||
/// Processes a node range.
|
||
/// </summary>
|
||
/// <param name="startNode">Start node of range.</param>
|
||
/// <param name="endNode">End node of range.</param>
|
||
/// <param name="e">MouseEventArgs.</param>
|
||
/// <param name="keys">Keys.</param>
|
||
/// <param name="tva">TreeViewAction.</param>
|
||
/// <param name="allowStartEdit">True if node can go to edit mode, false if not.</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Occurs before node goes into edit mode.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// occurs when a key is down.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Occurs after a node is collapsed.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Occurs when an item is being dragged.
|
||
/// </summary>
|
||
/// <param name="e"></param>
|
||
protected override void OnItemDrag(ItemDragEventArgs e)
|
||
{
|
||
e = new ItemDragEventArgs(MouseButtons.Left, this.SelectedNodes);
|
||
base.OnItemDrag(e);
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
|
||
#region SelectedNodesCollection
|
||
|
||
/// <summary>
|
||
/// Collection of selected nodes.
|
||
/// </summary>
|
||
public class NodesCollection : CollectionBase
|
||
{
|
||
#region Events
|
||
|
||
/// <summary>
|
||
/// Event fired when a tree node has been added to the collection.
|
||
/// </summary>
|
||
internal event TreeNodeEventHandler TreeNodeAdded;
|
||
|
||
/// <summary>
|
||
/// Event fired when a tree node has been removed to the collection.
|
||
/// </summary>
|
||
internal event TreeNodeEventHandler TreeNodeRemoved;
|
||
|
||
/// <summary>
|
||
/// Event fired when a tree node has been inserted to the collection.
|
||
/// </summary>
|
||
internal event TreeNodeEventHandler TreeNodeInserted;
|
||
|
||
/// <summary>
|
||
/// Event fired the collection has been cleared.
|
||
/// </summary>
|
||
internal event EventHandler SelectedNodesCleared;
|
||
|
||
#endregion
|
||
|
||
#region CollectionBase members
|
||
|
||
/// <summary>
|
||
/// Gets tree node at specified index.
|
||
/// </summary>
|
||
public TreeNode this[int index]
|
||
{
|
||
get { return ((TreeNode)List[index]); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a tree node to the collection.
|
||
/// </summary>
|
||
/// <param name="treeNode">Tree node to add.</param>
|
||
/// <returns>The position into which the new element was inserted.</returns>
|
||
public int Add(TreeNode treeNode)
|
||
{
|
||
if(TreeNodeAdded != null)
|
||
TreeNodeAdded(treeNode);
|
||
|
||
return List.Add(treeNode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inserts a tree node at specified index.
|
||
/// </summary>
|
||
/// <param name="index">The position into which the new element has to be inserted.</param>
|
||
/// <param name="treeNode">Tree node to insert.</param>
|
||
public void Insert(int index, TreeNode treeNode)
|
||
{
|
||
if(TreeNodeInserted != null)
|
||
TreeNodeInserted(treeNode);
|
||
|
||
List.Add(treeNode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removed a tree node from the collection.
|
||
/// </summary>
|
||
/// <param name="treeNode">Tree node to remove.</param>
|
||
public void Remove(TreeNode treeNode)
|
||
{
|
||
if(TreeNodeRemoved != null)
|
||
TreeNodeRemoved(treeNode);
|
||
|
||
List.Remove(treeNode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether treenode belongs to the collection.
|
||
/// </summary>
|
||
/// <param name="treeNode">Tree node to check.</param>
|
||
/// <returns>True if tree node belongs to the collection, false if not.</returns>
|
||
public bool Contains(TreeNode treeNode)
|
||
{
|
||
return List.Contains(treeNode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets index of tree node in the collection.
|
||
/// </summary>
|
||
/// <param name="treeNode">Tree node to get index of.</param>
|
||
/// <returns>Index of tree node in the collection.</returns>
|
||
public int IndexOf(TreeNode treeNode)
|
||
{
|
||
return List.IndexOf(treeNode);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region OnClear
|
||
|
||
/// <summary>
|
||
/// Occurs when collection is being cleared.
|
||
/// </summary>
|
||
protected override void OnClear()
|
||
{
|
||
if(SelectedNodesCleared != null)
|
||
SelectedNodesCleared(this, EventArgs.Empty);
|
||
|
||
base.OnClear();
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
|
||
#endregion
|
||
}
|