// Copyright (C) Stichting Deltares 2016. All rights preserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights preserved.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Core.Common.Base;
using Core.Common.Controls.TreeView.Properties;
using log4net;
using BaseResources = Core.Common.Base.Properties.Resources;
namespace Core.Common.Controls.TreeView
{
public class TreeViewController : IDisposable
{
private readonly TreeView treeView;
private readonly ICollection treeNodeInfos = new HashSet();
private readonly Dictionary tagTypeTreeNodeInfoLookup = new Dictionary();
private readonly int maximumTextLength = 259;
private readonly Dictionary treeNodeObserverLookup = new Dictionary();
private object data;
private int dropAtLocation;
private Point lastDragOverPoint;
private PlaceholderLocation lastPlaceholderLocation;
private TreeNode nodeDropTarget;
private TreeNode lastPlaceholderNode;
private Graphics placeHolderGraphics;
private static readonly ILog Log = LogManager.GetLogger(typeof(TreeViewController));
public event EventHandler TreeNodeDoubleClick;
public event EventHandler NodeUpdated; // TODO; Way to explicit!
public event EventHandler NodeDataDeleted; // TODO; Way to explicit!
public TreeViewController(TreeView treeView)
{
if (treeView == null)
{
throw new ArgumentException(Resources.TreeViewController_TreeViewController_Tree_view_can_t_be_null);
}
this.treeView = treeView;
// Ensure tree nodes are correctly aligned
treeView.ImageList = new ImageList
{
ColorDepth = ColorDepth.Depth32Bit
};
treeView.BeforeLabelEdit += TreeViewBeforeLabelEdit;
treeView.AfterLabelEdit += TreeViewAfterLabelEdit;
treeView.AfterCheck += TreeViewAfterCheck;
treeView.KeyDown += TreeViewKeyDown;
treeView.MouseClick += TreeViewMouseClick;
treeView.DoubleClick += TreeViewDoubleClick;
treeView.DragDrop += TreeViewDragDrop;
treeView.DragOver += TreeViewDragOver;
treeView.ItemDrag += TreeViewItemDrag;
treeView.DragLeave += TreeViewDragLeave;
treeView.DrawNode += TreeViewDrawNode;
}
///
/// Gets or sets the data to render in the tree view.
///
public object Data
{
get
{
return data;
}
set
{
RemoveAllNodes();
data = value;
if (data == null)
{
return;
}
AddRootNode();
treeView.SelectedNode = treeView.Nodes.Count > 0 ? treeView.Nodes[0] : null;
}
}
public IEnumerable TreeNodeInfos
{
get
{
return treeNodeInfos;
}
}
///
/// This method registers the provided .
///
/// The to register.
public void RegisterTreeNodeInfo(TreeNodeInfo treeNodeInfo)
{
treeNodeInfos.Add(treeNodeInfo);
tagTypeTreeNodeInfoLookup[treeNodeInfo.TagType] = treeNodeInfo;
}
public void DeleteNode(TreeNode selectedNode, TreeNodeInfo treeNodeInfo)
{
var message = string.Format(Resources.TreeView_DeleteNodeData_Are_you_sure_you_want_to_delete_the_following_item_0_, selectedNode.Text);
if (MessageBox.Show(message, BaseResources.Confirm, MessageBoxButtons.OKCancel) != DialogResult.OK)
{
return;
}
if (treeNodeInfo.OnNodeRemoved != null)
{
treeNodeInfo.OnNodeRemoved(selectedNode.Tag, selectedNode.Parent != null ? selectedNode.Parent.Tag : null);
}
OnNodeDataDeleted(selectedNode);
}
public void CollapseAll(TreeNode node)
{
node.Collapse();
foreach (var childNode in node.Nodes.OfType())
{
CollapseAll(childNode);
}
}
public void ExpandAll(TreeNode node)
{
node.Expand();
foreach (var childNode in node.Nodes.OfType())
{
ExpandAll(childNode);
}
}
///
/// This method searches all nodes in the for a node with a matching tag.
///
/// The node data to search the corresponding for.
/// The corresponding the provided node data or null if not found.
public TreeNode GetNodeByTag(object nodeData)
{
return treeView.Nodes.Count > 0 ? GetNodeByTag(treeView.Nodes[0], nodeData) : null;
}
private static TreeNode GetNodeByTag(TreeNode rootNode, object tag)
{
if (Equals(rootNode.Tag, tag))
{
return rootNode;
}
return rootNode.Nodes
.OfType()
.Select(n => GetNodeByTag(n, tag))
.FirstOrDefault(node => node != null);
}
///
/// This method updates the provided .
///
/// The to update.
/// Thrown when no corresponding can be found for the provided .
private void UpdateNode(TreeNode treeNode)
{
var treeNodeInfo = GetTreeNodeInfoForData(treeNode.Tag);
if (treeNodeInfo == null)
{
throw new InvalidOperationException("No tree node info registered");
}
// First of all refresh the child nodes as the other logic below might depend on the presence of child nodes
RefreshChildNodes(treeNode, treeNodeInfo);
if (treeNodeInfo.Text != null)
{
var text = treeNodeInfo.Text(treeNode.Tag);
// Having very big strings causes rendering problems in the tree view
treeNode.Text = text.Length > maximumTextLength
? text.Substring(0, maximumTextLength)
: text;
}
else
{
treeNode.Text = "";
}
treeNode.ForeColor = treeNodeInfo.ForeColor != null
? treeNodeInfo.ForeColor(treeNode.Tag)
: Color.FromKnownColor(KnownColor.ControlText);
if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(treeNode.Tag)
&& treeNodeInfo.IsChecked != null)
{
if (treeNode.Checked != treeNodeInfo.IsChecked(treeNode.Tag))
{
treeView.AfterCheck -= TreeViewAfterCheck;
treeNode.Checked = !treeNode.Checked;
treeView.AfterCheck += TreeViewAfterCheck;
}
treeNode.StateImageIndex = treeNode.Checked ? 1 : 0;
}
OnNodeUpdated(treeNode);
}
private void AddRootNode()
{
var rootNode = new TreeNode
{
Tag = data
};
UpdateNode(rootNode);
if (rootNode.Nodes.Count > 0)
{
rootNode.Expand();
}
treeView.Nodes.Add(rootNode);
treeNodeObserverLookup.Add(rootNode, new TreeNodeObserver(rootNode, this));
}
private void AddNode(TreeNode parentNode, object nodeData, int insertionIndex = -1)
{
var newNode = new TreeNode
{
Tag = nodeData
};
if (treeView.CheckBoxes)
{
newNode.Checked = parentNode.Checked;
}
UpdateNode(newNode);
if (insertionIndex != -1)
{
parentNode.Nodes.Insert(insertionIndex, newNode);
}
else
{
parentNode.Nodes.Add(newNode);
}
treeNodeObserverLookup.Add(newNode, new TreeNodeObserver(newNode, this));
}
private void RemoveAllNodes()
{
foreach (var treeNode in treeNodeObserverLookup.Keys)
{
treeNodeObserverLookup[treeNode].Dispose();
}
treeNodeObserverLookup.Clear();
treeView.Nodes.Clear();
}
private void RemoveTreeNodeFromLookupRecursively(TreeNode treeNode)
{
treeNodeObserverLookup[treeNode].Dispose();
treeNodeObserverLookup.Remove(treeNode);
foreach (var childNode in treeNode.Nodes.OfType())
{
RemoveTreeNodeFromLookupRecursively(childNode);
}
}
///
/// This method tries to return a object corresponding to the provided data.
///
/// The data to find the corresponding for.
/// The for the provided data or null if no corresponding was found.
private TreeNodeInfo GetTreeNodeInfoForData(object item)
{
if (item == null)
{
return null;
}
TreeNodeInfo treeNodeInfo;
// Try to find an exact match
tagTypeTreeNodeInfoLookup.TryGetValue(item.GetType(), out treeNodeInfo);
// Try to match based on class hierarchy
return treeNodeInfo ?? tagTypeTreeNodeInfoLookup.FirstOrDefault(kvp => kvp.Key.IsInstanceOfType(item)).Value;
}
private void RefreshChildNodes(TreeNode treeNode, TreeNodeInfo treeNodeInfo)
{
var currentTreeNodes = treeNode.Nodes.OfType().ToList();
var currentTreeNodesPerTag = currentTreeNodes.ToDictionary(ctn => ctn.Tag, ctn => ctn);
var newChildNodeObjects = treeNodeInfo.ChildNodeObjects != null
? treeNodeInfo.ChildNodeObjects(treeNode.Tag)
: new object[0];
treeNode.Nodes.Clear();
foreach (var newChildNodeObject in newChildNodeObjects)
{
// Try to recycle any exiting node
if (currentTreeNodesPerTag.ContainsKey(newChildNodeObject))
{
var existingNode = currentTreeNodesPerTag[newChildNodeObject];
treeNode.Nodes.Add(existingNode);
currentTreeNodes.Remove(existingNode);
}
else
{
// Create a new one otherwise
AddNode(treeNode, newChildNodeObject);
}
}
foreach (var removedNode in currentTreeNodes)
{
RemoveTreeNodeFromLookupRecursively(removedNode);
}
}
# region TreeView event handling
private void TreeViewBeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag);
if (treeNodeInfo.CanRename == null || !treeNodeInfo.CanRename(e.Node))
{
e.CancelEdit = true;
}
}
private void TreeViewAfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
// Check Label for null as this indicates the node edit was cancelled
if (e.Label == null)
{
return;
}
var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag);
if (treeNodeInfo.OnNodeRenamed != null)
{
treeNodeInfo.OnNodeRenamed(e.Node.Tag, e.Label);
}
}
private void TreeViewAfterCheck(object sender, TreeViewEventArgs e)
{
var treeNodeInfo = GetTreeNodeInfoForData(e.Node.Tag);
if (treeNodeInfo.OnNodeChecked != null)
{
treeNodeInfo.OnNodeChecked(e.Node);
}
}
private void TreeViewKeyDown(object sender, KeyEventArgs keyEventArgs)
{
var selectedNode = treeView.SelectedNode;
if (selectedNode == null)
{
return;
}
switch (keyEventArgs.KeyData)
{
case Keys.F5: // Refresh the selected node in the tree view
{
if (treeView.SelectedNode != null)
{
UpdateNode(treeView.SelectedNode);
}
break;
}
case Keys.F2: // Start editing the label of the selected node
{
selectedNode.BeginEdit();
break;
}
case Keys.Apps: // If implemented, show the context menu of the selected node
{
var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag);
// Update the context menu (relevant in case of keyboard navigation in the tree view)
selectedNode.ContextMenuStrip = treeNodeInfo.ContextMenuStrip != null
? treeNodeInfo.ContextMenuStrip(selectedNode.Tag, selectedNode, treeNodeInfo)
: null;
if (treeView.ContextMenu != null && selectedNode.ContextMenuStrip != null)
{
var location = selectedNode.Bounds.Location;
location.Offset(0, selectedNode.Bounds.Height);
selectedNode.ContextMenuStrip.Show(location);
}
break;
}
case Keys.Enter: // Perform the same action as on double click
{
OnTreeNodeDoubleClick();
break;
}
case Keys.Delete: // If allowed, delete the selected node
{
var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag);
if (treeNodeInfo.CanRemove == null || !treeNodeInfo.CanRemove(selectedNode.Tag, selectedNode.Parent != null ? selectedNode.Parent.Tag : null))
{
MessageBox.Show(Resources.TreeView_DeleteNodeData_The_selected_item_cannot_be_removed, BaseResources.Confirm, MessageBoxButtons.OK);
break;
}
DeleteNode(selectedNode, treeNodeInfo);
break;
}
case Keys.Space: // If applicable, change the checked state of the selected node
{
var treeNodeInfo = GetTreeNodeInfoForData(selectedNode.Tag);
if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(selectedNode.Tag))
{
selectedNode.Checked = !selectedNode.Checked;
}
break;
}
}
if (keyEventArgs.KeyData == (Keys.Control | Keys.Shift | Keys.Right)) // Expand all tree nodes
{
ExpandAll(selectedNode);
selectedNode.EnsureVisible();
}
if (keyEventArgs.KeyData == (Keys.Control | Keys.Shift | Keys.Left)) // Collapse all tree nodes
{
CollapseAll(selectedNode);
selectedNode.EnsureVisible();
}
}
private void TreeViewMouseClick(object sender, MouseEventArgs e)
{
var point = treeView.PointToClient(Cursor.Position);
var clickedNode = treeView.GetNodeAt(point);
if (clickedNode == null)
{
return;
}
var treeNodeInfo = GetTreeNodeInfoForData(clickedNode.Tag);
if ((e.Button & MouseButtons.Right) != 0)
{
treeView.SelectedNode = clickedNode;
// Update the context menu
clickedNode.ContextMenuStrip = treeNodeInfo.ContextMenuStrip != null
? treeNodeInfo.ContextMenuStrip(clickedNode.Tag, clickedNode, treeNodeInfo)
: null;
return;
}
var isOnCheckBox = IsOnCheckBox(point);
if (treeNodeInfo.CanCheck != null && treeNodeInfo.CanCheck(clickedNode.Tag) && isOnCheckBox)
{
clickedNode.Checked = !clickedNode.Checked;
}
}
private bool IsOnCheckBox(Point point)
{
return treeView.HitTest(point).Location.ToString() == "StateImage";
}
private void TreeViewDoubleClick(object sender, EventArgs e)
{
OnTreeNodeDoubleClick();
}
private void OnTreeNodeDoubleClick()
{
if (TreeNodeDoubleClick != null)
{
TreeNodeDoubleClick(treeView.SelectedNode, EventArgs.Empty);
}
}
private void TreeViewDragDrop(object sender, DragEventArgs e)
{
ClearPlaceHolders();
Point point = treeView.PointToClient(new Point(e.X, e.Y));
var nodeOver = treeView.GetNodeAt(point);
var nodeDragging = e.Data.GetData(typeof(TreeNode)) as TreeNode;
if (nodeOver == null || nodeDragging == null)
{
if (nodeOver != null)
{
e.Effect = DragDropEffects.All;
}
return;
}
// Handle dragged items which were originally higher up in the tree under the same parent (all indices shift by one)
if (e.Effect.Equals(DragDropEffects.Move) && nodeDragging.Parent == nodeDropTarget && nodeOver.Index > nodeDragging.Index)
{
if (dropAtLocation > 0)
{
dropAtLocation--;
}
}
// Ensure the drop location is never < 0
if (dropAtLocation < 0)
{
dropAtLocation = 0;
}
var treeNodeInfo = GetTreeNodeInfoForData(nodeDropTarget.Tag);
try
{
if (treeNodeInfo.OnDrop != null)
{
treeNodeInfo.OnDrop(nodeDragging, nodeDropTarget, ToDragOperation(e.Effect), dropAtLocation);
}
}
catch (Exception ex)
{
Log.Error(string.Format(Resources.TreeView_TreeViewDragDrop_Error_during_drag_drop_0_, ex.Message));
}
}
private void TreeViewDragOver(object sender, DragEventArgs e)
{
if (lastDragOverPoint.X == e.X && lastDragOverPoint.Y == e.Y)
{
return;
}
lastDragOverPoint = new Point(e.X, e.Y);
var point = treeView.PointToClient(lastDragOverPoint);
var nodeOver = treeView.GetNodeAt(point);
var nodeDragging = e.Data.GetData(typeof(TreeNode)) as TreeNode;
if (nodeOver == null || nodeDragging == null || nodeOver == nodeDragging || IsChildOf(nodeOver, nodeDragging))
{
ClearPlaceHolders();
return;
}
ScrollIntoView(point, nodeOver, sender);
PlaceholderLocation placeholderLocation = GetPlaceHoldersLocation(nodeDragging, nodeOver, e);
if (null == nodeDropTarget)
{
return;
}
var treeNodeInfo = GetTreeNodeInfoForData(nodeDropTarget.Tag);
DragOperations allowedOperations = treeNodeInfo.CanDrop != null
? treeNodeInfo.CanDrop(nodeDragging, nodeDropTarget, ToDragOperation(e.AllowedEffect))
: DragOperations.None;
e.Effect = ToDragDropEffects(allowedOperations);
if (PlaceholderLocation.None == placeholderLocation)
{
return;
}
// Determine whether ot not the node can be dropped based on the allowed operations.
// A node can also be a valid drop traget if it is the root item (nodeDragging.Parent == null).
var dragOperations = treeNodeInfo.CanDrop != null
? treeNodeInfo.CanDrop(nodeDragging, nodeDropTarget, allowedOperations)
: DragOperations.None;
if (DragOperations.None != dragOperations)
{
DrawPlaceholder(nodeOver, placeholderLocation);
}
else
{
ClearPlaceHolders();
e.Effect = DragDropEffects.None;
}
}
private void TreeViewItemDrag(object sender, ItemDragEventArgs e)
{
// gather allowed effects for the current item.
var sourceNode = (TreeNode)e.Item;
var treeNodeInfo = GetTreeNodeInfoForData(sourceNode.Tag);
DragOperations dragOperation = treeNodeInfo.CanDrag != null
? treeNodeInfo.CanDrag(sourceNode.Tag, sourceNode)
: DragOperations.None;
DragDropEffects effects = ToDragDropEffects(dragOperation);
if (effects == DragDropEffects.None)
{
return;
}
// store both treenode and tag of treenode in dataobject
// to be dragged.
IDataObject dataObject = new DataObject();
dataObject.SetData(typeof(TreeNode), sourceNode);
if (sourceNode.Tag != null)
{
dataObject.SetData(sourceNode.Tag.GetType(), sourceNode.Tag);
}
treeView.DoDragDrop(dataObject, effects);
}
private void TreeViewDragLeave(object sender, EventArgs e)
{
ClearPlaceHolders();
}
private void TreeViewDrawNode(object sender, DrawTreeNodeEventArgs e)
{
e.DrawDefault = false;
var selected = (e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected;
e.Node.DrawNode(GetTreeNodeInfoForData(e.Node.Tag), e.Graphics, selected);
}
private void DrawPlaceholder(TreeNode node, PlaceholderLocation location)
{
if (lastPlaceholderNode == node && lastPlaceholderLocation == location)
{
return;
}
ClearPlaceHolders();
lastPlaceholderNode = node;
lastPlaceholderLocation = location;
placeHolderGraphics = treeView.CreateGraphics();
node.DrawPlaceHolder(location, treeView.CreateGraphics());
}
private void ClearPlaceHolders()
{
if (placeHolderGraphics != null)
{
lastPlaceholderNode = null;
treeView.Refresh();
placeHolderGraphics.Dispose();
placeHolderGraphics = null;
}
}
private DragOperations ToDragOperation(DragDropEffects dragDropEffects)
{
return (DragOperations) Enum.Parse(typeof(DragOperations), dragDropEffects.ToString());
}
private bool IsChildOf(TreeNode childNode, TreeNode node)
{
while (childNode != null && childNode.Parent != null)
{
if (childNode.Parent.Equals(node))
{
return true;
}
childNode = childNode.Parent; // Walk up the tree
}
return false;
}
private static void ScrollIntoView(Point point, TreeNode nodeOver, object sender)
{
var treeView = sender as TreeView;
if (treeView == null)
{
return;
}
int delta = treeView.Height - point.Y;
if ((delta < treeView.Height / 2) && (delta > 0))
{
var nextVisibleNode = nodeOver.NextVisibleNode;
if (nextVisibleNode != null)
{
nextVisibleNode.EnsureVisible();
}
}
if ((delta > treeView.Height / 2) && (delta < treeView.Height))
{
var previousVisibleNode = nodeOver.PrevVisibleNode;
if (previousVisibleNode != null)
{
previousVisibleNode.EnsureVisible();
}
}
}
private PlaceholderLocation GetPlaceHoldersLocation(TreeNode nodeDragging, TreeNode nodeOver, DragEventArgs e)
{
var loc = PlaceholderLocation.None;
int offsetY = treeView.PointToClient(Cursor.Position).Y - nodeOver.Bounds.Top;
if (offsetY < nodeOver.Bounds.Height / 3 && nodeDragging.NextNode != nodeOver)
{
if (nodeOver.Parent != null)
{
var treeNodeInfo = GetTreeNodeInfoForData(nodeOver.Parent.Tag);
if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging, nodeOver))
{
nodeDropTarget = nodeOver.Parent;
dropAtLocation = nodeOver.Parent == null ? 0 : nodeOver.Parent.Nodes.IndexOf(nodeOver);
loc = PlaceholderLocation.Top;
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
loc = PlaceholderLocation.Middle;
}
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
loc = PlaceholderLocation.Middle;
}
}
else if ((nodeOver.Parent != null) && (offsetY > nodeOver.Bounds.Height - nodeOver.Bounds.Height / 3) &&
nodeDragging.PrevNode != nodeOver)
{
var treeNodeInfo = GetTreeNodeInfoForData(nodeOver.Parent.Tag);
if (treeNodeInfo.CanInsert != null && treeNodeInfo.CanInsert(nodeDragging, nodeOver))
{
nodeDropTarget = nodeOver.Parent;
dropAtLocation = nodeOver.Parent == null
? 0
: nodeOver.Parent.Nodes.IndexOf(nodeOver) + 1;
loc = PlaceholderLocation.Bottom;
}
else
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
loc = PlaceholderLocation.Middle;
}
}
else if (nodeDragging != nodeOver && offsetY < nodeOver.Bounds.Height - nodeOver.Bounds.Height / 4
&& offsetY > nodeOver.Bounds.Height / 4)
{
nodeDropTarget = nodeOver;
dropAtLocation = 0;
loc = PlaceholderLocation.Middle;
}
if (loc == PlaceholderLocation.None ||
(loc == PlaceholderLocation.Middle && nodeDropTarget == nodeDragging.Parent))
{
ClearPlaceHolders();
e.Effect = DragDropEffects.None;
}
return loc;
}
private DragDropEffects ToDragDropEffects(DragOperations operation)
{
return (DragDropEffects) Enum.Parse(typeof(DragDropEffects), operation.ToString());
}
# endregion
# region Event handling
private void OnNodeUpdated(TreeNode treeNode)
{
if (NodeUpdated != null)
{
NodeUpdated(treeNode, EventArgs.Empty);
}
}
private void OnNodeDataDeleted(TreeNode node)
{
if (NodeDataDeleted != null)
{
NodeDataDeleted(this, new TreeNodeDataDeletedEventArgs(node.Tag));
}
}
# endregion
# region Nested types
private class TreeNodeObserver : IDisposable, IObserver
{
private readonly TreeNode treeNode;
private readonly TreeViewController controller;
public TreeNodeObserver(TreeNode treeNode, TreeViewController controller)
{
this.treeNode = treeNode;
this.controller = controller;
var observable = treeNode.Tag as IObservable;
if (observable != null)
{
observable.Attach(this);
}
}
public void Dispose()
{
var observable = treeNode.Tag as IObservable;
if (observable != null)
{
observable.Detach(this);
}
}
public void UpdateObserver()
{
controller.UpdateNode(treeNode);
}
}
# endregion
public void Dispose()
{
treeView.BeforeLabelEdit -= TreeViewBeforeLabelEdit;
treeView.AfterLabelEdit -= TreeViewAfterLabelEdit;
treeView.AfterCheck -= TreeViewAfterCheck;
treeView.KeyDown -= TreeViewKeyDown;
treeView.MouseClick -= TreeViewMouseClick;
treeView.DoubleClick -= TreeNodeDoubleClick;
treeView.DragDrop -= TreeViewDragDrop;
treeView.DragOver -= TreeViewDragOver;
treeView.ItemDrag -= TreeViewItemDrag;
treeView.DragLeave -= TreeViewDragLeave;
treeView.DrawNode -= TreeViewDrawNode;
}
}
}