65.9K
CodeProject is changing. Read more.
Home

Three State TreeView – Part 1 of 2

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (15 votes)

Jun 2, 2006

CPOL

3 min read

viewsIcon

163296

downloadIcon

5324

Two part series on how to create a three state Treeview in .NET 2.0 without using Win32 or a state imagelist

Introduction

The .NET 2.0 TreeView control supports the use of check boxes in TreeNodes by setting the CheckBoxes property to True on an instance of the TreeView. However, the standard checkbox of a TreeNode supports only two states, Checked or Unchecked, and related nodes are completely unaware of the check state of related parent/child nodes. To address this problem, a third state, Indeterminate, will be introduced for a TreeNode as well as the ability to communicate state changes to related parent/child nodes.

Solution

To address the complexities of the problem surrounding the drawing of the control, this project is broken down into a two-part series:

Part 1, "Creating a Pseudo Three State Check Box TreeView: Cascading State Changes to Parent and Child Nodes," implements a variable state to track the current check state of a TreeNode as well as cascading the change of state to parent and child nodes. This is a pseudo solution because the third state, Indeterminate, is represented by a check mark, not the traditional square block (see Figure 1).

Figure 1

Part 2, "Creating a True Three State Check Box TreeView: Managing the Drawing of the TreeView," addresses the problem of drawing a three-state check box in the TreeView control that uses the third state Indeterminate of a check box (see Figure 2).

Figure 2

Points of Interest

To implement the state and state change behavior, it is necessary to define the states and override the behavior of the base TreeView and TreeNode controls.

The following is a summary of the behavior of states for related parent/child nodes.

Parent Node State Child Nodes
Checked All child nodes are checked.
Unchecked All child nodes are unchecked.
Indeterminate Some child nodes are checked, some are unchecked, and/or some are indeterminate.

These states are defined in code by a new enumeration, CheckBoxState.

/// <summary>
/// The available states for a three state check box.
/// </summary>
[FlagsAttribute] 
public enum CheckBoxState
{
    Unchecked = 1,
    Checked = 2,
    Indeterminate = CheckBoxState.Unchecked | CheckBoxState.Checked
}

When a node’s check box is checked or unchecked manually or programmatically, the state property of that node needs to be updated as well as the state of related nodes. The Toggle method of the TreeNode is overridden to update the state and to begin the process of updating the states of related parent/child nodes.

/// <summary>
/// The current state of the check box.
/// </summary>
private Enumerations.CheckBoxState mState = Enumerations.CheckBoxState.Unchecked;
public Enumerations.CheckBoxState State
{
    get { return this.mState; }
    set 
    {
        if (this.mState != value)
        {
            this.mState = value;

            // Ensure if checkboxes are used to make the checkbox checked or unchecked.
            // When going to a fully drawn control, this will be managed in the drawing
            // code.
            // Setting the Checked property in code will cause the OnAfterCheck to be
            // called and the action will be 'Unknown'; do not handle that case.
            if ((this.TreeView != null) && (this.TreeView.CheckBoxes))
                this.Checked = this.mState == Enumerations.CheckBoxState.Checked;
        }
    }
}

/// <summary>
/// Manages state changes from one state to the next.
/// </summary>
public new void Toggle()
{
    this.Toggle(this.State);
}

/// <summary>
/// Manages state changes from one state to the next.
/// </summary>
/// <param name="fromState">The state upon which to base the state change.</param>
public void Toggle(Enumerations.CheckBoxState fromState)
{
    switch (fromState)
    {
        case Enumerations.CheckBoxState.Unchecked:
            {
                this.State = Enumerations.CheckBoxState.Checked;
                break;
            }
        case Enumerations.CheckBoxState.Checked:
        case Enumerations.CheckBoxState.Indeterminate:
        default:
            {
                this.State = Enumerations.CheckBoxState.Unchecked;
                break;
            }
    }

    this.UpdateStateOfRelatedNodes();
}

/// <summary>
/// Manages updating related child and parent nodes of this instance.
/// </summary>
public void UpdateStateOfRelatedNodes()
{
    ThreeStateTreeView tv = this.TreeView as ThreeStateTreeView;
    if ((tv != null) && tv.CheckBoxes && tv.UseThreeStateCheckBoxes)
    {
        tv.BeginUpdate();

        // If want to cascade checkbox state changes to child nodes of this node and
        // if the current state is not intermediate, update the state of child nodes.
        if (this.State != Enumerations.CheckBoxState.Indeterminate)
            this.UpdateChildNodeState();

        this.UpdateParentNodeState(true);

        tv.EndUpdate();
    }
}

The TreeView’s OnAfterCheck method is overridden to manage the process of toggling the state for the current node and related parent/child nodes. The OnAfterCheck will get called when the Checked property of a node changes. This occurs when the user checks the check box via the mouse or keyboard. It also happens when the checked property is changed in code. When changed in code, the Action type is Unknown. This is a case in which we do NOT want to toggle the state as it is already in progress.

/// <summary>
/// Raises the AfterCheck event.
/// </summary>
protected override void OnAfterCheck(TreeViewEventArgs e)
{
    base.OnAfterCheck(e);

    if (this.UseThreeStateCheckBoxes)
    {
        switch (e.Action)
        {
            case TreeViewAction.ByKeyboard:
            case TreeViewAction.ByMouse:
                {
                    if (e.Node is ThreeStateTreeNode)
                    {
                        // Toggle to the next state.
                        ThreeStateTreeNode tn = e.Node as ThreeStateTreeNode;
                        tn.Toggle();
                    }

                    break;
                }
            case TreeViewAction.Collapse:
            case TreeViewAction.Expand:
            case TreeViewAction.Unknown:
            default:
                {
                    // Do nothing.
                    break;
                }
        }
    }
}

Conclusion

The addition of a state variable to a TreeNode allows us to introduce a third state, Indeterminate, for the TreeView control. The ability to notify related parent/child nodes of state changes also enhances the native TreeView’s behavior. In Part 2 of this series, the drawing of a true Indeterminate state check box will be addressed for a complete implementation of a Three State TreeView control.

To Do

  • Implement full designer support for the TreeNode Editor to support the ThreeStateTreeNode class.

References

Acknowledgements

I want to thank the following people for their thoughts, insights and words of advice while working on this article.

Ascentium Corporation is a Microsoft Gold Certified Partner, and an award-winning, integrated technology and marketing consultancy located in Bellevue, Washington.