| File: MS\Internal\Documents\ContentElementCollection.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: Generic Collection of Table related objects. // using System.Collections; using System.Windows; using System.Windows.Documents; namespace MS.Internal.Documents { /// <summary> /// A ContentElementCollection is an ordered collection of TItems. /// </summary> /// <remarks> /// ContentElementCollection provides public access for TItems /// reading and manipulating. /// </remarks> internal abstract class ContentElementCollection<TParent, TItem> : IList<TItem>, IList where TParent : TextElement, IAcceptInsertion where TItem : FrameworkContentElement, IIndexedChild<TParent> { //------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors internal ContentElementCollection(TParent owner) { Debug.Assert(owner != null); _owner = owner; Items = new TItem[DefaultCapacity]; } #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// <summary> /// <see cref="ICollection.CopyTo"/> /// </summary> /// <exception cref="ArgumentNullException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <exception cref="ArgumentException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <param name="array"><see cref="ICollection.CopyTo"/></param> /// <param name="index"><see cref="ICollection.CopyTo"/></param> public void CopyTo(Array array, int index) { ArgumentNullException.ThrowIfNull(array); if (array.Rank != 1) { throw new ArgumentException(SR.TableCollectionRankMultiDimNotSupported); } if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index), SR.TableCollectionOutOfRangeNeedNonNegNum); } if (array.Length - index < Size) { throw new ArgumentException(SR.TableCollectionInvalidOffLen); } Array.Copy(Items, 0, array, index, Size); } /// <summary> /// Strongly typed version of ICollection.CopyTo. /// </summary> /// <exception cref="ArgumentNullException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <exception cref="ArgumentException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <see cref="ICollection.CopyTo"/> /// </exception> /// <param name="array"><see cref="ICollection.CopyTo"/></param> /// <param name="index"><see cref="ICollection.CopyTo"/></param> /// <remarks> /// <see cref="ICollection.CopyTo"/> /// </remarks> public void CopyTo(TItem[] array, int index) { ArgumentNullException.ThrowIfNull(array); if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index), SR.TableCollectionOutOfRangeNeedNonNegNum); } if (array.Length - index < Size) { throw new ArgumentException(SR.TableCollectionInvalidOffLen); } Array.Copy(Items, 0, array, index, Size); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// <summary> /// <see cref="IEnumerable.GetEnumerator"/> /// </summary> internal IEnumerator GetEnumerator() { return (new ContentElementCollectionEnumeratorSimple(this)); } /// <summary> /// <see cref="IEnumerable<T>.GetEnumerator"/> /// </summary> IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator() { return (new ContentElementCollectionEnumeratorSimple(this)); } /// <summary> /// Appends a TItem to the end of the ContentElementCollection. /// </summary> /// <param name="item">The TItem to be added to the end of the ContentElementCollection.</param> /// <returns>The ContentElementCollection index at which the TItem has been added.</returns> /// <remarks>Adding a null is prohibited.</remarks> /// <exception cref="ArgumentNullException"> /// If the <c>item</c> value is null. /// </exception> /// <exception cref="ArgumentException"> /// If the new child already has a parent. /// </exception> public abstract void Add(TItem item); /// <summary> /// Removes all elements from the ContentElementCollection. /// </summary> /// <remarks> /// Count is set to zero. Capacity remains unchanged. /// To reset the capacity of the ContentElementCollection, call TrimToSize /// or set the Capacity property directly. /// </remarks> public abstract void Clear(); /// <summary> /// Determines whether a TItem is in the ContentElementCollection. /// </summary> /// <param name="item">The TItem to locate in the ContentElementCollection. /// The value can be a null reference.</param> /// <returns>true if TItem is found in the ContentElementCollection; /// otherwise, false.</returns> public bool Contains(TItem item) { if (BelongsToOwner(item)) { Debug.Assert(Items[item.Index] == item); return (true); } return (false); } /// <summary> /// Returns the zero-based index of the TItem. If the TItem is not /// in the ContentElementCollection, -1 is returned. /// </summary> /// <param name="item">The TItem to locate in the ContentElementCollection.</param> public int IndexOf(TItem item) { if (BelongsToOwner(item)) { return item.Index; } else { return (-1); } } /// <summary> /// Inserts a TItem into the ContentElementCollection at the specified index. /// </summary> /// <param name="index">The zero-based index at which value should be inserted.</param> /// <param name="item">The TItem to insert. </param> /// <exception cref="ArgumentOutOfRangeException"> /// <c>index</c>c> is less than zero. /// -or- /// <c>index</c> is greater than Count. /// </exception> /// <exception cref="ArgumentNullException"> /// If the <c>item</c> value is null. /// </exception> /// <remarks> /// If Count already equals Capacity, the capacity of the /// ContentElementCollection is increased before the new TItem is inserted. /// /// If index is equal to Count, TItem is added to the /// end of ContentElementCollection. /// /// The TItems that follow the insertion point move down to /// accommodate the new TItem. The indexes of the TItems that are /// moved are also updated. /// </remarks> public abstract void Insert(int index, TItem item); /// <summary> /// Removes the specified TItem from the ContentElementCollection. /// </summary> /// <param name="item">The TItem to remove from the ContentElementCollection.</param> /// <exception cref="ArgumentNullException"> /// If the <c>item</c> value is null. /// </exception> /// <exception cref="ArgumentException"> /// If the specified TItem is not in this collection. /// </exception> /// <remarks> /// The TItems that follow the removed TItem move up to occupy /// the vacated spot. The indices of the TItems that are moved /// also updated. /// </remarks> public abstract bool Remove(TItem item); /// <summary> /// Removes the TItem at the specified index. /// </summary> /// <param name="index">The zero-based index of the TItem to remove.</param> /// <exception cref="ArgumentOutOfRangeException"> /// <c>index</c> is less than zero /// - or - /// <c>index</c> is equal or greater than count. /// </exception> /// <remarks> /// The TItems that follow the removed TItem move up to occupy /// the vacated spot. The indices of the TItems that are moved /// also updated. /// </remarks> public abstract void RemoveAt(int index); /// <summary> /// Removes a range of TItems from the ContentElementCollection. /// </summary> /// <param name="index">The zero-based index of the range /// of TItems to remove</param> /// <param name="count">The number of TItems to remove.</param> /// <exception cref="ArgumentOutOfRangeException"> /// <c>index</c> is less than zero. /// -or- /// <c>count</c> is less than zero. /// </exception> /// <exception cref="ArgumentException"> /// <c>index</c> and <c>count</c> do not denote a valid range of TItems in the ContentElementCollection. /// </exception> /// <remarks> /// The TItems that follow the removed TItems move up to occupy /// the vacated spot. The indices of the TItems that are moved are /// also updated. /// </remarks> public abstract void RemoveRange(int index, int count); /// <summary> /// Sets the capacity to the actual number of elements in the ContentElementCollection. /// </summary> /// <remarks> /// This method can be used to minimize a ContentElementCollection's memory overhead /// if no new elements will be added to the collection. /// /// To completely clear all elements in a ContentElementCollection, call the Clear method /// before calling TrimToSize. /// </remarks> public void TrimToSize() { PrivateCapacity = Size; } #endregion Public Methods //------------------------------------------------------ // // Private Types // //------------------------------------------------------ #region Protected Types protected class ContentElementCollectionEnumeratorSimple : IEnumerator<TItem>, IEnumerator { internal ContentElementCollectionEnumeratorSimple(ContentElementCollection<TParent, TItem> collection) { Debug.Assert(collection != null); _collection = collection; _index = -1; Version = _collection.Version; _currentElement = collection; } public bool MoveNext() { if (Version != _collection.Version) { throw new InvalidOperationException(SR.EnumeratorVersionChanged); } if (_index < (_collection.Size - 1)) { _index++; _currentElement = _collection[_index]; return (true); } else { _currentElement = _collection; _index = _collection.Size; return (false); } } public TItem Current { get { if (_currentElement == _collection) { if (_index == -1) { throw new InvalidOperationException(SR.EnumeratorNotStarted); } else { throw new InvalidOperationException(SR.EnumeratorReachedEnd); } } return (TItem)_currentElement; } } /// <summary> /// <see cref="IEnumerator.Current"/> /// </summary> object IEnumerator.Current { get { return this.Current; } } public void Reset() { if (Version != _collection.Version) { throw new InvalidOperationException(SR.EnumeratorVersionChanged); } _currentElement = _collection; _index = -1; } public void Dispose() { GC.SuppressFinalize(this); } private ContentElementCollection<TParent, TItem> _collection; private int _index; protected int Version; private object _currentElement; } protected class DummyProxy : DependencyObject { } #endregion Protected Types //------------------------------------------------------------------- // // IList Members // //------------------------------------------------------------------- #region IList Members int IList.Add(object value) { ArgumentNullException.ThrowIfNull(value); TItem item = value as TItem; // <CR NOTE>: This chunk is moved to the correcponding override in TableCollumnCollection, to keep same behavior //if (item == null) //{ // throw new ArgumentException(SR.Format(SR.TableCollectionElementTypeExpected, typeof(TItem).Name), "value"); //} this.Add(item); return ((IList)this).IndexOf(item); } void IList.Clear() { this.Clear(); } bool IList.Contains(object value) { TItem item = value as TItem; if (item == null) { return false; } return this.Contains(item); } int IList.IndexOf(object value) { TItem item = value as TItem; if (item == null) { return -1; } return this.IndexOf(item); } void IList.Insert(int index, object value) { ArgumentNullException.ThrowIfNull(value); TItem newItem = value as TItem; if (newItem == null) { throw new ArgumentException(SR.Format(SR.TableCollectionElementTypeExpected, typeof(TItem).Name), nameof(value)); } this.Insert(index, newItem); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return this.IsReadOnly; } } void IList.Remove(object value) { TItem item = value as TItem; if (item == null) { return; } this.Remove(item); } void IList.RemoveAt(int index) { this.RemoveAt(index); } object IList.this[int index] { get { return this[index]; } set { ArgumentNullException.ThrowIfNull(value); TItem item = value as TItem; if (item == null) { throw new ArgumentException(SR.Format(SR.TableCollectionElementTypeExpected, typeof(TItem).Name), nameof(value)); } this[index] = item; } } #endregion IList Members //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties public abstract TItem this[int index] { get; set; } /// <summary> /// <see cref="ICollection.Count"/> /// </summary> public int Count { get { return (Size); } } /// <summary> /// <see cref="ICollection<T>.IsReadOnly"/> /// <seealso cref="IList.IsReadOnly"/> /// </summary> public bool IsReadOnly // bool IList.IsReadOnly {get;}; bool ICollection<T>.IsReadOnly {get;} { get { return false; } } /// <summary> /// <see cref="ICollection.IsSynchronized"/> /// <remarks> /// Always returns false. /// </remarks> /// </summary> public bool IsSynchronized { get { return (false); } } /// <summary> /// <see cref="ICollection.SyncRoot"/> /// </summary> public object SyncRoot { get { // Need to figure out what is the correct behavior. // current code won't do what's expected. return (this); } } /// <summary> /// Gets or sets the number of elements that the ContentElementCollection can contain. /// </summary> /// <value> /// The number of elements that the ContentElementCollection can contain. /// </value> /// <remarks> /// Capacity is the number of elements that the ContentElementCollection is capable of storing. /// Count is the number of Visuals that are actually in the ContentElementCollection. /// /// Capacity is always greater than or equal to Count. If Count exceeds /// Capacity while adding elements, the capacity of the ContentElementCollection is increased. /// /// By default the capacity is 8. /// </remarks> /// <exception cref="ArgumentOutOfRangeException"> /// Capacity is set to a value that is less than Count. /// </exception> /// <ExternalAPI/> public int Capacity { get { return PrivateCapacity; } set { PrivateCapacity = value; } } public TParent Owner { get { return _owner; } } #endregion Public Properties #region Protected Properties protected TItem[] Items { get { return _items; } private set { _items = value; } } protected int Size { get { return _size; } set { _size = value; } } protected int Version { get { return _version; } set { _version = value; } } protected int DefaultCapacity { get { return c_defaultCapacity; } } #endregion Protected Properties //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Internal Methods /// <summary> /// Ensures that the capacity of this list is at least the given minimum /// value. If the currect capacity of the list is less than min, the /// capacity is increased to min. /// </summary> internal void EnsureCapacity(int min) { if (PrivateCapacity < min) { PrivateCapacity = Math.Max(min, PrivateCapacity * 2); } } /// <summary> /// Sets the specified TItem at the specified index; /// Connects the item to the model tree; /// Notifies the TItem about the event. /// </summary> /// <exception cref="ArgumentException"> /// If the new item has already a parent or if the slot at the specified index is not null. /// </exception> /// <remarks> /// Note that the function requires that _item[index] == null and /// it also requires that the passed in item is not included into another ContentElementCollection. /// </remarks> internal abstract void PrivateConnectChild(int index, TItem item); /// <summary> /// Notifies the TItem about the event; /// Disconnects the item from the model tree; /// Sets the TItem's slot in the collection's array to null. /// </summary> internal abstract void PrivateDisconnectChild(TItem item); /// <summary> /// Removes specified TItem from the ContentElementCollection. /// </summary> /// <param name="item">TItem to remove.</param> internal void PrivateRemove(TItem item) { Debug.Assert(BelongsToOwner(item) && Items[item.Index] == item); int index = item.Index; PrivateDisconnectChild(item); --Size; for (int i = index; i < Size; ++i) { Debug.Assert(BelongsToOwner(Items[i + 1])); Items[i] = Items[i + 1]; Items[i].Index = i; } Items[Size] = null; } // helper method: return true if the item belongs to the collection's owner internal bool BelongsToOwner(TItem item) { if (item == null) return false; DependencyObject node = item.Parent; if (node is DummyProxy) { node = LogicalTreeHelper.GetParent(node); } return (node == Owner); } #endregion Internal Methods //------------------------------------------------------ // // Private Properties // //------------------------------------------------------ #region Internal Properties /// <summary> /// PrivateCapacity sets/gets the Capacity of the collection. /// </summary> internal int PrivateCapacity { get { return (Items.Length); } set { if (value != Items.Length) { if (value < Size) { throw new ArgumentOutOfRangeException(SR.TableCollectionNotEnoughCapacity); } if (value > 0) { TItem[] newItems = new TItem[value]; if (Size > 0) { Array.Copy(Items, 0, newItems, 0, Size); } Items = newItems; } else { Items = new TItem[DefaultCapacity]; } } } } #endregion Internal Properties //------------------------------------------------------ // // Protected Fields // //------------------------------------------------------ #region Protected Fields private readonly TParent _owner; // owner of the collection private TItem[] _items; // storage of items private int _size; // size of the collection private int _version; // version tracks updates in the collection protected const int c_defaultCapacity = 8; // default capacity of the collection #endregion Protected Fields } }