File: MS\Internal\Data\CompositeCollectionView.cs
Web Access
Project: src\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.
// See the LICENSE file in the project root for more information.
 
//
// Description: CompositeCollectionView provides the flattened view of an CompositeCollection.
//
// See specs at ItemsControl.mht
//              CollectionView.mht
//
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Data;
using MS.Internal;              // Invariant.Assert
using MS.Internal.Controls;
using System.Windows.Controls;
using MS.Internal.Utility;
using MS.Utility;
using MS.Internal.Hashing.PresentationFramework;    // HashHelper
 
#pragma warning disable 1634, 1691  // suppressing PreSharp warnings
 
namespace MS.Internal.Data
{
    /// <summary>
    /// CompositeCollectionView provides the flattened view of an CompositeCollection.
    /// </summary>
    internal sealed class CompositeCollectionView : CollectionView
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        // create the new CompositeCollectionView for a CompositeCollection
        internal CompositeCollectionView(CompositeCollection collection)
            : base(collection, -1 /* don't move to first */)    // base.ctor also subscribes to CollectionChanged event of CompositeCollection
        {
            _collection = collection;
            _collection.ContainedCollectionChanged += new NotifyCollectionChangedEventHandler(OnContainedCollectionChanged);
 
            // Do the equivalent of MoveCurrentToFirst(), without calling virtuals
            int currentPosition = PrivateIsEmpty ? -1 : 0;
            int count = PrivateIsEmpty ? 0 : 1;
            SetCurrent(GetItem(currentPosition, out _currentPositionX, out _currentPositionY), currentPosition, count);
        }
 
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Return the estimated number of records
        /// </summary>
        /// <remarks>
        /// Count includes the number of single item in the CompositeCollection
        /// and the counts from the collection views of any sub-collections
        /// contained in <seealso cref="CollectionContainer"/>.
        /// Empty collection containers do not add to the count.
        /// </remarks>
        public override int Count
        {
            get
            {
                // _count may also be updated through CacheCount() in
                // FindItem(), GetItem(), and OnContainedCollectionChanged()
 
                if (_count == -1)
                {
                    _count = CountDeep(_collection.Count);
                    // no need to raise PropertyChange for cache initialization
                }
                return _count;
            }
        }
 
        /// <summary>
        /// Returns true if the resulting (filtered) view is emtpy.
        /// </summary>
        /// <remarks>
        /// Checks with all the collection views of any sub-collections
        /// contained in <seealso cref="CollectionContainer"/>.
        /// </remarks>
        // This is faster than calling (Count == 0) because it stops at first item found
        public override bool IsEmpty
        {
            get { return PrivateIsEmpty; }
        }
 
        private bool PrivateIsEmpty
        {
            get
            {
                if (_count < 0)     // if count cache is invalid
                {
                    for (int i = 0; i < _collection.Count; ++i)
                    {
                        CollectionContainer cc = _collection[i] as CollectionContainer;
                        if (cc == null || cc.ViewCount != 0)    // single item or non-empty sub-collection
                        {
                            return false;
                        }
                    }
                    CacheCount(0);  // now that we know it's empty, cache it!
                }
                return (_count == 0);
            }
        }
 
        /// <summary>
        /// Return true if <seealso cref="CollectionView.CurrentItem"/> is beyond the end or the collection is empty.
        /// </summary>
        public override bool IsCurrentAfterLast
        {
            get
            {
                // REVIEW: should we return true whenever collection is empty?
                // This is bug-for-bug the same as ListCollView
                return (IsEmpty || (_currentPositionX >= _collection.Count));
            }
        }
 
        /// <summary>
        /// Return true if <seealso cref="CollectionView.CurrentItem"/> is before the beginning or the collection is empty.
        /// </summary>
        public override bool IsCurrentBeforeFirst
        {
            get
            {
                // REVIEW: should we return true whenever collection is empty?
                // This is bug-for-bug the same as ListCollView
                return (IsEmpty || (_currentPositionX < 0));
            }
        }
 
        /// <summary>
        /// Indicates whether or not this ICollectionView can do any filtering.
        /// When false, set <seealso cref="CollectionView.Filter"/> will throw an exception.
        /// </summary>
        public override bool CanFilter
        {
            get
            {
                return false;
            }
        }
 
        #endregion Public Properties
 
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// Return true if the item belongs to this view.  No assumptions are
        /// made about the item. This method will behave similarly to IList.Contains()
        /// and will do an exhaustive search through all items in the flattened view.
        /// </summary>
        public override bool Contains(object item)
        {
            return (FindItem(item, false) >= 0);
        }
 
        /// <summary>
        /// Return the index where the given item belongs
        /// </summary>
        /// <param name="item">data item</param>
        public override int IndexOf(object item)
        {
            return FindItem(item, false);
        }
 
        /// <summary>
        /// Retrieve item at the given zero-based index in this CollectionView.
        /// </summary>
        /// <remarks>
        /// <p>The index is evaluated with any SortDescriptions or Filter being set on this CollectionView.</p>
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if index is out of range
        /// </exception>
        public override object GetItemAt(int index)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(index);
 
            int positionX, positionY;
            object item = GetItem(index, out positionX, out positionY);
 
            if (item == s_afterLast)
            {
                // couldn't find item at index
                item = null;
                throw new ArgumentOutOfRangeException("index");
            }
            else
            {
                return item;
            }
        }
 
        /// <summary>
        /// Move <seealso cref="ICollectionView.CurrentItem"/> to the given item.
        /// If the item is not found, move to BeforeFirst.
        /// </summary>
        /// <param name="item">Move Current to this item.</param>
        /// <returns>true if <seealso cref="ICollectionView.CurrentItem"/> points to an item within the view.</returns>
        public override bool MoveCurrentTo(object item)
        {
            // if already on item, don't do anything
            if (ItemsControl.EqualsEx(CurrentItem, item))
            {
                // also check that we're not fooled by a false null CurrentItem
                if (item != null || IsCurrentInView)
                    return IsCurrentInView;
            }
 
            if (!IsEmpty)   // when empty, don't bother looking, and currency stays at BeforeFirst.
                FindItem(item, true);
            return IsCurrentInView;
        }
 
        /// <summary>
        /// Move <seealso cref="ICollectionView.CurrentItem"/> to the first item.
        /// </summary>
        /// <returns>true if <seealso cref="ICollectionView.CurrentItem"/> points to an item within the view.</returns>
        public override bool MoveCurrentToFirst()
        {
            if (IsEmpty)
                return false;
            return _MoveTo(0);
        }
 
        /// <summary>
        /// Move <seealso cref="ICollectionView.CurrentItem"/> to the last item.
        /// </summary>
        /// <returns>true if <seealso cref="ICollectionView.CurrentItem"/> points to an item within the view.</returns>
        public override bool MoveCurrentToLast()
        {
            bool oldIsCurrentAfterLast = IsCurrentAfterLast;
            bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
 
            int newPositionX, newPositionY;
            int lastPosition = Count - 1;
            object lastItem = GetLastItem(out newPositionX, out newPositionY);  // searches backwards
 
            if (((CurrentPosition != lastPosition) || (CurrentItem != lastItem))
                && OKToChangeCurrent())
            {
                _currentPositionX = newPositionX;
                _currentPositionY = newPositionY;
                SetCurrent(lastItem, lastPosition);
                OnCurrentChanged();
 
                if (IsCurrentAfterLast != oldIsCurrentAfterLast)
                    OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
                if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                    OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
                OnPropertyChanged(CurrentPositionPropertyName);
                OnPropertyChanged(CurrentItemPropertyName);
            }
            return IsCurrentInView;
        }
 
        /// <summary>
        /// Move <seealso cref="ICollectionView.CurrentItem"/> to the next item.
        /// </summary>
        /// <returns>true if <seealso cref="ICollectionView.CurrentItem"/> points to an item within the view.</returns>
        public override bool MoveCurrentToNext()
        {
            if (IsCurrentAfterLast)
                return false;
            return _MoveTo(CurrentPosition + 1);
        }
 
        /// <summary>
        /// Move <seealso cref="ICollectionView.CurrentItem"/> to the previous item.
        /// </summary>
        /// <returns>true if <seealso cref="ICollectionView.CurrentItem"/> points to an item within the view.</returns>
        public override bool MoveCurrentToPrevious()
        {
            if (IsCurrentBeforeFirst)
                return false;
            return _MoveTo(CurrentPosition - 1);
        }
 
        /// <summary>
        /// Move <seealso cref="CollectionView.CurrentItem"/> to the item at the given index.
        /// </summary>
        /// <param name="position">Move CurrentItem to this index</param>
        /// <returns>true if <seealso cref="CollectionView.CurrentItem"/> points to an item within the view.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// position is less than Before-First (-1) or greater than After-Last (Count)
        /// </exception>
        public override bool MoveCurrentToPosition(int position)
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(position, -1);
 
            int newPositionX, newPositionY;
            object item = GetItem(position, out newPositionX, out newPositionY);
 
            if (position != CurrentPosition || item != CurrentItem)
            {
                if (item == s_afterLast)
                {
                    item = null;
                    // check upper-bound only after GetItem() to avoid unnecessary pre-counting
                    ArgumentOutOfRangeException.ThrowIfGreaterThan(position, Count);
                }
 
                if (OKToChangeCurrent())
                {
                    bool oldIsCurrentAfterLast = IsCurrentAfterLast;
                    bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
 
                    _currentPositionX = newPositionX;
                    _currentPositionY = newPositionY;
                    SetCurrent(item, position);
                    OnCurrentChanged();
 
                    if (IsCurrentAfterLast != oldIsCurrentAfterLast)
                        OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
                    if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                        OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
                    OnPropertyChanged(CurrentPositionPropertyName);
                    OnPropertyChanged(CurrentItemPropertyName);
                }
            }
            return IsCurrentInView;
        }
 
        /// <summary>
        /// Re-create the view over the associated CompositeCollection
        /// </summary>
        /// <remarks>
        /// Since CompositeCollectionView does not support sorting and filtering,
        /// this will simply raise a Reset event to <seealso cref="INotifyCollectionChanged.CollectionChanged"/> listeners.
        /// </remarks>
        protected override void RefreshOverride()
        {
            ++_version;
 
            // tell listeners everything has changed
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
 
        #endregion Public Methods
 
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// Implementation of IEnumerable.GetEnumerator().
        /// This provides a way to enumerate the members of the collection
        /// without changing the currency.
        /// </summary>
        protected override IEnumerator GetEnumerator()
        {
            return new FlatteningEnumerator(_collection, this);
        }
 
        /// <summary>
        /// Handle CollectionChange events from CompositeCollection ("ground level").
        /// i.e. Add/Remove of single items or CollectionContainers, or Refresh
        /// </summary>
        protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
        {
            ValidateCollectionChangedEventArgs(args);
 
            bool moveCurrencyOffDeletedElement = false;
 
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                    {
                        // get the affected item (it can be a single first-level item or a CollectionContainer)
                        object item = null;
                        int startingIndex = -1;
                        if (args.Action == NotifyCollectionChangedAction.Add)
                        {
                            item = args.NewItems[0];
                            startingIndex = args.NewStartingIndex;
                        }
                        else
                        {
                            item = args.OldItems[0];
                            startingIndex = args.OldStartingIndex;
                        }
                        Debug.Assert(startingIndex >= 0, "Source composite collection failed to supply an index");
                        int index = startingIndex;
 
                        if (_traceLog != null)
                            _traceLog.Add("ProcessCollectionChanged  action = {0}  item = {1}",
                                        args.Action, TraceLog.IdFor(item));
 
                        CollectionContainer cc = item as CollectionContainer;
                        if (cc == null) // if a single item was added/removed
                        {
                            // translate the index into one that makes sense for the flat view
                            for (int k = index - 1; k >= 0; --k)
                            {
                                cc = _collection[k] as CollectionContainer;
                                if (cc != null)
                                {
                                    // count members of cc's view but not the cc itself
                                    index += cc.ViewCount - 1;
                                }
                            }
                            if (args.Action == NotifyCollectionChangedAction.Add)
                            {
                                if (_count >= 0)
                                    ++_count;
                                UpdateCurrencyAfterAdd(index, args.NewStartingIndex, true);
                            }
                            else if (args.Action == NotifyCollectionChangedAction.Remove)
                            {
                                if (_count >= 0)
                                    --_count;
                                UpdateCurrencyAfterRemove(index, args.OldStartingIndex, true);
                            }
 
                            args = new NotifyCollectionChangedEventArgs(args.Action, item, index);
                        }
                        else // else a whole collection container was added/removed
                        {
                            if (args.Action == NotifyCollectionChangedAction.Add)
                            {
                                if (_count >= 0)
                                    _count += cc.ViewCount;
                            }
                            else
                            {
                                // We only handle Add and Remove.  Make sure it's not some other action.
                                Debug.Assert(args.Action == NotifyCollectionChangedAction.Remove);
                                if (_count >= 0)
                                    _count -= cc.ViewCount;
                            }
 
                            if (startingIndex <= _currentPositionX)
                            {
                                if (args.Action == NotifyCollectionChangedAction.Add)
                                {
                                    ++_currentPositionX;
                                    SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                                }
                                else
                                {
                                    // We only handle Add and Remove.  Make sure it's not some other action.
                                    Invariant.Assert(args.Action == NotifyCollectionChangedAction.Remove);
                                    if (startingIndex == _currentPositionX)
                                    {
                                        moveCurrencyOffDeletedElement = true;
                                    }
                                    else // (args.StartingIndex < _currentPositionX)
                                    {
                                        --_currentPositionX;
                                        SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                                    }
                                }
                            }
 
                            // force refresh for all listeners
                            args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                        }
                    }
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    {
                        CollectionContainer newCollectionContainer = args.NewItems[0] as CollectionContainer;
                        CollectionContainer oldCollectionContainer = args.OldItems[0] as CollectionContainer;
                        int startingIndex = args.OldStartingIndex;
 
                        if (newCollectionContainer == null && oldCollectionContainer == null) // if a single item was added/removed
                        {
                            // translate the index into one that makes sense for the flat view
                            for (int k = startingIndex - 1; k >= 0; --k)
                            {
                                CollectionContainer cc = _collection[k] as CollectionContainer;
                                if (cc != null)
                                {
                                    // count members of cc's view but not the cc itself
                                    startingIndex += cc.ViewCount - 1;
                                }
                            }
 
                            if (startingIndex == CurrentPosition)
                                moveCurrencyOffDeletedElement = true;
 
                            args = new NotifyCollectionChangedEventArgs(args.Action, args.NewItems, args.OldItems, startingIndex);
                        }
                        else // else a whole collection container was replaced
                        {
                            if (_count >= 0)
                            {
                                _count -= oldCollectionContainer == null ? 1 : oldCollectionContainer.ViewCount;
                                _count += newCollectionContainer == null ? 1 : newCollectionContainer.ViewCount;
                            }
 
                            if (startingIndex < _currentPositionX)
                            {
                                SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                            }
                            else if (startingIndex == _currentPositionX)
                            {
                                moveCurrencyOffDeletedElement = true;
                            }
 
                            // force refresh for all listeners
                            args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                        }
                    }
                    break;
 
                case NotifyCollectionChangedAction.Move:
                    {
                        CollectionContainer oldCollectionContainer = args.OldItems[0] as CollectionContainer;
                        int oldStartingIndex = args.OldStartingIndex;
                        int newStartingIndex = args.NewStartingIndex;
 
                        if (oldCollectionContainer == null) // if a single item was added/removed
                        {
                            // no change to count for a move operation.
 
                            // translate the index into one that makes sense for the flat view
                            for (int k = oldStartingIndex - 1; k >= 0; --k)
                            {
                                CollectionContainer cc = _collection[k] as CollectionContainer;
                                if (cc != null)
                                {
                                    // count members of cc's view but not the cc itself
                                    oldStartingIndex += cc.ViewCount - 1;
                                }
                            }
 
                            // translate the index into one that makes sense for the flat view
                            for (int k = newStartingIndex - 1; k >= 0; --k)
                            {
                                CollectionContainer cc = _collection[k] as CollectionContainer;
                                if (cc != null)
                                {
                                    // count members of cc's view but not the cc itself
                                    newStartingIndex += cc.ViewCount - 1;
                                }
                            }
 
                            // if the entire move happened before or after the CurrentPosition, then
                            // there needn't be a change to currency.
                            if (oldStartingIndex == CurrentPosition)
                            {
                                moveCurrencyOffDeletedElement = true;
                            }
                            else if (newStartingIndex <= CurrentPosition && oldStartingIndex > CurrentPosition)
                            {
                                UpdateCurrencyAfterAdd(newStartingIndex, args.NewStartingIndex, true);
                            }
                            else if (oldStartingIndex < CurrentPosition && newStartingIndex >= CurrentPosition)
                            {
                                UpdateCurrencyAfterRemove(oldStartingIndex, args.OldStartingIndex, true);
                            }
 
                            args = new NotifyCollectionChangedEventArgs(args.Action, args.OldItems, newStartingIndex, oldStartingIndex);
                        }
                        else // else a whole collection container was moved
                        {
                            // no change to count for a move operation.
 
                            // if the entire move happened before or after the CurrentPosition, then
                            // there needn't be a change to currency.
                            if (oldStartingIndex == _currentPositionX)
                            {
                                moveCurrencyOffDeletedElement = true;
                            }
                            else if (newStartingIndex <= _currentPositionX && oldStartingIndex > _currentPositionX)
                            {
                                ++_currentPositionX;
                                SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                            }
                            else if (oldStartingIndex < _currentPositionX && newStartingIndex >= _currentPositionX)
                            {
                                --_currentPositionX;
                                SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                            }
 
                            // force refresh for all listeners
                            args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                        }
                    }
                    break;
 
 
                case NotifyCollectionChangedAction.Reset:
                    {
                        if (_traceLog != null)
                            _traceLog.Add("ProcessCollectionChanged  action = {0}", args.Action);
 
                        if (_collection.Count != 0)
                        {
                            //
                            //  This is to verify that a Reset event is raised IFF the CompositionCollection
                            //  was cleared.  To fully implement a Reset event otherwise can prove to be
                            //  quite complex.  For example, you must unhook the listeners to each of the
                            //  CollectionContainers that are no longer in the collection, hook up the new
                            //  ones, and figure out how to restore the currency to the correct item in
                            //  the correct sub-collection, or to BeforeFirst or AfterLast.
                            //
 
                            throw new InvalidOperationException(SR.CompositeCollectionResetOnlyOnClear);
                        }
 
                        _count = 0; // OnCollectionChanged(arg) below will raise PropChange for Count
                        if (_currentPositionX >= 0) // if current item was in view
                        {
                            OnCurrentChanging();
                            SetCurrentBeforeFirst();
                            OnCurrentChanged();
 
                            OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
                            OnPropertyChanged(CurrentPositionPropertyName);
                            OnPropertyChanged(CurrentItemPropertyName);
                        }
                    }
                    break;
 
                default:
                    throw new NotSupportedException(SR.Format(SR.UnexpectedCollectionChangeAction, args.Action));
            }
 
            ++_version;
            OnCollectionChanged(args);
 
            if (moveCurrencyOffDeletedElement)
            {
                _currentPositionY = 0;
                MoveCurrencyOffDeletedElement();
            }
 
#if DEBUG
            VerifyCurrencyIsConsistent();
#endif
        }
 
        #endregion Protected Methods
 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // collection changed event as received from the container (VIEW) of a sub-collection
        internal void OnContainedCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            ValidateCollectionChangedEventArgs(args);
 
            // Count must be invalidated instead of updated, because there might be
            // several change events raised from the same sub-collection change
            // (i.e. when the underlying collection is the basis of multiple collContainers.)
            _count = -1;
 
            int flatOldIndex = args.OldStartingIndex;
            int flatNewIndex = args.NewStartingIndex;
 
            // translate the index into one that makes sense for the flat view
            int x;
            int indexModifier = 0;
            for (x = 0; x < _collection.Count; ++x)
            {
                CollectionContainer cc = _collection[x] as CollectionContainer;
                if (cc != null)
                {
                    if (sender == cc)
                    {
                        break;
                    }
 
                    indexModifier += cc.ViewCount;
                }
                else // single item
                {
                    ++indexModifier;
                }
            }
            // if we didn't know the index to start with, we still don't
            if (args.OldStartingIndex >= 0)
                flatOldIndex += indexModifier;
            if (args.NewStartingIndex >= 0)
                flatNewIndex += indexModifier;
 
            if (x >= _collection.Count)
            {
                if (_traceLog != null)
                {
                    _traceLog.Add("Received ContainerCollectionChange from unknown sender {0}  action = {1} old item = {2}, new item = {3}",
                                    TraceLog.IdFor(sender), args.Action, TraceLog.IdFor(args.OldItems[0]), TraceLog.IdFor(args.NewItems[0]));
                    _traceLog.Add("Unhook CollectionChanged event handler from unknown sender.");
                }
 
                // Bonus: since we've spent the time looking through the whole list,
                // cache the count if we didn't have it!
                CacheCount(indexModifier);
                return;
            }
 
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    TraceContainerCollectionChange(sender, args.Action, null, args.NewItems[0]);
 
                    if (flatNewIndex < 0)
                    {
                        flatNewIndex = DeduceFlatIndexForAdd((CollectionContainer)sender, x);
                    }
 
                    UpdateCurrencyAfterAdd(flatNewIndex, x, false);
                    args = new NotifyCollectionChangedEventArgs(args.Action, args.NewItems[0], flatNewIndex);
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    TraceContainerCollectionChange(sender, args.Action, args.OldItems[0], null);
 
                    if (flatOldIndex < 0)
                    {
                        flatOldIndex = DeduceFlatIndexForRemove((CollectionContainer)sender, x, args.OldItems[0]);
                    }
 
                    UpdateCurrencyAfterRemove(flatOldIndex, x, false);
                    args = new NotifyCollectionChangedEventArgs(args.Action, args.OldItems[0], flatOldIndex);
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    TraceContainerCollectionChange(sender, args.Action, args.OldItems[0], args.NewItems[0]);
 
                    if (flatOldIndex == CurrentPosition)
                        MoveCurrencyOffDeletedElement();
                    args = new NotifyCollectionChangedEventArgs(args.Action, args.NewItems[0], args.OldItems[0], flatOldIndex);
                    break;
 
                case NotifyCollectionChangedAction.Move:
                    TraceContainerCollectionChange(sender, args.Action, args.OldItems[0], args.NewItems[0]);
 
                    if (flatOldIndex < 0)
                    {
                        flatOldIndex = DeduceFlatIndexForRemove((CollectionContainer)sender, x, args.NewItems[0]);
                    }
 
                    if (flatNewIndex < 0)
                    {
                        flatNewIndex = DeduceFlatIndexForAdd((CollectionContainer)sender, x);
                    }
 
                    UpdateCurrencyAfterMove(flatOldIndex, flatNewIndex, x, false);
                    args = new NotifyCollectionChangedEventArgs(args.Action, args.OldItems[0], flatNewIndex, flatOldIndex);
                    break;
 
                case NotifyCollectionChangedAction.Reset:
                    {
                        if (_traceLog != null)
                            _traceLog.Add("ContainerCollectionChange from {0}  action = {1}",
                                            TraceLog.IdFor(sender), args.Action);
 
                        UpdateCurrencyAfterRefresh(sender);
                    }
                    break;
 
                default:
                    throw new NotSupportedException(SR.Format(SR.UnexpectedCollectionChangeAction, args.Action));
            }
 
            ++_version;
            OnCollectionChanged(args);
        }
 
 
        // determine whether the items have reliable hash codes
        internal override bool HasReliableHashCodes()
        {
            // sample an item from each contained collection (bug 1738297)
            for (int k = 0, n = _collection.Count; k < n; ++k)
            {
                CollectionContainer cc = _collection[k] as CollectionContainer;
 
                if (cc != null)
                {
                    CollectionView cv = cc.View as CollectionView;
                    if (cv != null && !cv.HasReliableHashCodes())
                    {
                        return false;
                    }
                }
                else
                {
                    if (!HashHelper.HasReliableHashCode(_collection[k]))
                    {
                        return false;
                    }
                }
            }
 
            return true;
        }
 
        internal override void GetCollectionChangedSources(int level, Action<int, object, bool?, List<string>> format, List<string> sources)
        {
            format(level, this, false, sources);
            if (_collection != null)
            {
                _collection.GetCollectionChangedSources(level + 1, format, sources);
            }
        }
 
        #endregion
 
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        #region Private Properties
 
        private bool IsCurrentInView
        {
            get
            {
                return (0 <= _currentPositionX && _currentPositionX < _collection.Count);
            }
        }
 
        #endregion
 
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // if item does not exist in the collection, -1 is returned.
        // if changeCurrent, move current to result index (even for -1); this is cancelable.
        private int FindItem(object item, bool changeCurrent)
        {
            int positionX = 0;
            int positionY = 0;
            int index = 0;
            for (; positionX < _collection.Count; ++positionX)
            {
                CollectionContainer cc = _collection[positionX] as CollectionContainer;
 
                if (cc == null) // flat item
                {
                    if (ItemsControl.EqualsEx(_collection[positionX], item))
                    {
                        break;
                    }
                    ++index;
                }
                else    // CollContainer
                {
                    positionY = cc.ViewIndexOf(item);
                    if (positionY >= 0)
                    {
                        index += positionY;
                        break;
                    }
                    positionY = 0;
                    index += cc.ViewCount;  // flattened index
                }
            }
            if (positionX >= _collection.Count)
            {
                // Bonus: since we've spent the time looking through the whole list,
                // cache the count if we didn't have it!
                CacheCount(index);
                index = -1;
 
                // if caller wanted to changeCurrent, we'll move to BeforeFirst
                item = null;
                positionX = -1;
                positionY = 0;
            }
            if (changeCurrent)
            {
                if ((CurrentPosition != index) && OKToChangeCurrent())
                {
                    object oldCurrentItem = CurrentItem;
                    int oldCurrentPosition = CurrentPosition;
                    bool oldIsCurrentAfterLast = IsCurrentAfterLast;
                    bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
 
                    SetCurrent(item, index);
                    _currentPositionX = positionX;
                    _currentPositionY = positionY;
                    OnCurrentChanged();
 
                    if (IsCurrentAfterLast != oldIsCurrentAfterLast)
                        OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
                    if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                        OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
                    if (oldCurrentPosition != CurrentPosition)
                        OnPropertyChanged(CurrentPositionPropertyName);
 
                    if (oldCurrentItem != CurrentItem)
                        OnPropertyChanged(CurrentItemPropertyName);
                }
            }
            return index;
        }
 
        // if flatIndex is -1, null is returned
        // if flatIndex is greater than Count, s_afterLast is returned
        private object GetItem(int flatIndex, out int positionX, out int positionY)
        {
            positionY = 0;
 
            if (flatIndex == -1)
            {
                positionX = -1;
                return null;
            }
 
            if (_count >= 0 && flatIndex >= _count)
            {
                positionX = _collection.Count;
                return s_afterLast;
            }
 
            int searchIndex = 0;
 
            for (int i = 0; i < _collection.Count; ++i)
            {
                CollectionContainer cc = _collection[i] as CollectionContainer;
 
                if (cc == null)                 // flat item
                {
                    if (searchIndex == flatIndex)
                    {
                        positionX = i;
                        return _collection[i];
                    }
 
                    ++searchIndex;
                }
                else if (cc.Collection != null) // CollContainer
                {
                    // see if flatIndex falls within this collection:
                    int localIndex = flatIndex - searchIndex;
                    int count = cc.ViewCount;
 
                    if (localIndex < count)
                    {
                        positionX = i;
                        positionY = localIndex;
                        return cc.ViewItem(localIndex);
                    }
                    else
                    {
                        // try next
                        searchIndex += count;
                    }
                }
            }
            // Bonus: since we've spent the time looking through the whole list,
            // cache the count if we didn't have it!
            CacheCount(searchIndex);
 
            positionX = _collection.Count;
            return s_afterLast;
        }
 
        // Beginning at the specified (positionX, positionY),
        // look for an item to set as CurrentItem.
        // ALWAYS set _currentPositionX and _currentPositionY.
        private object GetNextItemFromXY(int positionX, int positionY)
        {
            Invariant.Assert(positionY >= 0);
 
            object item = null;
            for (; positionX < _collection.Count; ++positionX)
            {
                CollectionContainer cc = _collection[positionX] as CollectionContainer;
                if (cc == null)
                {
                    item = _collection[positionX];
                    positionY = 0;
                    break;
                }
                else if (positionY < cc.ViewCount)
                {
                    item = cc.ViewItem(positionY);
                    break;
                }
                else
                {
                    // after the initial positionX, forget the old Y value
                    positionY = 0;
                }
            }
            if (positionX < _collection.Count)
            {
                _currentPositionX = positionX;
                _currentPositionY = positionY;
            }
            else
            {
                _currentPositionX = _collection.Count;
                _currentPositionY = 0;
            }
            return item;
        }
 
        // Count items in CompositeCollection, including those in sub-collections, from 0 to end.
        // If end is _collection.Count, this returns count of all items in collection.
        private int CountDeep(int end)
        {
            if (Invariant.Strict)
                Invariant.Assert(end <= _collection.Count);
 
            int count = 0;
            for (int i = 0; i < end; ++i)
            {
                CollectionContainer cc = _collection[i] as CollectionContainer;
 
                if (cc == null) // flat item
                {
                    ++count;
                }
                else
                {
                    count += cc.ViewCount;
                }
            }
            return count;
        }
 
        private void CacheCount(int count)
        {
            // count cache may be wrong if underlying collection doesn't notify;
            // also, don't count initial cache as a change.
            bool countChanged = (_count != count && _count >= 0);
 
            _count = count;
 
            if (countChanged)
            {
                OnPropertyChanged(CountPropertyName);
            }
        }
 
        // Move to a given index.
        // This current-changing operation can be cancelled and it should not throw exceptions.
        // if the proposed index is after the end, current is set to AfterLast
        private bool _MoveTo(int proposed)
        {
            int newPositionX, newPositionY;
            object newCurrentItem = GetItem(proposed, out newPositionX, out newPositionY);
 
            if (proposed != CurrentPosition || newCurrentItem != CurrentItem)
            {
                // if we know the count, proposed should be in range.
                Invariant.Assert(_count < 0 || proposed <= _count);
 
                if (OKToChangeCurrent())
                {
                    object oldCurrentItem = CurrentItem;
                    int oldCurrentPosition = CurrentPosition;
                    bool oldIsCurrentAfterLast = IsCurrentAfterLast;
                    bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
 
                    _currentPositionX = newPositionX;
                    _currentPositionY = newPositionY;
 
                    if (newCurrentItem == s_afterLast)
                    {
                        SetCurrent(null, Count);   // Count has been cached from GetItem()
                    }
                    else
                    {
                        SetCurrent(newCurrentItem, proposed);
                    }
                    OnCurrentChanged();
 
                    if (IsCurrentAfterLast != oldIsCurrentAfterLast)
                        OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
                    if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                        OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
                    if (oldCurrentPosition != CurrentPosition)
                        OnPropertyChanged(CurrentPositionPropertyName);
 
                    if (oldCurrentItem != CurrentItem)
                        OnPropertyChanged(CurrentItemPropertyName);
                }
            }
            return IsCurrentInView;
        }
 
        private int DeduceFlatIndexForAdd(CollectionContainer sender, int x)
        {
            // sender didn't provide an index, but we need to know at least
            // whether the new item comes before or after CurrentPosition
            int flatIndex;
 
            if (_currentPositionX > x)
            {
                flatIndex = 0;
            }
            else if (_currentPositionX < x)
            {
                flatIndex = CurrentPosition + 1;
            }
            else
            {
                object item = ((CollectionContainer)sender).ViewItem(_currentPositionY);
                if (ItemsControl.EqualsEx(CurrentItem, item))
                {
                    flatIndex = CurrentPosition + 1;
                }
                else
                {
                    flatIndex = 0;
                }
            }
 
            return flatIndex;
        }
 
        private int DeduceFlatIndexForRemove(CollectionContainer sender, int x, object item)
        {
            // sender didn't provide an index, but we need to know at least
            // whether the removed item comes before or after CurrentPosition
            int flatIndex;
 
            if (_currentPositionX > x)
            {
                flatIndex = 0;
            }
            else if (_currentPositionX < x)
            {
                flatIndex = CurrentPosition + 1;
            }
            else
            {
                if (ItemsControl.EqualsEx(item, CurrentItem))
                {
                    flatIndex = CurrentPosition;
                }
                else
                {
                    object item2 = ((CollectionContainer)sender).ViewItem(_currentPositionY);
                    if (ItemsControl.EqualsEx(item, item2))
                    {
                        flatIndex = CurrentPosition + 1;
                    }
                    else
                    {
                        flatIndex = 0;
                    }
                }
            }
 
            return flatIndex;
        }
 
        // Update currency fields after a single item had been added.
        private void UpdateCurrencyAfterAdd(int flatIndex, int positionX, bool isCompositeItem)
        {
            if (flatIndex < 0)
                return;
 
            if (flatIndex <= CurrentPosition)
            {
                int newCurrentPosition = CurrentPosition + 1;
                if (isCompositeItem)           // if the add was a single item of CompositeCollection
                {
                    ++_currentPositionX;
                }
                else if (positionX == _currentPositionX) // else if it was in the current sub-collection
                {
                    ++_currentPositionY;
                }
                // else it was in a subcollection prior to the current collection,
                // in which case we don't need to adjust X-Y.
 
                // CurrentItem needs to be updated because we get notified of replace as Remove+Add
                // but that's not what really happened in the underlying collection
                SetCurrent(GetNextItemFromXY(_currentPositionX, _currentPositionY), newCurrentPosition);
            }
 
#if DEBUG
            VerifyCurrencyIsConsistent();
#endif
        }
 
        // Update currency fields after a single item had been removed.
        private void UpdateCurrencyAfterRemove(int flatIndex, int positionX, bool isCompositeItem)
        {
            if (flatIndex < 0)
                return;
 
            if (flatIndex < CurrentPosition)
            {
                SetCurrent(CurrentItem, CurrentPosition - 1);
                if (isCompositeItem)            // if the remove was a single item of CompositeCollection
                {
                    --_currentPositionX;
                }
                else if (positionX == _currentPositionX) // else if it was in the current sub-collection
                {
                    --_currentPositionY;
                }
                // else it was in a subcollection prior to the current collection,
                // in which case we don't need to adjust X-Y.
            }
            else if (flatIndex == CurrentPosition) // current item was removed
            {
                MoveCurrencyOffDeletedElement();
            }
 
#if DEBUG
            VerifyCurrencyIsConsistent();
#endif
        }
 
        // fix up CurrentPosition and CurrentItem after a collection change
        private void UpdateCurrencyAfterMove(int oldIndex, int newIndex, int positionX, bool isCompositeItem)
        {
            // if entire move was before or after current item, then there
            // is nothing that needs to be done.
            if ((oldIndex < CurrentPosition && newIndex < CurrentPosition)
                || (oldIndex > CurrentPosition && newIndex > CurrentPosition))
                return;
 
            if (newIndex <= CurrentPosition)
                UpdateCurrencyAfterAdd(newIndex, positionX, isCompositeItem);
 
            if (oldIndex <= CurrentPosition)
                UpdateCurrencyAfterRemove(oldIndex, positionX, isCompositeItem);
        }
 
        // Update currency fields after a sub-collection had been refreshed.
        private void UpdateCurrencyAfterRefresh(object refreshedObject)
        {
            Invariant.Assert(refreshedObject is CollectionContainer);
 
            object oldCurrentItem = CurrentItem;
            int oldCurrentPosition = CurrentPosition;
            bool oldIsCurrentAfterLast = IsCurrentAfterLast;
            bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
 
            if (IsCurrentInView && refreshedObject == _collection[_currentPositionX])
            {
                CollectionContainer cc = refreshedObject as CollectionContainer;
                if (cc.ViewCount == 0) // if the collection was emptied out
                {
                    // it's just as though the collection was deleted
                    _currentPositionY = 0;  // 0 is AfterLast for an empty collection
                    MoveCurrencyOffDeletedElement();
                }
                else // else try to restore currency to the old current item
                {
                    int positionY = cc.ViewIndexOf(CurrentItem);
                    if (positionY >= 0)
                    {
                        _currentPositionY = positionY;
                        SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                    }
                    else // else we give up and move to BeforeFirst
                    {
                        OnCurrentChanging();
                        SetCurrentBeforeFirst();
                        OnCurrentChanged();
                    }
                }
            }
            else
            {
                // Recalculate CurrentPosition if the refreshed collection was positioned before current collection
                for (int i = 0; i < _currentPositionX; ++i)
                {
                    if (_collection[i] == refreshedObject)
                    {
                        SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                        break;
                    }
                }
            }
 
            if (IsCurrentAfterLast != oldIsCurrentAfterLast)
                OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
            if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
                OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
            if (oldCurrentPosition != CurrentPosition)
                OnPropertyChanged(CurrentPositionPropertyName);
 
            if (oldCurrentItem != CurrentItem)
                OnPropertyChanged(CurrentItemPropertyName);
 
#if DEBUG
            VerifyCurrencyIsConsistent();
#endif
        }
 
        private void MoveCurrencyOffDeletedElement()
        {
            int oldCurrentPosition = CurrentPosition;
 
            // We fire current changing, ignoring cancelation - there's no choice.
            OnCurrentChanging();
 
            // find the next item to be current
            object newCurrentItem = GetNextItemFromXY(_currentPositionX, _currentPositionY);
            // if next item could not be found, go to the last item instead
            if (_currentPositionX >= _collection.Count)
            {
                newCurrentItem = GetLastItem(out _currentPositionX, out _currentPositionY);
                SetCurrent(newCurrentItem, Count - 1);
            }
            else
            {
                // recalculate position because the removed element could have been a collection
                SetCurrentPositionFromXY(_currentPositionX, _currentPositionY);
                SetCurrent(newCurrentItem, CurrentPosition);
            }
            OnCurrentChanged();
 
            OnPropertyChanged(CountPropertyName);
            OnPropertyChanged(CurrentItemPropertyName);
 
            if (IsCurrentAfterLast)
                OnPropertyChanged(IsCurrentAfterLastPropertyName);
 
            if (IsCurrentBeforeFirst)
                OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
 
            if (CurrentPosition != oldCurrentPosition)
                OnPropertyChanged(CurrentPositionPropertyName);
        }
 
        // note: if collection is empty, item and position returned is BeforeFirst
        private object GetLastItem(out int positionX, out int positionY)
        {
            object lastItem = null;
            positionX = -1;
            positionY = 0;
 
            if (_count != 0)    // unknown or HasItems
            {
                // seek backwards
                positionX = _collection.Count - 1;
                for (; positionX >= 0; --positionX)
                {
                    CollectionContainer cc = _collection[positionX] as CollectionContainer;
                    if (cc == null)
                    {
                        lastItem = _collection[positionX];
                        break;
                    }
                    else if (cc.ViewCount > 0)
                    {
                        positionY = cc.ViewCount - 1;
                        lastItem = cc.ViewItem(positionY);
                        break;
                    }
                }
                if (positionX < 0)  // no items? remember zero count
                {
                    CacheCount(0);
                }
            }
            return lastItem;
        }
 
        private void SetCurrentBeforeFirst()
        {
            _currentPositionX = -1;
            _currentPositionY = 0;
            SetCurrent(null, -1);
        }
 
        private void SetCurrentPositionFromXY(int x, int y)
        {
            if (IsCurrentBeforeFirst)
                SetCurrent(null, -1);
            else if (IsCurrentAfterLast)
                SetCurrent(null, Count);
            else
                SetCurrent(CurrentItem, CountDeep(x) + y);
        }
 
 
        // this method is here just to avoid the compiler error
        // error CS0649: Warning as Error: Field '..._traceLog' is never assigned to, and will always have its default value null
        void InitializeTraceLog()
        {
            _traceLog = new TraceLog(20);
        }
 
        private void TraceContainerCollectionChange(object sender, NotifyCollectionChangedAction action, object oldItem, object newItem)
        {
            if (_traceLog != null)
                _traceLog.Add("ContainerCollectionChange from {0}  action = {1} oldItem = {2} newItem = {3}",
                                TraceLog.IdFor(sender), action, TraceLog.IdFor(oldItem), TraceLog.IdFor(newItem));
        }
 
        private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewItems.Count != 1)
                        throw new NotSupportedException(SR.RangeActionsNotSupported);
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldItems.Count != 1)
                        throw new NotSupportedException(SR.RangeActionsNotSupported);
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    if (e.NewItems.Count != 1 || e.OldItems.Count != 1)
                        throw new NotSupportedException(SR.RangeActionsNotSupported);
                    break;
 
                case NotifyCollectionChangedAction.Move:
                    if (e.NewItems.Count != 1)
                        throw new NotSupportedException(SR.RangeActionsNotSupported);
                    if (e.NewStartingIndex < 0)
                        throw new InvalidOperationException(SR.CannotMoveToUnknownPosition);
                    break;
 
                case NotifyCollectionChangedAction.Reset:
                    break;
 
                default:
                    throw new NotSupportedException(SR.Format(SR.UnexpectedCollectionChangeAction, e.Action));
            }
        }
 
        /// <summary>
        /// Helper to raise a PropertyChanged event  />).
        /// </summary>
        private void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
 
 
        #endregion Private Methods
 
 
        //------------------------------------------------------
        //
        //  Private Types
        //
        //------------------------------------------------------
 
        #region Private Types
 
        /// <summary>
        /// IEnumerator implementation that does flattened forward-only enumeration over CompositeCollectionView.
        /// </summary>
        private class FlatteningEnumerator : IEnumerator, IDisposable
        {
            internal FlatteningEnumerator(CompositeCollection collection, CompositeCollectionView view)
            {
                Invariant.Assert(collection != null && view != null);
                _collection = collection;
                _view = view;
                _version = view._version;
                Reset();
            }
 
            public bool MoveNext()
            {
                CheckVersion();
                bool isCurrentInView = true;
 
                while (true)
                {
                    // advance within a collection container
                    if (_containerEnumerator != null)
                    {
                        if (_containerEnumerator.MoveNext())
                        {
                            _current = _containerEnumerator.Current;
                            break;
                        }
                        // when we reach the end of a container, prepare to move on
                        DisposeContainerEnumerator();
                    }
 
                    // move to the next item
                    if (++_index < _collection.Count)
                    {
                        object item = _collection[_index];
                        CollectionContainer cc = item as CollectionContainer;
 
                        // item is a container,  move into it
                        if (cc != null)
                        {
                            IEnumerable ie = cc.View;   // View is null when Collection is null
                            _containerEnumerator = (ie != null) ? ie.GetEnumerator() : null;
                            continue;
                        }
 
                        // plain item
                        _current = item;
                        break;
                    }
                    else
                    {
                        // no more items
                        _current = null;
                        _done = true;
                        isCurrentInView = false;
                        break;
                    }
                }
                return isCurrentInView;
            }
 
            public object Current
            {
                get
                {
                    // the spec for ICollectionView.CurrentItem says:
                    // InvalidOperationException: The enumerator is positioned before the first element of the collection or after the last element.
                    if (_index < 0)
                    {
#pragma warning suppress 6503 // ICollectionView.CurrentItem is documented to throw this exception
                        throw new InvalidOperationException(SR.EnumeratorNotStarted);
                    }
                    if (_done)
                    {
#pragma warning suppress 6503 // ICollectionView.CurrentItem is documented to throw this exception
                        throw new InvalidOperationException(SR.EnumeratorReachedEnd);
                    }
 
                    return _current;
                }
            }
 
            public void Reset()
            {
                CheckVersion();
                _index = -1;
                _current = null;
                DisposeContainerEnumerator();
                _done = false;
            }
 
            public void Dispose()
            {
                DisposeContainerEnumerator();
            }
 
            private void DisposeContainerEnumerator()
            {
                IDisposable d = _containerEnumerator as IDisposable;
                if (d != null)
                {
                    d.Dispose();
                }
 
                _containerEnumerator = null;
            }
 
            private void CheckVersion()
            {
                // note: there's a very unlikely possibility that the
                // version number wraps around back to the same number
                if (_isInvalidated || (_isInvalidated = (_version != _view._version)))
                    throw new InvalidOperationException(SR.EnumeratorVersionChanged);
            }
 
            private CompositeCollection _collection;
            private CompositeCollectionView _view;
            private int _index;
            private object _current;
            private IEnumerator _containerEnumerator;
            private bool _done;
            private bool _isInvalidated = false;
            private int _version;
        }
 
        #endregion Private Types
 
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        TraceLog _traceLog;
        CompositeCollection _collection;
 
        int _count = -1;
        int _version = 0;
 
        // Using X-Y coordinates to track current position in the composite collection:
        // X is the index in the first-level collection, whose members are items and subcollections
        // Y is the index into the subcollection, if any.  0, if not.
        int _currentPositionX = -1;
        int _currentPositionY = 0;
 
        private static readonly object s_afterLast = new Object();
 
        #endregion Private Fields
 
 
        //------------------------------------------------------
        //
        //  Debugging Aids
        //
        //------------------------------------------------------
 
        #region Debugging Aids
 
#if DEBUG
 
        // Verify that Currency is consistent.
        // However, we have to make an exception for cases when a collection is
        // being used multiple times inside a CompositeCollection.
        private void VerifyCurrencyIsConsistent()
        {
            if (IsCurrentInView)
            {
                int x, y;
                if (!ItemsControl.EqualsEx(CurrentItem, GetItem(CurrentPosition, out x, out y)) && !_collection.HasRepeatedCollection())
                    Debug.Assert(false, "CurrentItem is not consistent with CurrentPosition");
            }
            else
            {
                if ((CurrentItem != null) && !_collection.HasRepeatedCollection())
                    Debug.Assert(false, "CurrentItem is not consistent with CurrentPosition");
            }
        }
 
#endif
 
        #endregion Debugging Aids
    }
}