Click here to Skip to main content
Click here to Skip to main content
Alternative Article

Simple Tri-State TreeView

, 5 Mar 2012
Rate this:
Please Sign up or sign in to vote.
This is an alternative for "Simple Tri-State TreeView"

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 checkboxes 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 {
 
        // ~~~ fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        ImageList _ilStateImages;
        bool _bUseTriState;
        bool _bCheckBoxesVisible;
        bool _bPreventCheckEvent;
 
        // ~~~ constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        /// <summary>
        /// Creates a new instance
        /// of this control.
        /// </summary>
        public TriStateCBTreeView()
            : base() {
            _ilStateImages = new ImageList();                            // first we create our state image
            CheckBoxState cbsState = CheckBoxState.UncheckedNormal;      // list and pre-init check state.

            for (int i = 0; i <= 2; i++) {												                // let's iterate each tri-state
                Bitmap bmpCheckBox = new Bitmap(16, 16);				    // creating a new checkbox bitmap
                Graphics gfxCheckBox = Graphics.FromImage(bmpCheckBox);	    // and getting graphics object from
                switch (i)                                                  // it...
                {
                    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);  // ...rendering
                gfxCheckBox.Save();                                         // the checkbox and
                _ilStateImages.Images.Add(bmpCheckBox);					    // adding to state image list.
            }
 
            _bUseTriState = true;
        }
 
        // ~~~ properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        /// <summary>
        /// Gets or sets to display
        /// checkboxes in the tree
        /// view.
        /// </summary>
        [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; }
        }
 
        /// <summary>
        /// Gets or sets to support
        /// tri-state in the checkboxes
        /// or not.
        /// </summary>
        [Category("Appearance")]
        [Description("Sets tree view to use tri-state checkboxes or not.")]
        [DefaultValue(true)]
        public bool CheckBoxesTriState {
            get { return _bUseTriState; }
            set { _bUseTriState = value; }
        }
 
        // ~~~ functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        /// <summary>
        /// Changes nodes state.
        /// </summary>

        protected void SetParentState(TreeNode tNode) {
            TreeNode ParentNode = tNode.Parent;
            if (ParentNode != null) {
                try {
                    if (tNode.StateImageIndex == 2) {                   // if the current node has a mixed state
                        ParentNode.Checked = false;                     // then its parent is set to the same state
                        ParentNode.StateImageIndex = 2;
                        return;
                    }
                    int CheckedCount = 0;
                    int UnCheckedCount = 0;
                    foreach (TreeNode ChildNode in ParentNode.Nodes) {  // we count the checked and unchecked states
                        if (ChildNode.StateImageIndex <= 0)             // of each node at the current level
                            UnCheckedCount++;
                        else if (ChildNode.StateImageIndex == 1)
                            CheckedCount++;
                        if (ChildNode.StateImageIndex == 2 ||           // if one node has a mixed state or there
                           (CheckedCount > 0 && UnCheckedCount > 0)) {  // are checked and unchecked states, then
                            ParentNode.Checked = false;                 // the parent node is set to a mixed state
                            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);                         // the parent node becomes the current node
                }
            }
        }
        protected void SetChildrenState(TreeNode tNode, bool RootNode) {
            if (!RootNode) {
                tNode.Checked = (tNode.Parent.StateImageIndex == 1);    // the child state is inherited
                tNode.StateImageIndex = tNode.Parent.StateImageIndex;   // from the parent state
            }
            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)) {                     // we verify if the checked state has
                tNode.StateImageIndex = NewState;                       // not been canceled in a BeforeCheck event

                _bPreventCheckEvent = true;
 
                SetParentState(tNode);
                SetChildrenState(tNode, true);
 
                _bPreventCheckEvent = false;
            }
        }
 
        /// <summary>
        /// Initializes the nodes state.
        /// </summary>

        public void InitializeStates(TreeNodeCollection tNodes) {
            foreach (TreeNode tnCurrent in tNodes) {                   // set tree state image
                if (tnCurrent.StateImageIndex == -1) {                 // to each child node...
                    _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)					// nothing to do here if
                return;						// checkboxes are hidden.

            base.CheckBoxes = false;				// hide normal checkboxes...

            InitializeStates(this.Nodes);
        }
 
        /// <summary>
        /// Refreshes this control.
        /// </summary>

        public override void Refresh() {
            base.Refresh();
 
            InitializeCBImages();
        }
 
        // ~~~ events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        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 user clicked area
            if (e.X > e.Node.Bounds.Left - iSpacing ||		// *not* used by the state
                e.X < e.Node.Bounds.Left - (iSpacing + 14) ||	// image we can leave here.
                e.Button != MouseButtons.Left) {
                return;
            }
 
            SetState(e.Node, e.Node.Checked ? 0 : 1);
        }
    }

History 

05/03/2012 : first submission 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Fred_Informatix

United States United States
No Biography provided

Comments and Discussions

 
Questionrtl Pinmembersiminal21-Sep-12 10:43 
AnswerRe: rtl PinmemberFred_Informatix21-Sep-12 11:31 
GeneralRe: rtl [modified] Pinmembersiminal21-Sep-12 11:49 
GeneralRe: rtl PinmemberFred_Informatix23-Sep-12 10:01 
GeneralRe: rtl Pinmembersiminal23-Sep-12 10:06 
GeneralRe: rtl Pinmembersiminal21-Sep-12 23:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 5 Mar 2012
Article Copyright 2012 by Fred_Informatix
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid