1 namespace System.Web.UI.WebControls {
2     using System;
3     using System.Collections;
4     using System.Collections.Generic;
5     using System.ComponentModel;
6     using System.Globalization;
7     using System.Text;
8     using System.Web.UI;
9 
10     public sealed class TreeNodeCollection : ICollection, IStateManager {
11         private List<TreeNode> _list;
12         private TreeNode _owner;
13         private bool _updateParent;
14         private int _version;
15 
16         private bool _isTrackingViewState;
17 
18         private List<LogItem> _log;
19 
20 
TreeNodeCollection()21         public TreeNodeCollection() : this(null, true) {
22         }
23 
24 
TreeNodeCollection(TreeNode owner)25         public TreeNodeCollection(TreeNode owner) : this(owner, true) {
26         }
27 
TreeNodeCollection(TreeNode owner, bool updateParent)28         internal TreeNodeCollection(TreeNode owner, bool updateParent) {
29             _owner = owner;
30             _list = new List<TreeNode>();
31             _updateParent = updateParent;
32         }
33 
34 
35         public int Count {
36             get {
37                 return _list.Count;
38             }
39         }
40 
41 
42         public bool IsSynchronized {
43             get {
44                 return ((ICollection)_list).IsSynchronized;
45             }
46         }
47 
48         private List<LogItem> Log {
49             get {
50                 if (_log == null) {
51                     _log = new List<LogItem>();
52                 }
53                 return _log;
54             }
55         }
56 
57 
58         public object SyncRoot {
59             get {
60                 return ((ICollection)_list).SyncRoot;
61             }
62         }
63 
64 
65         public TreeNode this[int index] {
66             get {
67                 return _list[index];
68             }
69         }
70 
71 
Add(TreeNode child)72         public void Add(TreeNode child) {
73             AddAt(Count, child);
74         }
75 
76 
AddAt(int index, TreeNode child)77         public void AddAt(int index, TreeNode child) {
78             if (child == null) {
79                 throw new ArgumentNullException("child");
80             }
81 
82             if (_updateParent) {
83                 if (child.Owner != null && child.Parent == null) {
84                     child.Owner.Nodes.Remove(child);
85                 }
86                 if (child.Parent != null) {
87                     child.Parent.ChildNodes.Remove(child);
88                 }
89                 if (_owner != null) {
90                     child.SetParent(_owner);
91                     child.SetOwner(_owner.Owner);
92                 }
93             }
94 
95             _list.Insert(index, child);
96             _version++;
97 
98             if (_isTrackingViewState) {
99                 ((IStateManager)child).TrackViewState();
100                 child.SetDirty();
101             }
102             Log.Add(new LogItem(LogItemType.Insert, index, _isTrackingViewState));
103         }
104 
105 
Clear()106         public void Clear() {
107             if (this.Count == 0) return;
108             if (_owner != null) {
109                 TreeView owner = _owner.Owner;
110                 if (owner != null) {
111                     // Clear checked nodes if necessary
112                     if (owner.CheckedNodes.Count != 0) {
113                         owner.CheckedNodes.Clear();
114                     }
115                     TreeNode current = owner.SelectedNode;
116                     // Check if the selected item is under this collection
117                     while (current != null) {
118                         if (this.Contains(current)) {
119                             owner.SetSelectedNode(null);
120                             break;
121                         }
122                         current = current.Parent;
123                     }
124                 }
125             }
126             foreach (TreeNode node in _list) {
127                 node.SetParent(null);
128             }
129 
130             _list.Clear();
131             _version++;
132             if (_isTrackingViewState) {
133                 // Clearing invalidates all previous log entries, so we can just clear them out and save some space
134                 Log.Clear();
135             }
136             Log.Add(new LogItem(LogItemType.Clear, 0, _isTrackingViewState));
137         }
138 
139 
CopyTo(TreeNode[] nodeArray, int index)140         public void CopyTo(TreeNode[] nodeArray, int index) {
141             ((ICollection)this).CopyTo(nodeArray, index);
142         }
143 
144 
Contains(TreeNode c)145         public bool Contains(TreeNode c) {
146             return _list.Contains(c);
147         }
148 
FindNode(string[] path, int pos)149         internal TreeNode FindNode(string[] path, int pos) {
150             if (pos == path.Length) {
151                 return _owner;
152             }
153 
154             string pathPart = TreeView.UnEscape(path[pos]);
155             for (int i = 0; i < Count; i++) {
156                 TreeNode node = this[i];
157                 if (node.Value == pathPart) {
158                     return node.ChildNodes.FindNode(path, pos + 1);
159                 }
160             }
161 
162             return null;
163         }
164 
165 
GetEnumerator()166         public IEnumerator GetEnumerator() {
167             return new TreeNodeCollectionEnumerator(this);
168         }
169 
170 
IndexOf(TreeNode value)171         public int IndexOf(TreeNode value) {
172             return _list.IndexOf(value);
173         }
174 
175 
Remove(TreeNode value)176         public void Remove(TreeNode value) {
177             if (value == null) {
178                 throw new ArgumentNullException("value");
179             }
180 
181             int index = _list.IndexOf(value);
182             if (index != -1) {
183                 RemoveAt(index);
184             }
185         }
186 
187 
RemoveAt(int index)188         public void RemoveAt(int index) {
189             TreeNode node = _list[index];
190             if (_updateParent) {
191                 TreeView owner = node.Owner;
192                 if (owner != null) {
193                     if (owner.CheckedNodes.Count != 0) {
194                         // We have to scan the whole tree of subnodes to remove any checked nodes
195                         // (and unselect the selected node if it is a descendant).
196                         // That could badly hurt performance, except that removing a node is a pretty
197                         // exceptional event.
198                         UnCheckUnSelectRecursive(node);
199                     }
200                     else {
201                         // otherwise, we can just climb the tree up from the selected node
202                         // to see if it is a descendant of the removed node.
203                         TreeNode current = owner.SelectedNode;
204                         // Check if the selected item is under this collection
205                         while (current != null) {
206                             if (current == node) {
207                                 owner.SetSelectedNode(null);
208                                 break;
209                             }
210                             current = current.Parent;
211                         }
212                     }
213                 }
214                 node.SetParent(null);
215             }
216 
217             _list.RemoveAt(index);
218             _version++;
219             Log.Add(new LogItem(LogItemType.Remove, index, _isTrackingViewState));
220         }
221 
SetDirty()222         internal void SetDirty() {
223             foreach (LogItem item in Log) {
224                 item.Tracked = true;
225             }
226             for (int i = 0; i < Count; i++) {
227                 this[i].SetDirty();
228             }
229         }
230 
UnCheckUnSelectRecursive(TreeNode node)231         private static void UnCheckUnSelectRecursive(TreeNode node) {
232             TreeNodeCollection checkedNodes = node.Owner.CheckedNodes;
233             if (node.Checked) {
234                 checkedNodes.Remove(node);
235             }
236             TreeNode selectedNode = node.Owner.SelectedNode;
237             if (node == selectedNode) {
238                 node.Owner.SetSelectedNode(null);
239                 selectedNode = null;
240             }
241             // Only recurse if there could be some more work to do
242             if (selectedNode != null || checkedNodes.Count != 0) {
243                 foreach (TreeNode child in node.ChildNodes) {
244                     UnCheckUnSelectRecursive(child);
245                 }
246             }
247         }
248 
249         #region ICollection implementation
250 
ICollection.CopyTo(Array array, int index)251         void ICollection.CopyTo(Array array, int index) {
252             if (!(array is TreeNode[])) {
253                 throw new ArgumentException(SR.GetString(SR.TreeNodeCollection_InvalidArrayType), "array");
254             }
255             _list.CopyTo((TreeNode[])array, index);
256         }
257         #endregion
258 
259         #region IStateManager implementation
260 
261         /// <internalonly/>
262         bool IStateManager.IsTrackingViewState {
263             get {
264                 return _isTrackingViewState;
265             }
266         }
267 
268 
269         /// <internalonly/>
IStateManager.LoadViewState(object state)270         void IStateManager.LoadViewState(object state) {
271             object[] nodeState = (object[])state;
272             if (nodeState != null) {
273                 if (nodeState[0] != null) {
274                     string logString = (string)nodeState[0];
275                     // Process each log entry
276                     string[] items = logString.Split(',');
277                     for (int i = 0; i < items.Length; i++) {
278                         string[] parts = items[i].Split(':');
279                         LogItemType type = (LogItemType)Int32.Parse(parts[0], CultureInfo.InvariantCulture);
280                         int index = Int32.Parse(parts[1], CultureInfo.InvariantCulture);
281 
282                         if (type == LogItemType.Insert) {
283                             if (_owner != null && _owner.Owner != null) {
284                                 AddAt(index, _owner.Owner.CreateNode());
285                             }
286                             else {
287                                 AddAt(index, new TreeNode());
288                             }
289                         }
290                         else if (type == LogItemType.Remove) {
291                             RemoveAt(index);
292                         }
293                         else if (type == LogItemType.Clear) {
294                             Clear();
295                         }
296                     }
297                 }
298 
299                 for (int i = 0; i < nodeState.Length - 1; i++) {
300                     if ((nodeState[i + 1] != null) && (this[i] != null)) {
301                         ((IStateManager)this[i]).LoadViewState(nodeState[i + 1]);
302                     }
303                 }
304             }
305         }
306 
307 
308         /// <internalonly/>
IStateManager.SaveViewState()309         object IStateManager.SaveViewState() {
310             object[] nodes = new object[Count + 1];
311 
312             bool hasViewState = false;
313 
314             if ((_log != null) && (_log.Count > 0)) {
315                 // Construct a string representation of the log, delimiting entries with commas
316                 // and seperator command and index with a colon
317                 StringBuilder builder = new StringBuilder();
318                 int realLogCount = 0;
319                 for (int i = 0; i < _log.Count; i++) {
320                     LogItem item = _log[i];
321                     if (item.Tracked) {
322                         builder.Append((int)item.Type);
323                         builder.Append(":");
324                         builder.Append(item.Index);
325                         if (i < (_log.Count - 1)) {
326                             builder.Append(",");
327                         }
328 
329                         realLogCount++;
330                     }
331                 }
332 
333                 if (realLogCount > 0) {
334                     nodes[0] = builder.ToString();
335                     hasViewState = true;
336                 }
337             }
338 
339             for (int i = 0; i < Count; i++) {
340                 nodes[i + 1] = ((IStateManager)this[i]).SaveViewState();
341                 if (nodes[i + 1] != null) {
342                     hasViewState = true;
343                 }
344             }
345 
346             return (hasViewState ? nodes : null);
347         }
348 
349 
350         /// <internalonly/>
IStateManager.TrackViewState()351         void IStateManager.TrackViewState() {
352             _isTrackingViewState = true;
353             for (int i = 0; i < Count; i++) {
354                 ((IStateManager)this[i]).TrackViewState();
355             }
356         }
357         #endregion
358 
359         /// <devdoc>
360         ///     Convenience class for storing and using log entries.
361         /// </devdoc>
362         private class LogItem {
363             private LogItemType _type;
364             private int _index;
365             private bool _tracked;
366 
LogItem(LogItemType type, int index, bool tracked)367             public LogItem(LogItemType type, int index, bool tracked) {
368                 _type = type;
369                 _index = index;
370                 _tracked = tracked;
371             }
372 
373             public int Index {
374                 get {
375                     return _index;
376                 }
377             }
378 
379             public bool Tracked {
380                 get {
381                     return _tracked;
382                 }
383                 set {
384                     _tracked = value;
385                 }
386             }
387 
388             public LogItemType Type {
389                 get {
390                     return _type;
391                 }
392             }
393 
394         }
395 
396         /// <devdoc>
397         ///     Convenience enumeration for identifying log commands
398         /// </devdoc>
399         private enum LogItemType {
400             Insert = 0,
401             Remove = 1,
402             Clear = 2
403         }
404 
405         // This is a copy of the ArrayListEnumeratorSimple in ArrayList.cs
406         private class TreeNodeCollectionEnumerator : IEnumerator {
407             private TreeNodeCollection list;
408             private int index;
409             private int version;
410             private TreeNode currentElement;
411 
TreeNodeCollectionEnumerator(TreeNodeCollection list)412             internal TreeNodeCollectionEnumerator(TreeNodeCollection list) {
413                 this.list = list;
414                 this.index = -1;
415                 version = list._version;
416             }
417 
MoveNext()418             public bool MoveNext() {
419                 if (version != list._version)
420                     throw new InvalidOperationException(SR.GetString(SR.ListEnumVersionMismatch));
421 
422                 if (index < (list.Count - 1)) {
423                     index++;
424                     currentElement = list[index];
425                     return true;
426                 }
427                 else
428                     index = list.Count;
429                 return false;
430             }
431 
432             object IEnumerator.Current {
433                 get {
434                     return Current;
435                 }
436             }
437 
438             public TreeNode Current {
439                 get {
440                     if (index == -1)
441                         throw new InvalidOperationException(SR.GetString(SR.ListEnumCurrentOutOfRange));
442                     if (index >= list.Count)
443                         throw new InvalidOperationException(SR.GetString(SR.ListEnumCurrentOutOfRange));
444                     return currentElement;
445                 }
446             }
447 
Reset()448             public void Reset() {
449                 if (version != list._version)
450                     throw new InvalidOperationException(SR.GetString(SR.ListEnumVersionMismatch));
451                 currentElement = null;
452                 index = -1;
453             }
454         }
455     }
456 }
457