Introduction
My search for a Treeview
with 3-state-checkboxes led me to this article. It explains the logic properly and promises a part 2, in which the problems about ownerdrawing the checkboxes should be solved.
But that part 2 has, for whatever reason, never been written. But the author allowed the audience to feel free to write part 2. So I felt free. ;)
Repeat the Logic
The user can only check or uncheck Treenode
s - not set to indeterminate. Checking/unchecking a Node sets all childnodes to that new state. If a ParentNode
contains nodes of different states, then it will display the Indeterminate - state.
The Code
The principle is to use the StateImageList
-property with 3 Images: Unchecked
, Checked
, Indeterminate
. The dual logic is done well by the Treeview
as it is. Treeview
uses the first two images properly to display Checked
/Unchecked
. Nevertheless I carefully set the proper StateImageIndices
, although that's not needed (for dual logic).
But I need it to persist 3 states. When it comes to draw, I only have to draw the Indeterminated Checkbox
.
A problem was that I need to use TreeViewDrawMode.OwnerDrawAll
to figure out which node to draw. But I don't want to draw the nodes completely, because that's quite difficult (Checkbox
, optional Icon, SelectedIcon, Text, SelectedText, Focus). I just want to add my Indeterminated-Checkbox
, if necessary.
Unfortunately DrawMode.OwnerDrawAll
disables the _Paint
-Event, and there is no "AfterDrawNode
"-Event. So I had to subclass the windowmessages, observing, when the WM_PAINT
-windowmessage has passed. At that moment, I can draw my indeterminated-Checkbox
es, and they will not be overdrawn by the Treeview
.
So here you can look at the most important parts of the ThreeStateTreeview
, and I hope, it is commented well enough to make more explanations redundant.
protected override void OnAfterCheck(TreeViewEventArgs e) {
if(_skipCheckEvents) return;
_skipCheckEvents = true;
try {
TreeNode nd = e.Node;
int state = nd.StateImageIndex == 0 ? -1 : 0;
if((state == 0) != nd.Checked) return;
InheritCheckstate(nd, state);
nd = nd.Parent;
while(nd != null) {
if(state != 1) {
foreach(TreeNode ndChild in nd.Nodes) {
if(ndChild.StateImageIndex != state) {
state = 1;
break;
}
}
}
AssignState(nd, state);
nd = nd.Parent;
}
base.OnAfterCheck(e);
} finally { _skipCheckEvents = false; }
}
private void AssignState(TreeNode nd, int state) {
bool ck = state == 0;
bool stateInvalid = nd.StateImageIndex != state;
if(stateInvalid) nd.StateImageIndex = state;
if(nd.Checked != ck) {
nd.Checked = ck;
} else if(stateInvalid) {
this.Invalidate(GetCheckRect(nd));
}
}
private void InheritCheckstate(TreeNode nd, int state) {
AssignState(nd, state);
foreach(TreeNode ndChild in nd.Nodes) {
InheritCheckstate(ndChild, state);
}
}
public System.Windows.Forms.CheckState GetState(TreeNode nd) {
return (CheckState)nd.StateImageIndex + 1;
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e) {
if(e.Node.StateImageIndex == 1) _indeterminateds.Add(e.Node);
e.DrawDefault = true;
base.OnDrawNode(e);
}
protected override void WndProc(ref Message m) {
const int WM_Paint = 15;
base.WndProc(ref m);
if(m.Msg == WM_Paint) {
foreach(TreeNode nd in _indeterminateds) {
_graphics.DrawImage(_imgIndeterminate, GetCheckRect(nd).Location);
}
_indeterminateds.Clear();
}
}
Credits
- Three State Treeview - Part 1 - Although I didn't use a line of that code, it gave me the idea of how to synchronize the
Checked
-Property with the 3 options of StateImageIndex
, and how to avoid multiple Before-/After-Check-Events while updating the Treenode
states.
History
- 1st April, 2009: Initial post
- 18th May, 2010: Bugfix: Christo667 reported a well hidden bug, when programmatical set a nodes
Checked
-property to the same value, it had before (see on Message-board). The bug-reason was, in that case the common TreeView
raises redundant Before-/After-Checked-Events, and ThreeStateTreeview
toggled the nodes appearance, although it shouldn't.
Now ThreeStateTreeview
suppresses those redundant Events. That may be a bug-workaround for the common TreeView
as well.
Thank you, Christo! - BugFix of Standard-Treeview, when doubleclicking the Checkbox of a node.