mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-22 16:36:03 +01:00
16c88cc7b7
- Corrected folder selection; reduced clicks required to select folders; improved text; added delete button. - Moved FolderChangeDialogTreeView to the same folder as the other TreeViews; - Added missing null checks to the MutliSelectTreeView and hid properties HScrollPos and VScrollPos from VS's designer (the former was initialised to zero every time the designer was updated, causing the treeview images to disappear). Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
1200 lines
46 KiB
C#
1200 lines
46 KiB
C#
/* Copyright (c) Citrix Systems, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.Drawing;
|
|
using System.Collections;
|
|
using System.ComponentModel;
|
|
using System.Threading;
|
|
using XenCenterLib;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace XenAdmin.Controls
|
|
{
|
|
public enum TreeViewSelectionMode
|
|
{
|
|
SingleSelect,
|
|
MultiSelect,
|
|
MultiSelectSameRootBranch,
|
|
MultiSelectSameLevel,
|
|
MultiSelectSameLevelAndRootBranch,
|
|
MultiSelectSameParent
|
|
}
|
|
|
|
public interface IMultiSelectTreeNodeCollectionOwner
|
|
{
|
|
/// <summary>
|
|
/// Gets a complete collection of nodes owned.
|
|
/// </summary>
|
|
TreeNodeCollection Nodes { get;}
|
|
}
|
|
|
|
public partial class MultiSelectTreeView : TreeView, IMultiSelectTreeNodeCollectionOwner
|
|
{
|
|
private bool _nodeProcessedOnMouseDown;
|
|
private bool _selectionChanged;
|
|
private bool _wasDoubleClick;
|
|
private readonly InternalSelectedNodeCollection _selectedNodes = new InternalSelectedNodeCollection();
|
|
private readonly MultiSelectTreeSelectedNodeCollection _selectedNodesWrapper;
|
|
private int intMouseClicks;
|
|
private TreeViewSelectionMode _selectionMode;
|
|
private MultiSelectTreeNode _keysStartNode;
|
|
private MultiSelectTreeNode _mostRecentSelectedNode;
|
|
private MultiSelectTreeNode _nodeToStartEditOn;
|
|
private MultiSelectTreeNode _selectionMirrorPoint;
|
|
private MultiSelectTreeNodeCollection _nodes;
|
|
|
|
public Point LastMouseDownEventPosition { get; private set; }
|
|
|
|
public event TreeViewEventHandler AfterDeselect;
|
|
public event TreeViewEventHandler BeforeDeselect;
|
|
public event EventHandler SelectionsChanged;
|
|
public new event EventHandler<MultiSelectTreeViewCancelEventArgs> BeforeSelect;
|
|
public new event EventHandler<MultiSelectTreeViewItemDragEventArgs> ItemDrag;
|
|
|
|
public MultiSelectTreeView()
|
|
{
|
|
_selectedNodesWrapper = new MultiSelectTreeSelectedNodeCollection(this);
|
|
_nodes = new MultiSelectTreeNodeCollection(this);
|
|
}
|
|
|
|
public new MultiSelectTreeNodeCollection Nodes
|
|
{
|
|
get
|
|
{
|
|
return _nodes;
|
|
}
|
|
}
|
|
|
|
private MultiSelectTreeNode GetLastVisibleNode()
|
|
{
|
|
MultiSelectTreeNode nextVisibleNode = Nodes[0];
|
|
while (nextVisibleNode.NextVisibleNode != null)
|
|
{
|
|
nextVisibleNode = nextVisibleNode.NextVisibleNode;
|
|
}
|
|
return nextVisibleNode;
|
|
}
|
|
|
|
private MultiSelectTreeNode GetNextTreeNode(MultiSelectTreeNode start, bool down, int intNumber)
|
|
{
|
|
int num = 0;
|
|
MultiSelectTreeNode nextVisibleNode = start;
|
|
while (num < intNumber)
|
|
{
|
|
if (down)
|
|
{
|
|
if (nextVisibleNode.NextVisibleNode == null)
|
|
{
|
|
return nextVisibleNode;
|
|
}
|
|
nextVisibleNode = nextVisibleNode.NextVisibleNode;
|
|
}
|
|
else
|
|
{
|
|
if (nextVisibleNode.PrevVisibleNode == null)
|
|
{
|
|
return nextVisibleNode;
|
|
}
|
|
nextVisibleNode = nextVisibleNode.PrevVisibleNode;
|
|
}
|
|
num++;
|
|
}
|
|
return nextVisibleNode;
|
|
}
|
|
|
|
public int GetNodeLevel(MultiSelectTreeNode node)
|
|
{
|
|
int num = 0;
|
|
while ((node = node.Parent) != null)
|
|
{
|
|
num++;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
private int GetNumberOfVisibleNodes()
|
|
{
|
|
int num = 0;
|
|
for (MultiSelectTreeNode node = Nodes[0]; node != null; node = node.NextVisibleNode)
|
|
{
|
|
if (node.IsVisible)
|
|
{
|
|
num++;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
public MultiSelectTreeNode GetRootParent(MultiSelectTreeNode child)
|
|
{
|
|
MultiSelectTreeNode parent = child;
|
|
while (parent.Parent != null)
|
|
{
|
|
parent = parent.Parent;
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
private bool IsChildOf(MultiSelectTreeNode child, MultiSelectTreeNode parent)
|
|
{
|
|
for (MultiSelectTreeNode node = child; node != null; node = node.Parent)
|
|
{
|
|
if (node == parent)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool IsClickOnNode(MultiSelectTreeNode node, MouseEventArgs e)
|
|
{
|
|
return node != null && e.X < node.Bounds.X + node.Bounds.Width;
|
|
}
|
|
|
|
private bool IsNodeSelected(MultiSelectTreeNode node)
|
|
{
|
|
return node != null && _selectedNodes.Contains(node);
|
|
}
|
|
|
|
private bool IsPlusMinusClicked(MultiSelectTreeNode node, MouseEventArgs e)
|
|
{
|
|
return e.X < (20 + (GetNodeLevel(node) * 20)) - HScrollPos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs after a node is collapsed.
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected override void OnAfterCollapse(TreeViewEventArgs e)
|
|
{
|
|
_selectionChanged = false;
|
|
|
|
// All child nodes should be deselected
|
|
bool childSelected = false;
|
|
foreach (MultiSelectTreeNode node in e.Node.Nodes)
|
|
{
|
|
if (IsNodeSelected(node))
|
|
{
|
|
childSelected = true;
|
|
}
|
|
UnselectNodesRecursively(node, TreeViewAction.Collapse);
|
|
}
|
|
|
|
if (childSelected)
|
|
{
|
|
SelectNode((MultiSelectTreeNode)e.Node, true, TreeViewAction.Collapse);
|
|
}
|
|
|
|
OnSelectionsChanged();
|
|
|
|
base.OnAfterCollapse(e);
|
|
}
|
|
|
|
protected virtual void OnAfterDeselect(TreeViewEventArgs e)
|
|
{
|
|
TreeViewEventHandler handler = AfterDeselect;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnBeforeDeselect(TreeViewEventArgs e)
|
|
{
|
|
TreeViewEventHandler handler = BeforeDeselect;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
protected override void OnBeforeLabelEdit(NodeLabelEditEventArgs e)
|
|
{
|
|
_selectionChanged = false;
|
|
SelectNode((MultiSelectTreeNode)e.Node, true, TreeViewAction.ByMouse);
|
|
UnselectAllNodesExceptNode((MultiSelectTreeNode)e.Node, TreeViewAction.ByMouse);
|
|
OnSelectionsChanged();
|
|
}
|
|
|
|
protected sealed override void OnBeforeSelect(TreeViewCancelEventArgs e)
|
|
{
|
|
e.Cancel = true;
|
|
}
|
|
|
|
protected virtual void OnBeforeSelect(MultiSelectTreeViewCancelEventArgs e)
|
|
{
|
|
EventHandler<MultiSelectTreeViewCancelEventArgs> handler = BeforeSelect;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
protected sealed override void OnItemDrag(ItemDragEventArgs e)
|
|
{
|
|
OnItemDrag(new MultiSelectTreeViewItemDragEventArgs(MouseButtons.Left, SelectedNodes));
|
|
}
|
|
|
|
protected virtual void OnItemDrag(MultiSelectTreeViewItemDragEventArgs e)
|
|
{
|
|
EventHandler<MultiSelectTreeViewItemDragEventArgs> handler = ItemDrag;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
Keys none = Keys.None;
|
|
switch (e.Modifiers)
|
|
{
|
|
case Keys.Shift:
|
|
case Keys.Control:
|
|
case (Keys.Control | Keys.Shift):
|
|
none = Keys.Shift;
|
|
if (_keysStartNode == null)
|
|
{
|
|
_keysStartNode = _mostRecentSelectedNode;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_keysStartNode = null;
|
|
break;
|
|
}
|
|
int intNumber = 0;
|
|
MultiSelectTreeNode endNode = null;
|
|
switch (e.KeyCode)
|
|
{
|
|
case Keys.Prior:
|
|
intNumber = GetNumberOfVisibleNodes();
|
|
endNode = GetNextTreeNode(_mostRecentSelectedNode, false, intNumber);
|
|
break;
|
|
|
|
case Keys.Next:
|
|
intNumber = GetNumberOfVisibleNodes();
|
|
endNode = GetNextTreeNode(_mostRecentSelectedNode, true, intNumber);
|
|
break;
|
|
|
|
case Keys.End:
|
|
endNode = GetLastVisibleNode();
|
|
break;
|
|
|
|
case Keys.Home:
|
|
endNode = Nodes[0];
|
|
break;
|
|
|
|
case Keys.Left:
|
|
if (_mostRecentSelectedNode != null)
|
|
{
|
|
if (_mostRecentSelectedNode.IsExpanded)
|
|
_mostRecentSelectedNode.Collapse();
|
|
else
|
|
endNode = _mostRecentSelectedNode.Parent;
|
|
}
|
|
break;
|
|
|
|
case Keys.Up:
|
|
if (_mostRecentSelectedNode != null)
|
|
endNode = _mostRecentSelectedNode.PrevVisibleNode;
|
|
break;
|
|
|
|
case Keys.Right:
|
|
if (_mostRecentSelectedNode != null)
|
|
{
|
|
if (_mostRecentSelectedNode.IsExpanded)
|
|
{
|
|
endNode = _mostRecentSelectedNode.NextVisibleNode;
|
|
if (endNode != null && !endNode.Parent.Equals(_mostRecentSelectedNode))
|
|
endNode = null;
|
|
}
|
|
else
|
|
_mostRecentSelectedNode.Expand();
|
|
}
|
|
break;
|
|
|
|
case Keys.Down:
|
|
if (_mostRecentSelectedNode != null)
|
|
endNode = _mostRecentSelectedNode.NextVisibleNode;
|
|
break;
|
|
|
|
default:
|
|
base.OnKeyDown(e);
|
|
return;
|
|
}
|
|
if (endNode != null)
|
|
{
|
|
ProcessNodeRange(_keysStartNode, endNode, new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0), none, TreeViewAction.ByKeyboard, false);
|
|
_mostRecentSelectedNode = endNode;
|
|
}
|
|
if (_mostRecentSelectedNode != null)
|
|
{
|
|
MultiSelectTreeNode tnMostRecentSelectedNode = null;
|
|
switch (e.KeyCode)
|
|
{
|
|
case Keys.Prior:
|
|
tnMostRecentSelectedNode = GetNextTreeNode(_mostRecentSelectedNode, false, intNumber - 2);
|
|
break;
|
|
|
|
case Keys.Next:
|
|
tnMostRecentSelectedNode = GetNextTreeNode(_mostRecentSelectedNode, true, intNumber - 2);
|
|
break;
|
|
|
|
case Keys.End:
|
|
case Keys.Home:
|
|
tnMostRecentSelectedNode = _mostRecentSelectedNode;
|
|
break;
|
|
|
|
case Keys.Up:
|
|
tnMostRecentSelectedNode = GetNextTreeNode(_mostRecentSelectedNode, false, 5);
|
|
break;
|
|
|
|
case Keys.Down:
|
|
tnMostRecentSelectedNode = GetNextTreeNode(_mostRecentSelectedNode, true, 5);
|
|
break;
|
|
}
|
|
if (tnMostRecentSelectedNode != null)
|
|
{
|
|
if ((e.KeyData & Keys.Control) != 0 || (e.KeyData & Keys.Shift) != 0)
|
|
{
|
|
SuspendLayout();
|
|
int prevScrollPos = HScrollPos;
|
|
tnMostRecentSelectedNode.EnsureVisible();
|
|
HScrollPos = prevScrollPos;
|
|
ResumeLayout();
|
|
}
|
|
else
|
|
{
|
|
tnMostRecentSelectedNode.EnsureVisible();
|
|
}
|
|
}
|
|
}
|
|
base.OnKeyDown(e);
|
|
}
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
LastMouseDownEventPosition = new Point(e.X, e.Y);
|
|
_keysStartNode = null;
|
|
intMouseClicks = e.Clicks;
|
|
MultiSelectTreeNode nodeAt = (MultiSelectTreeNode)base.GetNodeAt(e.X, e.Y);
|
|
if (nodeAt != null)
|
|
{
|
|
if (!IsPlusMinusClicked(nodeAt, e) && (nodeAt != null) && IsClickOnNode(nodeAt, e) && !IsNodeSelected(nodeAt))
|
|
{
|
|
_nodeProcessedOnMouseDown = true;
|
|
ProcessNodeRange(_mostRecentSelectedNode, nodeAt, e, Control.ModifierKeys, TreeViewAction.ByMouse, true);
|
|
}
|
|
base.OnMouseDown(e);
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
if (!_nodeProcessedOnMouseDown)
|
|
{
|
|
MultiSelectTreeNode nodeAt = (MultiSelectTreeNode)base.GetNodeAt(e.X, e.Y);
|
|
if (IsClickOnNode(nodeAt, e))
|
|
{
|
|
ProcessNodeRange(_mostRecentSelectedNode, nodeAt, e, Control.ModifierKeys, TreeViewAction.ByMouse, true);
|
|
}
|
|
}
|
|
_nodeProcessedOnMouseDown = false;
|
|
base.OnMouseUp(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fires a SelectionsChanged event if the selection has changed.
|
|
/// </summary>
|
|
protected virtual void OnSelectionsChanged()
|
|
{
|
|
if (_selectionChanged)
|
|
{
|
|
EventHandler handler = SelectionsChanged;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces the SelectionsChanged event to fire.
|
|
/// </summary>
|
|
public void ForceSelectionsChanged()
|
|
{
|
|
EventHandler handler = SelectionsChanged;
|
|
|
|
if (handler != null)
|
|
{
|
|
handler(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
/// <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(MultiSelectTreeNode startNode, MultiSelectTreeNode endNode, MouseEventArgs e, Keys keys, TreeViewAction tva, bool allowStartEdit)
|
|
{
|
|
_selectionChanged = false; // prepare for OnSelectionsChanged
|
|
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
_wasDoubleClick = (intMouseClicks == 2);
|
|
|
|
MultiSelectTreeNode tnTemp = null;
|
|
int intNodeLevelStart;
|
|
|
|
if (((keys & Keys.Control) == 0) && ((keys & Keys.Shift) == 0))
|
|
{
|
|
// CTRL and SHIFT not held down
|
|
_selectionMirrorPoint = endNode;
|
|
int intNumberOfSelectedNodes = SelectedNodes.Count;
|
|
|
|
// If it was a double click, select node and suspend further processing
|
|
if (_wasDoubleClick)
|
|
{
|
|
base.OnMouseDown(e);
|
|
return;
|
|
}
|
|
|
|
if (!IsPlusMinusClicked(endNode, e))
|
|
{
|
|
bool blnNodeWasSelected = false;
|
|
if (IsNodeSelected(endNode))
|
|
blnNodeWasSelected = true;
|
|
|
|
|
|
UnselectAllNodesExceptNode(endNode, tva);
|
|
SelectNode(endNode, true, tva);
|
|
|
|
|
|
if ((blnNodeWasSelected) && (LabelEdit) && (allowStartEdit) && (!_wasDoubleClick) && (intNumberOfSelectedNodes <= 1))
|
|
{
|
|
// Node should be put in edit mode
|
|
_nodeToStartEditOn = endNode;
|
|
System.Threading.ThreadPool.QueueUserWorkItem(StartEdit);
|
|
}
|
|
}
|
|
}
|
|
else if (((keys & Keys.Control) != 0) && ((keys & Keys.Shift) == 0))
|
|
{
|
|
// CTRL held down
|
|
_selectionMirrorPoint = null;
|
|
|
|
if (!IsNodeSelected(endNode))
|
|
{
|
|
switch (_selectionMode)
|
|
{
|
|
case TreeViewSelectionMode.SingleSelect:
|
|
UnselectAllNodesExceptNode(endNode, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameRootBranch:
|
|
MultiSelectTreeNode tnAbsoluteParent2 = GetRootParent(endNode);
|
|
UnselectAllNodesNotBelongingToParent(tnAbsoluteParent2, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameLevel:
|
|
UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch:
|
|
MultiSelectTreeNode tnAbsoluteParent = GetRootParent(endNode);
|
|
UnselectAllNodesNotBelongingToParent(tnAbsoluteParent, tva);
|
|
UnselectAllNodesNotBelongingToLevel(GetNodeLevel(endNode), tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameParent:
|
|
MultiSelectTreeNode 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 (_selectionMirrorPoint == null)
|
|
{
|
|
_selectionMirrorPoint = startNode;
|
|
}
|
|
|
|
switch (_selectionMode)
|
|
{
|
|
case TreeViewSelectionMode.SingleSelect:
|
|
UnselectAllNodesExceptNode(endNode, tva);
|
|
SelectNode(endNode, true, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameRootBranch:
|
|
MultiSelectTreeNode 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)
|
|
{
|
|
MultiSelectTreeNode tnAbsoluteParent = GetRootParent(tnTemp);
|
|
if (tnAbsoluteParent == tnAbsoluteParentStartNode)
|
|
{
|
|
SelectNode(tnTemp, true, tva);
|
|
}
|
|
}
|
|
}
|
|
UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStartNode, tva);
|
|
UnselectNodesOutsideRange(_selectionMirrorPoint, 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(_selectionMirrorPoint, endNode, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameLevelAndRootBranch:
|
|
MultiSelectTreeNode 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);
|
|
MultiSelectTreeNode tnAbsoluteParent = GetRootParent(tnTemp);
|
|
if ((intNodeLevel == intNodeLevelStart) && (tnAbsoluteParent == tnAbsoluteParentStart))
|
|
{
|
|
SelectNode(tnTemp, true, tva);
|
|
}
|
|
}
|
|
}
|
|
UnselectAllNodesNotBelongingToParent(tnAbsoluteParentStart, tva);
|
|
UnselectAllNodesNotBelongingToLevel(intNodeLevelStart, tva);
|
|
UnselectNodesOutsideRange(_selectionMirrorPoint, endNode, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelect:
|
|
SelectNodesInsideRange(_selectionMirrorPoint, endNode, tva);
|
|
UnselectNodesOutsideRange(_selectionMirrorPoint, endNode, tva);
|
|
break;
|
|
|
|
case TreeViewSelectionMode.MultiSelectSameParent:
|
|
MultiSelectTreeNode 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)
|
|
{
|
|
MultiSelectTreeNode tnParent = tnTemp.Parent;
|
|
if (tnParent == tnParentStartNode)
|
|
{
|
|
SelectNode(tnTemp, true, tva);
|
|
}
|
|
}
|
|
}
|
|
UnselectAllNodesNotBelongingDirectlyToParent(tnParentStartNode, tva);
|
|
UnselectNodesOutsideRange(_selectionMirrorPoint, 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:
|
|
MultiSelectTreeNode 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)
|
|
{
|
|
MultiSelectTreeNode 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:
|
|
MultiSelectTreeNode 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);
|
|
MultiSelectTreeNode 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:
|
|
MultiSelectTreeNode 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)
|
|
{
|
|
MultiSelectTreeNode 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();
|
|
}
|
|
|
|
private bool SelectNode(MultiSelectTreeNode node, bool select, TreeViewAction tva)
|
|
{
|
|
bool selected = false;
|
|
if (node == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (select)
|
|
{
|
|
if (!IsNodeSelected(node))
|
|
{
|
|
MultiSelectTreeViewCancelEventArgs e = new MultiSelectTreeViewCancelEventArgs(node, false, tva);
|
|
OnBeforeSelect(e);
|
|
if (e.Cancel)
|
|
{
|
|
return false;
|
|
}
|
|
_selectedNodes.Add(node);
|
|
selected = true;
|
|
_selectionChanged = true;
|
|
OnAfterSelect(new TreeViewEventArgs(node, tva));
|
|
}
|
|
_mostRecentSelectedNode = node;
|
|
return selected;
|
|
}
|
|
if (IsNodeSelected(node))
|
|
{
|
|
OnBeforeDeselect(new TreeViewEventArgs(node));
|
|
_selectedNodes.Remove(node);
|
|
_selectionChanged = true;
|
|
|
|
OnAfterDeselect(new TreeViewEventArgs(node));
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
private void SelectNodesInsideRange(MultiSelectTreeNode startNode, MultiSelectTreeNode endNode, TreeViewAction tva)
|
|
{
|
|
if (startNode != null && endNode != null)
|
|
{
|
|
MultiSelectTreeNode firstNode = null;
|
|
MultiSelectTreeNode lastNode = null;
|
|
if (startNode.Bounds.Y < endNode.Bounds.Y)
|
|
{
|
|
firstNode = startNode;
|
|
lastNode = endNode;
|
|
}
|
|
else
|
|
{
|
|
firstNode = endNode;
|
|
lastNode = startNode;
|
|
}
|
|
SelectNode(firstNode, true, tva);
|
|
MultiSelectTreeNode nextVisibleNode = firstNode;
|
|
while (nextVisibleNode != lastNode && nextVisibleNode != null)
|
|
{
|
|
nextVisibleNode = nextVisibleNode.NextVisibleNode;
|
|
if (nextVisibleNode != null)
|
|
{
|
|
SelectNode(nextVisibleNode, true, tva);
|
|
}
|
|
}
|
|
SelectNode(lastNode, true, tva);
|
|
}
|
|
}
|
|
|
|
private void StartEdit(object state)
|
|
{
|
|
Thread.Sleep(200);
|
|
if (!_wasDoubleClick)
|
|
{
|
|
base.SelectedNode = _nodeToStartEditOn;
|
|
_nodeToStartEditOn.BeginEdit();
|
|
}
|
|
else
|
|
{
|
|
_wasDoubleClick = false;
|
|
}
|
|
}
|
|
|
|
private void UnselectAllNodes(TreeViewAction tva)
|
|
{
|
|
UnselectAllNodesExceptNode(null, tva);
|
|
}
|
|
|
|
private void UnselectAllNodesExceptNode(MultiSelectTreeNode nodeKeepSelected, TreeViewAction tva)
|
|
{
|
|
List<MultiSelectTreeNode> list = new List<MultiSelectTreeNode>();
|
|
|
|
foreach (MultiSelectTreeNode node in _selectedNodes)
|
|
{
|
|
if (nodeKeepSelected == null)
|
|
{
|
|
list.Add(node);
|
|
}
|
|
else if ((nodeKeepSelected != null) && (node != nodeKeepSelected))
|
|
{
|
|
list.Add(node);
|
|
}
|
|
}
|
|
foreach (MultiSelectTreeNode node2 in list)
|
|
{
|
|
SelectNode(node2, false, tva);
|
|
}
|
|
}
|
|
|
|
private void UnselectAllNodesNotBelongingDirectlyToParent(MultiSelectTreeNode parent, TreeViewAction tva)
|
|
{
|
|
ArrayList list = new ArrayList();
|
|
foreach (MultiSelectTreeNode node in _selectedNodes)
|
|
{
|
|
if (node.Parent != parent)
|
|
{
|
|
list.Add(node);
|
|
}
|
|
}
|
|
foreach (MultiSelectTreeNode node2 in list)
|
|
{
|
|
SelectNode(node2, false, tva);
|
|
}
|
|
}
|
|
|
|
private void UnselectAllNodesNotBelongingToLevel(int level, TreeViewAction tva)
|
|
{
|
|
ArrayList list = new ArrayList();
|
|
foreach (MultiSelectTreeNode node in _selectedNodes)
|
|
{
|
|
if (GetNodeLevel(node) != level)
|
|
{
|
|
list.Add(node);
|
|
}
|
|
}
|
|
foreach (MultiSelectTreeNode node2 in list)
|
|
{
|
|
SelectNode(node2, false, tva);
|
|
}
|
|
}
|
|
|
|
private void UnselectAllNodesNotBelongingToParent(MultiSelectTreeNode parent, TreeViewAction tva)
|
|
{
|
|
ArrayList list = new ArrayList();
|
|
foreach (MultiSelectTreeNode node in _selectedNodes)
|
|
{
|
|
if (!IsChildOf(node, parent))
|
|
{
|
|
list.Add(node);
|
|
}
|
|
}
|
|
foreach (MultiSelectTreeNode node2 in list)
|
|
{
|
|
SelectNode(node2, false, tva);
|
|
}
|
|
}
|
|
|
|
private void UnselectNodesOutsideRange(MultiSelectTreeNode startNode, MultiSelectTreeNode endNode, TreeViewAction tva)
|
|
{
|
|
if (startNode != null && endNode != null)
|
|
{
|
|
MultiSelectTreeNode node = null;
|
|
MultiSelectTreeNode node2 = null;
|
|
if (startNode.Bounds.Y < endNode.Bounds.Y)
|
|
{
|
|
node = startNode;
|
|
node2 = endNode;
|
|
}
|
|
else
|
|
{
|
|
node = endNode;
|
|
node2 = startNode;
|
|
}
|
|
MultiSelectTreeNode tn = node;
|
|
while (tn != null)
|
|
{
|
|
tn = tn.PrevVisibleNode;
|
|
if (tn != null)
|
|
{
|
|
SelectNode(tn, false, tva);
|
|
}
|
|
}
|
|
tn = node2;
|
|
while (tn != null)
|
|
{
|
|
tn = tn.NextVisibleNode;
|
|
if (tn != null)
|
|
{
|
|
SelectNode(tn, false, tva);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UnselectNodesRecursively(MultiSelectTreeNode tn, TreeViewAction tva)
|
|
{
|
|
SelectNode(tn, false, tva);
|
|
|
|
foreach (MultiSelectTreeNode node in tn.Nodes)
|
|
{
|
|
UnselectNodesRecursively(node, tva);
|
|
}
|
|
}
|
|
|
|
public new MultiSelectTreeNode SelectedNode
|
|
{
|
|
get
|
|
{
|
|
if (SelectedNodes.Count > 0)
|
|
{
|
|
return SelectedNodes[0];
|
|
}
|
|
return null;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
if (SelectedNode != null)
|
|
{
|
|
SelectedNodes.Clear();
|
|
}
|
|
}
|
|
else if (SelectedNodes.Count == 1)
|
|
{
|
|
if (SelectedNode != value)
|
|
{
|
|
SelectedNodes.SetContents(new MultiSelectTreeNode[] { value });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SelectedNodes.SetContents(new MultiSelectTreeNode[] { value });
|
|
}
|
|
}
|
|
}
|
|
|
|
public MultiSelectTreeSelectedNodeCollection SelectedNodes
|
|
{
|
|
get
|
|
{
|
|
return _selectedNodesWrapper;
|
|
}
|
|
}
|
|
|
|
[DefaultValue(TreeViewSelectionMode.SingleSelect)]
|
|
public TreeViewSelectionMode SelectionMode
|
|
{
|
|
get
|
|
{
|
|
return _selectionMode;
|
|
}
|
|
set
|
|
{
|
|
_selectionMode = value;
|
|
}
|
|
}
|
|
|
|
protected override void OnEnabledChanged(EventArgs e)
|
|
{
|
|
base.OnEnabledChanged(e);
|
|
SetEnabled(Nodes, Enabled);
|
|
}
|
|
|
|
private void SetEnabled(MultiSelectTreeNodeCollection nodes, bool enabled)
|
|
{
|
|
foreach (MultiSelectTreeNode node in nodes)
|
|
{
|
|
((IMultiSelectTreeNode)node).SetEnabled(enabled);
|
|
SetEnabled(node.Nodes, enabled);
|
|
}
|
|
}
|
|
|
|
private int ScrollInfo(Win32.ScrollBarConstants fnBar)
|
|
{
|
|
Win32.ScrollInfo si = new Win32.ScrollInfo();
|
|
si.cbSize = (uint)Marshal.SizeOf(si);
|
|
si.fMask = (int)Win32.ScrollInfoMask.SIF_POS;
|
|
if (!Win32.GetScrollInfo(Handle, (int)fnBar, ref si))
|
|
return 0;
|
|
|
|
return si.nPos;
|
|
}
|
|
|
|
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
|
|
public int HScrollPos
|
|
{
|
|
get
|
|
{
|
|
return ScrollInfo(Win32.ScrollBarConstants.SB_HORZ);
|
|
}
|
|
set
|
|
{
|
|
Win32.SendMessage(Handle, Win32.WM_HSCROLL, (IntPtr)(((int)Win32.ScrollBarCommands.SB_THUMBPOSITION) | (value << 16)), (IntPtr)0);
|
|
}
|
|
}
|
|
|
|
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
|
|
public int VScrollPos
|
|
{
|
|
get
|
|
{
|
|
return ScrollInfo(Win32.ScrollBarConstants.SB_VERT);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterates through every node of this <see cref="MultiSelectTreeView"/>.
|
|
/// </summary>
|
|
public IEnumerable<MultiSelectTreeNode> AllNodes
|
|
{
|
|
get
|
|
{
|
|
foreach (MultiSelectTreeNode node in Nodes)
|
|
{
|
|
yield return node;
|
|
|
|
foreach (MultiSelectTreeNode n in node.Descendants)
|
|
{
|
|
yield return n;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnHandleCreated(EventArgs e)
|
|
{
|
|
base.OnHandleCreated(e);
|
|
|
|
foreach (MultiSelectTreeNode node in AllNodes)
|
|
{
|
|
UpdateCheckboxVisibility(node);
|
|
}
|
|
}
|
|
|
|
public void UpdateCheckboxVisibility(MultiSelectTreeNode treeNode)
|
|
{
|
|
if (CheckBoxes && treeNode.Handle != IntPtr.Zero && Handle != IntPtr.Zero)
|
|
{
|
|
NativeMethods.TVITEM tvItem = new NativeMethods.TVITEM();
|
|
tvItem.hItem = treeNode.Handle;
|
|
tvItem.mask = NativeMethods.TVIF_STATE;
|
|
tvItem.stateMask = NativeMethods.TVIS_STATEIMAGEMASK;
|
|
tvItem.state = 0;
|
|
|
|
if (treeNode.ShowCheckBox && treeNode.Checked)
|
|
{
|
|
tvItem.state = 2 << 12;
|
|
}
|
|
else if (treeNode.ShowCheckBox)
|
|
{
|
|
tvItem.state = 1 << 12;
|
|
}
|
|
|
|
IntPtr lparam = Marshal.AllocHGlobal(Marshal.SizeOf(tvItem));
|
|
Marshal.StructureToPtr(tvItem, lparam, false);
|
|
Win32.SendMessage(Handle, NativeMethods.TVM_SETITEM, IntPtr.Zero, lparam);
|
|
}
|
|
}
|
|
|
|
#region NativeMethods class
|
|
|
|
private class NativeMethods
|
|
{
|
|
public const int TVIF_STATE = 0x8;
|
|
public const int TVIS_STATEIMAGEMASK = 0xF000;
|
|
public const int TV_FIRST = 0x1100;
|
|
public const int TVM_SETITEM = TV_FIRST + 63;
|
|
|
|
public struct TVITEM
|
|
{
|
|
#pragma warning disable 0649, 414, 169
|
|
public int mask;
|
|
public IntPtr hItem;
|
|
public int state;
|
|
public int stateMask;
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
public String lpszText;
|
|
public int cchTextMax;
|
|
public int iImage;
|
|
public int iSelectedImage;
|
|
public int cChildren;
|
|
public IntPtr lParam;
|
|
#pragma warning restore 0649, 414, 169
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|