|
// 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.
//
// See spec at CollectionView.mht
//
// Description: Proxy that adds context affinity to an ICollectionView that
// doesn't already have it.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Data;
namespace MS.Internal.Data
{
///<summary>
/// Proxy view, used to interpose between the UI and a view that doesn't
/// support context affinity.
///</summary>
internal class CollectionViewProxy : CollectionView, IEditableCollectionViewAddNewItem, ICollectionViewLiveShaping, IItemProperties
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
internal CollectionViewProxy(ICollectionView view)
: base(view.SourceCollection, false)
{
_view = view;
view.CollectionChanged += new NotifyCollectionChangedEventHandler(_OnViewChanged);
view.CurrentChanging += new CurrentChangingEventHandler(_OnCurrentChanging);
view.CurrentChanged += new EventHandler(_OnCurrentChanged);
INotifyPropertyChanged ipc = view as INotifyPropertyChanged;
if (ipc != null)
ipc.PropertyChanged += new PropertyChangedEventHandler(_OnPropertyChanged);
}
//------------------------------------------------------
//
// Interfaces
//
//------------------------------------------------------
#region ICollectionView
/// <summary>
/// Culture to use during sorting.
/// </summary>
public override System.Globalization.CultureInfo Culture
{
get { return ProxiedView.Culture; }
set { ProxiedView.Culture = value; }
}
/// <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().
/// If the caller knows that the item belongs to the
/// underlying collection, it is more efficient to call PassesFilter.
/// </summary>
public override bool Contains(object item)
{
return ProxiedView.Contains(item);
}
/// <summary>
/// SourceCollection is the original un-filtered collection of which
/// this ICollectionView is a view.
/// </summary>
public override IEnumerable SourceCollection
{
get { return base.SourceCollection; }
}
/// <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 InnerList or 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="CanSort"/> property to test if sorting is supported before adding
/// to SortDescriptions.
/// </exception>
public override Predicate<object> Filter
{
get { return ProxiedView.Filter; }
set { ProxiedView.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 ProxiedView.CanFilter; }
}
/// <summary>
/// Set/get Sort criteria to sort items in collection.
/// </summary>
/// <remarks>
/// <p>
/// Clear a sort criteria by assigning SortDescription.Empty to this property.
/// One or more sort criteria in form of <seealso cref="SortDescription"/>
/// can be used, each specifying a property and direction to sort by.
/// </p>
/// </remarks>
/// <exception cref="NotSupportedException">
/// Simpler implementations do not support sorting and will throw a NotSupportedException.
/// Use <seealso cref="CanSort"/> property to test if sorting is supported before adding
/// to SortDescriptions.
/// </exception>
public override SortDescriptionCollection SortDescriptions
{
get { return ProxiedView.SortDescriptions; }
}
/// <summary>
/// Test if this ICollectionView supports sorting before adding
/// to <seealso cref="SortDescriptions"/>.
/// </summary>
public override bool CanSort
{
get { return ProxiedView.CanSort; }
}
/// <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 ProxiedView.CanGroup; }
}
/// <summary>
/// The description of grouping, indexed by level.
/// </summary>
public override ObservableCollection<GroupDescription> GroupDescriptions
{
get { return ProxiedView.GroupDescriptions; }
}
/// <summary>
/// The top-level groups, constructed according to the descriptions
/// given in GroupDescriptions.
/// </summary>
public override ReadOnlyObservableCollection<object> Groups
{
get { return ProxiedView.Groups; }
}
/// Re-create the view, using any <seealso cref="SortDescriptions"/>.
public override void Refresh()
{
IndexedEnumerable indexer = (IndexedEnumerable)Interlocked.Exchange(ref _indexer, null);
if (indexer != null)
{
indexer.Invalidate();
}
ProxiedView.Refresh();
}
/// <summary>
/// Enter a Defer Cycle.
/// Defer cycles are used to coalesce changes to the ICollectionView.
/// </summary>
public override IDisposable DeferRefresh()
{
return ProxiedView.DeferRefresh();
}
/// <summary> Return current item. </summary>
public override object CurrentItem
{
get { return ProxiedView.CurrentItem; }
}
/// <summary>
/// The ordinal position of the <seealso cref="CurrentItem"/> within the (optionally
/// sorted and filtered) view.
/// </summary>
public override int CurrentPosition
{
get { return ProxiedView.CurrentPosition; }
}
/// <summary> Return true if currency is beyond the end (End-Of-File). </summary>
public override bool IsCurrentAfterLast
{
get { return ProxiedView.IsCurrentAfterLast; }
}
/// <summary> Return true if currency is before the beginning (Beginning-Of-File). </summary>
public override bool IsCurrentBeforeFirst
{
get { return ProxiedView.IsCurrentBeforeFirst; }
}
/// <summary> Move to the first item. </summary>
public override bool MoveCurrentToFirst()
{
return ProxiedView.MoveCurrentToFirst();
}
/// <summary> Move to the previous item. </summary>
public override bool MoveCurrentToPrevious()
{
return ProxiedView.MoveCurrentToPrevious();
}
/// <summary> Move to the next item. </summary>
public override bool MoveCurrentToNext()
{
return ProxiedView.MoveCurrentToNext();
}
/// <summary> Move to the last item. </summary>
public override bool MoveCurrentToLast()
{
return ProxiedView.MoveCurrentToLast();
}
/// <summary> Move to the given item. </summary>
public override bool MoveCurrentTo(object item)
{
return ProxiedView.MoveCurrentTo(item);
}
/// <summary>Move CurrentItem to this index</summary>
public override bool MoveCurrentToPosition(int position)
{
//
// If the index is out of range here, I'll let the
// ProxiedView be the one to make that determination.
//
return ProxiedView.MoveCurrentToPosition(position);
}
public override event CurrentChangingEventHandler CurrentChanging
{
add { PrivateCurrentChanging += value; }
remove { PrivateCurrentChanging -= value; }
}
public override event EventHandler CurrentChanged
{
add { PrivateCurrentChanged += value; }
remove { PrivateCurrentChanged -= value; }
}
#endregion ICollectionView
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// Return the number of records (or -1, meaning "don't know").
/// A virtualizing view should return the best estimate it can
/// without de-virtualizing all the data. A non-virtualizing view
/// should return the exact count of its (filtered) data.
/// </summary>
public override int Count
{
get { return EnumerableWrapper.Count; }
}
public override bool IsEmpty
{
get { return ProxiedView.IsEmpty; }
}
public ICollectionView ProxiedView
{
get
{
// VerifyAccess();
return _view;
}
}
#endregion Public Properties
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary> Return the index where the given de belongs, or -1 if this index is unknown.
/// More precisely, if this returns an index other than -1, it must always be true that
/// view[index-1] < de <= view[index], where the comparisons are done via
/// the view's IComparer.Compare method (if any).
/// </summary>
/// <param name="item">data item</param>
public override int IndexOf(object item)
{
return EnumerableWrapper.IndexOf(item);
}
/// <summary>
/// Return true if the item belongs to this view. The item is assumed to belong to the
/// underlying DataCollection; this method merely takes filters into account.
/// It is commonly used during collection-changed notifications to determine if the added/removed
/// item requires processing.
/// Returns true if no filter is set on collection view.
/// </summary>
public override bool PassesFilter(object item)
{
if (ProxiedView.CanFilter && ProxiedView.Filter != null &&
item != NewItemPlaceholder && item != ((IEditableCollectionView)this).CurrentAddItem)
return ProxiedView.Filter(item);
return true;
}
/// <summary>
/// Retrieve item at the given zero-based index in this CollectionView.
/// </summary>
/// <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);
return EnumerableWrapper[index];
}
/// <summary>
/// Detach from the source collection. (I.e. stop listening to the collection's
/// events, or anything else that makes the CollectionView ineligible for
/// garbage collection.)
/// </summary>
public override void DetachFromSourceCollection()
{
if (_view != null)
{
_view.CollectionChanged -= new NotifyCollectionChangedEventHandler(_OnViewChanged);
_view.CurrentChanging -= new CurrentChangingEventHandler(_OnCurrentChanging);
_view.CurrentChanged -= new EventHandler(_OnCurrentChanged);
INotifyPropertyChanged ipc = _view as INotifyPropertyChanged;
if (ipc != null)
ipc.PropertyChanged -= new PropertyChangedEventHandler(_OnPropertyChanged);
_view = null;
}
base.DetachFromSourceCollection();
}
#endregion Public Methods
#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 = ProxiedView as IEditableCollectionView;
if (ecv != null)
{
return ecv.NewItemPlaceholderPosition;
}
else
{
return NewItemPlaceholderPosition.None;
}
}
set
{
IEditableCollectionView ecv = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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="IEditableCollectionView.RemoveAt"/>.
/// </summary>
bool IEditableCollectionView.CanRemove
{
get
{
IEditableCollectionView ecv = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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 = ProxiedView 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>
bool ICollectionViewLiveShaping.CanChangeLiveSorting
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView 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>
bool ICollectionViewLiveShaping.CanChangeLiveFiltering
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView 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>
bool ICollectionViewLiveShaping.CanChangeLiveGrouping
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView 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
bool? ICollectionViewLiveShaping.IsLiveSorting
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveSorting : null;
}
set
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
cvls.IsLiveSorting = value;
else
throw new InvalidOperationException(SR.Format(SR.CannotChangeLiveShaping, "IsLiveSorting", "CanChangeLiveSorting"));
}
}
///<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>
bool? ICollectionViewLiveShaping.IsLiveFiltering
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveFiltering : null;
}
set
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
cvls.IsLiveFiltering = value;
else
throw new InvalidOperationException(SR.Format(SR.CannotChangeLiveShaping, "IsLiveFiltering", "CanChangeLiveFiltering"));
}
}
///<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>
bool? ICollectionViewLiveShaping.IsLiveGrouping
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
return (cvls != null) ? cvls.IsLiveGrouping : null;
}
set
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
cvls.IsLiveGrouping = value;
else
throw new InvalidOperationException(SR.Format(SR.CannotChangeLiveShaping, "IsLiveGrouping", "CanChangeLiveGrouping"));
}
}
///<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>
ObservableCollection<string> ICollectionViewLiveShaping.LiveSortingProperties
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
return cvls.LiveSortingProperties;
// use a dummy collection. Its elements are ignored, but at least it won't crash.
if (_liveSortingProperties == null)
_liveSortingProperties = new ObservableCollection<string>();
return _liveSortingProperties;
}
}
///<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>
ObservableCollection<string> ICollectionViewLiveShaping.LiveFilteringProperties
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
return cvls.LiveFilteringProperties;
// use a dummy collection. Its elements are ignored, but at least it won't crash.
if (_liveFilteringProperties == null)
_liveFilteringProperties = new ObservableCollection<string>();
return _liveFilteringProperties;
}
}
///<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>
ObservableCollection<string> ICollectionViewLiveShaping.LiveGroupingProperties
{
get
{
ICollectionViewLiveShaping cvls = ProxiedView as ICollectionViewLiveShaping;
if (cvls != null)
return cvls.LiveGroupingProperties;
// use a dummy collection. Its elements are ignored, but at least it won't crash.
if (_liveGroupingProperties == null)
_liveGroupingProperties = new ObservableCollection<string>();
return _liveGroupingProperties;
}
}
#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 = ProxiedView as IItemProperties;
if (iip != null)
{
return iip.ItemProperties;
}
else
{
return null;
}
}
}
#endregion IItemProperties
//------------------------------------------------------
//
// 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 ((IEnumerable)ProxiedView).GetEnumerator(); }
#endregion Protected Methods
#region Internal methods
internal override void GetCollectionChangedSources(int level, Action<int, object, bool?, List<string>> format, List<string> sources)
{
format(level, this, false, sources);
if (_view != null)
{
format(level + 1, _view, true, sources);
object collection = _view.SourceCollection;
if (collection != null)
{
format(level + 2, collection, null, sources);
}
}
}
#endregion Internal methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
void _OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
OnPropertyChanged(args);
}
void _OnViewChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// VerifyAccess(); // will throw an exception if caller is not in correct UiContext
OnCollectionChanged(args);
}
void _OnCurrentChanging(object sender, CurrentChangingEventArgs args)
{
// VerifyAccess(); // will throw an exception if caller is not in correct UiContext
if (PrivateCurrentChanging != null)
PrivateCurrentChanging(this, args);
}
void _OnCurrentChanged(object sender, EventArgs args)
{
// VerifyAccess(); // will throw an exception if caller is not in correct UiContext
if (PrivateCurrentChanged != null)
PrivateCurrentChanged(this, args);
}
private IndexedEnumerable EnumerableWrapper
{
get
{
if (_indexer == null)
{
IndexedEnumerable newIndexer = new IndexedEnumerable(ProxiedView, new Predicate<object>(this.PassesFilter));
Interlocked.CompareExchange(ref _indexer, newIndexer, null);
}
return _indexer;
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
ICollectionView _view;
IndexedEnumerable _indexer;
event CurrentChangingEventHandler PrivateCurrentChanging;
event EventHandler PrivateCurrentChanged;
ObservableCollection<string> _liveSortingProperties; // dummy collection
ObservableCollection<string> _liveFilteringProperties; // dummy collection
ObservableCollection<string> _liveGroupingProperties; // dummy collection
}
}
|