Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / Windows Forms

Three State Treeview - Part 2

Rate me:
Please Sign up or sign in to vote.
4.85/5 (16 votes)
22 Mar 2014CPOL2 min read 112.4K   3.3K   42   29
Treeview with Checkboxes supporting 3-state-logic

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 Treenodes - 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-Checkboxes, 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.

C#
protected override void OnAfterCheck(TreeViewEventArgs e) {
   /* Logic: All children of an (un)checked Node inherit its Checkstate
    * Parents recompute their state: if all children of a parent have same state, 
    * that one will be taken over as parents state - otherwise take Indeterminate 
    */
   if(_skipCheckEvents) return;/* changing any Treenodes .Checked-Property will raise 
                                     another Before- and After-Check. Skip'em */
   _skipCheckEvents = true;
   try {
      TreeNode nd = e.Node;
      /* uninitialized Nodes have StateImageIndex -1, 
       * so I associate StateImageIndex as follows:
       * -1: Unchecked
       *  0: Checked
       *  1: Indeterminate
       *  That corresponds to the System.Windows.Forms.Checkstate - enumeration, 
       *  but 1 less.
       *  Furthermore I ordered the images in that manner
       */
      int state = nd.StateImageIndex == 0 ? -1 : 0;      /* this state is already toggled.
          Note: -1 (Unchecked) and 1 (Indeterminate) both toggle to 0, 
                        that means: Checked */
      if((state == 0) != nd.Checked) return;       //suppress redundant AfterCheck-event
      InheritCheckstate(nd, state);         // inherit Checkstate to children
      // Parents recompute their state
      nd = nd.Parent;
      while(nd != null) {
         // At Indeterminate (==1) skip the children-query - 
         // every parent becomes Indeterminate
         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;                // changing .Checked-Property raises 
                // Invalidating internally
   } else if(stateInvalid) {
      // in general: the less and small the invalidated area, the less flickering
      // so avoid calling Invalidate() if possible, and only call, if really needed.
      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) {
   // compute the System.Windows.Forms.CheckState from a StateImageIndex 
   // is not that complicated
   return (CheckState)nd.StateImageIndex + 1;
}

protected override void OnDrawNode(DrawTreeNodeEventArgs e) {
   // here nothing is drawn. Only collect Indeterminated Nodes, 
   // to draw them later (in WndProc())
   // because drawing Treenodes properly (Text, Icon(s) Focus, Selection...) 
   // is very complicated
   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) {
      // at that point built-in drawing is completed - 
      // and I paint over the Indeterminate-Checkboxes
      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.

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGreat example! Is there an easy way to set the Background Image? Pin
Tim8w8-Apr-22 7:45
Tim8w8-Apr-22 7:45 
GeneralLot of flicker on re-size Pin
surya chundru14-Jan-11 10:56
surya chundru14-Jan-11 10:56 
QuestionCan this work in a x64 OS? Pin
Cody Tang30-Sep-10 17:13
professionalCody Tang30-Sep-10 17:13 
Answersorry - I don't know Pin
Mr.PoorEnglish30-Sep-10 19:48
Mr.PoorEnglish30-Sep-10 19:48 
GeneralRe: sorry - I don't know Pin
Cody Tang3-Oct-10 20:42
professionalCody Tang3-Oct-10 20:42 
Questionwhich is better control ?? Pin
kiquenet.com15-Jun-10 21:07
professionalkiquenet.com15-Jun-10 21:07 
AnswerRe: which is better control ?? [modified] Pin
Cody Tang30-Sep-10 17:19
professionalCody Tang30-Sep-10 17:19 
GeneralThree State Treeview - Part 1 not found !!! Pin
kiquenet.com15-Jun-10 20:23
professionalkiquenet.com15-Jun-10 20:23 
GeneralHas a problem when leaf nodes not checked consistently. Pin
Christo66717-May-10 12:10
Christo66717-May-10 12:10 
GeneralRe: Has a problem when leaf nodes not checked consistently (on initialization) [modified] Pin
Christo66717-May-10 13:20
Christo66717-May-10 13:20 
GeneralFixed Pin
Mr.PoorEnglish18-May-10 10:59
Mr.PoorEnglish18-May-10 10:59 
GeneralRe: Has a problem when leaf nodes not checked consistently (on initialization) Pin
Christo66718-May-10 12:28
Christo66718-May-10 12:28 
General"HerrGrottigDeutsche" Pin
Mr.PoorEnglish18-May-10 12:59
Mr.PoorEnglish18-May-10 12:59 
GeneralArgh, seems to have bugs if you rapidly double click nodes Pin
grahamoneale29-Apr-10 17:37
grahamoneale29-Apr-10 17:37 
GeneralRe: Argh, seems to have bugs if you rapidly double click nodes Pin
Mr.PoorEnglish30-Apr-10 3:10
Mr.PoorEnglish30-Apr-10 3:10 
GeneralRe: Argh, seems to have bugs if you rapidly double click nodes Pin
Mr.PoorEnglish30-Apr-10 7:37
Mr.PoorEnglish30-Apr-10 7:37 
GeneralAjaj Pin
konikula6-Jan-10 21:38
konikula6-Jan-10 21:38 
GeneralPS Pin
konikula6-Jan-10 22:01
konikula6-Jan-10 22:01 
GeneralDraw standard indeterminated checkboxes Pin
The_Chosen_One26-Nov-09 2:44
The_Chosen_One26-Nov-09 2:44 
GeneralRe: Draw standard indeterminated checkboxes Pin
Christo66719-May-10 5:46
Christo66719-May-10 5:46 
SuggestionRe: Draw standard indeterminated checkboxes Pin
jeffyoung123430-Aug-11 23:05
jeffyoung123430-Aug-11 23:05 
General(hopefully) fixed Pin
Mr.PoorEnglish31-Aug-11 0:02
Mr.PoorEnglish31-Aug-11 0:02 
GeneralOne bug Pin
a010405022-Nov-09 2:39
a010405022-Nov-09 2:39 
GeneralRe: One bug Pin
Mr.PoorEnglish22-Nov-09 5:40
Mr.PoorEnglish22-Nov-09 5:40 
GeneralRe: One bug Pin
a010405022-Nov-09 6:59
a010405022-Nov-09 6:59 

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

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