Custom Checkboxes in a .Net TreeView
Everyone who has worked with the built-in .Net TreeView certainly found that this control is good but not as good as it can be.
When we want to use checkboxes only on some of the nodes, we can't. The only way I acheived this was to disable the default checkboxes and
create fake checkboxes with images that react like the real ones. Because I always wanted my controls to look great, I added some code to
"highlight" those fake checkboxes when the user moves his cursor over the nodes.
Here are the icons I used to create my fake checkboxes :
Here is some code that will show you how to create custom checkboxes only for the nodes you want in a TreeView.
This piece of code is the ClickableNode class that represents a node that has a checkbox (created by the previous icons).
public class ClickableNode : TreeNode
{
private const string IMAGE_KEY_CHECKED = "box_checked";
private const string IMAGE_KEY_CHECKED_HOVER = "box_checked_over";
private const string IMAGE_KEY_UNCHECKED = "box_unchecked";
private const string IMAGE_KEY_UNCHECKED_HOVER = "box_unchecked_over";
private bool _checked = false;
public event MouseHoverEventHandler MouseHover;
public delegate void MouseHoverEventHandler(object sender, TreeNodeMouseHoverEventArgs e);
public event MouseLeaveEventHandler MouseLeave;
public delegate void MouseLeaveEventHandler(object sender, TreeNodeMouseHoverEventArgs e);
public ClickableNode(string text)
{
this.Text = text;
this.ImageKey = IMAGE_KEY_UNCHECKED;
this.SelectedImageKey = IMAGE_KEY_UNCHECKED;
}
public new bool Checked
{
get { return _checked; }
set {
_checked = value;
if (this.ImageKey == IMAGE_KEY_CHECKED_HOVER || this.ImageKey == IMAGE_KEY_UNCHECKED_HOVER)
{
if (_checked)
{
this.ImageKey = IMAGE_KEY_CHECKED_HOVER;
this.SelectedImageKey = IMAGE_KEY_CHECKED_HOVER;
}
else
{
this.ImageKey = IMAGE_KEY_UNCHECKED_HOVER;
this.SelectedImageKey = IMAGE_KEY_UNCHECKED_HOVER;
}
}
else
{
if (_checked)
{
this.ImageKey = IMAGE_KEY_CHECKED;
this.SelectedImageKey = IMAGE_KEY_CHECKED;
}
else
{
this.ImageKey = IMAGE_KEY_UNCHECKED;
this.SelectedImageKey = IMAGE_KEY_UNCHECKED;
}
}
}
}
private void ClickableNode_MouseHover(object sender, TreeNodeMouseHoverEventArgs e)
{
if (this.Checked)
{
e.Node.ImageKey = IMAGE_KEY_CHECKED_HOVER;
e.Node.SelectedImageKey = IMAGE_KEY_CHECKED_HOVER;
}
else
{
e.Node.ImageKey = IMAGE_KEY_UNCHECKED_HOVER;
e.Node.SelectedImageKey = IMAGE_KEY_UNCHECKED_HOVER;
}
}
private void ClickableNode_MouseLeave(object sender, TreeNodeMouseHoverEventArgs e)
{
if (this.Checked)
{
e.Node.ImageKey = IMAGE_KEY_CHECKED;
e.Node.SelectedImageKey = IMAGE_KEY_CHECKED;
}
else
{
e.Node.ImageKey = IMAGE_KEY_UNCHECKED;
e.Node.SelectedImageKey = IMAGE_KEY_UNCHECKED;
}
}
protected internal void OnMouseHover()
{
if (MouseHover != null)
MouseHover(this, new TreeNodeMouseHoverEventArgs(this));
}
protected internal void OnMouseLeave()
{
if (MouseLeave != null)
MouseLeave(this, new TreeNodeMouseHoverEventArgs(this));
}
}
...and here is the CustomCheckboxesTreeview class :
public class CustomCheckboxesTreeview : TreeView
{
private TreeNode _currentNodeMouseOver = null;
private void CustomCheckboxesTreeview_MouseMove(object sender, MouseEventArgs e)
{
TreeNode currentNode = this.HitTest(e.Location).Node;
if (currentNode is ClickableNode)
if (currentNode != null)
((ClickableNode)currentNode).OnMouseHover();
if (!object.ReferenceEquals(currentNode, _currentNodeMouseOver))
{
if (_currentNodeMouseOver != null && _currentNodeMouseOver is ClickableNode)
((ClickableNode)_currentNodeMouseOver).OnMouseLeave();
_currentNodeMouseOver = currentNode;
}
}
private void CustomCheckboxesTreeview_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
int iconWidth = this.ImageList.ImageSize.Width;
int nodeLeft = e.Node.Bounds.Left;
int clickLocation = e.X;
if (e.Node is ClickableNode)
if (clickLocation > nodeLeft - iconWidth && clickLocation < nodeLeft - 2)
((ClickableNode)e.Node).Checked = !((ClickableNode)e.Node).Checked;
}
}
I don't show this in this current blog entry but in this TreeView I also added a class for EditableNodes. Because the behavior of editing a node is at
the TreeView level, the TreeView checks if the TypeOf node is EditableNode and applies the editing behavior. For every other type of nodes, the TreeView
simply does its normal job.
I'm currently using this TreeView and it works great. This is a good alternate solution for the normal .Net TreeView.