Created
October 15, 2013 11:47
-
-
Save Larry57/6990337 to your computer and use it in GitHub Desktop.
Multi select TreeView found on Code Project - http://www.codeproject.com/Articles/20581/Multiselect-Treeview-Implementation.
This nice little piece of work requires some attention.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Windows.Forms; | |
namespace Graph.UI.Tools | |
{ | |
public class MultiSelectTreeview : TreeView | |
{ | |
#region Selected Node(s) Properties | |
private List<TreeNode> m_SelectedNodes = null; | |
public List<TreeNode> SelectedNodes | |
{ | |
get | |
{ | |
return m_SelectedNodes; | |
} | |
set | |
{ | |
ClearSelectedNodes(); | |
if (value != null) | |
{ | |
foreach (TreeNode node in value) | |
{ | |
ToggleNode(node, true); | |
} | |
} | |
} | |
} | |
// Note we use the new keyword to Hide the native treeview's SelectedNode property. | |
private TreeNode m_SelectedNode; | |
public new TreeNode SelectedNode | |
{ | |
get { return m_SelectedNode; } | |
set | |
{ | |
ClearSelectedNodes(); | |
if (value != null) | |
{ | |
SelectNode(value); | |
} | |
} | |
} | |
#endregion | |
public MultiSelectTreeview() | |
{ | |
m_SelectedNodes = new List<TreeNode>(); | |
base.SelectedNode = null; | |
} | |
#region Overridden Events | |
protected override void OnGotFocus(EventArgs e) | |
{ | |
// Make sure at least one node has a selection | |
// this way we can tab to the ctrl and use the | |
// keyboard to select nodes | |
try | |
{ | |
if (m_SelectedNode == null && this.TopNode != null) | |
{ | |
ToggleNode(this.TopNode, true); | |
} | |
base.OnGotFocus(e); | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnMouseDown(MouseEventArgs e) | |
{ | |
// If the user clicks on a node that was not | |
// previously selected, select it now. | |
try | |
{ | |
base.SelectedNode = null; | |
TreeNode node = this.GetNodeAt(e.Location); | |
if (node != null) | |
{ | |
int leftBound = node.Bounds.X; // - 20; // Allow user to click on image | |
int rightBound = node.Bounds.Right + 10; // Give a little extra room | |
if (e.Location.X > leftBound && e.Location.X < rightBound) | |
{ | |
if (ModifierKeys == Keys.None && (m_SelectedNodes.Contains(node))) | |
{ | |
// Potential Drag Operation | |
// Let Mouse Up do select | |
} | |
else | |
{ | |
SelectNode(node); | |
} | |
} | |
} | |
base.OnMouseDown(e); | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnMouseUp(MouseEventArgs e) | |
{ | |
// If the clicked on a node that WAS previously | |
// selected then, reselect it now. This will clear | |
// any other selected nodes. e.g. A B C D are selected | |
// the user clicks on B, now A C & D are no longer selected. | |
try | |
{ | |
// Check to see if a node was clicked on | |
TreeNode node = this.GetNodeAt(e.Location); | |
if (node != null) | |
{ | |
if (ModifierKeys == Keys.None && m_SelectedNodes.Contains(node)) | |
{ | |
int leftBound = node.Bounds.X; // -20; // Allow user to click on image | |
int rightBound = node.Bounds.Right + 10; // Give a little extra room | |
if (e.Location.X > leftBound && e.Location.X < rightBound) | |
{ | |
SelectNode(node); | |
} | |
} | |
} | |
base.OnMouseUp(e); | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnItemDrag(ItemDragEventArgs e) | |
{ | |
// If the user drags a node and the node being dragged is NOT | |
// selected, then clear the active selection, select the | |
// node being dragged and drag it. Otherwise if the node being | |
// dragged is selected, drag the entire selection. | |
try | |
{ | |
TreeNode node = e.Item as TreeNode; | |
if (node != null) | |
{ | |
if (!m_SelectedNodes.Contains(node)) | |
{ | |
SelectSingleNode(node); | |
ToggleNode(node, true); | |
} | |
} | |
base.OnItemDrag(e); | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnBeforeSelect(TreeViewCancelEventArgs e) | |
{ | |
// Never allow base.SelectedNode to be set! | |
try | |
{ | |
base.SelectedNode = null; | |
e.Cancel = true; | |
base.OnBeforeSelect(e); | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnAfterSelect(TreeViewEventArgs e) | |
{ | |
// Never allow base.SelectedNode to be set! | |
try | |
{ | |
base.OnAfterSelect(e); | |
base.SelectedNode = null; | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
} | |
protected override void OnKeyDown(KeyEventArgs e) | |
{ | |
// Handle all possible key strokes for the control. | |
// including navigation, selection, etc. | |
base.OnKeyDown(e); | |
if (e.KeyCode == Keys.ShiftKey) | |
return; | |
//this.BeginUpdate(); | |
bool bShift = (ModifierKeys == Keys.Shift); | |
try | |
{ | |
// Nothing is selected in the tree, this isn't a good state | |
// select the top node | |
if (m_SelectedNode == null && this.TopNode != null) | |
{ | |
ToggleNode(this.TopNode, true); | |
} | |
// Nothing is still selected in the tree, this isn't a good state, leave. | |
if (m_SelectedNode == null) | |
return; | |
if (e.KeyCode == Keys.Left) | |
{ | |
if (m_SelectedNode.IsExpanded && m_SelectedNode.Nodes.Count > 0) | |
{ | |
// Collapse an expanded node that has children | |
m_SelectedNode.Collapse(); | |
} | |
else if (m_SelectedNode.Parent != null) | |
{ | |
// Node is already collapsed, try to select its parent. | |
SelectSingleNode(m_SelectedNode.Parent); | |
} | |
} | |
else if (e.KeyCode == Keys.Right) | |
{ | |
if (!m_SelectedNode.IsExpanded) | |
{ | |
// Expand a collpased node's children | |
m_SelectedNode.Expand(); | |
} | |
else | |
{ | |
// Node was already expanded, select the first child | |
SelectSingleNode(m_SelectedNode.FirstNode); | |
} | |
} | |
else if (e.KeyCode == Keys.Up) | |
{ | |
// Select the previous node | |
if (m_SelectedNode.PrevVisibleNode != null) | |
{ | |
SelectNode(m_SelectedNode.PrevVisibleNode); | |
} | |
} | |
else if (e.KeyCode == Keys.Down) | |
{ | |
// Select the next node | |
if (m_SelectedNode.NextVisibleNode != null) | |
{ | |
SelectNode(m_SelectedNode.NextVisibleNode); | |
} | |
} | |
else if (e.KeyCode == Keys.Home) | |
{ | |
if (bShift) | |
{ | |
if (m_SelectedNode.Parent == null) | |
{ | |
// Select all of the root nodes up to this point | |
if (this.Nodes.Count > 0) | |
{ | |
SelectNode(this.Nodes[0]); | |
} | |
} | |
else | |
{ | |
// Select all of the nodes up to this point under this nodes parent | |
SelectNode(m_SelectedNode.Parent.FirstNode); | |
} | |
} | |
else | |
{ | |
// Select this first node in the tree | |
if (this.Nodes.Count > 0) | |
{ | |
SelectSingleNode(this.Nodes[0]); | |
} | |
} | |
} | |
else if (e.KeyCode == Keys.End) | |
{ | |
if (bShift) | |
{ | |
if (m_SelectedNode.Parent == null) | |
{ | |
// Select the last ROOT node in the tree | |
if (this.Nodes.Count > 0) | |
{ | |
SelectNode(this.Nodes[this.Nodes.Count - 1]); | |
} | |
} | |
else | |
{ | |
// Select the last node in this branch | |
SelectNode(m_SelectedNode.Parent.LastNode); | |
} | |
} | |
else | |
{ | |
if (this.Nodes.Count > 0) | |
{ | |
// Select the last node visible node in the tree. | |
// Don't expand branches incase the tree is virtual | |
TreeNode ndLast = this.Nodes[0].LastNode; | |
while (ndLast.IsExpanded && (ndLast.LastNode != null)) | |
{ | |
ndLast = ndLast.LastNode; | |
} | |
SelectSingleNode(ndLast); | |
} | |
} | |
} | |
else if (e.KeyCode == Keys.PageUp) | |
{ | |
// Select the highest node in the display | |
int nCount = this.VisibleCount; | |
TreeNode ndCurrent = m_SelectedNode; | |
while ((nCount) > 0 && (ndCurrent.PrevVisibleNode != null)) | |
{ | |
ndCurrent = ndCurrent.PrevVisibleNode; | |
nCount--; | |
} | |
SelectSingleNode(ndCurrent); | |
} | |
else if (e.KeyCode == Keys.PageDown) | |
{ | |
// Select the lowest node in the display | |
int nCount = this.VisibleCount; | |
TreeNode ndCurrent = m_SelectedNode; | |
while ((nCount) > 0 && (ndCurrent.NextVisibleNode != null)) | |
{ | |
ndCurrent = ndCurrent.NextVisibleNode; | |
nCount--; | |
} | |
SelectSingleNode(ndCurrent); | |
} | |
else | |
{ | |
// Assume this is a search character a-z, A-Z, 0-9, etc. | |
// Select the first node after the current node that | |
// starts with this character | |
string sSearch = ((char)e.KeyValue).ToString(); | |
TreeNode ndCurrent = m_SelectedNode; | |
while ((ndCurrent.NextVisibleNode != null)) | |
{ | |
ndCurrent = ndCurrent.NextVisibleNode; | |
if (ndCurrent.Text.StartsWith(sSearch)) | |
{ | |
SelectSingleNode(ndCurrent); | |
break; | |
} | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
HandleException(ex); | |
} | |
finally | |
{ | |
this.EndUpdate(); | |
} | |
} | |
#endregion | |
#region Helper Methods | |
private void SelectNode(TreeNode node) | |
{ | |
try | |
{ | |
this.BeginUpdate(); | |
if (m_SelectedNode == null || ModifierKeys == Keys.Control) | |
{ | |
// Ctrl+Click selects an unselected node, or unselects a selected node. | |
bool bIsSelected = m_SelectedNodes.Contains(node); | |
ToggleNode(node, !bIsSelected); | |
} | |
else if (ModifierKeys == Keys.Shift) | |
{ | |
// Shift+Click selects nodes between the selected node and here. | |
TreeNode ndStart = m_SelectedNode; | |
TreeNode ndEnd = node; | |
if (ndStart.Parent == ndEnd.Parent) | |
{ | |
// Selected node and clicked node have same parent, easy case. | |
if (ndStart.Index < ndEnd.Index) | |
{ | |
// If the selected node is beneath the clicked node walk down | |
// selecting each Visible node until we reach the end. | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.NextVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
else if (ndStart.Index == ndEnd.Index) | |
{ | |
// Clicked same node, do nothing | |
} | |
else | |
{ | |
// If the selected node is above the clicked node walk up | |
// selecting each Visible node until we reach the end. | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.PrevVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
} | |
else | |
{ | |
// Selected node and clicked node have same parent, hard case. | |
// We need to find a common parent to determine if we need | |
// to walk down selecting, or walk up selecting. | |
TreeNode ndStartP = ndStart; | |
TreeNode ndEndP = ndEnd; | |
int startDepth = Math.Min(ndStartP.Level, ndEndP.Level); | |
// Bring lower node up to common depth | |
while (ndStartP.Level > startDepth) | |
{ | |
ndStartP = ndStartP.Parent; | |
} | |
// Bring lower node up to common depth | |
while (ndEndP.Level > startDepth) | |
{ | |
ndEndP = ndEndP.Parent; | |
} | |
// Walk up the tree until we find the common parent | |
while (ndStartP.Parent != ndEndP.Parent) | |
{ | |
ndStartP = ndStartP.Parent; | |
ndEndP = ndEndP.Parent; | |
} | |
// Select the node | |
if (ndStartP.Index < ndEndP.Index) | |
{ | |
// If the selected node is beneath the clicked node walk down | |
// selecting each Visible node until we reach the end. | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.NextVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
else if (ndStartP.Index == ndEndP.Index) | |
{ | |
if (ndStart.Level < ndEnd.Level) | |
{ | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.NextVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
else | |
{ | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.PrevVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
} | |
else | |
{ | |
// If the selected node is above the clicked node walk up | |
// selecting each Visible node until we reach the end. | |
while (ndStart != ndEnd) | |
{ | |
ndStart = ndStart.PrevVisibleNode; | |
if (ndStart == null) | |
break; | |
ToggleNode(ndStart, true); | |
} | |
} | |
} | |
} | |
else | |
{ | |
// Just clicked a node, select it | |
SelectSingleNode(node); | |
} | |
OnAfterSelect(new TreeViewEventArgs(m_SelectedNode)); | |
} | |
finally | |
{ | |
this.EndUpdate(); | |
} | |
} | |
private void ClearSelectedNodes() | |
{ | |
try | |
{ | |
foreach (TreeNode node in m_SelectedNodes) | |
{ | |
node.BackColor = this.BackColor; | |
node.ForeColor = this.ForeColor; | |
} | |
} | |
finally | |
{ | |
m_SelectedNodes.Clear(); | |
m_SelectedNode = null; | |
} | |
} | |
private void SelectSingleNode(TreeNode node) | |
{ | |
if (node == null) | |
{ | |
return; | |
} | |
ClearSelectedNodes(); | |
ToggleNode(node, true); | |
node.EnsureVisible(); | |
} | |
private void ToggleNode(TreeNode node, bool bSelectNode) | |
{ | |
if (bSelectNode) | |
{ | |
m_SelectedNode = node; | |
if (!m_SelectedNodes.Contains(node)) | |
m_SelectedNodes.Add(node); | |
node.BackColor = SystemColors.Highlight; | |
node.ForeColor = SystemColors.HighlightText; | |
} | |
else | |
{ | |
m_SelectedNodes.Remove(node); | |
node.BackColor = this.BackColor; | |
node.ForeColor = this.ForeColor; | |
} | |
} | |
private void HandleException(Exception ex) | |
{ | |
// Perform some error handling here. | |
// We don't want to bubble errors to the CLR. | |
MessageBox.Show(ex.Message); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment