Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Data / BindingListCollectionView.cs / 3 / BindingListCollectionView.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) 2003 by Microsoft Corporation. All rights reserved.
//
//
//
// See spec at http://avalon/connecteddata/Specs/CollectionView.mht
//
// History:
// 06/02/2003 : [....] - Ported from DotNet tree
// 03/27/2004 : kenlai - Implement IList
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Data;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using MS.Internal; // Invariant.Assert
using MS.Internal.Data;
using MS.Utility;
namespace System.Windows.Data
{
///
/// based on and associated to
/// and , namely ADO DataViews.
///
public sealed class BindingListCollectionView : CollectionView, IComparer, IEditableCollectionView, IItemProperties
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Constructor
///
/// Underlying IBindingList
public BindingListCollectionView(IBindingList list)
: base(list)
{
InternalList = list;
_blv = list as IBindingListView;
_isDataView = (list is System.Data.DataView);
_cachedList = new ArrayList(InternalList);
SubscribeToChanges();
_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
///
/// 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)
{
if (IsCustomFilterSet)
return Contains(item); // need to ask inner list, not cheap but only way to determine
else
return true; // every item is contained
}
///
/// Return true if the item belongs to this view. No assumptions are
/// made about the item. This method will behave similarly to IList.Contains().
///
public override bool Contains(object item)
{
VerifyRefreshNotDeferred();
return (item == NewItemPlaceholder) ? (NewItemPlaceholderPosition != NewItemPlaceholderPosition.None)
: CollectionProxy.Contains(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");
_MoveTo(position);
return IsCurrentInView;
}
#endregion ICollectionView
//------------------------------------------------------
#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)
{
int i1 = InternalIndexOf(o1);
int i2 = InternalIndexOf(o2);
return (i1 - i2);
}
#endregion IComparer
/// 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);
}
///
/// 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 inner IBindingList.
///
///
///
/// If the underlying SourceCollection only implements IBindingList,
/// then only one sort criteria in form of a
/// can be added, specifying a property and direction to sort by.
/// Adding more than one SortDescription will cause a InvalidOperationException.
/// One such class is Generic BindingList
///
///
/// Classes like ADO's DataView (the view around a DataTable) do implement
/// IBindingListView which can support sorting by more than one property
/// and also filtering
///
///
/// Some IBindingList implementations do not support sorting; for those this property
/// will return an empty and immutable / read-only SortDescription collection.
/// Attempting to modify such a collection will cause NotSupportedException.
/// Use property on this CollectionView to test if sorting is supported
/// before modifying the returned collection.
///
///
public override SortDescriptionCollection SortDescriptions
{
get
{
if (InternalList.SupportsSorting)
{
if (_sort == null)
{
bool allowAdvancedSorting = _blv != null && _blv.SupportsAdvancedSorting;
_sort = new BindingListSortDescriptionCollection(allowAdvancedSorting);
((INotifyCollectionChanged)_sort).CollectionChanged += new NotifyCollectionChangedEventHandler(SortDescriptionsChanged);
}
return _sort;
}
else
return SortDescriptionCollection.Empty;
}
}
///
/// Test if this ICollectionView supports sorting before adding
/// to .
///
///
/// ListCollectionView does implement an IComparer based sorting.
///
public override bool CanSort
{
get
{
return InternalList.SupportsSorting;
}
}
private IComparer ActiveComparer
{
get { return _comparer; }
set
{
_comparer = value;
_group.ActiveComparer = value;
}
}
///
/// BindingListCollectionView does not support callback-based filtering.
/// Use instead.
///
public override bool CanFilter
{
get
{
return false;
}
}
///
/// Gets or sets the filter to be used to exclude items from the collection of items returned by the data source .
///
///
/// Before assigning, test if this CollectionView supports custom filtering
/// .
/// The actual syntax depends on the implementer of IBindingListView. ADO's DataView is
/// a common example, see for its supported
/// filter expression syntax.
///
public string CustomFilter
{
get { return _customFilter; }
set
{
if (!CanCustomFilter)
throw new NotSupportedException(SR.Get(SRID.BindingListCannotCustomFilter));
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "CustomFilter"));
_customFilter = value;
RefreshOrDefer();
}
}
///
/// Test if this CollectionView supports custom filtering before assigning
/// a filter string to .
///
public bool CanCustomFilter
{
get
{
return ((_blv != null) && _blv.SupportsFiltering);
}
}
///
/// 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
///
/// A delegate to select the group description as a function of the
/// parent group and its level.
///
[DefaultValue(null)]
public GroupDescriptionSelectorCallback GroupBySelector
{
get { return _group.GroupBySelector; }
set
{
if (!CanGroup)
throw new NotSupportedException();
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "GroupBySelector"));
_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 (NewItemPlaceholderPosition == NewItemPlaceholderPosition.None &&
CollectionProxy.Count == 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
#region IEditableCollectionView
#region Adding new items
///
/// Indicates whether to include a placeholder for a new item, and if so,
/// where to put it.
///
public NewItemPlaceholderPosition NewItemPlaceholderPosition
{
get { return _newItemPlaceholderPosition; }
set
{
VerifyRefreshNotDeferred();
if (value != _newItemPlaceholderPosition && IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "NewItemPlaceholderPosition", "AddNew"));
NotifyCollectionChangedEventArgs args = null;
int oldIndex=-1, newIndex=-1;
// we're adding, removing, or moving the placeholder.
// Determine the appropriate events.
switch (value)
{
case NewItemPlaceholderPosition.None:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
oldIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
NewItemPlaceholder,
oldIndex);
break;
case NewItemPlaceholderPosition.AtEnd:
oldIndex = InternalCount - 1;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
NewItemPlaceholder,
oldIndex);
break;
}
break;
case NewItemPlaceholderPosition.AtBeginning:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
newIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
NewItemPlaceholder,
newIndex);
break;
case NewItemPlaceholderPosition.AtBeginning:
break;
case NewItemPlaceholderPosition.AtEnd:
oldIndex = InternalCount - 1;
newIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
NewItemPlaceholder,
newIndex,
oldIndex);
break;
}
break;
case NewItemPlaceholderPosition.AtEnd:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
newIndex = InternalCount;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
NewItemPlaceholder,
newIndex);
break;
case NewItemPlaceholderPosition.AtBeginning:
oldIndex = 0;
newIndex = InternalCount - 1;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
NewItemPlaceholder,
newIndex,
oldIndex);
break;
case NewItemPlaceholderPosition.AtEnd:
break;
}
break;
}
// now make the change and raise the events
if (args != null)
{
_newItemPlaceholderPosition = value;
if (!_isGrouping)
{
base.OnCollectionChanged(null, args);
}
else
{
if (oldIndex >= 0)
{
int index = (oldIndex == 0) ? 0 : _group.Items.Count - 1;
_group.RemoveSpecialItem(index, NewItemPlaceholder, false /*loading*/);
}
if (newIndex >= 0)
{
int index = (newIndex == 0) ? 0 : _group.Items.Count;
_group.InsertSpecialItem(index, NewItemPlaceholder, false /*loading*/);
}
}
OnPropertyChanged("NewItemPlaceholderPosition");
}
}
}
///
/// Return true if the view supports .
///
public bool CanAddNew
{
get { return !IsEditingItem && InternalList.AllowNew; }
}
///
/// Add a new item to the underlying collection. Returns the new item.
/// After calling AddNew and changing the new item as desired, either
/// or should be
/// called to complete the transaction.
///
public object AddNew()
{
VerifyRefreshNotDeferred();
if (IsEditingItem)
{
CommitEdit(); // implicitly close a previous EditItem
}
CommitNew(); // implicitly close a previous AddNew
if (!CanAddNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNew"));
_newItemIndex = -2; // this is a signal that the next ItemAdded event comes from AddNew
object newItem = InternalList.AddNew();
Debug.Assert(_newItemIndex != -2 && newItem == _newItem, "AddNew did not raise expected events");
MoveCurrentTo(newItem);
ISupportInitialize isi = newItem as ISupportInitialize;
if (isi != null)
{
isi.BeginInit();
}
// DataView.AddNew calls BeginEdit on the new item, but other implementations
// of IBL don't. Make up for them.
if (!IsDataView)
{
IEditableObject ieo = newItem as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
return newItem;
}
// Calling IBL.AddNew() will raise an ItemAdded event. We handle this specially
// to adjust the position of the new item in the view (it should be adjacent
// to the placeholder), and cache the new item for use by the other APIs
// related to AddNew. This method is called from ProcessCollectionChanged.
// The index gives the adjusted position of the newItem in the view; this
// differs from its position in the source collection by 1 if we've added
// a placeholder at the beginning.
void BeginAddNew(object newItem, int index)
{
Debug.Assert(_newItemIndex == -2 && _newItem == null, "unexpected call to BeginAddNew");
// remember the new item and its position in the underlying list
_newItem = newItem;
_newItemIndex = index;
// adjust the position of the new item
// (not needed when grouping, as we'll be inserting into the group structure)
int position = index;
if (!_isGrouping)
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
-- _newItemIndex;
position = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
position = InternalCount - 2;
break;
}
}
// raise events as if the new item appeared in the adjusted position
ProcessCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
newItem,
position));
}
///
/// Complete the transaction started by . The new
/// item remains in the collection, and the view's sort, filter, and grouping
/// specifications (if any) are applied to the new item.
///
public void CommitNew()
{
if (IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CommitNew", "EditItem"));
VerifyRefreshNotDeferred();
if (_newItem == null)
return;
// commit the new item
ICancelAddNew ican = InternalList as ICancelAddNew;
IEditableObject ieo;
if (ican != null)
{
ican.EndNew(_newItemIndex);
}
else if ((ieo = _newItem as IEditableObject) != null)
{
ieo.EndEdit();
}
// DataView raises events that cause us to update the view
// correctly (including leaving AddNew mode). BindingList does not
// raise these events. If they haven't happened, do the work now.
if (_newItem != null)
{
int delta = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
NotifyCollectionChangedEventArgs args = ProcessCommitNew(_newItemIndex, _newItemIndex + delta);
if (args != null)
{
base.OnCollectionChanged(InternalList, args);
}
}
}
///
/// Complete the transaction started by . The new
/// item is removed from the collection.
///
public void CancelNew()
{
if (IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CancelNew", "EditItem"));
VerifyRefreshNotDeferred();
if (_newItem == null)
return;
// cancel the AddNew
ICancelAddNew ican = InternalList as ICancelAddNew;
IEditableObject ieo;
if (ican != null)
{
ican.CancelNew(_newItemIndex);
}
else if ((ieo = _newItem as IEditableObject) != null)
{
ieo.CancelEdit();
}
// DataView raises events that cause us to update the view
// correctly (including leaving AddNew mode). BindingList does not
// raise these events. If they haven't happened, do the work now.
if (_newItem != null)
{
Debug.Assert(true);
}
}
// Common functionality used by CommitNew, CancelNew, and when the
// new item is removed by Remove or Refresh.
object EndAddNew(bool cancel)
{
object newItem = _newItem;
_newItem = null; // leave "adding-new" mode
IEditableObject ieo = newItem as IEditableObject;
if (ieo != null)
{
if (cancel)
{
ieo.CancelEdit();
}
else
{
ieo.EndEdit();
}
}
ISupportInitialize isi = newItem as ISupportInitialize;
if (isi != null)
{
isi.EndInit();
}
return newItem;
}
NotifyCollectionChangedEventArgs ProcessCommitNew(int fromIndex, int toIndex)
{
if (_isGrouping)
{
CommitNewForGrouping();
return null;
}
// CommitNew either causes the list to raise an event, or not.
// In either case, leave AddNew mode and raise a Move event if needed.
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
fromIndex = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
fromIndex = InternalCount - 2;
break;
}
object newItem = EndAddNew(false);
NotifyCollectionChangedEventArgs result = null;
if (fromIndex != toIndex)
{
result = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, newItem, toIndex, fromIndex);
}
return result;
}
void CommitNewForGrouping()
{
// for grouping we cannot pretend that the new item moves to a different position,
// since it may actually appear in several new positions (belonging to several groups).
// Instead, we remove the item from its temporary position, then add it to the groups
// as if it had just been added to the underlying collection.
int index;
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
default:
index = _group.Items.Count - 1;
break;
case NewItemPlaceholderPosition.AtBeginning:
index = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
index = _group.Items.Count - 2;
break;
}
// End the AddNew transaction
object newItem = EndAddNew(false);
// remove item from its temporary position
_group.RemoveSpecialItem(index, newItem, false /*loading*/);
// add it to the groups
AddItemToGroups(newItem);
}
///
/// Returns true if an transaction is in progress.
///
public bool IsAddingNew
{
get { return (_newItem != null); }
}
///
/// When an transaction is in progress, this property
/// returns the new item. Otherwise it returns null.
///
public object CurrentAddItem
{
get { return _newItem; }
}
#endregion Adding new items
#region Removing items
///
/// Return true if the view supports and
/// .
///
public bool CanRemove
{
get { return !IsEditingItem && !IsAddingNew && InternalList.AllowRemove; }
}
///
/// 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).
///
public void RemoveAt(int index)
{
if (IsEditingItem || IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "Remove"));
VerifyRefreshNotDeferred();
// convert the index from "view-relative" to "list-relative"
object item = GetItemAt(index);
if (item == CollectionView.NewItemPlaceholder)
throw new InvalidOperationException(SR.Get(SRID.RemovingPlaceholder));
if (_isGrouping)
{
index = InternalList.IndexOf(item);
}
else
{
int delta = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
index = index - delta;
}
// remove the item from the list
InternalList.RemoveAt(index);
}
///
/// Remove the given item from the underlying collection.
///
public void Remove(object item)
{
int index = InternalIndexOf(item);
if (index >= 0)
{
RemoveAt(index);
}
}
#endregion Removing items
#region Transactional editing of an item
///
/// Begins an editing transaction on the given item. The transaction is
/// completed by calling either or
/// . 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.
///
public void EditItem(object item)
{
VerifyRefreshNotDeferred();
if (item == NewItemPlaceholder)
throw new ArgumentException(SR.Get(SRID.CannotEditPlaceholder), "item");
if (IsAddingNew)
{
if (Object.Equals(item, _newItem))
return; // EditItem(newItem) is a no-op
CommitNew(); // implicitly close a previous AddNew
}
CommitEdit(); // implicitly close a previous EditItem transaction
_editItem = item;
IEditableObject ieo = item as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
///
/// Complete the transaction started by .
/// The pending changes (if any) to the item are committed.
///
public void CommitEdit()
{
if (IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CommitEdit", "AddNew"));
VerifyRefreshNotDeferred();
if (_editItem == null)
return;
IEditableObject ieo = _editItem as IEditableObject;
object editItem = _editItem;
_editItem = null;
if (ieo != null)
{
ieo.EndEdit();
}
// editing may change the item's group names (and we can't tell whether
// it really did). The best we can do is remove the item and re-insert
// it.
if (_isGrouping)
{
RemoveItemFromGroups(editItem);
AddItemToGroups(editItem);
return;
}
}
///
/// Complete the transaction started by .
/// The pending changes (if any) to the item are discarded.
///
public void CancelEdit()
{
if (IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CancelEdit", "AddNew"));
VerifyRefreshNotDeferred();
if (_editItem == null)
return;
IEditableObject ieo = _editItem as IEditableObject;
_editItem = null;
if (ieo != null)
{
ieo.CancelEdit();
}
else
throw new InvalidOperationException(SR.Get(SRID.CancelEditNotSupported));
}
private void ImplicitlyCancelEdit()
{
IEditableObject ieo = _editItem as IEditableObject;
_editItem = null;
if (ieo != null)
{
ieo.CancelEdit();
}
}
///
/// 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 , or if the view has special
/// knowledge about the item that it can use to support rollback of pending
/// changes.
///
public bool CanCancelEdit
{
get { return (_editItem is IEditableObject); }
}
///
/// Returns true if an transaction is in progress.
///
public bool IsEditingItem
{
get { return (_editItem != null); }
}
///
/// When an transaction is in progress, this property
/// returns the affected item. Otherwise it returns null.
///
public object CurrentEditItem
{
get { return _editItem; }
}
#endregion Transactional editing of an item
#endregion IEditableCollectionView
#region IItemProperties
///
/// 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.
///
public ReadOnlyCollection ItemProperties
{
get { return GetItemProperties(); }
}
#endregion IItemProperties
//-----------------------------------------------------
//
// Protected Methods
//
//-----------------------------------------------------
#region Protected Methods
///
/// 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)
{
_shadowList = new ArrayList((ICollection)SourceCollection);
}
}
object oldCurrentItem = CurrentItem;
int oldCurrentPosition = IsEmpty ? 0 : CurrentPosition;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
// force currency off the collection (gives user a chance to save dirty information)
OnCurrentChanging();
// changing filter and sorting will cause the inner IBindingList(View) to
// raise refresh action; ignore those until done setting filter/sort
_ignoreInnerRefresh = true;
// IBindingListView can support filtering
if (IsCustomFilterSet)
{
_isFiltered = true;
_blv.Filter = _customFilter;
}
else if (_isFiltered)
{
// app has cleared filter
_isFiltered = false;
_blv.RemoveFilter();
}
if ((_sort != null) && (_sort.Count > 0) && (CollectionProxy != null) && (CollectionProxy.Count > 0))
{
// convert Avalon SortDescription collection to .Net
// (i.e. string property names become PropertyDescriptors)
ListSortDescriptionCollection sorts = ConvertSortDescriptionCollection(_sort);
if (sorts.Count > 0)
{
_isSorted = true;
if (_blv == null)
InternalList.ApplySort(sorts[0].PropertyDescriptor, sorts[0].SortDirection);
else
_blv.ApplySort(sorts);
}
ActiveComparer = new SortFieldComparer(_sort, Culture);
}
else if (_isSorted)
{
// undo any previous sorting
_isSorted = false;
InternalList.RemoveSort();
ActiveComparer = null;
}
PrepareGroups();
// reset currency
if (oldIsCurrentBeforeFirst || IsEmpty)
{
SetCurrent(null, -1);
}
else if (oldIsCurrentAfterLast)
{
SetCurrent(null, InternalCount);
}
else
{
// oldCurrentItem may be null
// if there are duplicates, use the position of the first matching item
//ISSUE windows#868101 DataRowView.IndexOf(oldCurrentItem) returns wrong index, wrong current item gets restored
int newPosition = InternalIndexOf(oldCurrentItem);
if (newPosition < 0)
{
// oldCurrentItem not found: move to first item
object newItem;
newPosition = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ?
1 : 0;
if (newPosition < InternalCount && (newItem = InternalItemAt(newPosition)) != NewItemPlaceholder)
{
SetCurrent(newItem, newPosition);
}
else
{
SetCurrent(null, -1);
}
}
else
{
SetCurrent(oldCurrentItem, newPosition);
}
}
// refresh cached list with any changes
_cachedList = new ArrayList(InternalList);
_ignoreInnerRefresh = false;
// 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);
}
///
/// 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 (_shadowList == null || args.Action == NotifyCollectionChangedAction.Reset)
{
_shadowList = new ArrayList((ICollection)SourceCollection);
// 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;
}
}
///
/// Must be implemented by the derived classes to process a single change on the
/// UIContext. The UIContext will have allready been entered by now.
///
///
/// The NotifyCollectionChangedEventArgs to be processed.
///
protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
{
bool shouldRaiseEvent = false;
ValidateCollectionChangedEventArgs(args);
int originalCurrentPosition = CurrentPosition;
int oldCurrentPosition = CurrentPosition;
object oldCurrentItem = CurrentItem;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
bool moveCurrency = false;
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (_newItemIndex == -2)
{
// The ItemAdded event came from AddNew.
BeginAddNew(args.NewItems[0], args.NewStartingIndex);
}
else if (_isGrouping)
AddItemToGroups(args.NewItems[0]);
else
{
AdjustCurrencyForAdd(args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Remove:
if (_isGrouping)
RemoveItemFromGroups(args.OldItems[0]);
else
{
moveCurrency = AdjustCurrencyForRemove(args.OldStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Replace:
if (_isGrouping)
{
RemoveItemFromGroups(args.OldItems[0]);
AddItemToGroups(args.NewItems[0]);
}
else
{
moveCurrency = AdjustCurrencyForReplace(args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Move:
if (!_isGrouping)
{
AdjustCurrencyForMove(args.OldStartingIndex, args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Reset:
if (_isGrouping)
RefreshOrDefer();
else
shouldRaiseEvent = true;
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, args.Action));
}
if (UpdatedOutsideDispatcher)
{
if (_applyChangeToShadow)
{
AdjustShadowCopy(args);
}
_applyChangeToShadow = true;
}
// 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;
if (shouldRaiseEvent)
{
OnCollectionChanged(args);
// 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 (moveCurrency)
{
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);
}
#endregion Protected Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
///
/// Protected accessor to private count.
///
private int InternalCount
{
get
{
if (_isGrouping)
return _group.ItemCount;
return ((NewItemPlaceholderPosition == NewItemPlaceholderPosition.None) ? 0 : 1) +
CollectionProxy.Count;
}
}
private bool IsDataView
{
get { return _isDataView; }
}
#endregion Private Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
///
/// Return index of item in the internal list.
///
private int InternalIndexOf(object item)
{
if (_isGrouping)
{
return _group.LeafIndexOf(item);
}
if (item == NewItemPlaceholder)
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
return -1;
case NewItemPlaceholderPosition.AtBeginning:
return 0;
case NewItemPlaceholderPosition.AtEnd:
return InternalCount - 1;
}
}
else if (IsAddingNew && Object.Equals(item, _newItem))
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
return 1;
case NewItemPlaceholderPosition.AtEnd:
return InternalCount - 2;
}
}
int index = CollectionProxy.IndexOf(item);
// When you delete the last item from the list,
// ADO returns a bad value. Item will be "invalid", in the
// sense that it is not connected to a table. But IndexOf(item)
// returns 10, even though there are only 10 entries in the list.
// Looks like they're just returning item.Index without checking
// anything. So we have to do the checking for them.
if (index >= CollectionProxy.Count)
{
index = -1;
}
if (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning && index >= 0)
{
index += IsAddingNew ? 2 : 1;
}
return index;
}
///
/// Return item at the given index in the internal list.
///
private object InternalItemAt(int index)
{
if (_isGrouping)
{
return _group.LeafAt(index);
}
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
if (index == 0)
return NewItemPlaceholder;
--index;
if (IsAddingNew)
{
if (index == 0)
return _newItem;
if (index <= _newItemIndex+1)
-- index;
}
break;
case NewItemPlaceholderPosition.AtEnd:
if (index == InternalCount - 1)
return NewItemPlaceholder;
if (IsAddingNew && index == InternalCount-2)
return _newItem;
break;
}
return CollectionProxy[index];
}
///
/// Return true if internal list contains the item.
///
private bool InternalContains(object item)
{
if (item == NewItemPlaceholder)
return (NewItemPlaceholderPosition != NewItemPlaceholderPosition.None);
return (!_isGrouping) ? CollectionProxy.Contains(item) : (_group.LeafIndexOf(item) >= 0);
}
///
/// Return an enumerator for the internal list.
///
private IEnumerator InternalGetEnumerator()
{
if (!_isGrouping)
{
return new PlaceholderAwareEnumerator(this, CollectionProxy.GetEnumerator(), NewItemPlaceholderPosition, _newItem);
}
else
{
return _group.GetLeafEnumerator();
}
}
// Adjust the ShadowCopy so that it accurately reflects the state of the
// Data Collection immediately after the CollectionChangeEvent
private void AdjustShadowCopy(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_shadowList.Insert(e.NewStartingIndex, e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Remove:
_shadowList.RemoveAt(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
_shadowList[e.OldStartingIndex] = e.NewItems[0];
break;
case NotifyCollectionChangedAction.Move:
_shadowList.RemoveAt(e.OldStartingIndex);
_shadowList.Insert(e.NewStartingIndex, e.NewItems[0]);
break;
}
}
// true if CurrentPosition points to item within view
private bool IsCurrentInView
{
get { return (0 <= CurrentPosition && CurrentPosition < InternalCount); }
}
// move to a given index
private void _MoveTo (int proposed)
{
if (proposed == CurrentPosition || IsEmpty)
return;
object proposedCurrentItem = (0 <= proposed && proposed < InternalCount) ? GetItemAt(proposed) : null;
if (proposedCurrentItem == NewItemPlaceholder)
return; // ignore moves to the placeholder
if (OKToChangeCurrent())
{
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
SetCurrent(proposedCurrentItem, proposed);
OnCurrentChanged();
// notify that the properties have changed.
if (IsCurrentAfterLast != oldIsCurrentAfterLast)
OnPropertyChanged(IsCurrentAfterLastPropertyName);
if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
OnPropertyChanged(CurrentPositionPropertyName);
OnPropertyChanged(CurrentItemPropertyName);
}
}
// subscribe to change notifications
private void SubscribeToChanges ()
{
if (InternalList.SupportsChangeNotification)
{
InternalList.ListChanged += new ListChangedEventHandler(OnListChanged);
}
}
// IBindingList has changed
// At this point we may not have entered the UIContext, but
// the call to base.OnCollectionChanged will marshall the change over
private void OnListChanged(object sender, ListChangedEventArgs args)
{
if (_ignoreInnerRefresh && (args.ListChangedType == ListChangedType.Reset))
return;
NotifyCollectionChangedEventArgs forwardedArgs = null;
object item = null;
int delta = _isGrouping ? 0 : (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
int index = args.NewIndex;
switch (args.ListChangedType)
{
case ListChangedType.ItemAdded:
// Some implementations of IBindingList raise an extra ItemAdded event
// when the new item (from a previous call to AddNew) is "committed".
// [The IBindingList documentation suggests that all implementations
// should do this, but only DataView seems to obey this rather
// bizarre requirement.] We will ignore these extra events, unless
// they arise from a commit that we initiated. There's
// no way to detect them from the event args; we do it the same
// way WinForms.DataGridView does - by comparing counts.
if (InternalList.Count == _cachedList.Count)
{
if (IsAddingNew && index == _newItemIndex)
{
Debug.Assert(_newItem == InternalList[index], "unexpected item while committing AddNew");
forwardedArgs = ProcessCommitNew(index + delta, index + delta);
}
}
else
{
// normal ItemAdded event
item = InternalList[index];
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index + delta);
_cachedList.Insert(index, item);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemAdded: Expect underlying IBL and cached list to be of same length");
if (index <= _newItemIndex)
{
++ _newItemIndex;
}
}
break;
case ListChangedType.ItemDeleted:
item = _cachedList[index];
_cachedList.RemoveAt(index);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemDeleted: Expect underlying IBL and cached list to be of same length");
if (index < _newItemIndex)
{
-- _newItemIndex;
}
// implicitly cancel AddNew and/or EditItem transactions if the relevant item is removed
if (item == CurrentEditItem)
{
ImplicitlyCancelEdit();
}
if (item == CurrentAddItem)
{
EndAddNew(true);
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.AtBeginning:
index = 0;
break;
case NewItemPlaceholderPosition.AtEnd:
index = InternalCount - 1;
break;
}
}
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index + delta);
break;
case ListChangedType.ItemMoved:
if (IsAddingNew && args.OldIndex == _newItemIndex)
{
// ItemMoved applied to the new item. We assume this is the result
// of committing a new item when a sort is in effect - the item
// moves to its sorted position. There's no way to verify this assumption.
item = _newItem;
Debug.Assert(item == InternalList[index], "unexpected item while committing AddNew");
forwardedArgs = ProcessCommitNew(args.OldIndex, index + delta);
}
else
{
// normal ItemMoved event
item = InternalList[index];
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, index+delta, args.OldIndex+delta);
if (args.OldIndex < _newItemIndex && _newItemIndex < args.NewIndex)
{
-- _newItemIndex;
}
else if (args.NewIndex <= _newItemIndex && _newItemIndex < args.OldIndex)
{
++ _newItemIndex;
}
}
_cachedList.RemoveAt(args.OldIndex);
_cachedList.Insert(args.NewIndex, item);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemMoved: Expect underlying IBL and cached list to be of same length");
break;
case ListChangedType.ItemChanged:
if (!_itemsRaisePropertyChanged.HasValue)
{
// check whether individual items raise PropertyChanged events
// (DataRowView does)
item = InternalList[args.NewIndex];
_itemsRaisePropertyChanged = (item is INotifyPropertyChanged);
}
// if items raise PropertyChanged, we can ignore ItemChanged;
// otherwise, treat it like a Reset
if (!_itemsRaisePropertyChanged.Value)
{
goto case ListChangedType.Reset;
}
break;
case ListChangedType.Reset:
// treat all other changes like Reset
case ListChangedType.PropertyDescriptorAdded:
case ListChangedType.PropertyDescriptorChanged:
case ListChangedType.PropertyDescriptorDeleted:
// implicitly cancel EditItem transactions
if (IsEditingItem)
{
ImplicitlyCancelEdit();
}
// adjust AddNew transactions, depending on whether the new item
// survived the Reset
if (IsAddingNew)
{
_newItemIndex = InternalList.IndexOf(_newItem);
if (_newItemIndex < 0)
{
EndAddNew(true);
}
}
RefreshOrDefer();
break;
}
if (forwardedArgs != null)
{
base.OnCollectionChanged(sender, forwardedArgs);
}
}
// 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 [....] 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
// return true if the current item was removed
private bool AdjustCurrencyForRemove(int index)
{
bool result = (index == CurrentPosition);
// adjust current index if deletion is earlier
if (index < CurrentPosition)
{
SetCurrent(CurrentItem, CurrentPosition - 1);
}
return result;
}
// 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
// return true if the current item was replaced
private bool AdjustCurrencyForReplace(int index)
{
bool result = (index == CurrentPosition);
if (result)
{
SetCurrent(GetItemAt(index), index);
}
return result;
}
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;
OnCurrentChanging();
if (newPosition < 0)
SetCurrent(null, newPosition);
else
SetCurrent(InternalItemAt(newPosition), newPosition);
OnCurrentChanged();
}
private IList CollectionProxy
{
get
{
if (UpdatedOutsideDispatcher)
return _shadowList;
else
return InternalList;
}
}
///
/// Accessor to private _internalList field.
///
private IBindingList InternalList
{
get { return _internalList; }
set { _internalList = value; }
}
private bool IsCustomFilterSet
{
get { return ((_blv != null) && !String.IsNullOrEmpty(_customFilter)); }
}
// 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; }
}
// SortDescription was added/removed, refresh CollView
private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "Sorting"));
RefreshOrDefer();
}
// convert from Avalon SortDescriptions to the corresponding .NET collection
private ListSortDescriptionCollection ConvertSortDescriptionCollection(SortDescriptionCollection sorts)
{
ListSortDescriptionCollection convertedSorts = null;
//
ITypedList itl = InternalList as ITypedList;
if (itl != null)
{
PropertyDescriptorCollection pdc = itl.GetItemProperties(null);
if ((pdc == null) || (pdc.Count == 0))
throw new ArgumentException(SR.Get(SRID.CannotDetermineSortByPropertiesForCollection));
ListSortDescription[] sortDescriptions = new ListSortDescription[sorts.Count];
for (int i = 0; i < sorts.Count; i++)
{
PropertyDescriptor dd = pdc.Find(sorts[i].PropertyName, true);
if (dd == null)
{
string typeName = itl.GetListName(null);
throw new ArgumentException(SR.Get(SRID.PropertyToSortByNotFoundOnType, typeName, sorts[i].PropertyName));
}
ListSortDescription sd = new ListSortDescription(dd, sorts[i].Direction);
sortDescriptions[i] = sd;
}
convertedSorts = new ListSortDescriptionCollection(sortDescriptions);
}
else
{
// cannot resolve property names to PropertyDescriptors -> return emtpy collection
convertedSorts = new ListSortDescriptionCollection(new ListSortDescription[] {});
}
return convertedSorts;
}
#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 (with special cases for placeholder and new item)
if (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)
{
_group.InsertSpecialItem(0, NewItemPlaceholder, true /*loading*/);
if (IsAddingNew)
{
_group.InsertSpecialItem(1, _newItem, true /*loading*/);
}
}
for (int k=0, n=InternalList.Count; k
/// Helper to raise a PropertyChanged event />).
///
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion Private Methods
private class BindingListSortDescriptionCollection : SortDescriptionCollection
{
internal BindingListSortDescriptionCollection(bool allowMultipleDescriptions)
{
_allowMultipleDescriptions = allowMultipleDescriptions;
}
///
/// called by base class ObservableCollection<T> when an item is added to list;
///
protected override void InsertItem(int index, SortDescription item)
{
if (!_allowMultipleDescriptions && (this.Count > 0))
{
throw new InvalidOperationException(SR.Get(SRID.BindingListCanOnlySortByOneProperty));
}
base.InsertItem(index, item);
}
private bool _allowMultipleDescriptions;
}
//-----------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private IBindingList _internalList;
private CollectionViewGroupRoot _group;
private bool _isGrouping;
private IBindingListView _blv;
private BindingListSortDescriptionCollection _sort;
private ArrayList _shadowList;
private bool _applyChangeToShadow = false;
private bool _isSorted;
private IComparer _comparer;
private string _customFilter;
private bool _isFiltered;
private bool _ignoreInnerRefresh;
private bool? _itemsRaisePropertyChanged;
private bool _isDataView;
private object _newItem;
private object _editItem;
private int _newItemIndex; // position of _newItem in the source collection
private NewItemPlaceholderPosition _newItemPlaceholderPosition;
// to handle ItemRemoved directly, we need to remember the items -
// IBL's event args tell us the index, not the item itself
private ArrayList _cachedList;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
//
// Copyright (C) 2003 by Microsoft Corporation. All rights reserved.
//
//
//
// See spec at http://avalon/connecteddata/Specs/CollectionView.mht
//
// History:
// 06/02/2003 : [....] - Ported from DotNet tree
// 03/27/2004 : kenlai - Implement IList
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Data;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using MS.Internal; // Invariant.Assert
using MS.Internal.Data;
using MS.Utility;
namespace System.Windows.Data
{
///
/// based on and associated to
/// and , namely ADO DataViews.
///
public sealed class BindingListCollectionView : CollectionView, IComparer, IEditableCollectionView, IItemProperties
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Constructor
///
/// Underlying IBindingList
public BindingListCollectionView(IBindingList list)
: base(list)
{
InternalList = list;
_blv = list as IBindingListView;
_isDataView = (list is System.Data.DataView);
_cachedList = new ArrayList(InternalList);
SubscribeToChanges();
_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
///
/// 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)
{
if (IsCustomFilterSet)
return Contains(item); // need to ask inner list, not cheap but only way to determine
else
return true; // every item is contained
}
///
/// Return true if the item belongs to this view. No assumptions are
/// made about the item. This method will behave similarly to IList.Contains().
///
public override bool Contains(object item)
{
VerifyRefreshNotDeferred();
return (item == NewItemPlaceholder) ? (NewItemPlaceholderPosition != NewItemPlaceholderPosition.None)
: CollectionProxy.Contains(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");
_MoveTo(position);
return IsCurrentInView;
}
#endregion ICollectionView
//------------------------------------------------------
#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)
{
int i1 = InternalIndexOf(o1);
int i2 = InternalIndexOf(o2);
return (i1 - i2);
}
#endregion IComparer
/// 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);
}
///
/// 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 inner IBindingList.
///
///
///
/// If the underlying SourceCollection only implements IBindingList,
/// then only one sort criteria in form of a
/// can be added, specifying a property and direction to sort by.
/// Adding more than one SortDescription will cause a InvalidOperationException.
/// One such class is Generic BindingList
///
///
/// Classes like ADO's DataView (the view around a DataTable) do implement
/// IBindingListView which can support sorting by more than one property
/// and also filtering
///
///
/// Some IBindingList implementations do not support sorting; for those this property
/// will return an empty and immutable / read-only SortDescription collection.
/// Attempting to modify such a collection will cause NotSupportedException.
/// Use property on this CollectionView to test if sorting is supported
/// before modifying the returned collection.
///
///
public override SortDescriptionCollection SortDescriptions
{
get
{
if (InternalList.SupportsSorting)
{
if (_sort == null)
{
bool allowAdvancedSorting = _blv != null && _blv.SupportsAdvancedSorting;
_sort = new BindingListSortDescriptionCollection(allowAdvancedSorting);
((INotifyCollectionChanged)_sort).CollectionChanged += new NotifyCollectionChangedEventHandler(SortDescriptionsChanged);
}
return _sort;
}
else
return SortDescriptionCollection.Empty;
}
}
///
/// Test if this ICollectionView supports sorting before adding
/// to .
///
///
/// ListCollectionView does implement an IComparer based sorting.
///
public override bool CanSort
{
get
{
return InternalList.SupportsSorting;
}
}
private IComparer ActiveComparer
{
get { return _comparer; }
set
{
_comparer = value;
_group.ActiveComparer = value;
}
}
///
/// BindingListCollectionView does not support callback-based filtering.
/// Use instead.
///
public override bool CanFilter
{
get
{
return false;
}
}
///
/// Gets or sets the filter to be used to exclude items from the collection of items returned by the data source .
///
///
/// Before assigning, test if this CollectionView supports custom filtering
/// .
/// The actual syntax depends on the implementer of IBindingListView. ADO's DataView is
/// a common example, see for its supported
/// filter expression syntax.
///
public string CustomFilter
{
get { return _customFilter; }
set
{
if (!CanCustomFilter)
throw new NotSupportedException(SR.Get(SRID.BindingListCannotCustomFilter));
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "CustomFilter"));
_customFilter = value;
RefreshOrDefer();
}
}
///
/// Test if this CollectionView supports custom filtering before assigning
/// a filter string to .
///
public bool CanCustomFilter
{
get
{
return ((_blv != null) && _blv.SupportsFiltering);
}
}
///
/// 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
///
/// A delegate to select the group description as a function of the
/// parent group and its level.
///
[DefaultValue(null)]
public GroupDescriptionSelectorCallback GroupBySelector
{
get { return _group.GroupBySelector; }
set
{
if (!CanGroup)
throw new NotSupportedException();
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "GroupBySelector"));
_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 (NewItemPlaceholderPosition == NewItemPlaceholderPosition.None &&
CollectionProxy.Count == 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
#region IEditableCollectionView
#region Adding new items
///
/// Indicates whether to include a placeholder for a new item, and if so,
/// where to put it.
///
public NewItemPlaceholderPosition NewItemPlaceholderPosition
{
get { return _newItemPlaceholderPosition; }
set
{
VerifyRefreshNotDeferred();
if (value != _newItemPlaceholderPosition && IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "NewItemPlaceholderPosition", "AddNew"));
NotifyCollectionChangedEventArgs args = null;
int oldIndex=-1, newIndex=-1;
// we're adding, removing, or moving the placeholder.
// Determine the appropriate events.
switch (value)
{
case NewItemPlaceholderPosition.None:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
oldIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
NewItemPlaceholder,
oldIndex);
break;
case NewItemPlaceholderPosition.AtEnd:
oldIndex = InternalCount - 1;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
NewItemPlaceholder,
oldIndex);
break;
}
break;
case NewItemPlaceholderPosition.AtBeginning:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
newIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
NewItemPlaceholder,
newIndex);
break;
case NewItemPlaceholderPosition.AtBeginning:
break;
case NewItemPlaceholderPosition.AtEnd:
oldIndex = InternalCount - 1;
newIndex = 0;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
NewItemPlaceholder,
newIndex,
oldIndex);
break;
}
break;
case NewItemPlaceholderPosition.AtEnd:
switch (_newItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
newIndex = InternalCount;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
NewItemPlaceholder,
newIndex);
break;
case NewItemPlaceholderPosition.AtBeginning:
oldIndex = 0;
newIndex = InternalCount - 1;
args = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
NewItemPlaceholder,
newIndex,
oldIndex);
break;
case NewItemPlaceholderPosition.AtEnd:
break;
}
break;
}
// now make the change and raise the events
if (args != null)
{
_newItemPlaceholderPosition = value;
if (!_isGrouping)
{
base.OnCollectionChanged(null, args);
}
else
{
if (oldIndex >= 0)
{
int index = (oldIndex == 0) ? 0 : _group.Items.Count - 1;
_group.RemoveSpecialItem(index, NewItemPlaceholder, false /*loading*/);
}
if (newIndex >= 0)
{
int index = (newIndex == 0) ? 0 : _group.Items.Count;
_group.InsertSpecialItem(index, NewItemPlaceholder, false /*loading*/);
}
}
OnPropertyChanged("NewItemPlaceholderPosition");
}
}
}
///
/// Return true if the view supports .
///
public bool CanAddNew
{
get { return !IsEditingItem && InternalList.AllowNew; }
}
///
/// Add a new item to the underlying collection. Returns the new item.
/// After calling AddNew and changing the new item as desired, either
/// or should be
/// called to complete the transaction.
///
public object AddNew()
{
VerifyRefreshNotDeferred();
if (IsEditingItem)
{
CommitEdit(); // implicitly close a previous EditItem
}
CommitNew(); // implicitly close a previous AddNew
if (!CanAddNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNew"));
_newItemIndex = -2; // this is a signal that the next ItemAdded event comes from AddNew
object newItem = InternalList.AddNew();
Debug.Assert(_newItemIndex != -2 && newItem == _newItem, "AddNew did not raise expected events");
MoveCurrentTo(newItem);
ISupportInitialize isi = newItem as ISupportInitialize;
if (isi != null)
{
isi.BeginInit();
}
// DataView.AddNew calls BeginEdit on the new item, but other implementations
// of IBL don't. Make up for them.
if (!IsDataView)
{
IEditableObject ieo = newItem as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
return newItem;
}
// Calling IBL.AddNew() will raise an ItemAdded event. We handle this specially
// to adjust the position of the new item in the view (it should be adjacent
// to the placeholder), and cache the new item for use by the other APIs
// related to AddNew. This method is called from ProcessCollectionChanged.
// The index gives the adjusted position of the newItem in the view; this
// differs from its position in the source collection by 1 if we've added
// a placeholder at the beginning.
void BeginAddNew(object newItem, int index)
{
Debug.Assert(_newItemIndex == -2 && _newItem == null, "unexpected call to BeginAddNew");
// remember the new item and its position in the underlying list
_newItem = newItem;
_newItemIndex = index;
// adjust the position of the new item
// (not needed when grouping, as we'll be inserting into the group structure)
int position = index;
if (!_isGrouping)
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
-- _newItemIndex;
position = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
position = InternalCount - 2;
break;
}
}
// raise events as if the new item appeared in the adjusted position
ProcessCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
newItem,
position));
}
///
/// Complete the transaction started by . The new
/// item remains in the collection, and the view's sort, filter, and grouping
/// specifications (if any) are applied to the new item.
///
public void CommitNew()
{
if (IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CommitNew", "EditItem"));
VerifyRefreshNotDeferred();
if (_newItem == null)
return;
// commit the new item
ICancelAddNew ican = InternalList as ICancelAddNew;
IEditableObject ieo;
if (ican != null)
{
ican.EndNew(_newItemIndex);
}
else if ((ieo = _newItem as IEditableObject) != null)
{
ieo.EndEdit();
}
// DataView raises events that cause us to update the view
// correctly (including leaving AddNew mode). BindingList does not
// raise these events. If they haven't happened, do the work now.
if (_newItem != null)
{
int delta = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
NotifyCollectionChangedEventArgs args = ProcessCommitNew(_newItemIndex, _newItemIndex + delta);
if (args != null)
{
base.OnCollectionChanged(InternalList, args);
}
}
}
///
/// Complete the transaction started by . The new
/// item is removed from the collection.
///
public void CancelNew()
{
if (IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CancelNew", "EditItem"));
VerifyRefreshNotDeferred();
if (_newItem == null)
return;
// cancel the AddNew
ICancelAddNew ican = InternalList as ICancelAddNew;
IEditableObject ieo;
if (ican != null)
{
ican.CancelNew(_newItemIndex);
}
else if ((ieo = _newItem as IEditableObject) != null)
{
ieo.CancelEdit();
}
// DataView raises events that cause us to update the view
// correctly (including leaving AddNew mode). BindingList does not
// raise these events. If they haven't happened, do the work now.
if (_newItem != null)
{
Debug.Assert(true);
}
}
// Common functionality used by CommitNew, CancelNew, and when the
// new item is removed by Remove or Refresh.
object EndAddNew(bool cancel)
{
object newItem = _newItem;
_newItem = null; // leave "adding-new" mode
IEditableObject ieo = newItem as IEditableObject;
if (ieo != null)
{
if (cancel)
{
ieo.CancelEdit();
}
else
{
ieo.EndEdit();
}
}
ISupportInitialize isi = newItem as ISupportInitialize;
if (isi != null)
{
isi.EndInit();
}
return newItem;
}
NotifyCollectionChangedEventArgs ProcessCommitNew(int fromIndex, int toIndex)
{
if (_isGrouping)
{
CommitNewForGrouping();
return null;
}
// CommitNew either causes the list to raise an event, or not.
// In either case, leave AddNew mode and raise a Move event if needed.
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
fromIndex = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
fromIndex = InternalCount - 2;
break;
}
object newItem = EndAddNew(false);
NotifyCollectionChangedEventArgs result = null;
if (fromIndex != toIndex)
{
result = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, newItem, toIndex, fromIndex);
}
return result;
}
void CommitNewForGrouping()
{
// for grouping we cannot pretend that the new item moves to a different position,
// since it may actually appear in several new positions (belonging to several groups).
// Instead, we remove the item from its temporary position, then add it to the groups
// as if it had just been added to the underlying collection.
int index;
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
default:
index = _group.Items.Count - 1;
break;
case NewItemPlaceholderPosition.AtBeginning:
index = 1;
break;
case NewItemPlaceholderPosition.AtEnd:
index = _group.Items.Count - 2;
break;
}
// End the AddNew transaction
object newItem = EndAddNew(false);
// remove item from its temporary position
_group.RemoveSpecialItem(index, newItem, false /*loading*/);
// add it to the groups
AddItemToGroups(newItem);
}
///
/// Returns true if an transaction is in progress.
///
public bool IsAddingNew
{
get { return (_newItem != null); }
}
///
/// When an transaction is in progress, this property
/// returns the new item. Otherwise it returns null.
///
public object CurrentAddItem
{
get { return _newItem; }
}
#endregion Adding new items
#region Removing items
///
/// Return true if the view supports and
/// .
///
public bool CanRemove
{
get { return !IsEditingItem && !IsAddingNew && InternalList.AllowRemove; }
}
///
/// 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).
///
public void RemoveAt(int index)
{
if (IsEditingItem || IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "Remove"));
VerifyRefreshNotDeferred();
// convert the index from "view-relative" to "list-relative"
object item = GetItemAt(index);
if (item == CollectionView.NewItemPlaceholder)
throw new InvalidOperationException(SR.Get(SRID.RemovingPlaceholder));
if (_isGrouping)
{
index = InternalList.IndexOf(item);
}
else
{
int delta = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
index = index - delta;
}
// remove the item from the list
InternalList.RemoveAt(index);
}
///
/// Remove the given item from the underlying collection.
///
public void Remove(object item)
{
int index = InternalIndexOf(item);
if (index >= 0)
{
RemoveAt(index);
}
}
#endregion Removing items
#region Transactional editing of an item
///
/// Begins an editing transaction on the given item. The transaction is
/// completed by calling either or
/// . 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.
///
public void EditItem(object item)
{
VerifyRefreshNotDeferred();
if (item == NewItemPlaceholder)
throw new ArgumentException(SR.Get(SRID.CannotEditPlaceholder), "item");
if (IsAddingNew)
{
if (Object.Equals(item, _newItem))
return; // EditItem(newItem) is a no-op
CommitNew(); // implicitly close a previous AddNew
}
CommitEdit(); // implicitly close a previous EditItem transaction
_editItem = item;
IEditableObject ieo = item as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
///
/// Complete the transaction started by .
/// The pending changes (if any) to the item are committed.
///
public void CommitEdit()
{
if (IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CommitEdit", "AddNew"));
VerifyRefreshNotDeferred();
if (_editItem == null)
return;
IEditableObject ieo = _editItem as IEditableObject;
object editItem = _editItem;
_editItem = null;
if (ieo != null)
{
ieo.EndEdit();
}
// editing may change the item's group names (and we can't tell whether
// it really did). The best we can do is remove the item and re-insert
// it.
if (_isGrouping)
{
RemoveItemFromGroups(editItem);
AddItemToGroups(editItem);
return;
}
}
///
/// Complete the transaction started by .
/// The pending changes (if any) to the item are discarded.
///
public void CancelEdit()
{
if (IsAddingNew)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringTransaction, "CancelEdit", "AddNew"));
VerifyRefreshNotDeferred();
if (_editItem == null)
return;
IEditableObject ieo = _editItem as IEditableObject;
_editItem = null;
if (ieo != null)
{
ieo.CancelEdit();
}
else
throw new InvalidOperationException(SR.Get(SRID.CancelEditNotSupported));
}
private void ImplicitlyCancelEdit()
{
IEditableObject ieo = _editItem as IEditableObject;
_editItem = null;
if (ieo != null)
{
ieo.CancelEdit();
}
}
///
/// 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 , or if the view has special
/// knowledge about the item that it can use to support rollback of pending
/// changes.
///
public bool CanCancelEdit
{
get { return (_editItem is IEditableObject); }
}
///
/// Returns true if an transaction is in progress.
///
public bool IsEditingItem
{
get { return (_editItem != null); }
}
///
/// When an transaction is in progress, this property
/// returns the affected item. Otherwise it returns null.
///
public object CurrentEditItem
{
get { return _editItem; }
}
#endregion Transactional editing of an item
#endregion IEditableCollectionView
#region IItemProperties
///
/// 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.
///
public ReadOnlyCollection ItemProperties
{
get { return GetItemProperties(); }
}
#endregion IItemProperties
//-----------------------------------------------------
//
// Protected Methods
//
//-----------------------------------------------------
#region Protected Methods
///
/// 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)
{
_shadowList = new ArrayList((ICollection)SourceCollection);
}
}
object oldCurrentItem = CurrentItem;
int oldCurrentPosition = IsEmpty ? 0 : CurrentPosition;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
// force currency off the collection (gives user a chance to save dirty information)
OnCurrentChanging();
// changing filter and sorting will cause the inner IBindingList(View) to
// raise refresh action; ignore those until done setting filter/sort
_ignoreInnerRefresh = true;
// IBindingListView can support filtering
if (IsCustomFilterSet)
{
_isFiltered = true;
_blv.Filter = _customFilter;
}
else if (_isFiltered)
{
// app has cleared filter
_isFiltered = false;
_blv.RemoveFilter();
}
if ((_sort != null) && (_sort.Count > 0) && (CollectionProxy != null) && (CollectionProxy.Count > 0))
{
// convert Avalon SortDescription collection to .Net
// (i.e. string property names become PropertyDescriptors)
ListSortDescriptionCollection sorts = ConvertSortDescriptionCollection(_sort);
if (sorts.Count > 0)
{
_isSorted = true;
if (_blv == null)
InternalList.ApplySort(sorts[0].PropertyDescriptor, sorts[0].SortDirection);
else
_blv.ApplySort(sorts);
}
ActiveComparer = new SortFieldComparer(_sort, Culture);
}
else if (_isSorted)
{
// undo any previous sorting
_isSorted = false;
InternalList.RemoveSort();
ActiveComparer = null;
}
PrepareGroups();
// reset currency
if (oldIsCurrentBeforeFirst || IsEmpty)
{
SetCurrent(null, -1);
}
else if (oldIsCurrentAfterLast)
{
SetCurrent(null, InternalCount);
}
else
{
// oldCurrentItem may be null
// if there are duplicates, use the position of the first matching item
//ISSUE windows#868101 DataRowView.IndexOf(oldCurrentItem) returns wrong index, wrong current item gets restored
int newPosition = InternalIndexOf(oldCurrentItem);
if (newPosition < 0)
{
// oldCurrentItem not found: move to first item
object newItem;
newPosition = (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ?
1 : 0;
if (newPosition < InternalCount && (newItem = InternalItemAt(newPosition)) != NewItemPlaceholder)
{
SetCurrent(newItem, newPosition);
}
else
{
SetCurrent(null, -1);
}
}
else
{
SetCurrent(oldCurrentItem, newPosition);
}
}
// refresh cached list with any changes
_cachedList = new ArrayList(InternalList);
_ignoreInnerRefresh = false;
// 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);
}
///
/// 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 (_shadowList == null || args.Action == NotifyCollectionChangedAction.Reset)
{
_shadowList = new ArrayList((ICollection)SourceCollection);
// 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;
}
}
///
/// Must be implemented by the derived classes to process a single change on the
/// UIContext. The UIContext will have allready been entered by now.
///
///
/// The NotifyCollectionChangedEventArgs to be processed.
///
protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
{
bool shouldRaiseEvent = false;
ValidateCollectionChangedEventArgs(args);
int originalCurrentPosition = CurrentPosition;
int oldCurrentPosition = CurrentPosition;
object oldCurrentItem = CurrentItem;
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
bool moveCurrency = false;
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (_newItemIndex == -2)
{
// The ItemAdded event came from AddNew.
BeginAddNew(args.NewItems[0], args.NewStartingIndex);
}
else if (_isGrouping)
AddItemToGroups(args.NewItems[0]);
else
{
AdjustCurrencyForAdd(args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Remove:
if (_isGrouping)
RemoveItemFromGroups(args.OldItems[0]);
else
{
moveCurrency = AdjustCurrencyForRemove(args.OldStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Replace:
if (_isGrouping)
{
RemoveItemFromGroups(args.OldItems[0]);
AddItemToGroups(args.NewItems[0]);
}
else
{
moveCurrency = AdjustCurrencyForReplace(args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Move:
if (!_isGrouping)
{
AdjustCurrencyForMove(args.OldStartingIndex, args.NewStartingIndex);
shouldRaiseEvent = true;
}
break;
case NotifyCollectionChangedAction.Reset:
if (_isGrouping)
RefreshOrDefer();
else
shouldRaiseEvent = true;
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, args.Action));
}
if (UpdatedOutsideDispatcher)
{
if (_applyChangeToShadow)
{
AdjustShadowCopy(args);
}
_applyChangeToShadow = true;
}
// 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;
if (shouldRaiseEvent)
{
OnCollectionChanged(args);
// 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 (moveCurrency)
{
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);
}
#endregion Protected Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
///
/// Protected accessor to private count.
///
private int InternalCount
{
get
{
if (_isGrouping)
return _group.ItemCount;
return ((NewItemPlaceholderPosition == NewItemPlaceholderPosition.None) ? 0 : 1) +
CollectionProxy.Count;
}
}
private bool IsDataView
{
get { return _isDataView; }
}
#endregion Private Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
///
/// Return index of item in the internal list.
///
private int InternalIndexOf(object item)
{
if (_isGrouping)
{
return _group.LeafIndexOf(item);
}
if (item == NewItemPlaceholder)
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
return -1;
case NewItemPlaceholderPosition.AtBeginning:
return 0;
case NewItemPlaceholderPosition.AtEnd:
return InternalCount - 1;
}
}
else if (IsAddingNew && Object.Equals(item, _newItem))
{
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
return 1;
case NewItemPlaceholderPosition.AtEnd:
return InternalCount - 2;
}
}
int index = CollectionProxy.IndexOf(item);
// When you delete the last item from the list,
// ADO returns a bad value. Item will be "invalid", in the
// sense that it is not connected to a table. But IndexOf(item)
// returns 10, even though there are only 10 entries in the list.
// Looks like they're just returning item.Index without checking
// anything. So we have to do the checking for them.
if (index >= CollectionProxy.Count)
{
index = -1;
}
if (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning && index >= 0)
{
index += IsAddingNew ? 2 : 1;
}
return index;
}
///
/// Return item at the given index in the internal list.
///
private object InternalItemAt(int index)
{
if (_isGrouping)
{
return _group.LeafAt(index);
}
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.None:
break;
case NewItemPlaceholderPosition.AtBeginning:
if (index == 0)
return NewItemPlaceholder;
--index;
if (IsAddingNew)
{
if (index == 0)
return _newItem;
if (index <= _newItemIndex+1)
-- index;
}
break;
case NewItemPlaceholderPosition.AtEnd:
if (index == InternalCount - 1)
return NewItemPlaceholder;
if (IsAddingNew && index == InternalCount-2)
return _newItem;
break;
}
return CollectionProxy[index];
}
///
/// Return true if internal list contains the item.
///
private bool InternalContains(object item)
{
if (item == NewItemPlaceholder)
return (NewItemPlaceholderPosition != NewItemPlaceholderPosition.None);
return (!_isGrouping) ? CollectionProxy.Contains(item) : (_group.LeafIndexOf(item) >= 0);
}
///
/// Return an enumerator for the internal list.
///
private IEnumerator InternalGetEnumerator()
{
if (!_isGrouping)
{
return new PlaceholderAwareEnumerator(this, CollectionProxy.GetEnumerator(), NewItemPlaceholderPosition, _newItem);
}
else
{
return _group.GetLeafEnumerator();
}
}
// Adjust the ShadowCopy so that it accurately reflects the state of the
// Data Collection immediately after the CollectionChangeEvent
private void AdjustShadowCopy(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_shadowList.Insert(e.NewStartingIndex, e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Remove:
_shadowList.RemoveAt(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
_shadowList[e.OldStartingIndex] = e.NewItems[0];
break;
case NotifyCollectionChangedAction.Move:
_shadowList.RemoveAt(e.OldStartingIndex);
_shadowList.Insert(e.NewStartingIndex, e.NewItems[0]);
break;
}
}
// true if CurrentPosition points to item within view
private bool IsCurrentInView
{
get { return (0 <= CurrentPosition && CurrentPosition < InternalCount); }
}
// move to a given index
private void _MoveTo (int proposed)
{
if (proposed == CurrentPosition || IsEmpty)
return;
object proposedCurrentItem = (0 <= proposed && proposed < InternalCount) ? GetItemAt(proposed) : null;
if (proposedCurrentItem == NewItemPlaceholder)
return; // ignore moves to the placeholder
if (OKToChangeCurrent())
{
bool oldIsCurrentAfterLast = IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
SetCurrent(proposedCurrentItem, proposed);
OnCurrentChanged();
// notify that the properties have changed.
if (IsCurrentAfterLast != oldIsCurrentAfterLast)
OnPropertyChanged(IsCurrentAfterLastPropertyName);
if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
OnPropertyChanged(IsCurrentBeforeFirstPropertyName);
OnPropertyChanged(CurrentPositionPropertyName);
OnPropertyChanged(CurrentItemPropertyName);
}
}
// subscribe to change notifications
private void SubscribeToChanges ()
{
if (InternalList.SupportsChangeNotification)
{
InternalList.ListChanged += new ListChangedEventHandler(OnListChanged);
}
}
// IBindingList has changed
// At this point we may not have entered the UIContext, but
// the call to base.OnCollectionChanged will marshall the change over
private void OnListChanged(object sender, ListChangedEventArgs args)
{
if (_ignoreInnerRefresh && (args.ListChangedType == ListChangedType.Reset))
return;
NotifyCollectionChangedEventArgs forwardedArgs = null;
object item = null;
int delta = _isGrouping ? 0 : (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning) ? 1 : 0;
int index = args.NewIndex;
switch (args.ListChangedType)
{
case ListChangedType.ItemAdded:
// Some implementations of IBindingList raise an extra ItemAdded event
// when the new item (from a previous call to AddNew) is "committed".
// [The IBindingList documentation suggests that all implementations
// should do this, but only DataView seems to obey this rather
// bizarre requirement.] We will ignore these extra events, unless
// they arise from a commit that we initiated. There's
// no way to detect them from the event args; we do it the same
// way WinForms.DataGridView does - by comparing counts.
if (InternalList.Count == _cachedList.Count)
{
if (IsAddingNew && index == _newItemIndex)
{
Debug.Assert(_newItem == InternalList[index], "unexpected item while committing AddNew");
forwardedArgs = ProcessCommitNew(index + delta, index + delta);
}
}
else
{
// normal ItemAdded event
item = InternalList[index];
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index + delta);
_cachedList.Insert(index, item);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemAdded: Expect underlying IBL and cached list to be of same length");
if (index <= _newItemIndex)
{
++ _newItemIndex;
}
}
break;
case ListChangedType.ItemDeleted:
item = _cachedList[index];
_cachedList.RemoveAt(index);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemDeleted: Expect underlying IBL and cached list to be of same length");
if (index < _newItemIndex)
{
-- _newItemIndex;
}
// implicitly cancel AddNew and/or EditItem transactions if the relevant item is removed
if (item == CurrentEditItem)
{
ImplicitlyCancelEdit();
}
if (item == CurrentAddItem)
{
EndAddNew(true);
switch (NewItemPlaceholderPosition)
{
case NewItemPlaceholderPosition.AtBeginning:
index = 0;
break;
case NewItemPlaceholderPosition.AtEnd:
index = InternalCount - 1;
break;
}
}
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index + delta);
break;
case ListChangedType.ItemMoved:
if (IsAddingNew && args.OldIndex == _newItemIndex)
{
// ItemMoved applied to the new item. We assume this is the result
// of committing a new item when a sort is in effect - the item
// moves to its sorted position. There's no way to verify this assumption.
item = _newItem;
Debug.Assert(item == InternalList[index], "unexpected item while committing AddNew");
forwardedArgs = ProcessCommitNew(args.OldIndex, index + delta);
}
else
{
// normal ItemMoved event
item = InternalList[index];
forwardedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, index+delta, args.OldIndex+delta);
if (args.OldIndex < _newItemIndex && _newItemIndex < args.NewIndex)
{
-- _newItemIndex;
}
else if (args.NewIndex <= _newItemIndex && _newItemIndex < args.OldIndex)
{
++ _newItemIndex;
}
}
_cachedList.RemoveAt(args.OldIndex);
_cachedList.Insert(args.NewIndex, item);
Invariant.Assert(InternalList.Count == _cachedList.Count, "ItemMoved: Expect underlying IBL and cached list to be of same length");
break;
case ListChangedType.ItemChanged:
if (!_itemsRaisePropertyChanged.HasValue)
{
// check whether individual items raise PropertyChanged events
// (DataRowView does)
item = InternalList[args.NewIndex];
_itemsRaisePropertyChanged = (item is INotifyPropertyChanged);
}
// if items raise PropertyChanged, we can ignore ItemChanged;
// otherwise, treat it like a Reset
if (!_itemsRaisePropertyChanged.Value)
{
goto case ListChangedType.Reset;
}
break;
case ListChangedType.Reset:
// treat all other changes like Reset
case ListChangedType.PropertyDescriptorAdded:
case ListChangedType.PropertyDescriptorChanged:
case ListChangedType.PropertyDescriptorDeleted:
// implicitly cancel EditItem transactions
if (IsEditingItem)
{
ImplicitlyCancelEdit();
}
// adjust AddNew transactions, depending on whether the new item
// survived the Reset
if (IsAddingNew)
{
_newItemIndex = InternalList.IndexOf(_newItem);
if (_newItemIndex < 0)
{
EndAddNew(true);
}
}
RefreshOrDefer();
break;
}
if (forwardedArgs != null)
{
base.OnCollectionChanged(sender, forwardedArgs);
}
}
// 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 [....] 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
// return true if the current item was removed
private bool AdjustCurrencyForRemove(int index)
{
bool result = (index == CurrentPosition);
// adjust current index if deletion is earlier
if (index < CurrentPosition)
{
SetCurrent(CurrentItem, CurrentPosition - 1);
}
return result;
}
// 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
// return true if the current item was replaced
private bool AdjustCurrencyForReplace(int index)
{
bool result = (index == CurrentPosition);
if (result)
{
SetCurrent(GetItemAt(index), index);
}
return result;
}
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;
OnCurrentChanging();
if (newPosition < 0)
SetCurrent(null, newPosition);
else
SetCurrent(InternalItemAt(newPosition), newPosition);
OnCurrentChanged();
}
private IList CollectionProxy
{
get
{
if (UpdatedOutsideDispatcher)
return _shadowList;
else
return InternalList;
}
}
///
/// Accessor to private _internalList field.
///
private IBindingList InternalList
{
get { return _internalList; }
set { _internalList = value; }
}
private bool IsCustomFilterSet
{
get { return ((_blv != null) && !String.IsNullOrEmpty(_customFilter)); }
}
// 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; }
}
// SortDescription was added/removed, refresh CollView
private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (IsAddingNew || IsEditingItem)
throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedDuringAddOrEdit, "Sorting"));
RefreshOrDefer();
}
// convert from Avalon SortDescriptions to the corresponding .NET collection
private ListSortDescriptionCollection ConvertSortDescriptionCollection(SortDescriptionCollection sorts)
{
ListSortDescriptionCollection convertedSorts = null;
//
ITypedList itl = InternalList as ITypedList;
if (itl != null)
{
PropertyDescriptorCollection pdc = itl.GetItemProperties(null);
if ((pdc == null) || (pdc.Count == 0))
throw new ArgumentException(SR.Get(SRID.CannotDetermineSortByPropertiesForCollection));
ListSortDescription[] sortDescriptions = new ListSortDescription[sorts.Count];
for (int i = 0; i < sorts.Count; i++)
{
PropertyDescriptor dd = pdc.Find(sorts[i].PropertyName, true);
if (dd == null)
{
string typeName = itl.GetListName(null);
throw new ArgumentException(SR.Get(SRID.PropertyToSortByNotFoundOnType, typeName, sorts[i].PropertyName));
}
ListSortDescription sd = new ListSortDescription(dd, sorts[i].Direction);
sortDescriptions[i] = sd;
}
convertedSorts = new ListSortDescriptionCollection(sortDescriptions);
}
else
{
// cannot resolve property names to PropertyDescriptors -> return emtpy collection
convertedSorts = new ListSortDescriptionCollection(new ListSortDescription[] {});
}
return convertedSorts;
}
#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 (with special cases for placeholder and new item)
if (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)
{
_group.InsertSpecialItem(0, NewItemPlaceholder, true /*loading*/);
if (IsAddingNew)
{
_group.InsertSpecialItem(1, _newItem, true /*loading*/);
}
}
for (int k=0, n=InternalList.Count; k
/// Helper to raise a PropertyChanged event />).
///
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion Private Methods
private class BindingListSortDescriptionCollection : SortDescriptionCollection
{
internal BindingListSortDescriptionCollection(bool allowMultipleDescriptions)
{
_allowMultipleDescriptions = allowMultipleDescriptions;
}
///
/// called by base class ObservableCollection<T> when an item is added to list;
///
protected override void InsertItem(int index, SortDescription item)
{
if (!_allowMultipleDescriptions && (this.Count > 0))
{
throw new InvalidOperationException(SR.Get(SRID.BindingListCanOnlySortByOneProperty));
}
base.InsertItem(index, item);
}
private bool _allowMultipleDescriptions;
}
//-----------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private IBindingList _internalList;
private CollectionViewGroupRoot _group;
private bool _isGrouping;
private IBindingListView _blv;
private BindingListSortDescriptionCollection _sort;
private ArrayList _shadowList;
private bool _applyChangeToShadow = false;
private bool _isSorted;
private IComparer _comparer;
private string _customFilter;
private bool _isFiltered;
private bool _ignoreInnerRefresh;
private bool? _itemsRaisePropertyChanged;
private bool _isDataView;
private object _newItem;
private object _editItem;
private int _newItemIndex; // position of _newItem in the source collection
private NewItemPlaceholderPosition _newItemPlaceholderPosition;
// to handle ItemRemoved directly, we need to remember the items -
// IBL's event args tell us the index, not the item itself
private ArrayList _cachedList;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.