Introduction
I had too many problems with the original code and decided to rewrite most of it.
Using the Code
"Refreshing" the treeview
after a change is not needed any longer (but still works). The checkbox of a newly added node will appear automatically if you set its state with the function "SetState
". Alternatively, you can display the checkbox
es in their initial state with the functions "InitializeCBImages
" (for all nodes) or "InitializeStates
" (for a specific node collection).
BeforeCheck
and AfterCheck
are now triggered only for the node modified by the SetState
function or a mouse click. Other changes (to parents and children state) trigger a new event: AutoCheck
.
Here's the new code for the class:
public class TriStateCBTreeView : TreeView {
ImageList _ilStateImages;
bool _bUseTriState;
bool _bCheckBoxesVisible;
bool _bPreventCheckEvent;
public TriStateCBTreeView()
: base() {
_ilStateImages = new ImageList();
CheckBoxState cbsState = CheckBoxState.UncheckedNormal;
for (int i = 0; i <= 2; i++) {
Bitmap bmpCheckBox = new Bitmap(16, 16);
Graphics gfxCheckBox = Graphics.FromImage(bmpCheckBox);
switch (i)
{
case 0: cbsState = CheckBoxState.UncheckedNormal; break;
case 1: cbsState = CheckBoxState.CheckedNormal; break;
case 2: cbsState = CheckBoxState.MixedNormal; break;
}
CheckBoxRenderer.DrawCheckBox(gfxCheckBox, new Point(2, 2), cbsState);
gfxCheckBox.Save();
_ilStateImages.Images.Add(bmpCheckBox);
}
_bUseTriState = true;
}
[Category("Appearance")]
[Description("Sets tree view to display checkboxes or not.")]
[DefaultValue(false)]
public new bool CheckBoxes {
get { return _bCheckBoxesVisible; }
set {
_bCheckBoxesVisible = value;
base.CheckBoxes = _bCheckBoxesVisible;
this.StateImageList = _bCheckBoxesVisible ? _ilStateImages : null;
}
}
[Browsable(false)]
public new ImageList StateImageList {
get { return base.StateImageList; }
set { base.StateImageList = value; }
}
[Category("Appearance")]
[Description("Sets tree view to use tri-state checkboxes or not.")]
[DefaultValue(true)]
public bool CheckBoxesTriState {
get { return _bUseTriState; }
set { _bUseTriState = value; }
}
protected void SetParentState(TreeNode tNode) {
TreeNode ParentNode = tNode.Parent;
if (ParentNode != null) {
try {
if (tNode.StateImageIndex == 2) {
ParentNode.Checked = false;
ParentNode.StateImageIndex = 2;
return;
}
int CheckedCount = 0;
int UnCheckedCount = 0;
foreach (TreeNode ChildNode in ParentNode.Nodes) {
if (ChildNode.StateImageIndex <= 0)
UnCheckedCount++;
else if (ChildNode.StateImageIndex == 1)
CheckedCount++;
if (ChildNode.StateImageIndex == 2 ||
(CheckedCount > 0 && UnCheckedCount > 0)) {
ParentNode.Checked = false;
ParentNode.StateImageIndex = 2;
return;
}
}
if (UnCheckedCount > 0) {
ParentNode.Checked = false;
ParentNode.StateImageIndex = 0;
}
else if (CheckedCount > 0) {
ParentNode.Checked = true;
ParentNode.StateImageIndex = 1;
}
}
finally {
SetParentState(ParentNode);
}
}
}
protected void SetChildrenState(TreeNode tNode, bool RootNode) {
if (!RootNode) {
tNode.Checked = (tNode.Parent.StateImageIndex == 1);
tNode.StateImageIndex = tNode.Parent.StateImageIndex;
}
foreach (TreeNode ChildNode in tNode.Nodes)
SetChildrenState(ChildNode, false);
}
public void SetState(TreeNode tNode, int NewState) {
if (NewState < 0 || NewState > 2)
NewState = 0;
tNode.Checked = (NewState == 1);
if (tNode.Checked == (NewState == 1)) {
tNode.StateImageIndex = NewState;
_bPreventCheckEvent = true;
SetParentState(tNode);
SetChildrenState(tNode, true);
_bPreventCheckEvent = false;
}
}
public void InitializeStates(TreeNodeCollection tNodes) {
foreach (TreeNode tnCurrent in tNodes) {
if (tnCurrent.StateImageIndex == -1) {
_bPreventCheckEvent = true;
if (tnCurrent.Parent != null) {
tnCurrent.Checked = tnCurrent.Parent.Checked;
tnCurrent.StateImageIndex = tnCurrent.Parent.StateImageIndex;
}
else
tnCurrent.StateImageIndex = tnCurrent.Checked ? 1 : 0;
_bPreventCheckEvent = false;
}
InitializeStates(tnCurrent.Nodes);
}
}
public void InitializeCBImages() {
if (!CheckBoxes)
return;
base.CheckBoxes = false;
InitializeStates(this.Nodes);
}
public override void Refresh() {
base.Refresh();
InitializeCBImages();
}
protected override void OnLayout(LayoutEventArgs levent) {
base.OnLayout(levent);
InitializeCBImages();
}
protected override void OnAfterExpand(TreeViewEventArgs e) {
if (CheckBoxes)
InitializeStates(e.Node.Nodes);
base.OnAfterExpand(e);
}
public delegate void AutoCheckEventHandler(object sender, TreeViewEventArgs e);
public event AutoCheckEventHandler AutoCheck;
protected override void OnBeforeCheck(TreeViewCancelEventArgs e) {
if (_bPreventCheckEvent)
return;
base.OnBeforeCheck(e);
}
protected override void OnAfterCheck(TreeViewEventArgs e) {
if (_bPreventCheckEvent) {
if (AutoCheck != null)
AutoCheck(this, e);
return;
}
base.OnAfterCheck(e);
}
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) {
base.OnNodeMouseClick(e);
int iSpacing = ImageList == null ? 0 : 20;
if (e.X > e.Node.Bounds.Left - iSpacing ||
e.X < e.Node.Bounds.Left - (iSpacing + 14) ||
e.Button != MouseButtons.Left) {
return;
}
SetState(e.Node, e.Node.Checked ? 0 : 1);
}
}
History
- 05/03/2012: First submission