forked from jamisonr/DBMulticast
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTriStateTreeView.cs
More file actions
224 lines (188 loc) · 8.84 KB
/
TriStateTreeView.cs
File metadata and controls
224 lines (188 loc) · 8.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// Copyright (CPOL) 2011 RikTheVeggie - see http://www.codeproject.com/info/cpol10.aspx
// Tri-State Tree View http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=202435
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace DBMulticast
{
// <summary>
// A Tri-State TreeView designed for on-demand populating of the tree
// </summary>
// <remarks>
// 'Mixed' nodes retain their checked state, meaning they can be checked or unchecked according to their current state
// Tree can be navigated by keyboard (cursor keys & space)
// No need to do anything special in calling code
// </remarks>
/// <summary>
/// Provides a tree view
/// control supporting
/// tri-state checkboxes.
/// </summary>
public class TriStateTreeView : TreeView
{
// ~~~ fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
readonly ImageList ilStateImages;
bool bUseTriState;
bool bCheckBoxesVisible;
bool bPreventCheckEvent;
// ~~~ constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>
/// Creates a new instance
/// of this control.
/// </summary>
public TriStateTreeView()
{
CheckBoxState cbsState;
Graphics gfxCheckBox;
Bitmap bmpCheckBox;
ilStateImages = new ImageList(); // first we create our state image
cbsState = CheckBoxState.UncheckedNormal; // list and pre-init check state.
for (int i = 0; i <= 2; i++)
{ // let's iterate each tri-state
bmpCheckBox = new Bitmap(16, 16); // creating a new checkbox bitmap
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 the checkbox and...
gfxCheckBox.Save();
ilStateImages.Images.Add(bmpCheckBox); // ...adding to sate 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;
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>
/// Refreshes this
/// control.
/// </summary>
public override void Refresh()
{
Stack<TreeNode> stNodes;
TreeNode tnStacked;
base.Refresh();
if (!CheckBoxes) // nothing to do here if
return; // checkboxes are hidden.
base.CheckBoxes = false; // hide normal checkboxes...
stNodes = new Stack<TreeNode>(Nodes.Count); // create a new stack and
foreach (TreeNode tnCurrent in Nodes) // push each root node.
stNodes.Push(tnCurrent);
while (stNodes.Count > 0)
{ // let's pop node from stack,
tnStacked = stNodes.Pop(); // set correct state image
if (tnStacked.StateImageIndex == -1) // index if not already done
tnStacked.StateImageIndex = tnStacked.Checked ? 1 : 0; // and push each child to stack
for (int i = 0; i < tnStacked.Nodes.Count; i++) // too until there are no
stNodes.Push(tnStacked.Nodes[i]); // nodes left on stack.
}
}
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
Refresh();
}
protected override void OnAfterExpand(TreeViewEventArgs e)
{
base.OnAfterExpand(e);
foreach (TreeNode tnCurrent in e.Node.Nodes) // set tree state image
if (tnCurrent.StateImageIndex == -1) // to each child node...
tnCurrent.StateImageIndex = tnCurrent.Checked ? 1 : 0;
}
protected override void OnAfterCheck(TreeViewEventArgs e)
{
base.OnAfterCheck(e);
if (bPreventCheckEvent)
return;
OnNodeMouseClick(new TreeNodeMouseClickEventArgs(e.Node, MouseButtons.None, 0, 0, 0));
}
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
{
Stack<TreeNode> stNodes;
TreeNode tnBuffer;
bool bMixedState;
int iSpacing;
int iIndex;
base.OnNodeMouseClick(e);
bPreventCheckEvent = true;
iSpacing = ImageList == null ? 0 : 18; // if user clicked area
if ((e.X > e.Node.Bounds.Left - iSpacing || // *not* used by the state
e.X < e.Node.Bounds.Left - (iSpacing + 16)) && // image we can leave here.
e.Button != MouseButtons.None)
{ return; }
tnBuffer = e.Node; // buffer clicked node and
if (e.Button == MouseButtons.Left) // flip its check state.
tnBuffer.Checked = !tnBuffer.Checked;
tnBuffer.StateImageIndex = tnBuffer.Checked ? // set state image index
1 : tnBuffer.StateImageIndex; // correctly.
OnAfterCheck(new TreeViewEventArgs(tnBuffer, TreeViewAction.ByMouse));
stNodes = new Stack<TreeNode>(tnBuffer.Nodes.Count); // create a new stack and
stNodes.Push(tnBuffer); // push buffered node first.
do
{ // let's pop node from stack,
tnBuffer = stNodes.Pop(); // inherit buffered node's
tnBuffer.Checked = e.Node.Checked; // check state and push
for (int i = 0; i < tnBuffer.Nodes.Count; i++) // each child on the stack
stNodes.Push(tnBuffer.Nodes[i]); // until there is no node
} while (stNodes.Count > 0); // left.
bMixedState = false;
tnBuffer = e.Node; // re-buffer clicked node.
while (tnBuffer.Parent != null)
{ // while we get a parent we
foreach (TreeNode tnChild in tnBuffer.Parent.Nodes) // determine mixed check states
bMixedState |= (tnChild.Checked != tnBuffer.Checked | // and convert current check
tnChild.StateImageIndex == 2); // state to state image index.
iIndex = (int)Convert.ToUInt32(tnBuffer.Checked); // set parent's check state and
tnBuffer.Parent.Checked = bMixedState || (iIndex > 0); // state image in dependency
if (bMixedState) // of mixed state.
tnBuffer.Parent.StateImageIndex = CheckBoxesTriState ? 2 : 1;
else
tnBuffer.Parent.StateImageIndex = iIndex;
tnBuffer = tnBuffer.Parent; // finally buffer parent and
} // loop here.
bPreventCheckEvent = false;
}
}
}