| File: System\Windows\Media\VisualCollection.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Description: // The VisualCollection implementation is based on the // CLR's Lightning ArrayList implementation. using System.Collections; using MS.Internal; //------------------------------------------------------------------------------ // - There is an exception thrown inside of ConnectChild which could render // the collection inconsistent. // - Performance: RemoveRange moves and nulls entry. It is better to null out // after we moved all the items. //------------------------------------------------------------------------------ namespace System.Windows.Media { /// <summary> /// A VisualCollection is a ordered collection of Visuals. /// </summary> /// <remarks> /// A VisualCollection has implied context affinity. It is a violation to access /// the VisualCollectionfrom a different context than the owning ContainerVisual belongs /// to. /// </remarks> public sealed class VisualCollection : ICollection { private Visual[] _items; private int _size; private Visual _owner; // We reserve bit 1 to keep track of readonly state. Bits // 32..2 are used for our version counter. // // Version RO // +----------------------------------+---+ // | bit 32..2 | 1 | // +----------------------------------+---+ // private uint _data; private const int c_defaultCapacity = 4; private const float c_growFactor = 1.5f; internal int InternalCount { get { return _size; } } /// <summary> /// Returns a reference to the internal Visual children array. /// </summary> /// <remarks> /// This array should never given out. /// It is only used for internal code /// to enumerate through the children. /// </remarks> internal Visual[] InternalArray { get { return _items; } } /// <summary> /// Creates a VisualCollection. /// </summary> public VisualCollection(Visual parent) { ArgumentNullException.ThrowIfNull(parent); _owner = parent; } internal void VerifyAPIReadOnly() { Debug.Assert(_owner != null); _owner.VerifyAPIReadOnly(); } internal void VerifyAPIReadOnly(Visual other) { Debug.Assert(_owner != null); _owner.VerifyAPIReadOnly(other); } internal void VerifyAPIReadWrite() { Debug.Assert(_owner != null); _owner.VerifyAPIReadWrite(); VerifyNotReadOnly(); } internal void VerifyAPIReadWrite(Visual other) { Debug.Assert(_owner != null); _owner.VerifyAPIReadWrite(other); VerifyNotReadOnly(); } internal void VerifyNotReadOnly() { if (IsReadOnlyInternal) { throw new InvalidOperationException(SR.VisualCollection_ReadOnly); } } /// <summary> /// Gets the number of elements in the collection. /// </summary> public int Count { get { VerifyAPIReadOnly(); return InternalCount; } } /// <summary> /// True if the collection allows modifications, otherwise false. /// </summary> public bool IsReadOnly { get { VerifyAPIReadOnly(); return IsReadOnlyInternal; } } /// <summary> /// Gets a value indicating whether access to the ICollection /// is synchronized (thread-safe). /// </summary> public bool IsSynchronized { get { VerifyAPIReadOnly(); return false; } } /// <summary> /// Gets an object that can be used to synchronize access /// to the ICollection. /// /// ??? Figure out what we need to return here. We do have context /// affinity which renders this property useless. /// /// ArrayList returns "this". I am still not sure what this is /// used for. Check! /// </summary> public object SyncRoot { get { VerifyAPIReadOnly(); return this; } } /// <summary> /// Copies the Visual collection to the specified array starting at the specified index. /// </summary> public void CopyTo(Array array, int index) { VerifyAPIReadOnly(); ArgumentNullException.ThrowIfNull(array); if (array.Rank != 1) { throw new ArgumentException(SR.Collection_BadRank); } ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThan(index, array.Length - _size); // System.Array does not have a CopyTo method that takes a count. Therefore // the loop is programmed here out. for (int i=0; i < _size; i++) { array.SetValue(_items[i], i+index); } } /// <summary> /// Copies the Visual collection to the specified array starting at the specified index. /// </summary> public void CopyTo(Visual[] array, int index) { // Remark: This is the strongly typed version of the ICollection.CopyTo method. // FXCop requires us to implement this method. VerifyAPIReadOnly(); ArgumentNullException.ThrowIfNull(array); ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThan(index, array.Length - _size); // System.Array does not have a CopyTo method that takes a count. Therefore // the loop is programmed here out. for (int i=0; i < _size; i++) { array[i+index] = _items[i]; } } // ---------------------------------------------------------------- // ArrayList like operations for the VisualCollection // ---------------------------------------------------------------- /// <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> private void EnsureCapacity(int min) { if (InternalCapacity < min) { InternalCapacity = Math.Max(min, (int)(InternalCapacity * c_growFactor)); } } /// <summary> /// InternalCapacity sets/gets the Capacity of the collection. /// </summary> internal int InternalCapacity { get { return _items != null ? _items.Length : 0; } set { int currentCapacity = _items != null ? _items.Length : 0; if (value != currentCapacity) { if (value < _size) { throw new ArgumentOutOfRangeException(nameof(value), SR.VisualCollection_NotEnoughCapacity); } if (value > 0) { Visual[] newItems = new Visual[value]; if (_size > 0) { Debug.Assert(_items != null); Array.Copy(_items, 0, newItems, 0, _size); } _items = newItems; } else { Debug.Assert(value == 0, "There shouldn't be a case where value != 0."); Debug.Assert(_size == 0, "Size must be 0 here."); _items = null; } } } } /// <summary> /// Gets or sets the number of elements that the VisualCollection can contain. /// </summary> /// <value> /// The number of elements that the VisualCollection can contain. /// </value> /// <remarks> /// Capacity is the number of elements that the VisualCollection is capable of storing. /// Count is the number of Visuals that are actually in the VisualCollection. /// /// Capacity is always greater than or equal to Count. If Count exceeds /// Capacity while adding elements, the capacity of the VisualCollection is increased. /// /// By default the capacity is 0. /// </remarks> /// <exception cref="ArgumentOutOfRangeException">Capacity is set to a value that is less than Count.</exception> public int Capacity { get { VerifyAPIReadOnly(); return InternalCapacity; } set { VerifyAPIReadWrite(); InternalCapacity = value; } } /// <summary> /// Indexer for the VisualCollection. Gets or sets the Visual stored at the /// zero-based index of the VisualCollection. /// </summary> /// <remarks>This property provides the ability to access a specific Visual in the /// VisualCollection by using the following systax: <c>myVisualCollection[index]</c>.</remarks> /// <exception cref="ArgumentOutOfRangeException"><c>index</c> is less than zero -or- <c>index</c> is equal to or greater than Count.</exception> /// <exception cref="ArgumentException">If the new child has already a parent or if the slot a the specified index is not null.</exception> public Visual this[int index] { get { // We should likely skip the context checks here for performance reasons. // The guy who gets the Visual won't be able to access the Visual anyway if he is in the wrong context. // MediaSystem.VerifyContext(_owner); ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size); return _items[index]; } set { VerifyAPIReadWrite(value); ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size); Visual child = _items[index]; if ((value == null) && (child != null)) { DisconnectChild(index); } else if (value != null) { if (child != null) { throw new System.ArgumentException(SR.VisualCollection_EntryInUse); } if ((value._parent != null) // Only a visual that isn't a visual parent or || value.IsRootElement) // are a root node of a visual target can be set into the collection. { throw new System.ArgumentException(SR.VisualCollection_VisualHasParent); } ConnectChild(index, value); } } } /// <summary> /// Sets the specified visual at the specified index into the child /// collection. It also corrects the parent. /// Note that the function requires that _item[index] == null and it /// also requires that the passed in child is not connected to another Visual. /// </summary> /// <exception cref="ArgumentException">If the new child has already a parent or if the slot a the specified index is not null.</exception> private void ConnectChild(int index, Visual value) { // // -- Approved By The Core Team -- // // Do not allow foreign threads to change the tree. // (This is a noop if this object is not assigned to a Dispatcher.) // // We also need to ensure that the tree is homogenous with respect // to the dispatchers that the elements belong to. // _owner.VerifyAccess(); value.VerifyAccess(); // It is invalid to modify the children collection that we // might be iterating during a property invalidation tree walk. if (_owner.IsVisualChildrenIterationInProgress) { throw new InvalidOperationException(SR.CannotModifyVisualChildrenDuringTreeWalk); } Debug.Assert(value != null); Debug.Assert(_items[index] == null); Debug.Assert(value._parent == null); Debug.Assert(!value.IsRootElement); value._parentIndex = index; _items[index] = value; IncrementVersion(); // Notify the Visual tree about the children changes. _owner.InternalAddVisualChild(value); } /// <summary> /// Disconnects a child. /// </summary> private void DisconnectChild(int index) { Debug.Assert(_items[index] != null); Visual child = _items[index]; // // -- Approved By The Core Team -- // // Do not allow foreign threads to change the tree. // (This is a noop if this object is not assigned to a Dispatcher.) // child.VerifyAccess(); Visual oldParent = VisualTreeHelper.GetContainingVisual2D(child._parent); int oldParentIndex = child._parentIndex; // It is invalid to modify the children collection that we // might be iterating during a property invalidation tree walk. if (oldParent.IsVisualChildrenIterationInProgress) { throw new InvalidOperationException(SR.CannotModifyVisualChildrenDuringTreeWalk); } _items[index] = null; #if DEBUG child._parentIndex = -1; #endif IncrementVersion(); _owner.InternalRemoveVisualChild(child); } /// <summary> /// Appends a Visual to the end of the VisualCollection. /// </summary> /// <param name="visual">The Visual to be added to the end of the VisualCollection.</param> /// <returns>The VisualCollection index at which the Visual has been added.</returns> /// <remarks>Adding a null is allowed.</remarks> /// <exception cref="ArgumentException">If the new child has already a parent.</exception> public int Add(Visual visual) { VerifyAPIReadWrite(visual); if ((visual != null) && ((visual._parent != null) // Only visuals that are not connected to another tree || visual.IsRootElement)) // or a visual target can be added. { throw new System.ArgumentException(SR.VisualCollection_VisualHasParent); } if ((_items == null) || (_size == _items.Length)) { EnsureCapacity(_size+1); } int addedPosition = _size++; Debug.Assert(_items[addedPosition] == null); if (visual != null) { ConnectChild(addedPosition, visual); } IncrementVersion(); return addedPosition; } /// <summary> /// Returns the zero-based index of the Visual. If the Visual is not /// in the VisualCollection -1 is returned. If null is passed to the method, the index /// of the first entry with null is returned. If there is no null entry -1 is returned. /// </summary> /// <param name="visual">The Visual to locate in the VisualCollection.</param> /// <remark>Runtime of this method is constant if the argument is not null. If the argument is /// null, the runtime of this method is linear in the size of the collection. /// </remark> public int IndexOf(Visual visual) { VerifyAPIReadOnly(); if (visual == null) { // If the passed in argument is null, we find the first index with a null // entry and return it. for (int i = 0; i < _size; i++) { if (_items[i] == null) { return i; } } // No null entry found, return -1. return -1; } else if (visual._parent != _owner) { return -1; } else { return visual._parentIndex; } } /// <summary> /// Removes the specified visual from the VisualCollection. /// </summary> /// <param name="visual">The Visual to remove from the VisualCollection.</param> /// <remarks> /// The Visuals that follow the removed Visuals move up to occupy /// the vacated spot. The indexes of the Visuals that are moved are /// also updated. /// /// If visual is null then the first null entry is removed. Note that removing /// a null entry is linear in the size of the collection. /// </remarks> public void Remove(Visual visual) { VerifyAPIReadWrite(visual); InternalRemove(visual); } private void InternalRemove(Visual visual) { int indexToRemove = -1; if (visual != null) { if (visual._parent != _owner) { // If the Visual is not in this collection we silently return without // failing. This is the same behavior that ArrayList implements. See // also Windows OS return; } Debug.Assert(visual._parent != null); indexToRemove = visual._parentIndex; DisconnectChild(indexToRemove); } else { // This is the case where visual == null. We then remove the first null // entry. for (int i = 0; i < _size; i++) { if (_items[i] == null) { indexToRemove = i; break; } } } if (indexToRemove != -1) { --_size; for (int i = indexToRemove; i < _size; i++) { Visual child = _items[i+1]; child?._parentIndex = i; _items[i] = child; } _items[_size] = null; } } private uint Version { get { // >> 1 because bit 1 is our read-only flag. See comments // on the _data field. return _data >> 1; } } private void IncrementVersion() { // += 2 because bit 1 is our read-only flag. Explicitly unchecked // because we expect this number to "roll over" after 2 billion calls. // See comments on _data field. unchecked { _data += 2; } } private bool IsReadOnlyInternal { get { // Bit 1 is our read-only flag. See comments on the _data field. return (_data & 0x01) == 0x01; } } // Puts the collection into a ReadOnly state. Viewport3DVisual does this // on construction to prevent the addition of 2D children. internal void SetReadOnly() { // Bit 1 is our read-only flag. See comments on the _data field. _data |= 0x01; } /// <summary> /// Determines whether a visual is in the VisualCollection. /// </summary> public bool Contains(Visual visual) { VerifyAPIReadOnly(visual); if (visual == null) { for (int i=0; i < _size; i++) { if (_items[i] == null) { return true; } } return false; } else { return (visual._parent == _owner); } } /// <summary> /// Removes all elements from the VisualCollection. /// </summary> /// <remarks> /// Count is set to zero. Capacity remains unchanged. /// To reset the capacity of the VisualCollection, /// set the Capacity property directly. /// </remarks> public void Clear() { VerifyAPIReadWrite(); for (int i=0; i < _size; i++) { if (_items[i] != null) { Debug.Assert(_items[i]._parent == _owner); DisconnectChild(i); } _items[i] = null; } _size = 0; IncrementVersion(); } /// <summary> /// Inserts an element into the VisualCollection at the specified index. /// </summary> /// <param name="index">The zero-based index at which value should be inserted.</param> /// <param name="visual">The Visual to insert. </param> /// <exception cref="ArgumentOutOfRangeException"> /// index is less than zero. /// /// -or- /// /// index is greater than Count. /// </exception> /// <remarks> /// If Count already equals Capacity, the capacity of the /// VisualCollection is increased before the new Visual /// is inserted. /// /// If index is equal to Count, value is added to the /// end of VisualCollection. /// /// The Visuals that follow the insertion point move down to /// accommodate the new Visual. The indexes of the Visuals that are /// moved are also updated. /// </remarks> public void Insert(int index, Visual visual) { VerifyAPIReadWrite(visual); ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThan(index, _size); if ((visual != null) && ((visual._parent != null) // Only visuals that are not connected to another tree || visual.IsRootElement)) // or a visual target can be added. { throw new System.ArgumentException(SR.VisualCollection_VisualHasParent); } if ((_items == null) || (_size == _items.Length)) { EnsureCapacity(_size + 1); } for (int i = _size-1; i >= index; i--) { Visual child = _items[i]; child?._parentIndex = i+1; _items[i+1] = child; } _items[index] = null; _size++; if (visual != null) { ConnectChild(index, visual); } // Note SetVisual that increments the version to ensure proper enumerator // functionality. } /// <summary> /// Removes the Visual at the specified index. /// </summary> /// <param name="index">The zero-based index of the visual to remove.</param> /// <exception cref="ArgumentOutOfRangeException">index is less than zero /// - or - index is equal or greater than count.</exception> /// <remarks> /// The Visuals that follow the removed Visuals move up to occupy /// the vacated spot. The indexes of the Visuals that are moved are /// also updated. /// </remarks> public void RemoveAt(int index) { VerifyAPIReadWrite(); ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size); InternalRemove(_items[index]); } /// <summary> /// Removes a range of Visuals from the VisualCollection. /// </summary> /// <param name="index">The zero-based index of the range /// of elements to remove</param> /// <param name="count">The number of elements to remove.</param> /// <exception cref="ArgumentOutOfRangeException"> /// index is less than zero. /// -or- /// count is less than zero. /// </exception> /// <exception cref="ArgumentException"> /// index and count do not denote a valid range of elements in the VisualCollection. /// </exception> /// <remarks> /// The Visuals that follow the removed Visuals move up to occupy /// the vacated spot. The indexes of the Visuals that are moved are /// also updated. /// </remarks> public void RemoveRange(int index, int count) { VerifyAPIReadWrite(); // Do we really need this extra check index >= _size. ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfNegative(count); ArgumentOutOfRangeException.ThrowIfGreaterThan(count, _size - index); if (count > 0) { for (int i = index; i < index + count; i++) { if (_items[i] != null) { DisconnectChild(i); _items[i] = null; } } _size -= count; for (int i = index; i < _size; i++) { Visual child = _items[i + count]; child?._parentIndex = i; _items[i] = child; _items[i + count] = null; } IncrementVersion(); // Incrementing version number here to be consistent with the ArrayList // implementation. } } /// <summary> /// Moves a child inside this collection to right before the given sibling. Avoids unparenting / reparenting costs. /// This is a dangerous internal method as it moves children positions without notifying any external code. /// If the given sibling is null it moves the item to the end of the collection. /// </summary> /// <param name="visual"></param> /// <param name="destination"></param> internal void Move(Visual visual, Visual destination) { int newIndex; int oldIndex; Invariant.Assert(visual != null, "we don't support moving a null visual"); if (visual._parent == _owner) { oldIndex = visual._parentIndex; newIndex = destination != null ? destination._parentIndex : _size; Debug.Assert(visual._parent != null); Debug.Assert(destination == null || destination._parent == visual._parent); Debug.Assert(newIndex >= 0 && newIndex <= _size, "New index is invalid"); if (oldIndex != newIndex) { if (oldIndex < newIndex) { // move items left to right // source Visual will get the index of one before the destination Visual newIndex--; for (int i = oldIndex; i < newIndex; i++) { Visual child = _items[i + 1]; child?._parentIndex = i; _items[i] = child; } } else { // move items right to left // source visual will get the index of the destination Visual, which will in turn // be pushed to the right. for (int i = oldIndex; i > newIndex; i--) { Visual child = _items[i - 1]; child?._parentIndex = i; _items[i] = child; } } visual._parentIndex = newIndex; _items[newIndex] = visual; } } return; } // ---------------------------------------------------------------- // IEnumerable Interface // ---------------------------------------------------------------- /// <summary> /// Returns an enumerator that can iterate through the VisualCollection. /// </summary> /// <returns>Enumerator that enumerates the VisualCollection in order.</returns> IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// <summary> /// Returns an enumerator that can iterate through the VisualCollection. /// </summary> /// <returns>Enumerator that enumerates the VisualCollection in order.</returns> public Enumerator GetEnumerator() { VerifyAPIReadOnly(); return new Enumerator(this); } /// <summary> /// This is a simple VisualCollection enumerator that is based on /// the ArrayListEnumeratorSimple that is used for ArrayLists. /// /// The following comment is from the CLR people: /// For a straightforward enumeration of the entire ArrayList, /// this is faster, because it's smaller. Benchmarks showed /// this. /// </summary> public struct Enumerator : IEnumerator { private VisualCollection _collection; private int _index; // -1 means not started. -2 means that it reached the end. private uint _version; private Visual _currentElement; internal Enumerator(VisualCollection collection) { _collection = collection; _index = -1; // not started. _version = _collection.Version; _currentElement = null; } /// <summary> /// Advances the enumerator to the next element of the collection. /// </summary> public bool MoveNext() { _collection.VerifyAPIReadOnly(); if (_version == _collection.Version) { if ((_index > -2) && (_index < (_collection.InternalCount - 1))) { _index++; _currentElement = _collection[_index]; return true; } else { _currentElement = null; _index = -2; // -2 <=> reached the end. return false; } } else { throw new InvalidOperationException(SR.Enumerator_CollectionChanged); } } /// <summary> /// Gets the current Visual. /// </summary> object IEnumerator.Current { get { return this.Current; } } /// <summary> /// Gets the current Visual. /// </summary> public Visual Current { get { if (_index < 0) { if (_index == -1) { // Not started. throw new InvalidOperationException(SR.Enumerator_NotStarted); } else { // Reached the end. Debug.Assert(_index == -2); throw new InvalidOperationException(SR.Enumerator_ReachedEnd); } } return _currentElement; } } /// <summary> /// Sets the enumerator to its initial position, which is before the first element in the collection. /// </summary> public void Reset() { _collection.VerifyAPIReadOnly(); if (_version != _collection.Version) throw new InvalidOperationException(SR.Enumerator_CollectionChanged); _index = -1; // not started. } } } }