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