|
// 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: ItemCollection holds the list of items that constitute the content of a ItemsControl.
//
// See specs at ItemsControl.mht
// model tree -- design.doc
//
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Data; // for CollectionContainer
using MS.Internal.Controls;
using MS.Internal.Data; // for IndexedEnumerable
using MS.Internal.KnownBoxes; // for BooleanBoxes
using MS.Internal.Utility;
namespace System.Windows.Controls
{
/// <summary>
/// ItemCollection will contain items shaped as strings, objects, xml nodes,
/// elements, as well as other collections. (It will not promote elements from
/// contained collections; to "flatten" contained collections, assign a
/// <seealso cref="System.Windows.Data.CompositeCollection"/> to
/// the ItemsSource property on the ItemsControl.)
/// A <seealso cref="System.Windows.Controls.ItemsControl"/> uses the data
/// in the ItemCollection to generate its content according to its ItemTemplate.
/// </summary>
/// <remarks>
/// When first created, ItemCollection is in an uninitialized state, neither
/// ItemsSource-mode nor direct-mode. It will hold settings like SortDescriptions and Filter
/// until the mode is determined, then assign the settings to the active view.
/// When uninitialized, calls to the list-modifying members will put the
/// ItemCollection in direct mode, and setting the ItemsSource will put the
/// ItemCollection in ItemsSource mode.
/// </remarks>
[Localizability(LocalizationCategory.Ignore)]
public sealed class ItemCollection : CollectionView, IList, IEditableCollectionViewAddNewItem, ICollectionViewLiveShaping, IItemProperties, IWeakEventListener
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
// ItemCollection cannot be created standalone, it is created by ItemsControl
/// <summary>
/// Initializes a new instance of ItemCollection that is empty and has default initial capacity.
/// </summary>
/// <param name="modelParent">model parent of this item collection</param>
/// <remarks>
/// </remarks>
internal ItemCollection(DependencyObject modelParent)
: base(EmptyEnumerable.Instance, false)
{
_modelParent = new WeakReference(modelParent);
}
/// <summary>
/// Initializes a new instance of ItemCollection that is empty and has specified initial capacity.
/// </summary>
/// <param name="modelParent">model parent of this item collection</param>
/// <param name="capacity">The number of items that the new list is initially capable of storing</param>
/// <remarks>
/// Some ItemsControl implementations have better idea how many items to anticipate,
/// capacity parameter lets them tailor the initial size.
/// </remarks>
internal ItemCollection(FrameworkElement modelParent, int capacity)
: base(EmptyEnumerable.Instance, false)
{
_defaultCapacity = capacity;
_modelParent = new WeakReference(modelParent);
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
//------------------------------------------------------
#region ICurrentItem
// These currency methods do not call OKToChangeCurrent() because
// ItemCollection already picks up and forwards the CurrentChanging
// event from the inner _collectionView.
/// <summary>
/// Move <seealso cref="CurrentItem"/> to the first item.
/// </summary>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentToFirst()
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentToFirst();
}
/// <summary>
/// Move <seealso cref="CurrentItem"/> to the next item.
/// </summary>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentToNext()
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentToNext();
}
/// <summary>
/// Move <seealso cref="CurrentItem"/> to the previous item.
/// </summary>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentToPrevious()
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentToPrevious();
}
/// <summary>
/// Move <seealso cref="CurrentItem"/> to the last item.
/// </summary>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentToLast()
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentToLast();
}
/// <summary>
/// Move <seealso cref="ICollectionView.CurrentItem"/> to the given item.
/// </summary>
/// <param name="item">Move CurrentItem to this item.</param>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentTo(object item)
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentTo(item);
}
/// <summary>
/// Move <seealso cref="CurrentItem"/> to the item at the given index.
/// </summary>
/// <param name="position">Move CurrentItem to this index</param>
/// <returns>true if <seealso cref="CurrentItem"/> points to an item within the view.</returns>
public override bool MoveCurrentToPosition(int position)
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.MoveCurrentToPosition(position);
}
#endregion ICurrentItem
#region IList
/// <summary>
/// Returns an enumerator object for this ItemCollection
/// </summary>
/// <returns>
/// Enumerator object for this ItemCollection
/// </returns>
protected override IEnumerator GetEnumerator()
{
if (!EnsureCollectionView())
return EmptyEnumerator.Instance;
return ((IEnumerable)_collectionView).GetEnumerator();
}
/// <summary>
/// Add an item to this collection.
/// </summary>
/// <param name="newItem">
/// New item to be added to collection
/// </param>
/// <returns>
/// Zero-based index where the new item is added. -1 if the item could not be added.
/// </returns>
/// <remarks>
/// To facilitate initialization of direct-mode ItemsControls with Sort and/or Filter,
/// Add() is permitted when ItemsControl is initializing, even if a Sort or Filter has been set.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// trying to add an item which already has a different model/logical parent
/// - or -
/// trying to add an item when the ItemCollection is in ItemsSource mode.
/// </exception>
public int Add(object newItem)
{
CheckIsUsingInnerView();
int index = _internalView.Add(newItem);
ModelParent.SetValue(ItemsControl.HasItemsPropertyKey, BooleanBoxes.TrueBox);
return index;
}
/// <summary>
/// Clears the collection. Releases the references on all items
/// currently in the collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// the ItemCollection is read-only because it is in ItemsSource mode
/// </exception>
public void Clear()
{
// Not using CheckIsUsingInnerView() because we don't want to create internal list
VerifyRefreshNotDeferred();
if (IsUsingItemsSource)
{
throw new InvalidOperationException(SR.ItemsSourceInUse);
}
if (_internalView != null)
{
_internalView.Clear();
}
ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey);
}
/// <summary>
/// Checks to see if a given item is in this collection and in the view
/// </summary>
/// <param name="containItem">
/// The item whose membership in this collection is to be checked.
/// </param>
/// <returns>
/// True if the collection contains the given item and the item passes the active filter
/// </returns>
public override bool Contains(object containItem)
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.Contains(containItem);
}
/// <summary>
/// Makes a shallow copy of object references from this
/// ItemCollection to the given target array
/// </summary>
/// <param name="array">
/// Target of the copy operation
/// </param>
/// <param name="index">
/// Zero-based index at which the copy begins
/// </param>
public void CopyTo(Array array, int index)
{
ArgumentNullException.ThrowIfNull(array);
if (array.Rank > 1)
throw new ArgumentException(SR.BadTargetArray, "array"); // array is multidimensional.
ArgumentOutOfRangeException.ThrowIfNegative(index);
// use the view instead of the collection, because it may have special sort/filter
if (!EnsureCollectionView())
return; // there is no collection (bind returned no collection) and therefore nothing to copy
VerifyRefreshNotDeferred();
IndexedEnumerable.CopyTo(_collectionView, array, index);
}
/// <summary>
/// Finds the index in this collection/view where the given item is found.
/// </summary>
/// <param name="item">
/// The item whose index in this collection/view is to be retrieved.
/// </param>
/// <returns>
/// Zero-based index into the collection/view where the given item can be
/// found. Otherwise, -1
/// </returns>
public override int IndexOf(object item)
{
if (!EnsureCollectionView())
return -1;
VerifyRefreshNotDeferred();
return _collectionView.IndexOf(item);
}
/// <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)
{
// only check lower bound because Count could be expensive
ArgumentOutOfRangeException.ThrowIfNegative(index);
VerifyRefreshNotDeferred();
if (!EnsureCollectionView())
throw new InvalidOperationException(SR.ItemCollectionHasNoCollection);
if (_collectionView == _internalView)
{
// check upper bound here because we know it's not expensive
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _internalView.Count);
}
return _collectionView.GetItemAt(index);
}
/// <summary>
/// Insert an item in the collection at a given index. All items
/// after the given position are moved down by one.
/// </summary>
/// <param name="insertIndex">
/// The index at which to inser the item
/// </param>
/// <param name="insertItem">
/// The item reference to be added to the collection
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown when trying to add an item which already has a different model/logical parent
/// or when the ItemCollection is read-only because it is in ItemsSource mode
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if index is out of range
/// </exception>
public void Insert(int insertIndex, object insertItem)
{
CheckIsUsingInnerView();
_internalView.Insert(insertIndex, insertItem);
ModelParent.SetValue(ItemsControl.HasItemsPropertyKey, BooleanBoxes.TrueBox);
}
/// <summary>
/// Removes the given item reference from the collection or view.
/// All remaining items move up by one.
/// </summary>
/// <exception cref="InvalidOperationException">
/// the ItemCollection is read-only because it is in ItemsSource mode or there
/// is a sort or filter in effect
/// </exception>
/// <param name="removeItem">
/// The item to be removed.
/// </param>
public void Remove(object removeItem)
{
CheckIsUsingInnerView();
_internalView.Remove(removeItem);
if (IsEmpty)
{
ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey);
}
}
/// <summary>
/// Removes an item from the collection or view at the given index.
/// All remaining items move up by one.
/// </summary>
/// <param name="removeIndex">
/// The index at which to remove an item.
/// </param>
/// <exception cref="InvalidOperationException">
/// the ItemCollection is read-only because it is in ItemsSource mode
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if index is out of range
/// </exception>
public void RemoveAt(int removeIndex)
{
CheckIsUsingInnerView();
_internalView.RemoveAt(removeIndex);
if (IsEmpty)
{
ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey);
}
}
#endregion IList
/// <summary>
/// Return true if the item is acceptable to the active filter, if any.
/// It is commonly used during collection-changed notifications to
/// determine if the added/removed item requires processing.
/// </summary>
/// <returns>
/// true if the item passes the filter or if no filter is set on collection view.
/// </returns>
public override bool PassesFilter(object item)
{
if (!EnsureCollectionView())
return true;
return _collectionView.PassesFilter(item);
}
/// <summary>
/// Re-create the view, using any <seealso cref="SortDescriptions"/> and/or <seealso cref="Filter"/>.
/// </summary>
protected override void RefreshOverride()
{
if (_collectionView != null)
{
if (_collectionView.NeedsRefresh)
{
_collectionView.Refresh();
}
else
{
// if the view is up to date, we only need to raise the Reset event
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// Read-only property for the number of items stored in this collection of objects
/// </summary>
/// <remarks>
/// returns 0 if the ItemCollection is uninitialized or
/// there is no collection in ItemsSource mode
/// </remarks>
public override int Count
{
get
{
if (!EnsureCollectionView())
return 0;
VerifyRefreshNotDeferred();
return _collectionView.Count;
}
}
/// <summary>
/// Returns true if the resulting (filtered) view is emtpy.
/// </summary>
public override bool IsEmpty
{
get
{
if (!EnsureCollectionView())
return true;
VerifyRefreshNotDeferred();
return _collectionView.IsEmpty;
}
}
/// <summary>
/// Indexer property to retrieve or replace the item at the given
/// zero-based offset into the collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// trying to set an item which already has a different model/logical parent; or,
/// trying to set when in ItemsSource mode; or,
/// the ItemCollection is uninitialized; or,
/// in ItemsSource mode, the binding on ItemsSource does not provide a collection.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if index is out of range
/// </exception>
public object this[int index]
{
get
{
return GetItemAt(index);
}
set
{
CheckIsUsingInnerView();
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _internalView.Count);
_internalView[index] = value;
}
}
/// <summary>
/// The ItemCollection's underlying collection or the user provided ItemsSource collection
/// </summary>
public override IEnumerable SourceCollection
{
get
{
if (IsUsingItemsSource)
{
return ItemsSource;
}
else
{
EnsureInternalView();
return this;
}
}
}
/// <summary>
/// Returns true if this view needs to be refreshed
/// (i.e. when the view is not consistent with the current sort or filter).
/// </summary>
/// <returns>
/// true when SortDescriptions or Filter is changed while refresh is deferred,
/// or in direct-mode, when an item have been added while SortDescriptions or Filter is in place.
/// </returns>
public override bool NeedsRefresh
{
get
{
return (EnsureCollectionView()) ? _collectionView.NeedsRefresh : false;
}
}
/// <summary>
/// Collection of Sort criteria to sort items in ItemCollection.
/// </summary>
/// <remarks>
/// <p>
/// Sorting is supported for items in the ItemsControl.Items collection;
/// if a collection is assigned to ItemsControl.ItemsSource, the capability to sort
/// depends on the CollectionView for that inner collection.
/// Simpler implementations of CollectionVIew do not support sorting and will return an empty
/// and immutable / read-only SortDescription collection.
/// Attempting to modify such a collection will cause NotSupportedException.
/// Use <seealso cref="CanSort"/> property on CollectionView to test if sorting is supported
/// before modifying the returned collection.
/// </p>
/// <p>
/// One or more sort criteria in form of <seealso cref="SortDescription"/>
/// can be added, each specifying a property and direction to sort by.
/// </p>
/// </remarks>
public override SortDescriptionCollection SortDescriptions
{
get
{
// always hand out this ItemCollection's SortDescription collection;
// in ItemsSource mode the inner collection view will be kept in synch with this collection
if (MySortDescriptions == null)
{
MySortDescriptions = new SortDescriptionCollection();
if (_collectionView != null)
{
// no need to do this under the monitor - we haven't hooked up events yet
CloneList(MySortDescriptions, _collectionView.SortDescriptions);
}
((INotifyCollectionChanged)MySortDescriptions).CollectionChanged += new NotifyCollectionChangedEventHandler(SortDescriptionsChanged);
}
return MySortDescriptions;
}
}
/// <summary>
/// Test if this ICollectionView supports sorting before adding
/// to <seealso cref="SortDescriptions"/>.
/// </summary>
public override bool CanSort
{
get
{
return (EnsureCollectionView()) ? _collectionView.CanSort : true;
}
}
/// <summary>
/// Set/get a filter callback to filter out items in collection.
/// This property will always accept a filter, but the collection view for the
/// underlying ItemsSource may not actually support filtering.
/// Please check <seealso cref="CanFilter"/>
/// </summary>
/// <exception cref="NotSupportedException">
/// Collections assigned to ItemsSource may not support filtering and could throw a NotSupportedException.
/// Use <seealso cref="CanFilter"/> property to test if filtering is supported before assigning
/// a non-null Filter value.
/// </exception>
public override Predicate<object> Filter
{
get
{
return (EnsureCollectionView()) ? _collectionView.Filter : MyFilter;
}
set
{
MyFilter = value;
if (_collectionView != null)
_collectionView.Filter = value;
}
}
/// <summary>
/// Test if this ICollectionView supports filtering before assigning
/// a filter callback to <seealso cref="Filter"/>.
/// </summary>
public override bool CanFilter
{
get
{
return (EnsureCollectionView()) ? _collectionView.CanFilter : true;
}
}
/// <summary>
/// Returns true if this view really supports grouping.
/// When this returns false, the rest of the interface is ignored.
/// </summary>
public override bool CanGroup
{
get
{
return (EnsureCollectionView()) ? _collectionView.CanGroup : false;
}
}
/// <summary>
/// The description of grouping, indexed by level.
/// </summary>
public override ObservableCollection<GroupDescription> GroupDescriptions
{
get
{
// always hand out this ItemCollection's GroupDescription collection;
// in ItemsSource mode the inner collection view will be kept in synch with this collection
if (MyGroupDescriptions == null)
{
MyGroupDescriptions = new ObservableCollection<GroupDescription>();
if (_collectionView != null)
{
// no need to do this under the monitor - we haven't hooked up events yet
CloneList(MyGroupDescriptions, _collectionView.GroupDescriptions);
}
((INotifyCollectionChanged)MyGroupDescriptions).CollectionChanged += new NotifyCollectionChangedEventHandler(GroupDescriptionsChanged);
}
return MyGroupDescriptions;
}
}
/// <summary>
/// The top-level groups, constructed according to the descriptions
/// given in GroupDescriptions and/or GroupBySelector.
/// </summary>
public override ReadOnlyObservableCollection<object> Groups
{
get
{
return (EnsureCollectionView()) ? _collectionView.Groups : null;
}
}
/// <summary>
/// Enter a Defer Cycle.
/// Defer cycles are used to coalesce changes to the ICollectionView.
/// </summary>
public override IDisposable DeferRefresh()
{
// if already deferred (level > 0) and there is a _collectionView, there should be a _deferInnerRefresh
Debug.Assert(_deferLevel == 0 || _collectionView == null || _deferInnerRefresh != null);
// if not already deferred, there should NOT be a _deferInnerRefresh
Debug.Assert(_deferLevel != 0 || _deferInnerRefresh == null);
if (_deferLevel == 0 && _collectionView != null)
{
_deferInnerRefresh = _collectionView.DeferRefresh();
}
++_deferLevel; // do this after inner DeferRefresh, in case it throws
return new DeferHelper(this);
}
/// <summary>
/// Gets a value indicating whether access to the ItemCollection is synchronized (thread-safe).
/// </summary>
bool ICollection.IsSynchronized
{
get
{
return false;
}
}
#pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C#
/// <summary>
/// Returns an object to be used in thread synchronization.
/// </summary>
/// <exception cref="NotSupportedException">
/// ItemCollection cannot provide a sync root for synchronization while
/// in ItemsSource mode. Please use the ItemsSource directly to
/// get its sync root.
/// </exception>
object ICollection.SyncRoot
{
get
{
if (IsUsingItemsSource)
{
// see discussion in XML comment above.
#pragma warning suppress 6503 // "Property get methods should not throw exceptions."
throw new NotSupportedException(SR.ItemCollectionShouldUseInnerSyncRoot);
}
return _internalView.SyncRoot;
}
}
#pragma warning restore 1634, 1691
/// <summary>
/// Gets a value indicating whether the IList has a fixed size.
/// An ItemCollection can usually grow dynamically,
/// this call will commonly return FixedSize = False.
/// In ItemsSource mode, this call will return IsFixedSize = True.
/// </summary>
bool IList.IsFixedSize
{
get
{
return IsUsingItemsSource;
}
}
/// <summary>
/// Gets a value indicating whether the IList is read-only.
/// An ItemCollection is usually writable,
/// this call will commonly return IsReadOnly = False.
/// In ItemsSource mode, this call will return IsReadOnly = True.
/// </summary>
bool IList.IsReadOnly
{
get
{
return IsUsingItemsSource;
}
}
//------------------------------------------------------
#region ICurrentItem
/// <summary>
/// The ordinal position of the <seealso cref="CurrentItem"/> within the (optionally
/// sorted and filtered) view.
/// </summary>
public override int CurrentPosition
{
get
{
if (!EnsureCollectionView())
return -1;
VerifyRefreshNotDeferred();
return _collectionView.CurrentPosition;
}
}
/// <summary>
/// Return current item.
/// </summary>
public override object CurrentItem
{
get
{
if (!EnsureCollectionView())
return null;
VerifyRefreshNotDeferred();
return _collectionView.CurrentItem;
}
}
/// <summary>
/// Return true if <seealso cref="ICollectionView.CurrentItem"/> is beyond the end (End-Of-File).
/// </summary>
public override bool IsCurrentAfterLast
{
get
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.IsCurrentAfterLast;
}
}
/// <summary>
/// Return true if <seealso cref="ICollectionView.CurrentItem"/> is before the beginning (Beginning-Of-File).
/// </summary>
public override bool IsCurrentBeforeFirst
{
get
{
if (!EnsureCollectionView())
return false;
VerifyRefreshNotDeferred();
return _collectionView.IsCurrentBeforeFirst;
}
}
#endregion ICurrentItem
#endregion Public Properties
#region IEditableCollectionView
#region Adding new items
/// <summary>
/// Indicates whether to include a placeholder for a new item, and if so,
/// where to put it.
/// </summary>
NewItemPlaceholderPosition IEditableCollectionView.NewItemPlaceholderPosition
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.NewItemPlaceholderPosition;
}
else
{
return NewItemPlaceholderPosition.None;
}
}
set
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.NewItemPlaceholderPosition = value;
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "NewItemPlaceholderPosition"));
}
}
}
/// <summary>
/// Return true if the view supports <seealso cref="IEditableCollectionView.AddNew"/>.
/// </summary>
bool IEditableCollectionView.CanAddNew
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.CanAddNew;
}
else
{
return false;
}
}
}
/// <summary>
/// Add a new item to the underlying collection. Returns the new item.
/// After calling AddNew and changing the new item as desired, either
/// <seealso cref="IEditableCollectionView.CommitNew"/> or <seealso cref="IEditableCollectionView.CancelNew"/> should be
/// called to complete the transaction.
/// </summary>
object IEditableCollectionView.AddNew()
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.AddNew();
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "AddNew"));
}
}
/// <summary>
/// Complete the transaction started by <seealso cref="IEditableCollectionView.AddNew"/>. The new
/// item remains in the collection, and the view's sort, filter, and grouping
/// specifications (if any) are applied to the new item.
/// </summary>
void IEditableCollectionView.CommitNew()
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.CommitNew();
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "CommitNew"));
}
}
/// <summary>
/// Complete the transaction started by <seealso cref="IEditableCollectionView.AddNew"/>. The new
/// item is removed from the collection.
/// </summary>
void IEditableCollectionView.CancelNew()
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.CancelNew();
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "CancelNew"));
}
}
/// <summary>
/// Returns true if an </seealso cref="IEditableCollectionView.AddNew"> transaction is in progress.
/// </summary>
bool IEditableCollectionView.IsAddingNew
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.IsAddingNew;
}
else
{
return false;
}
}
}
/// <summary>
/// When an </seealso cref="IEditableCollectionView.AddNew"> transaction is in progress, this property
/// returns the new item. Otherwise it returns null.
/// </summary>
object IEditableCollectionView.CurrentAddItem
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.CurrentAddItem;
}
else
{
return null;
}
}
}
#endregion Adding new items
#region Removing items
/// <summary>
/// Return true if the view supports <seealso cref="IEditableCollectionView.Remove"/> and
/// <seealso cref="RemoveAt"/>.
/// </summary>
bool IEditableCollectionView.CanRemove
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.CanRemove;
}
else
{
return false;
}
}
}
/// <summary>
/// Remove the item at the given index from the underlying collection.
/// The index is interpreted with respect to the view (not with respect to
/// the underlying collection).
/// </summary>
void IEditableCollectionView.RemoveAt(int index)
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.RemoveAt(index);
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "RemoveAt"));
}
}
/// <summary>
/// Remove the given item from the underlying collection.
/// </summary>
void IEditableCollectionView.Remove(object item)
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.Remove(item);
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "Remove"));
}
}
#endregion Removing items
#region Transactional editing of an item
/// <summary>
/// Begins an editing transaction on the given item. The transaction is
/// completed by calling either <seealso cref="IEditableCollectionView.CommitEdit"/> or
/// <seealso cref="IEditableCollectionView.CancelEdit"/>. Any changes made to the item during
/// the transaction are considered "pending", provided that the view supports
/// the notion of "pending changes" for the given item.
/// </summary>
void IEditableCollectionView.EditItem(object item)
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.EditItem(item);
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "EditItem"));
}
}
/// <summary>
/// Complete the transaction started by <seealso cref="IEditableCollectionView.EditItem"/>.
/// The pending changes (if any) to the item are committed.
/// </summary>
void IEditableCollectionView.CommitEdit()
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.CommitEdit();
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "CommitEdit"));
}
}
/// <summary>
/// Complete the transaction started by <seealso cref="IEditableCollectionView.EditItem"/>.
/// The pending changes (if any) to the item are discarded.
/// </summary>
void IEditableCollectionView.CancelEdit()
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
ecv.CancelEdit();
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "CancelEdit"));
}
}
/// <summary>
/// Returns true if the view supports the notion of "pending changes" on the
/// current edit item. This may vary, depending on the view and the particular
/// item. For example, a view might return true if the current edit item
/// implements <seealso cref="IEditableObject"/>, or if the view has special
/// knowledge about the item that it can use to support rollback of pending
/// changes.
/// </summary>
bool IEditableCollectionView.CanCancelEdit
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.CanCancelEdit;
}
else
{
return false;
}
}
}
/// <summary>
/// Returns true if an </seealso cref="IEditableCollectionView.EditItem"> transaction is in progress.
/// </summary>
bool IEditableCollectionView.IsEditingItem
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.IsEditingItem;
}
else
{
return false;
}
}
}
/// <summary>
/// When an </seealso cref="IEditableCollectionView.EditItem"> transaction is in progress, this property
/// returns the affected item. Otherwise it returns null.
/// </summary>
object IEditableCollectionView.CurrentEditItem
{
get
{
IEditableCollectionView ecv = _collectionView as IEditableCollectionView;
if (ecv != null)
{
return ecv.CurrentEditItem;
}
else
{
return null;
}
}
}
#endregion Transactional editing of an item
#endregion IEditableCollectionView
#region IEditableCollectionViewAddNewItem
/// <summary>
/// Return true if the view supports <seealso cref="IEditableCollectionViewAddNewItem.AddNewItem"/>.
/// </summary>
bool IEditableCollectionViewAddNewItem.CanAddNewItem
{
get
{
IEditableCollectionViewAddNewItem ani = _collectionView as IEditableCollectionViewAddNewItem;
if (ani != null)
{
return ani.CanAddNewItem;
}
else
{
return false;
}
}
}
/// <summary>
/// Add a new item to the underlying collection. Returns the new item.
/// After calling AddNewItem and changing the new item as desired, either
/// <seealso cref="IEditableCollectionView.CommitNew"/> or <seealso cref="IEditableCollectionView.CancelNew"/> should be
/// called to complete the transaction.
/// </summary>
object IEditableCollectionViewAddNewItem.AddNewItem(object newItem)
{
IEditableCollectionViewAddNewItem ani = _collectionView as IEditableCollectionViewAddNewItem;
if (ani != null)
{
return ani.AddNewItem(newItem);
}
else
{
throw new InvalidOperationException(SR.Format(SR.MemberNotAllowedForView, "AddNewItem"));
}
}
#endregion IEditableCollectionViewAddNewItem
#region ICollectionViewLiveShaping
///<summary>
/// Gets a value that indicates whether this view supports turning live sorting on or off.
///</summary>
public bool CanChangeLiveSorting
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.CanChangeLiveSorting : false;
}
}
///<summary>
/// Gets a value that indicates whether this view supports turning live filtering on or off.
///</summary>
public bool CanChangeLiveFiltering
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.CanChangeLiveFiltering : false;
}
}
///<summary>
/// Gets a value that indicates whether this view supports turning live grouping on or off.
///</summary>
public bool CanChangeLiveGrouping
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.CanChangeLiveGrouping : false;
}
}
///<summary>
/// Gets or sets a value that indicates whether live sorting is enabled.
/// The value may be null if the view does not know whether live sorting is enabled.
/// Calling the setter when CanChangeLiveSorting is false will throw an
/// InvalidOperationException.
///</summary
public bool? IsLiveSorting
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveSorting : null;
}
set
{
MyIsLiveSorting = value;
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
if (cvls != null && cvls.CanChangeLiveSorting)
cvls.IsLiveSorting = value;
}
}
///<summary>
/// Gets or sets a value that indicates whether live filtering is enabled.
/// The value may be null if the view does not know whether live filtering is enabled.
/// Calling the setter when CanChangeLiveFiltering is false will throw an
/// InvalidOperationException.
///</summary>
public bool? IsLiveFiltering
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveFiltering : null;
}
set
{
MyIsLiveFiltering = value;
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
if (cvls != null && cvls.CanChangeLiveFiltering)
cvls.IsLiveFiltering = value;
}
}
///<summary>
/// Gets or sets a value that indicates whether live grouping is enabled.
/// The value may be null if the view does not know whether live grouping is enabled.
/// Calling the setter when CanChangeLiveGrouping is false will throw an
/// InvalidOperationException.
///</summary>
public bool? IsLiveGrouping
{
get
{
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveGrouping : null;
}
set
{
MyIsLiveGrouping = value;
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
if (cvls != null && cvls.CanChangeLiveGrouping)
cvls.IsLiveGrouping = value;
}
}
///<summary>
/// Gets a collection of strings describing the properties that
/// trigger a live-sorting recalculation.
/// The strings use the same format as SortDescription.PropertyName.
///</summary>
///<notes>
/// When this collection is empty, the view will use the PropertyName strings
/// from its SortDescriptions.
///
/// This collection is useful when sorting is described code supplied
/// by the application (e.g. ListCollectionView.CustomSort).
/// In this case the view does not know which properties the code examines;
/// the application should tell the view by adding the relevant properties
/// to the LiveSortingProperties collection.
///</notes>
public ObservableCollection<string> LiveSortingProperties
{
get
{
// always hand out this ItemCollection's LiveSortingProperties collection;
// in ItemsSource mode the inner collection view will be kept in synch with this collection
if (MyLiveSortingProperties == null)
{
MyLiveSortingProperties = new ObservableCollection<string>();
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
// no need to do this under the monitor - we haven't hooked up events yet
CloneList(MyLiveSortingProperties, icvls.LiveSortingProperties);
}
((INotifyCollectionChanged)MyLiveSortingProperties).CollectionChanged += new NotifyCollectionChangedEventHandler(LiveSortingChanged);
}
return MyLiveSortingProperties;
}
}
///<summary>
/// Gets a collection of strings describing the properties that
/// trigger a live-filtering recalculation.
/// The strings use the same format as SortDescription.PropertyName.
///</summary>
///<notes>
/// Filtering is described by a Predicate. The view does not
/// know which properties the Predicate examines; the application should
/// tell the view by adding the relevant properties to the LiveFilteringProperties
/// collection.
///</notes>
public ObservableCollection<string> LiveFilteringProperties
{
get
{
// always hand out this ItemCollection's LiveFilteringProperties collection;
// in ItemsSource mode the inner collection view will be kept in synch with this collection
if (MyLiveFilteringProperties == null)
{
MyLiveFilteringProperties = new ObservableCollection<string>();
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
// no need to do this under the monitor - we haven't hooked up events yet
CloneList(MyLiveFilteringProperties, icvls.LiveFilteringProperties);
}
((INotifyCollectionChanged)MyLiveFilteringProperties).CollectionChanged += new NotifyCollectionChangedEventHandler(LiveFilteringChanged);
}
return MyLiveFilteringProperties;
}
}
///<summary>
/// Gets a collection of strings describing the properties that
/// trigger a live-grouping recalculation.
/// The strings use the same format as PropertyGroupDescription.PropertyName.
///</summary>
///<notes>
/// When this collection is empty, the view will use the PropertyName strings
/// from its GroupDescriptions.
///
/// This collection is useful when grouping is described code supplied
/// by the application (e.g. PropertyGroupDescription.Converter).
/// In this case the view does not know which properties the code examines;
/// the application should tell the view by adding the relevant properties
/// to the LiveGroupingProperties collection.
///</notes>
public ObservableCollection<string> LiveGroupingProperties
{
get
{
// always hand out this ItemCollection's LiveGroupingProperties collection;
// in ItemsSource mode the inner collection view will be kept in synch with this collection
if (MyLiveGroupingProperties == null)
{
MyLiveGroupingProperties = new ObservableCollection<string>();
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
// no need to do this under the monitor - we haven't hooked up events yet
CloneList(MyLiveGroupingProperties, icvls.LiveGroupingProperties);
}
((INotifyCollectionChanged)MyLiveGroupingProperties).CollectionChanged += new NotifyCollectionChangedEventHandler(LiveGroupingChanged);
}
return MyLiveGroupingProperties;
}
}
#endregion ICollectionViewLiveShaping
#region IItemProperties
/// <summary>
/// Returns information about the properties available on items in the
/// underlying collection. This information may come from a schema, from
/// a type descriptor, from a representative item, or from some other source
/// known to the view.
/// </summary>
ReadOnlyCollection<ItemPropertyInfo> IItemProperties.ItemProperties
{
get
{
IItemProperties iip = _collectionView as IItemProperties;
if (iip != null)
{
return iip.ItemProperties;
}
else
{
return null;
}
}
}
#endregion IItemProperties
//------------------------------------------------------
//
// Internal API
//
//------------------------------------------------------
#region Internal API
internal DependencyObject ModelParent
{
get { return (DependencyObject)_modelParent.Target; }
}
internal FrameworkElement ModelParentFE
{
get { return ModelParent as FrameworkElement; }
}
// This puts the ItemCollection into ItemsSource mode.
internal void SetItemsSource(IEnumerable value, Func<object, object> GetSourceItem = null)
{
// Allow this while refresh is deferred.
// If we're switching from Normal mode, first make sure it's legal.
if (!IsUsingItemsSource && (_internalView != null) && (_internalView.RawCount > 0))
{
throw new InvalidOperationException(SR.CannotUseItemsSource);
}
_itemsSource = value;
_isUsingItemsSource = true;
SetCollectionView(CollectionViewSource.GetDefaultCollectionView(_itemsSource, ModelParent, GetSourceItem));
}
// This returns ItemCollection to direct mode.
internal void ClearItemsSource()
{
if (IsUsingItemsSource)
{
// return to normal mode
_itemsSource = null;
_isUsingItemsSource = false;
SetCollectionView(_internalView); // it's ok if _internalView is null; just like uninitialized
}
else
{
// already in normal mode - no-op
}
}
// Read-only property used by ItemsControl
internal IEnumerable ItemsSource
{
get
{
return _itemsSource;
}
}
internal bool IsUsingItemsSource
{
get
{
return _isUsingItemsSource;
}
}
internal CollectionView CollectionView
{
get { return _collectionView; }
}
internal void BeginInit()
{
Debug.Assert(_isInitializing == false);
_isInitializing = true;
if (_collectionView != null) // disconnect from collectionView to cut extraneous events
UnhookCollectionView(_collectionView);
}
internal void EndInit()
{
Debug.Assert(_isInitializing == true);
EnsureCollectionView();
_isInitializing = false; // now we allow collectionView to be hooked up again
if (_collectionView != null)
{
HookCollectionView(_collectionView);
Refresh(); // apply any sort or filter for the first time
}
}
internal IEnumerator LogicalChildren
{
get
{
EnsureInternalView();
return _internalView.LogicalChildren;
}
}
internal override void GetCollectionChangedSources(int level, Action<int, object, bool?, List<string>> format, List<string> sources)
{
format(level, this, false, sources);
if (_collectionView != null)
{
_collectionView.GetCollectionChangedSources(level+1, format, sources);
}
}
#endregion Internal API
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
private new bool IsRefreshDeferred
{
get { return _deferLevel > 0; }
}
#endregion
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
// ===== Lazy creation of InternalView =====
// When ItemCollection is instantiated, it is uninitialized (_collectionView == null).
// It remains so until SetItemsSource() puts it into ItemsSource mode
// or a modifying method call such as Add() or Insert() puts it into direct mode.
// Several ItemCollection methods check EnsureCollectionView, which returns false if
// (_collectionView == null) and (InternalView == null), and it can mean two things:
// 1) ItemCollection is uninitialized
// 2) ItemsControl is in ItemsSource mode, but the ItemsSource binding returned null
// for either of these cases, a reasonable default return value or behavior is provided.
// EnsureCollectionView() will set _collectionView to the InternalView if the mode is correct.
bool EnsureCollectionView()
{
if (_collectionView == null && !IsUsingItemsSource && _internalView != null)
{
// If refresh is not necessary, fake initialization so that SetCollectionView
// doesn't raise a refresh event.
if (_internalView.IsEmpty)
{
bool wasInitializing = _isInitializing;
_isInitializing = true;
SetCollectionView(_internalView);
_isInitializing = wasInitializing;
}
else
{
SetCollectionView(_internalView);
}
// If we're not in Begin/End Init, now's a good time to hook up listeners
if (!_isInitializing)
HookCollectionView(_collectionView);
}
return (_collectionView != null);
}
void EnsureInternalView()
{
if (_internalView == null)
{
// lazy creation of the InnerItemCollectionView
_internalView = new InnerItemCollectionView(_defaultCapacity, this);
}
}
// Change the collection view in use, unhook/hook event handlers
void SetCollectionView(CollectionView view)
{
if (_collectionView == view)
return;
if (_collectionView != null)
{
// Unhook events first, to avoid unnecessary refresh while it is still the active view.
if (!_isInitializing)
UnhookCollectionView(_collectionView);
if (IsRefreshDeferred) // we've been deferring refresh on the _collectionView
{
// end defer refresh on the _collectionView that we're letting go
_deferInnerRefresh.Dispose();
_deferInnerRefresh = null;
}
}
bool raiseReset = false;
_collectionView = view;
InvalidateEnumerableWrapper();
if (_collectionView != null)
{
_deferInnerRefresh = _collectionView.DeferRefresh();
ApplySortFilterAndGroup();
// delay event hook-up when initializing. see BeginInit() and EndInit().
if (!_isInitializing)
HookCollectionView(_collectionView);
if (!IsRefreshDeferred)
{
// make sure we get at least one refresh
raiseReset = !_collectionView.NeedsRefresh;
_deferInnerRefresh.Dispose(); // This fires refresh event that should reach ItemsControl listeners
_deferInnerRefresh = null;
}
// when refresh is deferred, we hold on to the inner DeferRefresh until EndDefer()
}
else // ItemsSource binding returned null
{
if (!IsRefreshDeferred)
{
raiseReset = true;
}
}
if (raiseReset)
{
// notify listeners that the view is changed
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
// with a new view, we have new live shaping behavior
OnPropertyChanged(new PropertyChangedEventArgs("IsLiveSorting"));
OnPropertyChanged(new PropertyChangedEventArgs("IsLiveFiltering"));
OnPropertyChanged(new PropertyChangedEventArgs("IsLiveGrouping"));
}
void ApplySortFilterAndGroup()
{
if (!IsShapingActive)
return;
// Only apply sort/filter/group if new view supports it and ItemCollection has real values
if (_collectionView.CanSort)
{
// if user has added SortDescriptions to this.SortDescriptions, those settings get pushed to
// the newly attached collection view
// if no SortDescriptions are set on ItemCollection,
// the inner collection view's .SortDescriptions gets copied to this.SortDescriptions
// when switching back to direct mode and no user-set on this.SortDescriptions
// then clear any .SortDescriptions set from previous inner collection view
SortDescriptionCollection source = (IsSortingSet) ? MySortDescriptions : _collectionView.SortDescriptions;
SortDescriptionCollection target = (IsSortingSet) ? _collectionView.SortDescriptions : MySortDescriptions;
using (SortDescriptionsMonitor.Enter())
{
CloneList(target, source);
}
}
if (_collectionView.CanFilter && MyFilter != null)
_collectionView.Filter = MyFilter;
if (_collectionView.CanGroup)
{
// if user has added GroupDescriptions to this.GroupDescriptions, those settings get pushed to
// the newly attached collection view
// if no GroupDescriptions are set on ItemCollection,
// the inner collection view's .GroupDescriptions gets copied to this.GroupDescriptions
// when switching back to direct mode and no user-set on this.GroupDescriptions
// then clear any .GroupDescriptions set from previous inner collection view
ObservableCollection<GroupDescription> source = (IsGroupingSet) ? MyGroupDescriptions : _collectionView.GroupDescriptions;
ObservableCollection<GroupDescription> target = (IsGroupingSet) ? _collectionView.GroupDescriptions : MyGroupDescriptions;
using (GroupDescriptionsMonitor.Enter())
{
CloneList(target, source);
}
}
ICollectionViewLiveShaping cvls = _collectionView as ICollectionViewLiveShaping;
if (cvls != null)
{
if (MyIsLiveSorting != null && cvls.CanChangeLiveSorting)
{
cvls.IsLiveSorting = MyIsLiveSorting;
}
if (MyIsLiveFiltering != null && cvls.CanChangeLiveFiltering)
{
cvls.IsLiveFiltering = MyIsLiveFiltering;
}
if (MyIsLiveGrouping != null && cvls.CanChangeLiveGrouping)
{
cvls.IsLiveGrouping = MyIsLiveGrouping;
}
}
}
void HookCollectionView(CollectionView view)
{
CollectionChangedEventManager.AddHandler(view, OnViewCollectionChanged);
CurrentChangingEventManager.AddHandler(view, OnCurrentChanging);
CurrentChangedEventManager.AddHandler(view, OnCurrentChanged);
PropertyChangedEventManager.AddHandler(view, OnViewPropertyChanged, String.Empty);
SortDescriptionCollection sort = view.SortDescriptions;
if (sort != null && sort != SortDescriptionCollection.Empty)
{
CollectionChangedEventManager.AddHandler(sort, OnInnerSortDescriptionsChanged);
}
ObservableCollection<GroupDescription> group = view.GroupDescriptions;
if (group != null)
{
CollectionChangedEventManager.AddHandler(group, OnInnerGroupDescriptionsChanged);
}
ICollectionViewLiveShaping iclvs = view as ICollectionViewLiveShaping;
if (iclvs != null)
{
ObservableCollection<string> liveSortingProperties = iclvs.LiveSortingProperties;
if (liveSortingProperties != null)
{
CollectionChangedEventManager.AddHandler(liveSortingProperties, OnInnerLiveSortingChanged);
}
ObservableCollection<string> liveFilteringProperties = iclvs.LiveFilteringProperties;
if (liveFilteringProperties != null)
{
CollectionChangedEventManager.AddHandler(liveFilteringProperties, OnInnerLiveFilteringChanged);
}
ObservableCollection<string> liveGroupingProperties = iclvs.LiveGroupingProperties;
if (liveGroupingProperties != null)
{
CollectionChangedEventManager.AddHandler(liveGroupingProperties, OnInnerLiveGroupingChanged);
}
}
}
void UnhookCollectionView(CollectionView view)
{
CollectionChangedEventManager.RemoveHandler(view, OnViewCollectionChanged);
CurrentChangingEventManager.RemoveHandler(view, OnCurrentChanging);
CurrentChangedEventManager.RemoveHandler(view, OnCurrentChanged);
PropertyChangedEventManager.RemoveHandler(view, OnViewPropertyChanged, String.Empty);
SortDescriptionCollection sort = view.SortDescriptions;
if (sort != null && sort != SortDescriptionCollection.Empty)
{
CollectionChangedEventManager.RemoveHandler(sort, OnInnerSortDescriptionsChanged);
}
ObservableCollection<GroupDescription> group = view.GroupDescriptions;
if (group != null)
{
CollectionChangedEventManager.RemoveHandler(group, OnInnerGroupDescriptionsChanged);
}
ICollectionViewLiveShaping iclvs = view as ICollectionViewLiveShaping;
if (iclvs != null)
{
ObservableCollection<string> liveSortingProperties = iclvs.LiveSortingProperties;
if (liveSortingProperties != null)
{
CollectionChangedEventManager.RemoveHandler(liveSortingProperties, OnInnerLiveSortingChanged);
}
ObservableCollection<string> liveFilteringProperties = iclvs.LiveFilteringProperties;
if (liveFilteringProperties != null)
{
CollectionChangedEventManager.RemoveHandler(liveFilteringProperties, OnInnerLiveFilteringChanged);
}
ObservableCollection<string> liveGroupingProperties = iclvs.LiveGroupingProperties;
if (liveGroupingProperties != null)
{
CollectionChangedEventManager.RemoveHandler(liveGroupingProperties, OnInnerLiveGroupingChanged);
}
}
// cancel any pending AddNew or EditItem transactions
IEditableCollectionView iev = _collectionView as IEditableCollectionView;
if (iev != null)
{
if (iev.IsAddingNew)
{
iev.CancelNew();
}
if (iev.IsEditingItem)
{
if (iev.CanCancelEdit)
{
iev.CancelEdit();
}
else
{
iev.CommitEdit();
}
}
}
}
void OnViewCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// when the collection changes, the enumerator is no longer valid.
// This should be detected by IndexedEnumerable, but isn't because
// of bug in CollectionView (CollectionView's enumerators
// do not invalidate after a collection change).
// As a partial remedy discard the
// enumerator here.
//
// Remove this line when the CollectionView bug is fixed.
InvalidateEnumerableWrapper();
// notify listeners on ItemsControl (like ItemContainerGenerator)
OnCollectionChanged(e);
}
void OnCurrentChanged(object sender, EventArgs e)
{
Debug.Assert(sender == _collectionView);
OnCurrentChanged();
}
void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
Debug.Assert(sender == _collectionView);
OnCurrentChanging(e);
}
void OnViewPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
}
/// <summary>
/// Handle events from the centralized event table
/// </summary>
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return false; // this method is no longer used (but must remain, for compat)
}
// Before any modifying access, first call CheckIsUsingInnerView() because
// a) InternalView is lazily created
// b) modifying access is only allowed when the InnerView is being used
// c) modifying access is only allowed when Refresh is not deferred
void CheckIsUsingInnerView()
{
if (IsUsingItemsSource)
throw new InvalidOperationException(SR.ItemsSourceInUse);
EnsureInternalView();
EnsureCollectionView();
Debug.Assert(_collectionView != null);
VerifyRefreshNotDeferred();
}
void EndDefer()
{
--_deferLevel;
if (_deferLevel == 0)
{
// if there is a _collectionView, there should be a _deferInnerRefresh
Debug.Assert(_collectionView == null || _deferInnerRefresh != null);
if (_deferInnerRefresh != null)
{
// set _deferInnerRefresh to null before calling Dispose,
// in case Dispose throws an exception.
IDisposable deferInnerRefresh = _deferInnerRefresh;
_deferInnerRefresh = null;
deferInnerRefresh.Dispose();
}
else
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
// Helper to validate that we are not in the middle of a DeferRefresh
// and throw if that is the case. The reason that this *new* version of VerifyRefreshNotDeferred
// on ItemCollection is needed is that ItemCollection has its own *new* IsRefreshDeferred
// which overrides IsRefreshDeferred on the base class (CollectionView), and we need to
// be sure that we reference that member on the derived class.
private new void VerifyRefreshNotDeferred()
{
#pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C#
#pragma warning disable 6503
// If the Refresh is being deferred to change filtering or sorting of the
// data by this CollectionView, then CollectionView will not reflect the correct
// state of the underlying data.
if (IsRefreshDeferred)
throw new InvalidOperationException(SR.NoCheckOrChangeWhenDeferred);
#pragma warning restore 6503
#pragma warning restore 1634, 1691
}
// SortDescription was added/removed to/from this ItemCollection.SortDescriptions, refresh CollView
private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SortDescriptionsMonitor.Busy)
return;
// if we have an inner collection view, keep its .SortDescriptions collection it up-to-date
if (_collectionView != null && _collectionView.CanSort)
{
using (SortDescriptionsMonitor.Enter())
{
SynchronizeCollections<SortDescription>(e, MySortDescriptions, _collectionView.SortDescriptions);
}
}
IsSortingSet = true; // most recent change came from ItemCollection
}
// SortDescription was added/removed to/from inner collectionView
private void OnInnerSortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!IsShapingActive || SortDescriptionsMonitor.Busy)
return;
// keep this ItemColl.SortDescriptions in synch with inner collection view's
using (SortDescriptionsMonitor.Enter())
{
SynchronizeCollections<SortDescription>(e, _collectionView.SortDescriptions, MySortDescriptions);
}
IsSortingSet = false; // most recent change came from inner collection view
}
// GroupDescription was added/removed to/from this ItemCollection.GroupDescriptions, refresh CollView
private void GroupDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (GroupDescriptionsMonitor.Busy)
return;
// if we have an inner collection view, keep its .SortDescriptions collection it up-to-date
if (_collectionView != null && _collectionView.CanGroup)
{
using (GroupDescriptionsMonitor.Enter())
{
SynchronizeCollections<GroupDescription>(e, MyGroupDescriptions, _collectionView.GroupDescriptions);
}
}
IsGroupingSet = true; // most recent change came from ItemCollection
}
// GroupDescription was added/removed to/from inner collectionView
private void OnInnerGroupDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!IsShapingActive || GroupDescriptionsMonitor.Busy)
return;
// keep this ItemColl.GroupDescriptions in synch with inner collection view's
using (GroupDescriptionsMonitor.Enter())
{
SynchronizeCollections<GroupDescription>(e, _collectionView.GroupDescriptions, MyGroupDescriptions);
}
IsGroupingSet = false; // most recent change came from inner collection view
}
// Property was added/removed to/from this ItemCollection.LiveSortingProperties, refresh CollView
private void LiveSortingChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (LiveSortingMonitor.Busy)
return;
// if we have an inner collection view, keep its LiveSortingProperties collection in sync
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveSortingMonitor.Enter())
{
SynchronizeCollections<string>(e, MyLiveSortingProperties, icvls.LiveSortingProperties);
}
}
IsLiveSortingSet = true; // most recent change came from ItemCollection
}
// Property was added/removed to/from inner collectionView's LiveSortingProperties
private void OnInnerLiveSortingChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!IsShapingActive || LiveSortingMonitor.Busy)
return;
// keep this ItemColl.LiveSortingProperties in sync with inner collection view's
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveSortingMonitor.Enter())
{
SynchronizeCollections<string>(e, icvls.LiveSortingProperties, MyLiveSortingProperties);
}
}
IsLiveSortingSet = false; // most recent change came from inner collection view
}
// Property was added/removed to/from this ItemCollection.LiveFilteringProperties, refresh CollView
private void LiveFilteringChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (LiveFilteringMonitor.Busy)
return;
// if we have an inner collection view, keep its LiveFilteringProperties collection in sync
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveFilteringMonitor.Enter())
{
SynchronizeCollections<string>(e, MyLiveFilteringProperties, icvls.LiveFilteringProperties);
}
}
IsLiveFilteringSet = true; // most recent change came from ItemCollection
}
// Property was added/removed to/from inner collectionView's LiveFilteringProperties
private void OnInnerLiveFilteringChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!IsShapingActive || LiveFilteringMonitor.Busy)
return;
// keep this ItemColl.LiveFilteringProperties in sync with inner collection view's
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveFilteringMonitor.Enter())
{
SynchronizeCollections<string>(e, icvls.LiveFilteringProperties, MyLiveFilteringProperties);
}
}
IsLiveFilteringSet = false; // most recent change came from inner collection view
}
// Property was added/removed to/from this ItemCollection.LiveGroupingProperties, refresh CollView
private void LiveGroupingChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (LiveGroupingMonitor.Busy)
return;
// if we have an inner collection view, keep its LiveGroupingProperties collection in sync
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveGroupingMonitor.Enter())
{
SynchronizeCollections<string>(e, MyLiveGroupingProperties, icvls.LiveGroupingProperties);
}
}
IsLiveGroupingSet = true; // most recent change came from ItemCollection
}
// Property was added/removed to/from inner collectionView's LiveGroupingProperties
private void OnInnerLiveGroupingChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!IsShapingActive || LiveGroupingMonitor.Busy)
return;
// keep this ItemColl.LiveGroupingProperties in sync with inner collection view's
ICollectionViewLiveShaping icvls = _collectionView as ICollectionViewLiveShaping;
if (icvls != null)
{
using (LiveGroupingMonitor.Enter())
{
SynchronizeCollections<string>(e, icvls.LiveGroupingProperties, MyLiveGroupingProperties);
}
}
IsLiveGroupingSet = false; // most recent change came from inner collection view
}
// keep collections in sync
private void SynchronizeCollections<T>(NotifyCollectionChangedEventArgs e, Collection<T> origin, Collection<T> clone)
{
if (clone == null)
return; // the clone might be lazily-created
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Debug.Assert(e.NewStartingIndex >= 0);
if (clone.Count + e.NewItems.Count != origin.Count)
goto case NotifyCollectionChangedAction.Reset;
for (int i = 0; i < e.NewItems.Count; i++)
{
clone.Insert(e.NewStartingIndex + i, (T)e.NewItems[i]);
}
break;
case NotifyCollectionChangedAction.Remove:
if (clone.Count - e.OldItems.Count != origin.Count)
goto case NotifyCollectionChangedAction.Reset;
Debug.Assert(e.OldStartingIndex >= 0);
for (int i = 0; i < e.OldItems.Count; i++)
{
clone.RemoveAt(e.OldStartingIndex);
}
break;
case NotifyCollectionChangedAction.Replace:
Debug.Assert(e.OldStartingIndex >= 0);
if (clone.Count != origin.Count)
goto case NotifyCollectionChangedAction.Reset;
for (int i = 0; i < e.OldItems.Count; i++)
{
clone[e.OldStartingIndex + i] = (T)e.NewItems[i];
}
break;
case NotifyCollectionChangedAction.Move:
Debug.Assert(e.OldStartingIndex >= 0);
if (clone.Count != origin.Count)
goto case NotifyCollectionChangedAction.Reset;
if (e.NewItems.Count == 1)
{
clone.RemoveAt(e.OldStartingIndex);
clone.Insert(e.NewStartingIndex, (T)e.NewItems[0]);
}
else
{
for (int i = 0; i < e.OldItems.Count; i++)
{
clone.RemoveAt(e.OldStartingIndex);
}
for (int i = 0; i < e.NewItems.Count; i++)
{
clone.Insert(e.NewStartingIndex + i, (T)e.NewItems[i]);
}
}
break;
// this arm also handles cases where the two collections have gotten
// out of sync (typically because exceptions prevented a previous sync
// from happening)
case NotifyCollectionChangedAction.Reset:
CloneList(clone, origin);
break;
default:
throw new NotSupportedException(SR.Format(SR.UnexpectedCollectionChangeAction, e.Action));
}
}
private void CloneList(IList clone, IList master)
{
// if either party is null, do nothing. Allowing null lets the caller
// avoid a lazy instantiation of the Sort/Group description collection.
if (clone == null || master == null)
return;
if (clone.Count > 0)
{
clone.Clear();
}
for (int i = 0, n = master.Count; i < n; ++i)
{
clone.Add(master[i]);
}
}
#endregion Private Methods
#region Shaping storage
private bool IsShapingActive
{
get { return _shapingStorage != null; }
}
private void EnsureShapingStorage()
{
if (!IsShapingActive)
{
_shapingStorage = new ShapingStorage();
}
}
private SortDescriptionCollection MySortDescriptions
{
get { return IsShapingActive ? _shapingStorage._sort : null; }
set { EnsureShapingStorage(); _shapingStorage._sort = value; }
}
private bool IsSortingSet
{
get { return IsShapingActive ? _shapingStorage._isSortingSet : false; }
set
{
Debug.Assert(IsShapingActive, "Shaping storage not available");
_shapingStorage._isSortingSet = value;
}
}
private MonitorWrapper SortDescriptionsMonitor
{
get
{
if (_shapingStorage._sortDescriptionsMonitor == null)
_shapingStorage._sortDescriptionsMonitor = new MonitorWrapper();
return _shapingStorage._sortDescriptionsMonitor;
}
}
private Predicate<object> MyFilter
{
get { return IsShapingActive ? _shapingStorage._filter : null; }
set { EnsureShapingStorage(); _shapingStorage._filter = value; }
}
private ObservableCollection<GroupDescription> MyGroupDescriptions
{
get { return IsShapingActive ? _shapingStorage._groupBy : null; }
set { EnsureShapingStorage(); _shapingStorage._groupBy = value; }
}
private bool IsGroupingSet
{
get { return IsShapingActive ? _shapingStorage._isGroupingSet : false; }
set
{
if (IsShapingActive)
_shapingStorage._isGroupingSet = value;
else
Debug.Assert(!value, "Shaping storage not available");
}
}
private MonitorWrapper GroupDescriptionsMonitor
{
get
{
if (_shapingStorage._groupDescriptionsMonitor == null)
_shapingStorage._groupDescriptionsMonitor = new MonitorWrapper();
return _shapingStorage._groupDescriptionsMonitor;
}
}
private bool? MyIsLiveSorting
{
get { return IsShapingActive ? _shapingStorage._isLiveSorting : null; }
set { EnsureShapingStorage(); _shapingStorage._isLiveSorting = value; }
}
private ObservableCollection<string> MyLiveSortingProperties
{
get { return IsShapingActive ? _shapingStorage._liveSortingProperties : null; }
set { EnsureShapingStorage(); _shapingStorage._liveSortingProperties = value; }
}
private bool IsLiveSortingSet
{
get { return IsShapingActive ? _shapingStorage._isLiveSortingSet : false; }
set
{
Debug.Assert(IsShapingActive, "Shaping storage not available");
_shapingStorage._isLiveSortingSet = value;
}
}
private MonitorWrapper LiveSortingMonitor
{
get
{
if (_shapingStorage._liveSortingMonitor == null)
_shapingStorage._liveSortingMonitor = new MonitorWrapper();
return _shapingStorage._liveSortingMonitor;
}
}
private bool? MyIsLiveFiltering
{
get { return IsShapingActive ? _shapingStorage._isLiveFiltering : null; }
set { EnsureShapingStorage(); _shapingStorage._isLiveFiltering = value; }
}
private ObservableCollection<string> MyLiveFilteringProperties
{
get { return IsShapingActive ? _shapingStorage._liveFilteringProperties : null; }
set { EnsureShapingStorage(); _shapingStorage._liveFilteringProperties = value; }
}
private bool IsLiveFilteringSet
{
get { return IsShapingActive ? _shapingStorage._isLiveFilteringSet : false; }
set
{
Debug.Assert(IsShapingActive, "Shaping storage not available");
_shapingStorage._isLiveFilteringSet = value;
}
}
private MonitorWrapper LiveFilteringMonitor
{
get
{
if (_shapingStorage._liveFilteringMonitor == null)
_shapingStorage._liveFilteringMonitor = new MonitorWrapper();
return _shapingStorage._liveFilteringMonitor;
}
}
private bool? MyIsLiveGrouping
{
get { return IsShapingActive ? _shapingStorage._isLiveGrouping : null; }
set { EnsureShapingStorage(); _shapingStorage._isLiveGrouping = value; }
}
private ObservableCollection<string> MyLiveGroupingProperties
{
get { return IsShapingActive ? _shapingStorage._liveGroupingProperties : null; }
set { EnsureShapingStorage(); _shapingStorage._liveGroupingProperties = value; }
}
private bool IsLiveGroupingSet
{
get { return IsShapingActive ? _shapingStorage._isLiveGroupingSet : false; }
set
{
Debug.Assert(IsShapingActive, "Shaping storage not available");
_shapingStorage._isLiveGroupingSet = value;
}
}
private MonitorWrapper LiveGroupingMonitor
{
get
{
if (_shapingStorage._liveGroupingMonitor == null)
_shapingStorage._liveGroupingMonitor = new MonitorWrapper();
return _shapingStorage._liveGroupingMonitor;
}
}
#endregion Shaping storage
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private InnerItemCollectionView _internalView; // direct-mode list and view
private IEnumerable _itemsSource; // ItemsControl.ItemsSource property
private CollectionView _collectionView; // delegate ICollectionView
private int _defaultCapacity = 16;
private bool _isUsingItemsSource; // true when using ItemsSource
private bool _isInitializing; // when true, ItemCollection does not listen to events of _collectionView
private int _deferLevel;
private IDisposable _deferInnerRefresh;
private ShapingStorage _shapingStorage;
private WeakReference _modelParent; // use WeakRef to avoid leaking the parent
#endregion Private Fields
//------------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
// ItemCollection rarely uses shaping directly. Make it pay-for-play
private class ShapingStorage
{
public bool _isSortingSet; // true when user has added to this.SortDescriptions
public bool _isGroupingSet; // true when user has added to this.GroupDescriptions
public bool _isLiveSortingSet; // true when user has added to this.LiveSortingProperties
public bool _isLiveFilteringSet; // true when user has added to this.LiveFilteringProperties
public bool _isLiveGroupingSet; // true when user has added to this.LiveGroupingProperties
public SortDescriptionCollection _sort; // storage for SortDescriptions; will forward to _collectionView.SortDescriptions when available
public Predicate<object> _filter; // storage for Filter when _collectionView is not available
public ObservableCollection<GroupDescription> _groupBy; // storage for GroupDescriptions; will forward to _collectionView.GroupDescriptions when available
public bool? _isLiveSorting; // true if live Sorting is requested
public bool? _isLiveFiltering; // true if live Filtering is requested
public bool? _isLiveGrouping; // true if live Grouping is requested
public ObservableCollection<string> _liveSortingProperties; // storage for LiveSortingProperties; will forward to _collectionView.LiveSortingProperties when available
public ObservableCollection<string> _liveFilteringProperties; // storage for LiveFilteringProperties; will forward to _collectionView.LiveFilteringProperties when available
public ObservableCollection<string> _liveGroupingProperties; // storage for LiveGroupingProperties; will forward to _collectionView.LiveGroupingProperties when available
public MonitorWrapper _sortDescriptionsMonitor;
public MonitorWrapper _groupDescriptionsMonitor;
public MonitorWrapper _liveSortingMonitor;
public MonitorWrapper _liveFilteringMonitor;
public MonitorWrapper _liveGroupingMonitor;
}
private class DeferHelper : IDisposable
{
public DeferHelper(ItemCollection itemCollection)
{
_itemCollection = itemCollection;
}
public void Dispose()
{
if (_itemCollection != null)
{
_itemCollection.EndDefer();
_itemCollection = null;
}
GC.SuppressFinalize(this);
}
private ItemCollection _itemCollection;
}
#endregion
}
}
|