Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Framework / System / Windows / Data / ListCollectionView.cs / 3 / ListCollectionView.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) 2003 by Microsoft Corporation. All rights reserved.
//
//
//
// Description: ICollectionView for collections implementing IList
//
// See spec at [....]/connecteddata/Specs/CollectionView.mht
//
// History:
// 06/02/2003 : [....] - Ported from DotNet tree
// 03/27/2004 : [....] - Implement IList
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using MS.Internal;
using MS.Internal.Data;
using MS.Utility;
namespace System.Windows.Data
{
///
/// based on and associated to .
///
public class ListCollectionView : CollectionView, IComparer
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Constructor
///
/// Underlying IList
public ListCollectionView(IList list)
: base(list)
{
_internalList = list;
if (InternalList.Count == 0) // don't call virtual IsEmpty in ctor
{
SetCurrent(null, -1, 0);
}
else
{
SetCurrent(InternalList[0], 0, 1);
}
_group = new CollectionViewGroupRoot(this);
_group.GroupDescriptionChanged += new EventHandler(OnGroupDescriptionChanged);
((INotifyCollectionChanged)_group).CollectionChanged += new NotifyCollectionChangedEventHandler(OnGroupChanged);
((INotifyCollectionChanged)_group.GroupDescriptions).CollectionChanged += new NotifyCollectionChangedEventHandler(OnGroupByChanged);
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
//------------------------------------------------------
#region ICollectionView
///
/// Re-create the view over the associated IList
///
///
/// Any sorting and filtering will take effect during Refresh.
///
protected override void RefreshOverride()
{
lock(SyncRoot)
{
ClearChangeLog();
if (UpdatedOutsideDispatcher)
{
ShadowCollection = new ArrayList((ICollection)SourceCollection);
}
}
object oldCurrentItem = CurrentItem;
int oldCurrentPosition = IsEmpty ? -1 : CurrentPosition;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
// force currency off the collection (gives user a chance to save dirty information)
OnCurrentChanging();
IList list = UpdatedOutsideDispatcher ? ShadowCollection : (SourceCollection as IList);
PrepareSortAndFilter(list);
// if there's no sort/filter, just use the collection's array
if (!UsesLocalArray)
{
_internalList = list;
}
else
{
_internalList = PrepareLocalArray(list);
}
PrepareGroups();
if (oldIsCurrentBeforeFirst || IsEmpty)
{
SetCurrent(null, -1);
}
else if (oldIsCurrentAfterLast)
{
SetCurrent(null, InternalCount);
}
else // set currency back to old current item
{
// oldCurrentItem may be null
// if there are duplicates, use the position of the first matching item
int newPosition = InternalIndexOf(oldCurrentItem);
if (newPosition < 0)
{
// oldCurrentItem not found: move to first item
SetCurrent(InternalItemAt(0), 0);
}
else
{
SetCurrent(oldCurrentItem, newPosition);
}
}
// tell listeners everything has changed
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
OnCurrentChanged();
if (IsCurrentAfterLast != oldIsCurrentAfterLast)
OnPropertyChanged(IsCurrentAfterLastPropertyName);
if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
if (oldCurrentPosition != CurrentPosition)
OnPropertyChanged(CurrentPositionPropertyName);
if (oldCurrentItem != CurrentItem)
OnPropertyChanged(CurrentItemPropertyName);
}
///
/// Return true if the item belongs to this view. No assumptions are
/// made about the item. This method will behave similarly to IList.Contains()
/// and will do an exhaustive search through all items in this view.
/// If the caller knows that the item belongs to the
/// underlying collection, it is more efficient to call PassesFilter.
///
public override bool Contains(object item)
{
VerifyRefreshNotDeferred();
return InternalContains(item);
}
///
/// Move to the item at the given index.
///
/// Move CurrentItem to this index
/// true if points to an item within the view.
public override bool MoveCurrentToPosition(int position)
{
VerifyRefreshNotDeferred();
if (position < -1 || position > InternalCount)
throw new ArgumentOutOfRangeException("position");
if ( (position != CurrentPosition || !IsCurrentInSync)
&& OKToChangeCurrent())
{
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
if (0 <= position && position < InternalCount)
{
SetCurrent(InternalItemAt(position), position);
}
else
{
SetCurrent(null, position);
}
OnCurrentChanged();
// notify that the properties have changed.
if (IsCurrentAfterLast != oldIsCurrentAfterLast)
OnPropertyChanged(IsCurrentAfterLastPropertyName);
if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
OnPropertyChanged(CurrentPositionPropertyName);
OnPropertyChanged(CurrentItemPropertyName);
}
return IsCurrentInView;
}
///
/// Returns true if this view really supports grouping.
/// When this returns false, the rest of the interface is ignored.
///
public override bool CanGroup
{
get { return true; }
}
///
/// The description of grouping, indexed by level.
///
public override ObservableCollection GroupDescriptions
{
get { return _group.GroupDescriptions; }
}
///
/// The top-level groups, constructed according to the descriptions
/// given in GroupDescriptions and/or GroupBySelector.
///
public override ReadOnlyObservableCollection Groups
{
get { return (IsGrouping) ? _group.Items : null; }
}
#endregion ICollectionView
///
/// 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.
///
public override bool PassesFilter(object item)
{
return ActiveFilter == null || ActiveFilter(item);
}
/// Return the index where the given item belongs, or -1 if this index is unknown.
///
///
/// If this method returns an index other than -1, it must always be true that
/// view[index-1] < item <= view[index], where the comparisons are done via
/// the view's IComparer.Compare method (if any).
/// (This method is used by a listener's (e.g. System.Windows.Controls.ItemsControl)
/// CollectionChanged event handler to speed up its reaction to insertion and deletion of items.
/// If IndexOf is not implemented, a listener does a binary search using IComparer.Compare.)
///
/// data item
public override int IndexOf(object item)
{
VerifyRefreshNotDeferred();
return InternalIndexOf(item);
}
///
/// Retrieve item at the given zero-based index in this CollectionView.
///
///
/// The index is evaluated with any SortDescriptions or Filter being set on this CollectionView.
///
///
/// Thrown if index is out of range
///
public override object GetItemAt(int index)
{
VerifyRefreshNotDeferred();
return InternalItemAt(index);
}
//------------------------------------------------------
#region IComparer
/// Return -, 0, or +, according to whether o1 occurs before, at, or after o2 (respectively)
///
/// first object
/// second object
///
/// Compares items by their resp. index in the IList.
///
int IComparer.Compare(object o1, object o2)
{
return Compare(o1, o2);
}
/// Return -, 0, or +, according to whether o1 occurs before, at, or after o2 (respectively)
///
/// first object
/// second object
///
/// Compares items by their resp. index in the IList.
///
protected virtual int Compare(object o1, object o2)
{
if (!IsGrouping)
{
if (ActiveComparer != null)
return ActiveComparer.Compare(o1, o2);
int i1 = InternalList.IndexOf(o1);
int i2 = InternalList.IndexOf(o2);
return (i1 - i2);
}
else
{
int i1 = InternalIndexOf(o1);
int i2 = InternalIndexOf(o2);
return (i1 - i2);
}
}
#endregion IComparer
///
/// Implementation of IEnumerable.GetEnumerator().
/// This provides a way to enumerate the members of the collection
/// without changing the currency.
///
protected override IEnumerator GetEnumerator()
{
VerifyRefreshNotDeferred();
return InternalGetEnumerator();
}
#endregion Public Methods
//-----------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
//-----------------------------------------------------
#region ICollectionView
///
/// Collection of Sort criteria to sort items in this view over the SourceCollection.
///
///
///
/// One or more sort criteria in form of
/// can be added, each specifying a property and direction to sort by.
///
///
public override SortDescriptionCollection SortDescriptions
{
get
{
if (_sort == null)
SetSortDescriptions(new SortDescriptionCollection());
return _sort;
}
}
///
/// Test if this ICollectionView supports sorting before adding
/// to .
///
///
/// ListCollectionView does implement an IComparer based sorting.
///
public override bool CanSort
{
get { return true; }
}
///
/// Test if this ICollectionView supports filtering before assigning
/// a filter callback to .
///
public override bool CanFilter
{
get { return true; }
}
#endregion ICollectionView
///
/// Set a custom comparer to sort items using an object that implements IComparer.
///
///
/// Setting the Sort criteria has no immediate effect,
/// an explicit call by the app is required.
/// Note: Setting the custom comparer object will clear previously set .
///
public IComparer CustomSort
{
get { return _customSort; }
set
{
_customSort = value;
SetSortDescriptions(null);
RefreshOrDefer();
}
}
///
/// A delegate to select the group description as a function of the
/// parent group and its level.
///
[DefaultValue(null)]
public virtual GroupDescriptionSelectorCallback GroupBySelector
{
get { return _group.GroupBySelector; }
set
{
if (!CanGroup)
throw new NotSupportedException();
_group.GroupBySelector = value;
RefreshOrDefer();
}
}
///
/// Return the estimated number of records (or -1, meaning "don't know").
///
public override int Count
{
get
{
VerifyRefreshNotDeferred();
return InternalCount;
}
}
///
/// Returns true if the resulting (filtered) view is emtpy.
///
public override bool IsEmpty
{
get { return (InternalCount == 0); }
}
///
/// Setting this to true informs the view that the list of items
/// (after applying the sort and filter, if any) is already in the
/// correct order for grouping. This allows the view to use a more
/// efficient algorithm to build the groups.
///
public bool IsDataInGroupOrder
{
get { return _group.IsDataInGroupOrder; }
set { _group.IsDataInGroupOrder = value; }
}
#endregion Public Properties
//-----------------------------------------------------
//
// Protected Methods
//
//-----------------------------------------------------
#region Protected Methods
///
/// Called by the the base class to notify derived class that
/// a CollectionChange has been posted to the message queue.
/// The purpose of this notification is to allow CollectionViews to
/// take a snapshot of whatever information is needed at the time
/// of the Post (most likely the state of the Data Collection).
///
///
/// The NotifyCollectionChangedEventArgs that is added to the change log
///
protected override void OnBeginChangeLogging(NotifyCollectionChangedEventArgs args)
{
if (args == null)
throw new ArgumentNullException("args");
if (ShadowCollection == null || args.Action == NotifyCollectionChangedAction.Reset)
{
ShadowCollection = new ArrayList((ICollection)SourceCollection);
if (!UsesLocalArray)
{
_internalList = ShadowCollection;
}
// the first change processed in ProcessChangeLog does
// not need to be applied to the ShadowCollection in
// ProcessChangeLog because the change will already be
// reflected as a result of copying the Collection.
_applyChangeToShadow = false;
}
}
///
/// Handle CollectionChange events
///
protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (args == null)
throw new ArgumentNullException("args");
ValidateCollectionChangedEventArgs(args);
int adjustedOldIndex = -1;
int adjustedNewIndex = -1;
// apply the change to the shadow copy
if (UpdatedOutsideDispatcher)
{
if (_applyChangeToShadow)
{
if (args.Action != NotifyCollectionChangedAction.Reset)
{
if (args.Action != NotifyCollectionChangedAction.Remove && args.NewStartingIndex < 0
|| args.Action != NotifyCollectionChangedAction.Add && args.OldStartingIndex < 0)
{
Debug.Assert(false, "Cannot update collection view from outside UIContext without index in event args");
return; //
}
else
{
AdjustShadowCopy(args);
}
}
}
_applyChangeToShadow = true;
}
// If the Action is Reset then we do a Refresh.
if (args.Action == NotifyCollectionChangedAction.Reset)
{
RefreshOrDefer();
return; // the Refresh raises collection change event, so there's nothing left to do
}
// If the Action is one that can be expected to have a valid NewItems[0] and NewStartingIndex then
// adjust the index for filtering and sorting.
if (args.Action != NotifyCollectionChangedAction.Remove)
{
adjustedNewIndex = AdjustBefore(NotifyCollectionChangedAction.Add, args.NewItems[0], args.NewStartingIndex);
}
// If the Action is one that can be expected to have a valid OldItems[0] and OldStartingIndex then
// adjust the index for filtering and sorting.
if (args.Action != NotifyCollectionChangedAction.Add)
{
adjustedOldIndex = AdjustBefore(NotifyCollectionChangedAction.Remove, args.OldItems[0], args.OldStartingIndex);
}
// Finding out the effective Action after filtering and sorting.
//
NotifyCollectionChangedAction effectiveAction = args.Action;
if (adjustedOldIndex == adjustedNewIndex && adjustedOldIndex >= 0)
{
effectiveAction = NotifyCollectionChangedAction.Replace;
}
else if (adjustedOldIndex == -1) // old index is unknown
{
// we weren't told the old index, but it may have been in the view.
if (adjustedNewIndex < 0)
{
// The new item will not be in the filtered view,
// so an Add is a no-op and anything else is a Remove.
if (args.Action == NotifyCollectionChangedAction.Add)
return;
effectiveAction = NotifyCollectionChangedAction.Remove;
}
}
else if (adjustedOldIndex < -1) // old item is known to be NOT in filtered view
{
if (adjustedNewIndex < 0)
{
// since the old item wasn't in the filtered view, and the new
// item would not be in the filtered view, this is a no-op.
return;
}
else
{
effectiveAction = NotifyCollectionChangedAction.Add;
}
}
else // old item was in view
{
if (adjustedNewIndex < 0)
{
effectiveAction = NotifyCollectionChangedAction.Remove;
}
else
{
effectiveAction = NotifyCollectionChangedAction.Move;
}
}
int originalCurrentPosition = CurrentPosition;
int oldCurrentPosition = CurrentPosition;
object oldCurrentItem = CurrentItem;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
// in the case of a replace that has a new adjustedPosition
// (likely caused by sorting), the only way to effectively communicate
// this change is through raising Remove followed by Insert.
NotifyCollectionChangedEventArgs args2 = null;
switch (effectiveAction)
{
case NotifyCollectionChangedAction.Add:
// insert into private view
if (UsesLocalArray)
{
InternalList.Insert(adjustedNewIndex, args.NewItems[0]);
}
if (!IsGrouping)
{
AdjustCurrencyForAdd(adjustedNewIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.NewItems[0], adjustedNewIndex);
}
else
{
AddItemToGroups(args.NewItems[0]);
}
break;
case NotifyCollectionChangedAction.Remove:
// remove from private view
if (UsesLocalArray)
{
InternalList.RemoveAt(adjustedOldIndex);
}
if (!IsGrouping)
{
AdjustCurrencyForRemove(adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.OldItems[0], adjustedOldIndex);
}
else
{
RemoveItemFromGroups(args.OldItems[0]);
}
break;
case NotifyCollectionChangedAction.Replace:
// replace item in private view
if (UsesLocalArray)
{
InternalList[adjustedOldIndex] = args.NewItems[0];
}
if (!IsGrouping)
{
AdjustCurrencyForReplace(adjustedOldIndex);
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.NewItems[0], args.OldItems[0], adjustedOldIndex);
}
else
{
RemoveItemFromGroups(args.OldItems[0]);
AddItemToGroups(args.NewItems[0]);
}
break;
case NotifyCollectionChangedAction.Move:
// remove from private view
bool simpleMove = args.OldItems[0] == args.NewItems[0];
if (UsesLocalArray)
{
InternalList.RemoveAt(adjustedOldIndex);
// we arrived at the adjustedNewIndex when the
// item at adjustedOldIndex was still in the collection.
// now the that oldItem has been removed, the adjustedNewIndex
// may be off by one.
if (adjustedOldIndex < adjustedNewIndex)
--adjustedNewIndex;
InternalList.Insert(adjustedNewIndex, args.NewItems[0]);
}
if (!IsGrouping)
{
AdjustCurrencyForMove(adjustedOldIndex, adjustedNewIndex);
if (simpleMove)
{
// simple move
args = new NotifyCollectionChangedEventArgs(effectiveAction, args.OldItems[0], adjustedNewIndex, adjustedOldIndex);
}
else
{
// move/replace
args2 = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, adjustedNewIndex);
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems, adjustedOldIndex);
}
}
else
{
if (!simpleMove)
{
RemoveItemFromGroups(args.OldItems[0]);
AddItemToGroups(args.NewItems[0]);
}
}
break;
default:
Invariant.Assert(false, SR.Get(SRID.UnexpectedCollectionChangeAction, effectiveAction));
break;
}
// remember whether scalar properties of the view have changed.
// They may change again during the collection change event, so we
// need to do the test before raising that event.
bool afterLastHasChanged = (IsCurrentAfterLast != oldIsCurrentAfterLast);
bool beforeFirstHasChanged = (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst);
bool currentPositionHasChanged = (CurrentPosition != oldCurrentPosition);
bool currentItemHasChanged = (CurrentItem != oldCurrentItem);
// take a new snapshot of the scalar properties, so that we can detect
// changes made during the collection change event
oldIsCurrentAfterLast = IsCurrentAfterLast;
oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
oldCurrentPosition = CurrentPosition;
oldCurrentItem = CurrentItem;
// base class will raise an event to our listeners
if (!IsGrouping)
{
// we've already returned if (args.Action == NotifyCollectionChangedAction.Reset) above
OnCollectionChanged(args);
if (args2 != null)
OnCollectionChanged(args2);
// Any scalar properties that changed don't need a further notification,
// but do need a new snapshot
if (IsCurrentAfterLast != oldIsCurrentAfterLast)
{
afterLastHasChanged = false;
oldIsCurrentAfterLast = IsCurrentAfterLast;
}
if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
{
beforeFirstHasChanged = false;
oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
}
if (CurrentPosition != oldCurrentPosition)
{
currentPositionHasChanged = false;
oldCurrentPosition = CurrentPosition;
}
if (CurrentItem != oldCurrentItem)
{
currentItemHasChanged = false;
oldCurrentItem = CurrentItem;
}
}
// currency has to change after firing the deletion event,
// so event handlers have the right picture
if (_currentElementWasRemoved)
{
MoveCurrencyOffDeletedElement(originalCurrentPosition);
// changes to the scalar properties need notification
afterLastHasChanged = afterLastHasChanged || (IsCurrentAfterLast != oldIsCurrentAfterLast);
beforeFirstHasChanged = beforeFirstHasChanged || (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst);
currentPositionHasChanged = currentPositionHasChanged || (CurrentPosition != oldCurrentPosition);
currentItemHasChanged = currentItemHasChanged || (CurrentItem != oldCurrentItem);
}
// notify that the properties have changed. We may end up doing
// double notification for properties that change during the collection
// change event, but that's not harmful. Detecting the double change
// is more trouble than it's worth.
if (afterLastHasChanged)
OnPropertyChanged(IsCurrentAfterLastPropertyName);
if (beforeFirstHasChanged)
OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
if (currentPositionHasChanged)
OnPropertyChanged(CurrentPositionPropertyName);
if (currentItemHasChanged)
OnPropertyChanged(CurrentItemPropertyName);
}
///
/// Return index of item in the internal list.
///
protected int InternalIndexOf(object item)
{
return (!IsGrouping) ? InternalList.IndexOf(item) : _group.LeafIndexOf(item);
}
///
/// Return item at the given index in the internal list.
///
protected object InternalItemAt(int index)
{
return (!IsGrouping) ? InternalList[index] : _group.LeafAt(index);
}
///
/// Return true if internal list contains the item.
///
protected bool InternalContains(object item)
{
return (!IsGrouping) ? InternalList.Contains(item) : (_group.LeafIndexOf(item) >= 0);
}
///
/// Return an enumerator for the internal list.
///
protected IEnumerator InternalGetEnumerator()
{
return (!IsGrouping) ? InternalList.GetEnumerator() : _group.GetLeafEnumerator();
}
///
/// True if a private copy of the data is needed for sorting and filtering
///
protected bool UsesLocalArray
{
get { return ActiveComparer != null || ActiveFilter != null; }
}
///
/// Protected accessor to private _internalList field.
///
protected IList InternalList
{
get { return _internalList; }
}
///
/// Protected accessor to private _activeComparer field.
///
protected IComparer ActiveComparer
{
get { return _activeComparer; }
set
{
_activeComparer = value;
_group.ActiveComparer = value;
}
}
///
/// Protected accessor to private _activeFilter field.
///
protected Predicate ActiveFilter
{
get { return _activeFilter; }
set { _activeFilter = value; }
}
///
/// Protected accessor to _isGrouping field.
///
protected bool IsGrouping
{
get { return _isGrouping; }
}
///
/// Protected accessor to private count.
///
protected int InternalCount
{
get { return (!IsGrouping) ? InternalList.Count : _group.ItemCount; }
}
#endregion Protected Methods
//------------------------------------------------------
//
// Internal Methods
//
//-----------------------------------------------------
#region Internal Methods
///
/// Contains a snapshot of the ICollectionView.SourceCollection
/// at the time that a change notification is posted.
/// This is done in OnBeginChangeLogging.
///
internal ArrayList ShadowCollection
{
get { return _shadowCollection; }
set { _shadowCollection = value; }
}
//
internal void AdjustShadowCopy(NotifyCollectionChangedEventArgs e)
{
int tempIndex;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex > _unknownIndex)
{
ShadowCollection.Insert(e.NewStartingIndex, e.NewItems[0]);
}
else
{
ShadowCollection.Add(e.NewItems[0]);
}
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex > _unknownIndex)
{
ShadowCollection.RemoveAt(e.OldStartingIndex);
}
else
{
ShadowCollection.Remove(e.OldItems[0]);
}
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldStartingIndex > _unknownIndex)
{
ShadowCollection[e.OldStartingIndex] = e.NewItems[0];
}
else
{
// allow the ShadowCollection to throw the IndexOutOfRangeException
// if the item is not found.
tempIndex = ShadowCollection.IndexOf(e.OldItems[0]);
ShadowCollection[e.OldStartingIndex] = e.NewItems[0];
}
break;
case NotifyCollectionChangedAction.Move:
if (e.OldStartingIndex > _unknownIndex)
{
ShadowCollection.RemoveAt(e.OldStartingIndex);
}
else
{
ShadowCollection.Remove(e.OldItems[0]);
ShadowCollection.Insert(e.NewStartingIndex, e.NewItems[0]);
}
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action));
}
}
// returns true if this ListCollectionView has sort descriptions,
// without tripping off lazy creation of .SortDescriptions collection
internal bool HasSortDescriptions
{
get { return ((_sort != null) && (_sort.Count > 0)); }
}
#endregion Internal Methods
#region Private Properties
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
// true if CurrentPosition points to item within view
private bool IsCurrentInView
{
get { return (0 <= CurrentPosition && CurrentPosition < InternalCount); }
}
// can the group name(s) for an item change after we've grouped the item?
private bool CanGroupNamesChange
{
// There's no way we can deduce this - the app has to tell us.
// If this is true, removing a grouped item is quite difficult.
// We cannot rely on its group names to tell us which group we inserted
// it into (they may have been different at insertion time), so we
// have to do a linear search.
get { return true; }
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems.Count != 1)
throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported));
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems.Count != 1)
throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported));
break;
case NotifyCollectionChangedAction.Replace:
if (e.NewItems.Count != 1 || e.OldItems.Count != 1)
throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported));
break;
case NotifyCollectionChangedAction.Move:
if (e.NewItems.Count != 1)
throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported));
if (e.NewStartingIndex < 0)
throw new InvalidOperationException(SR.Get(SRID.CannotMoveToUnknownPosition));
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action));
}
}
///
/// Create, filter and sort the local index array.
/// called from Refresh(), override in derived classes as needed.
///
/// new ILIst to associate this view with
/// new local array to use for this view
private IList PrepareLocalArray(IList list)
{
if (list == null)
throw new ArgumentNullException("list");
// filter the collection's array into the local array
ArrayList al;
if (ActiveFilter == null)
{
al = new ArrayList(list);
}
else
{
al = new ArrayList(list.Count); //
for (int k = 0; k < list.Count; ++k)
{
if (ActiveFilter(list[k]))
al.Add(list[k]);
}
}
// sort the local array
if (ActiveComparer != null)
{
al.Sort(ActiveComparer);
}
return al;
}
private void MoveCurrencyOffDeletedElement(int oldCurrentPosition)
{
int lastPosition = InternalCount - 1; // OK if last is -1
// if position falls beyond last position, move back to last position
int newPosition = (oldCurrentPosition < lastPosition) ? oldCurrentPosition : lastPosition;
// reset this to false before raising events to avoid problems in re-entrancy
_currentElementWasRemoved = false;
OnCurrentChanging();
if (newPosition < 0)
SetCurrent(null, newPosition);
else
SetCurrent(InternalItemAt(newPosition), newPosition);
OnCurrentChanged();
}
// Convert the collection's index to an index into the view.
// Return -1 if the index is unknown or moot (Reset events).
// Return -2 if the event doesn't apply to this view.
private int AdjustBefore (NotifyCollectionChangedAction action, object item, int index)
{
// index is not relevant to Reset events
if (action == NotifyCollectionChangedAction.Reset)
return -1;
IList ilFull = (UpdatedOutsideDispatcher ? ShadowCollection : SourceCollection) as IList;
// validate input
if (index < -1 || index > ilFull.Count)
throw new InvalidOperationException(SR.Get(SRID.CollectionChangeIndexOutOfRange, index, ilFull.Count));
if (action == NotifyCollectionChangedAction.Add)
{
if (index >= 0)
{
if (!Object.Equals(item, ilFull[index]))
throw new InvalidOperationException(SR.Get(SRID.AddedItemNotAtIndex, index));
}
else
{
// event didn't specify index - determine it the hard way
index = ilFull.IndexOf(item);
if (index < 0)
throw new InvalidOperationException(SR.Get(SRID.AddedItemNotInCollection));
}
}
// if there's no sort or filter, use the index into the full array
if (!UsesLocalArray)
{
return index;
}
// if the item isn't in the filter, return -2
if (!this.PassesFilter(item))
return -2;
if (action == NotifyCollectionChangedAction.Add)
{
// search the local array. If there's no sort order, use the index
// in the full array as the sort key.
IComparer comparer = (ActiveComparer != null) ? ActiveComparer
: new ListOrdinalComparer(ilFull, item, index);
ArrayList al = InternalList as ArrayList;
index = (al != null) ? index = al.BinarySearch(item, comparer) : -1;
if (index < 0)
index = ~index;
}
else if (action == NotifyCollectionChangedAction.Remove)
{
// a deleted item should already be in the local array
index = InternalList.IndexOf(item);
}
else
{
index = -1;
}
return index;
}
// fix up CurrentPosition and CurrentItem after a collection change
private void AdjustCurrencyForAdd(int index)
{
if (InternalCount == 1)
{
// added first item; set current at BeforeFirst
SetCurrent(null, -1);
}
else if (index <= CurrentPosition) // adjust current index if insertion is earlier
{
int newPosition = CurrentPosition + 1;
if (newPosition < InternalCount)
{
// CurrentItem might be out of sync if underlying list is not INCC
// or if this Add is the result of a Replace (Rem + Add)
SetCurrent(GetItemAt(newPosition), newPosition);
}
else
{
SetCurrent(null, InternalCount);
}
}
}
// fix up CurrentPosition and CurrentItem after a collection change
private void AdjustCurrencyForRemove(int index)
{
// adjust current index if deletion is earlier
if (index < CurrentPosition)
{
SetCurrent(CurrentItem, CurrentPosition - 1);
}
// remember to move currency off the deleted element
else if (index == CurrentPosition)
{
_currentElementWasRemoved = true;
}
}
// fix up CurrentPosition and CurrentItem after a collection change
private void AdjustCurrencyForMove(int oldIndex, int newIndex)
{
if (oldIndex == CurrentPosition)
{
// moving the current item - currency moves with the item (bug 1942184)
SetCurrent(GetItemAt(newIndex), newIndex);
}
else if (oldIndex < CurrentPosition && CurrentPosition <= newIndex)
{
// moving an item from before current position to after -
// current item shifts back one position
SetCurrent(CurrentItem, CurrentPosition - 1);
}
else if (newIndex <= CurrentPosition && CurrentPosition < oldIndex)
{
// moving an item from after current position to before -
// current item shifts ahead one position
SetCurrent(CurrentItem, CurrentPosition + 1);
}
// else no change necessary
}
// fix up CurrentPosition and CurrentItem after a collection change
private void AdjustCurrencyForReplace(int index)
{
// remember to move currency off the deleted element
if (index == CurrentPosition)
{
_currentElementWasRemoved = true;
}
}
// build the sort and filter information from the relevant properties
private void PrepareSortAndFilter(IList list)
{
// sort: prepare the comparer
if (_customSort != null)
{
ActiveComparer = _customSort;
}
else if (_sort != null && _sort.Count > 0)
{
// test for Xml in a way that doesn't JIT in System.Xml unless we really need it
string name = SourceCollection.GetType().Name;
if (name == "XmlDataCollection")
{
PrepareXmlComparer();
}
else
{
ActiveComparer = new SortFieldComparer(_sort, Culture);
}
}
else
{
ActiveComparer = null;
}
// filter: prepare the Predicate filter
ActiveFilter = Filter;
}
// set up the Xml comparer - code is isolated here to avoid loading System.Xml
private void PrepareXmlComparer()
{
XmlDataCollection xdc = SourceCollection as XmlDataCollection;
Invariant.Assert(_sort != null);
ActiveComparer = new XmlNodeComparer(_sort, xdc.XmlNamespaceManager, Culture);
}
// set new SortDescription collection; rehook collection change notification handler
private void SetSortDescriptions(SortDescriptionCollection descriptions)
{
if (_sort != null)
{
((INotifyCollectionChanged)_sort).CollectionChanged -= new NotifyCollectionChangedEventHandler(SortDescriptionsChanged);
}
_sort = descriptions;
if (_sort != null)
{
Invariant.Assert(_sort.Count == 0, "must be empty SortDescription collection");
((INotifyCollectionChanged)_sort).CollectionChanged += new NotifyCollectionChangedEventHandler(SortDescriptionsChanged);
}
}
// SortDescription was added/removed, refresh CollectionView
private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_customSort = null;
RefreshOrDefer();
}
#region Grouping
// divide the data items into groups
void PrepareGroups()
{
// discard old groups
_group.Clear();
// initialize the synthetic top level group
_group.Initialize();
// if there's no grouping, there's nothing to do
_isGrouping = (_group.GroupBy != null);
if (!_isGrouping)
return;
// loop through the sorted/filtered list of items, dividing them
// into groups
for (int k=0, n=InternalList.Count; k
/// Helper to raise a PropertyChanged event />).
///
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
//-----------------------------------------------------
private class ListOrdinalComparer : IComparer
{
IList _ilFull;
object _item;
int _index;
internal ListOrdinalComparer(IList ilFull, object item, int index)
{
_ilFull = ilFull;
_item = item;
_index = index;
}
// IComparer
public int Compare(object o1, object o2)
{
int i1 = Object.Equals(o1, _item) ? _index : _ilFull.IndexOf(o1);
int i2 = Object.Equals(o2, _item) ? _index : _ilFull.IndexOf(o2);
return (i1 - i2);
}
}
#endregion Private Methods
//-----------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
private IList _internalList;
private CollectionViewGroupRoot _group;
private bool _isGrouping;
private IComparer _activeComparer;
private Predicate _activeFilter;
private SortDescriptionCollection _sort;
private IComparer _customSort;
private ArrayList _shadowCollection;
private bool _applyChangeToShadow = false;
private bool _currentElementWasRemoved; // true if we need to MoveCurrencyOffDeletedElement
private const int _unknownIndex = -1;
#endregion Private Fields
}
///
/// A delegate to select the group description as a function of the
/// parent group and its level.
///
public delegate GroupDescription GroupDescriptionSelectorCallback(CollectionViewGroup group, int level);
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.