File: System\Windows\Controls\MultipleCopiesCollection.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.
 
 
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Data;
 
namespace System.Windows.Controls
{
    /// <summary>
    ///     A collection that simulates holding multiple copies of the same item.  Used as the ItemsSource for the DataGridCellsPresenter.
    ///     For our purposes this mirrors the DataGrid.Columns collection in that it has the same number of items and changes whenever 
    ///     the columns collection changes (though the items in it are obviously different; each item is the data object for a given row).  
    /// </summary>
    internal class MultipleCopiesCollection :
        IList,
        ICollection,
        IEnumerable,
        INotifyCollectionChanged,
        INotifyPropertyChanged
    {
        #region Construction
 
        internal MultipleCopiesCollection(object item, int count)
        {
            Debug.Assert(item != null, "item should not be null.");
            Debug.Assert(count >= 0, "count should not be negative.");
 
            CopiedItem = item;
            _count = count;
        }
 
        #endregion
 
        #region Item Management
 
        /// <summary>
        ///     Takes a collection change notifcation and causes the MultipleCopies collection to sync to the changes.  For example,
        ///     if an item was removed at a given index, the MultipleCopiesCollection also removes an item at the same index and fires
        ///     its own collection changed event.
        /// </summary>
        internal void MirrorCollectionChange(NotifyCollectionChangedEventArgs e)
        {            
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    Debug.Assert(
                        e.NewItems.Count == 1, 
                        "We're mirroring the Columns collection which is an ObservableCollection and only supports adding one item at a time");
                    Insert(e.NewStartingIndex);
                    break;
 
                case NotifyCollectionChangedAction.Move:
                    Debug.Assert(
                        e.NewItems.Count == 1,
                        "We're mirroring the Columns collection which is an ObservableCollection and only supports moving one item at a time");
                    Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    Debug.Assert(
                        e.OldItems.Count == 1,
                        "We're mirroring the Columns collection which is an ObservableCollection and only supports removing one item at a time");
                    RemoveAt(e.OldStartingIndex);
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    Debug.Assert(
                        e.NewItems.Count == 1,
                        "We're mirroring the Columns collection which is an ObservableCollection and only supports replacing one item at a time");
                    OnReplace(CopiedItem, CopiedItem, e.NewStartingIndex);
                    break;
 
                case NotifyCollectionChangedAction.Reset:
                    Reset();
                    break;
            }            
        }
 
        /// <summary>
        ///     Syncs up the count with the given one.  This is used when we know we've missed a CollectionChanged event (say this 
        ///     MultipleCopiesCollection is inside a DataGridRow that was virtualized and recycled).  It attempts to resync
        ///     by adjusting the count and firing the proper property change notifications.
        /// </summary>
        /// <remarks>
        ///     This method works in concert with the DataGridCellsPresenter.  We don't know where items were removed / added, so containers
        ///     (DataGridCells) based off this collection could be stale (wrong column).  The cells presenter updates them.  We could have also 
        ///     just fired a Reset event here and not bothered with work in the cells presenter, but that would cause all cells to be regenerated. 
        ///     
        ///     Note that this method is designed to sync up to ALL collection changes that may have happened.  
        ///     The job of this method is made significantly easier by the fact that the MultipleCopiesCollection really only cares about
        ///     the count of items in the given collection (since we keep it in sync with the DataGrid Columns collection but host
        ///     a DataGridRow as the item).  This means we don't care about Move, Replace, etc.
        /// </remarks>
        internal void SyncToCount(int newCount)
        {
            int oldCount = RepeatCount;
 
            if (newCount != oldCount)
            {
                if (newCount > oldCount)
                {
                    // Insert at end
                    InsertRange(oldCount, newCount - oldCount);
                }
                else
                {
                    // Remove from the end
                    int numToRemove = oldCount - newCount;
                    RemoveRange(oldCount - numToRemove, numToRemove);
                }
 
                Debug.Assert(RepeatCount == newCount, "We should have properly updated the RepeatCount");
            }
        }
 
        /// <summary>
        ///     This is the item that is returned multiple times.
        /// </summary>
        internal object CopiedItem
        {
            get 
            { 
                return _item; 
            }
 
            set
            {
                if (value == CollectionView.NewItemPlaceholder)
                {
                    // If we populate the collection with the CollectionView's
                    // NewItemPlaceholder, it will confuse the CollectionView.
                    value = DataGrid.NewItemPlaceholder;
                }
 
                if (_item != value)
                {
                    object oldValue = _item;
                    _item = value;
 
                    OnPropertyChanged(IndexerName);
 
                    // Report replacing each item with the new item
                    for (int i = 0; i < _count; i++)
                    {
                        OnReplace(oldValue, _item, i);
                    }
                }
            }
        }
 
        /// <summary>
        ///     This is the number of times the item is to be repeated.
        /// </summary>
        private int RepeatCount
        {
            get 
            { 
                return _count; 
            }
 
            set
            {
                if (_count != value)
                {
                    _count = value;
                    OnPropertyChanged(CountName);
                    OnPropertyChanged(IndexerName);
                }
            }
        }
       
        private void Insert(int index)
        {
            RepeatCount++;
            OnCollectionChanged(NotifyCollectionChangedAction.Add, CopiedItem, index);
        }
 
        private void InsertRange(int index, int count)
        {
            // True range operations are not supported by CollectionView so we instead fire many changed events.
            for (int i = 0; i < count; i++)
            {
                Insert(index);
                index++;
            }
        }
 
        private void Move(int oldIndex, int newIndex)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, CopiedItem, newIndex, oldIndex));
        }
 
        private void RemoveAt(int index)
        {
            Debug.Assert((index >= 0) && (index < RepeatCount), "Index out of range");
      
            RepeatCount--;
            OnCollectionChanged(NotifyCollectionChangedAction.Remove, CopiedItem, index);  
        }
 
        private void RemoveRange(int index, int count)
        {
            // True range operations are not supported by CollectionView so we instead fire many changed events.
            for (int i = 0; i < count; i++)
            {
                RemoveAt(index);
            }
        }
 
        private void OnReplace(object oldItem, object newItem, int index)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, index));
        }
 
        private void Reset()
        {
            RepeatCount = 0;
            OnCollectionReset();
        }
 
        #endregion
 
        #region IList Members
 
        public int Add(object value)
        {
            throw new NotSupportedException(SR.DataGrid_ReadonlyCellsItemsSource);
        }
 
        public void Clear()
        {
            throw new NotSupportedException(SR.DataGrid_ReadonlyCellsItemsSource);
        }
 
        public bool Contains(object value)
        {
            ArgumentNullException.ThrowIfNull(value);
 
            return _item == value;
        }
 
        public int IndexOf(object value)
        {
            ArgumentNullException.ThrowIfNull(value);
 
            return (_item == value) ? 0 : -1;
        }
 
        public void Insert(int index, object value)
        {
            throw new NotSupportedException(SR.DataGrid_ReadonlyCellsItemsSource);
        }
 
        public bool IsFixedSize
        {
            get { return false; }
        }
 
        public bool IsReadOnly
        {
            get { return true; }
        }
 
        public void Remove(object value)
        {
            throw new NotSupportedException(SR.DataGrid_ReadonlyCellsItemsSource);
        }
 
        void IList.RemoveAt(int index)
        {
            throw new NotSupportedException(SR.DataGrid_ReadonlyCellsItemsSource);
        }
 
        public object this[int index]
        {
            get
            {
                ArgumentOutOfRangeException.ThrowIfNegative(index);
                ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, RepeatCount);
 
                Debug.Assert(_item != null, "_item should be non-null.");
                return _item;
            }
 
            set
            {
                throw new InvalidOperationException();
            }
        }
 
        #endregion
 
        #region ICollection Members
 
        public void CopyTo(Array array, int index)
        {
            throw new NotSupportedException();
        }
 
        public int Count
        {
            get { return RepeatCount; }
        }
 
        public bool IsSynchronized
        {
            get { return false; }
        }
 
        public object SyncRoot
        {
            get { return this; }
        }
 
        #endregion
 
        #region IEnumerable Members
 
        public IEnumerator GetEnumerator()
        {
            return new MultipleCopiesCollectionEnumerator(this);
        }
 
        private class MultipleCopiesCollectionEnumerator : IEnumerator
        {
            public MultipleCopiesCollectionEnumerator(MultipleCopiesCollection collection)
            {
                _collection = collection;
                _item = _collection.CopiedItem;
                _count = _collection.RepeatCount;
                _current = -1;
            }
 
            #region IEnumerator Members
 
            object IEnumerator.Current
            {
                get
                {
                    if (_current >= 0)
                    {
                        if (_current < _count)
                        {
                            return _item;
                        }
                        else
                        {
                            throw new InvalidOperationException();
                        }
                    }
                    else
                    {
                        return null;
                    }
                }
            }
 
            bool IEnumerator.MoveNext()
            {
                if (IsCollectionUnchanged)
                {
                    int newIndex = _current + 1;
                    if (newIndex < _count)
                    {
                        _current = newIndex;
                        return true;
                    }
 
                    return false;
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }
 
            void IEnumerator.Reset()
            {
                if (IsCollectionUnchanged)
                {
                    _current = -1;
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }
 
            private bool IsCollectionUnchanged
            {
                get
                {
                    return (_collection.RepeatCount == _count) && (_collection.CopiedItem == _item);
                }
            }
 
            #endregion
 
            #region Data
 
            private object _item;
            private int _count;
            private int _current;
            private MultipleCopiesCollection _collection;
 
            #endregion
        }
 
        #endregion
 
        #region INotifyCollectionChanged Members
 
        public event NotifyCollectionChangedEventHandler CollectionChanged;
 
        /// <summary>
        ///     Helper to raise a CollectionChanged event when an item is added or removed.
        /// </summary>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
        }
 
        /// <summary>
        ///     Helper to raise a CollectionChanged event when the collection is cleared.
        /// </summary>
        private void OnCollectionReset()
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
 
        private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, e);
            }
        }
 
        #endregion
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        /// <summary>
        ///     Helper to raise a PropertyChanged event.
        /// </summary>
        private void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
 
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }
 
        #endregion
 
        #region Data
 
        private object _item; // consider WeakReference
        private int _count;
 
        private const string CountName = "Count";
        private const string IndexerName = "Item[]";
 
        #endregion
    }
}