/* 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 { /// /// Gets a complete collection of nodes owned. /// 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 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; } /// /// Occurs after a node is collapsed. /// /// 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 handler = BeforeSelect; if (handler != null) { handler(this, e); } } public new event EventHandler BeforeSelect; protected sealed override void OnItemDrag(ItemDragEventArgs e) { OnItemDrag(new MultiSelectTreeViewItemDragEventArgs(MouseButtons.Left, SelectedNodes)); } protected virtual void OnItemDrag(MultiSelectTreeViewItemDragEventArgs e) { EventHandler handler = ItemDrag; if (handler != null) { handler(this, e); } } public new event EventHandler ItemDrag; 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.IsExpanded) _mostRecentSelectedNode.Collapse(); else endNode = _mostRecentSelectedNode.Parent; break; case Keys.Up: endNode = _mostRecentSelectedNode.PrevVisibleNode; break; case Keys.Right: if (_mostRecentSelectedNode.IsExpanded) { endNode = _mostRecentSelectedNode.NextVisibleNode; if (endNode != null && !endNode.Parent.Equals(_mostRecentSelectedNode)) endNode = null; } else _mostRecentSelectedNode.Expand(); break; case Keys.Down: 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); } /// /// Fires a SelectionsChanged event if the selection has changed. /// protected virtual void OnSelectionsChanged() { if (_selectionChanged) { EventHandler handler = SelectionsChanged; if (handler != null) { handler(this, EventArgs.Empty); } } } /// /// Forces the SelectionsChanged event to fire. /// public void ForceSelectionsChanged() { EventHandler handler = SelectionsChanged; if (handler != null) { handler(this, EventArgs.Empty); } } /// /// 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(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 list = new List(); 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; } 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); } } public int VScrollPos { get { return ScrollInfo(Win32.ScrollBarConstants.SB_VERT); } } /// /// Iterates through every node of this . /// public IEnumerable 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 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 } } #endregion } }