Introduction
Microsoft provides a TreeView
control which isn't able to display tree state checkboxes. There are many ways to implement this feature such as window proc hooking and the other stuff. This article will show you another, more simpler way using techniques provided by the .NET Framework only.
This control only inherits the original tree view control replacing CheckBoxes
property usage with usage of the StateImageList
and primarily overriding the node click event procedure to handle setting the tri-state.
To conform to all Windows UI styles, the state image list will be built dynamically using the CheckBoxRenderer
class provided by the .NET Framework while the control is created.
Background
There may be needs to have a tree view control showing on parent nodes that check states its children aren't all the same. Further there may be needs, refreshing check states up / down the tree while checking a sub-/super-node.
Using techniques such as window proc hooking isn't the right way for everyone because it isn't always easy to debug. Further using static images would avoid displaying right checkboxes on different Windows UIs.
Using the Control / Code
Add the TriStateCheckBoxTreeView.cs to your project and simply drop the control to a form. You'll find the differences to the normal tree view control provided by the .NET Framework in the property grid. There is now a property to enable tri-state usage directly below the CheckBoxes
-property.
Check state checking is done as normal using the Checked
property from the tree node. To determine all three possible states, you may check the StateImageIndex
giving the following three possible indexes:
- 0 - Unchecked
- 1 - Checked
- 2 - Mixed
Rendering checkboxes using CheckBoxRenderer
Bitmap GetCheckBoxBitmap(CheckBoxState myState)
{
Bitmap bmpCheckBox = new Bitmap(16, 16);
Graphics gfxCheckBox = Graphics.FromImage(bmpCheckBox);
CheckBoxRenderer.DrawCheckBox(gfxCheckBox, new Point(2, 2), myState);
gfxCheckBox.Save();
return bmpCheckBox;
}
Setting the tri-state
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
{
Stack<TreeNode> stNodes;
TreeNode tnBuffer;
bool bMixedState;
int iSpacing;
int iIndex;
base.OnNodeMouseClick(e);
iSpacing = ImageList == null ? 0 : 18;
if (e.X > e.Node.Bounds.Left - iSpacing ||
e.X < e.Node.Bounds.Left - (iSpacing + 16))
{ return; }
tnBuffer = e.Node;
tnBuffer.Checked = !tnBuffer.Checked;
stNodes = new Stack<TreeNode>(tnBuffer.Nodes.Count);
stNodes.Push(tnBuffer);
do {
tnBuffer = stNodes.Pop();
tnBuffer.Checked = e.Node.Checked;
for (int i = 0; i < tnBuffer.Nodes.Count; i++)
stNodes.Push(tnBuffer.Nodes[i]);
} while (stNodes.Count > 0);
bMixedState = false;
tnBuffer = e.Node;
while (tnBuffer.Parent != null) {
foreach (TreeNode tnChild in tnBuffer.Parent.Nodes)
bMixedState |= (tnChild.Checked != tnBuffer.Checked);
iIndex = (int)Convert.ToUInt32(tnBuffer.Checked);
tnBuffer.Parent.Checked = bMixedState || (iIndex > 0);
if (bMixedState)
tnBuffer.Parent.StateImageIndex = CheckBoxesTriState ? 2 : 1;
else
tnBuffer.Parent.StateImageIndex = iIndex;
tnBuffer = tnBuffer.Parent;
}
}
Limitations
This code has been written as simple as possible. There is one limitation you should keep in mind: If you're adding nodes from code you have to call control's Refresh()
after adding has been completed to ensure all nodes got the right state.
History
- 17 February 2010: First version
- 18 February 2010: Extended sections "Introduction", "Background", "Using the control / code", fixed demo project
- 18 March 2010: Small cosmetics to comments
- 30 March 2011: Updated demo project