ItemCollection.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Controls / ItemCollection.cs / 1305600 / ItemCollection.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) 2003 by Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: ItemCollection holds the list of items that constitute the content of a ItemsControl. 
//
// See specs at http://avalon/connecteddata/Specs/ItemsControl.mht 
//              http://avalon/coreUI/Specs/model%20tree%20--%20design.doc
//
// History:
//  05/22/2003 : [....] - Created 
//  09/22/2004 : kenlai - moved aggregating functionality to CompositeCollection
// 
//--------------------------------------------------------------------------- 

 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Collections.Specialized;
using System.ComponentModel; 
using System.Diagnostics; 

using System.Windows; 
using System.Windows.Data;  // for CollectionContainer
using System.Windows.Markup;

using MS.Utility; 
using MS.Internal;
using MS.Internal.Controls; 
using MS.Internal.Data;     // for IndexedEnumerable 
using MS.Internal.KnownBoxes; // for BooleanBoxes
using MS.Internal.Utility; 


namespace System.Windows.Controls
{ 
    /// 
    /// ItemCollection will contain items shaped as strings, objects, xml nodes, 
    /// elements, as well as other collections.  (It will not promote elements from 
    /// contained collections; to "flatten" contained collections, assign a
    ///  to 
    /// the ItemsSource property on the ItemsControl.)
    /// A  uses the data
    /// in the ItemCollection to generate its content according to its ItemTemplate.
    ///  
    /// 
    /// When first created, ItemCollection is in an uninitialized state, neither 
    /// ItemsSource-mode nor direct-mode.  It will hold settings like SortDescriptions and Filter 
    /// until the mode is determined, then assign the settings to the active view.
    /// When uninitialized, calls to the list-modifying members will put the 
    /// ItemCollection in direct mode, and setting the ItemsSource will put the
    /// ItemCollection in ItemsSource mode.
    /// 
 
    [Localizability(LocalizationCategory.Ignore)]
    public sealed class ItemCollection : CollectionView, IList, IEditableCollectionViewAddNewItem, IItemProperties, IWeakEventListener 
    { 
        //-----------------------------------------------------
        // 
        //  Constructors
        //
        //-----------------------------------------------------
 
        #region Constructors
        // ItemCollection cannot be created standalone, it is created by ItemsControl 
 
        /// 
        /// Initializes a new instance of ItemCollection that is empty and has default initial capacity. 
        /// 
        /// model parent of this item collection
        /// 
        ///  
        internal ItemCollection(DependencyObject modelParent)
            : base(EmptyEnumerable.Instance, false) 
        { 
            _modelParent = new WeakReference(modelParent);
        } 

        /// 
        /// Initializes a new instance of ItemCollection that is empty and has specified initial capacity.
        ///  
        /// model parent of this item collection
        /// The number of items that the new list is initially capable of storing 
        ///  
        /// Some ItemsControl implementations have better idea how many items to anticipate,
        /// capacity parameter lets them tailor the initial size. 
        /// 
        internal ItemCollection(FrameworkElement modelParent, int capacity)
            : base(EmptyEnumerable.Instance, false)
        { 
            _defaultCapacity = capacity;
            _modelParent = new WeakReference(modelParent); 
        } 
        #endregion Constructors
 

        //------------------------------------------------------
        //
        //  Public Methods 
        //
        //----------------------------------------------------- 
 
        #region Public Methods
 
        //------------------------------------------------------
        #region ICurrentItem

        // These currency methods do not call OKToChangeCurrent() because 
        // ItemCollection already picks up and forwards the CurrentChanging
        // event from the inner _collectionView. 
 
        /// 
        /// Move  to the first item. 
        /// 
        /// true if  points to an item within the view.
        public override bool MoveCurrentToFirst()
        { 
            if (!EnsureCollectionView())
                return false; 
 
            VerifyRefreshNotDeferred();
 
            return _collectionView.MoveCurrentToFirst();
        }

        ///  
        /// Move  to the next item.
        ///  
        /// true if  points to an item within the view. 
        public override bool MoveCurrentToNext()
        { 
            if (!EnsureCollectionView())
                return false;

            VerifyRefreshNotDeferred(); 

            return _collectionView.MoveCurrentToNext(); 
        } 

        ///  
        /// Move  to the previous item.
        /// 
        /// true if  points to an item within the view.
        public override bool MoveCurrentToPrevious() 
        {
            if (!EnsureCollectionView()) 
                return false; 

            VerifyRefreshNotDeferred(); 

            return _collectionView.MoveCurrentToPrevious();
        }
 
        /// 
        /// Move  to the last item. 
        ///  
        /// true if  points to an item within the view.
        public override bool MoveCurrentToLast() 
        {
            if (!EnsureCollectionView())
                return false;
 
            VerifyRefreshNotDeferred();
 
            return _collectionView.MoveCurrentToLast(); 
        }
 
        /// 
        /// Move  to the given item.
        /// 
        /// Move CurrentItem to this item. 
        /// true if  points to an item within the view.
        public override bool MoveCurrentTo(object item) 
        { 
            if (!EnsureCollectionView())
                return false; 

            VerifyRefreshNotDeferred();

            return _collectionView.MoveCurrentTo(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)
        { 
            if (!EnsureCollectionView())
                return false; 
 
            VerifyRefreshNotDeferred();
 
            return _collectionView.MoveCurrentToPosition(position);
        }

 
        #endregion ICurrentItem
 
        #region IList 

        ///  
        ///     Returns an enumerator object for this ItemCollection
        /// 
        /// 
        ///     Enumerator object for this ItemCollection 
        /// 
        protected override IEnumerator GetEnumerator() 
        { 
            if (!EnsureCollectionView())
                return EmptyEnumerator.Instance; 

            return ((IEnumerable)_collectionView).GetEnumerator();
        }
 
        /// 
        ///     Add an item to this collection. 
        ///  
        /// 
        ///     New item to be added to collection 
        /// 
        /// 
        ///     Zero-based index where the new item is added.  -1 if the item could not be added.
        ///  
        /// 
        ///     To facilitate initialization of direct-mode ItemsControls with Sort and/or Filter, 
        /// Add() is permitted when ItemsControl is initializing, even if a Sort or Filter has been set. 
        /// 
        ///  
        /// trying to add an item which already has a different model/logical parent
        /// - or -
        /// trying to add an item when the ItemCollection is in ItemsSource mode.
        ///  
        public int Add(object newItem)
        { 
            CheckIsUsingInnerView(); 
            int index = _internalView.Add(newItem);
            ModelParent.SetValue(ItemsControl.HasItemsPropertyKey, BooleanBoxes.TrueBox); 
            return index;
        }

        ///  
        ///     Clears the collection.  Releases the references on all items
        /// currently in the collection. 
        ///  
        /// 
        /// the ItemCollection is read-only because it is in ItemsSource mode 
        /// 
        public void Clear()
        {
            // Not using CheckIsUsingInnerView() because we don't want to create internal list 

            VerifyRefreshNotDeferred(); 
 
            if (IsUsingItemsSource)
            { 
                throw new InvalidOperationException(SR.Get(SRID.ItemsSourceInUse));
            }

            if (_internalView != null) 
            {
                _internalView.Clear(); 
            } 
            ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey);
        } 

        /// 
        ///     Checks to see if a given item is in this collection and in the view
        ///  
        /// 
        ///     The item whose membership in this collection is to be checked. 
        ///  
        /// 
        ///     True if the collection contains the given item and the item passes the active filter 
        /// 
        public override bool Contains(object containItem)
        {
            if (!EnsureCollectionView()) 
                return false;
 
            VerifyRefreshNotDeferred(); 

            return _collectionView.Contains(containItem); 
        }

        /// 
        ///     Makes a shallow copy of object references from this 
        ///     ItemCollection to the given target array
        ///  
        ///  
        ///     Target of the copy operation
        ///  
        /// 
        ///     Zero-based index at which the copy begins
        /// 
        public void CopyTo(Array array, int index) 
        {
            if (array == null) 
                throw new ArgumentNullException("array"); 
            if (array.Rank > 1)
                throw new ArgumentException(SR.Get(SRID.BadTargetArray), "array"); // array is multidimensional. 
            if (index < 0)
                throw new ArgumentOutOfRangeException("index");

            // use the view instead of the collection, because it may have special sort/filter 
            if (!EnsureCollectionView())
                return;  // there is no collection (bind returned no collection) and therefore nothing to copy 
 
            VerifyRefreshNotDeferred();
 
            IndexedEnumerable.CopyTo(_collectionView, array, index);
        }

        ///  
        ///     Finds the index in this collection/view where the given item is found.
        ///  
        ///  
        ///     The item whose index in this collection/view is to be retrieved.
        ///  
        /// 
        ///     Zero-based index into the collection/view where the given item can be
        /// found.  Otherwise, -1
        ///  
        public override int IndexOf(object item)
        { 
            if (!EnsureCollectionView()) 
                return -1;
 
            VerifyRefreshNotDeferred();

            return _collectionView.IndexOf(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) { // only check lower bound because Count could be expensive if (index < 0) throw new ArgumentOutOfRangeException("index"); VerifyRefreshNotDeferred(); if (!EnsureCollectionView()) throw new InvalidOperationException(SR.Get(SRID.ItemCollectionHasNoCollection)); if (_collectionView == _internalView) { // check upper bound here because we know it's not expensive if (index >= _internalView.Count) throw new ArgumentOutOfRangeException("index"); } return _collectionView.GetItemAt(index); } /// /// Insert an item in the collection at a given index. All items /// after the given position are moved down by one. /// /// /// The index at which to inser the item /// /// /// The item reference to be added to the collection /// /// /// Thrown when trying to add an item which already has a different model/logical parent /// or when the ItemCollection is read-only because it is in ItemsSource mode /// /// /// Thrown if index is out of range /// public void Insert(int insertIndex, object insertItem) { CheckIsUsingInnerView(); _internalView.Insert(insertIndex, insertItem); ModelParent.SetValue(ItemsControl.HasItemsPropertyKey, BooleanBoxes.TrueBox); } /// /// Removes the given item reference from the collection or view. /// All remaining items move up by one. /// /// /// the ItemCollection is read-only because it is in ItemsSource mode or there /// is a sort or filter in effect /// /// /// The item to be removed. /// public void Remove(object removeItem) { CheckIsUsingInnerView(); _internalView.Remove(removeItem); if (IsEmpty) { ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey); } } /// /// Removes an item from the collection or view at the given index. /// All remaining items move up by one. /// /// /// The index at which to remove an item. /// /// /// the ItemCollection is read-only because it is in ItemsSource mode /// /// /// Thrown if index is out of range /// public void RemoveAt(int removeIndex) { CheckIsUsingInnerView(); _internalView.RemoveAt(removeIndex); if (IsEmpty) { ModelParent.ClearValue(ItemsControl.HasItemsPropertyKey); } } #endregion IList /// /// Return true if the item is acceptable to the active filter, if any. /// It is commonly used during collection-changed notifications to /// determine if the added/removed item requires processing. /// /// /// true if the item passes the filter or if no filter is set on collection view. /// public override bool PassesFilter(object item) { if (!EnsureCollectionView()) return true; return _collectionView.PassesFilter(item); } /// /// Re-create the view, using any and/or . /// protected override void RefreshOverride() { if (_collectionView != null) { if (_collectionView.NeedsRefresh) { _collectionView.Refresh(); } else { // if the view is up to date, we only need to raise the Reset event OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //----------------------------------------------------- #region Public Properties /// /// Read-only property for the number of items stored in this collection of objects /// /// /// returns 0 if the ItemCollection is uninitialized or /// there is no collection in ItemsSource mode /// public override int Count { get { if (!EnsureCollectionView()) return 0; VerifyRefreshNotDeferred(); return _collectionView.Count; } } /// /// Returns true if the resulting (filtered) view is emtpy. /// public override bool IsEmpty { get { if (!EnsureCollectionView()) return true; VerifyRefreshNotDeferred(); return _collectionView.IsEmpty; } } /// /// Indexer property to retrieve or replace the item at the given /// zero-based offset into the collection. /// /// /// trying to set an item which already has a different model/logical parent; or, /// trying to set when in ItemsSource mode; or, /// the ItemCollection is uninitialized; or, /// in ItemsSource mode, the binding on ItemsSource does not provide a collection. /// /// /// Thrown if index is out of range /// public object this[int index] { get { return GetItemAt(index); } set { CheckIsUsingInnerView(); if (index < 0 || index >= _internalView.Count) throw new ArgumentOutOfRangeException("index"); _internalView[index] = value; } } /// /// The ItemCollection's underlying collection or the user provided ItemsSource collection /// public override IEnumerable SourceCollection { get { if (IsUsingItemsSource) { return ItemsSource; } else { EnsureInternalView(); return this; } } } /// /// Returns true if this view needs to be refreshed /// (i.e. when the view is not consistent with the current sort or filter). /// /// /// true when SortDescriptions or Filter is changed while refresh is deferred, /// or in direct-mode, when an item have been added while SortDescriptions or Filter is in place. /// public override bool NeedsRefresh { get { return (EnsureCollectionView()) ? _collectionView.NeedsRefresh : false; } } /// /// Collection of Sort criteria to sort items in ItemCollection. /// /// ///

/// Sorting is supported for items in the ItemsControl.Items collection; /// if a collection is assigned to ItemsControl.ItemsSource, the capability to sort /// depends on the CollectionView for that inner collection. /// Simpler implementations of CollectionVIew do not support sorting and will return an empty /// and immutable / read-only SortDescription collection. /// Attempting to modify such a collection will cause NotSupportedException. /// Use property on CollectionView to test if sorting is supported /// before modifying the returned collection. ///

///

/// One or more sort criteria in form of /// can be added, each specifying a property and direction to sort by. ///

///
public override SortDescriptionCollection SortDescriptions { get { // always hand out this ItemCollection's SortDescription collection; // in ItemsSource mode the inner collection view will be kept in synch with this collection if (_sort == null) { _sort = new SortDescriptionCollection(); if (_collectionView != null) { // no need to do this under the monitor - we haven't hooked up events yet CloneList(_sort, _collectionView.SortDescriptions); } ((INotifyCollectionChanged)_sort).CollectionChanged += new NotifyCollectionChangedEventHandler(SortDescriptionsChanged); } return _sort; } } /// /// Test if this ICollectionView supports sorting before adding /// to . /// public override bool CanSort { get { return (EnsureCollectionView()) ? _collectionView.CanSort : true; } } /// /// Set/get a filter callback to filter out items in collection. /// This property will always accept a filter, but the collection view for the /// underlying ItemsSource may not actually support filtering. /// Please check /// /// /// Collections assigned to ItemsSource may not support filtering and could throw a NotSupportedException. /// Use property to test if filtering is supported before assigning /// a non-null Filter value. /// public override Predicate Filter { get { return (EnsureCollectionView()) ? _collectionView.Filter : _filter; } set { _filter = value; if (_collectionView != null) _collectionView.Filter = value; } } /// /// Test if this ICollectionView supports filtering before assigning /// a filter callback to . /// public override bool CanFilter { get { return (EnsureCollectionView()) ? _collectionView.CanFilter : true; } } /// /// 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 (EnsureCollectionView()) ? _collectionView.CanGroup : false; } } /// /// The description of grouping, indexed by level. /// public override ObservableCollection GroupDescriptions { get { // always hand out this ItemCollection's GroupDescriptions collection; // in ItemsSource mode the inner collection view will be kept in synch with this collection if (_groupBy == null) { _groupBy = new ObservableCollection(); if (_collectionView != null) { // no need to do this under the monitor - we haven't hooked up events yet CloneList(_groupBy, _collectionView.GroupDescriptions); } _groupBy.CollectionChanged += new NotifyCollectionChangedEventHandler(GroupDescriptionsChanged); } return _groupBy; } } /// /// The top-level groups, constructed according to the descriptions /// given in GroupDescriptions and/or GroupBySelector. /// public override ReadOnlyObservableCollection Groups { get { return (EnsureCollectionView()) ? _collectionView.Groups : null; } } /// /// Enter a Defer Cycle. /// Defer cycles are used to coalesce changes to the ICollectionView. /// public override IDisposable DeferRefresh() { // if already deferred (level > 0) and there is a _collectionView, there should be a _deferInnerRefresh Debug.Assert(_deferLevel == 0 || _collectionView == null || _deferInnerRefresh != null); // if not already deferred, there should NOT be a _deferInnerRefresh Debug.Assert(_deferLevel != 0 || _deferInnerRefresh == null); if (_deferLevel == 0 && _collectionView != null) { _deferInnerRefresh = _collectionView.DeferRefresh(); } ++_deferLevel; // do this after inner DeferRefresh, in case it throws return new DeferHelper(this); } /// /// Gets a value indicating whether access to the ItemCollection is synchronized (thread-safe). /// bool ICollection.IsSynchronized { get { return false; } } #pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C# /// /// Returns an object to be used in thread synchronization. /// /// /// ItemCollection cannot provide a [....] root for synchronization while /// in ItemsSource mode. Please use the ItemsSource directly to /// get its [....] root. /// object ICollection.SyncRoot { get { if (IsUsingItemsSource) { // see discussion in XML comment above. #pragma warning suppress 6503 // "Property get methods should not throw exceptions." throw new NotSupportedException(SR.Get(SRID.ItemCollectionShouldUseInnerSyncRoot)); } return _internalView.SyncRoot; } } #pragma warning restore 1634, 1691 /// /// Gets a value indicating whether the IList has a fixed size. /// An ItemCollection can usually grow dynamically, /// this call will commonly return FixedSize = False. /// In ItemsSource mode, this call will return IsFixedSize = True. /// bool IList.IsFixedSize { get { return IsUsingItemsSource; } } /// /// Gets a value indicating whether the IList is read-only. /// An ItemCollection is usually writable, /// this call will commonly return IsReadOnly = False. /// In ItemsSource mode, this call will return IsReadOnly = True. /// bool IList.IsReadOnly { get { return IsUsingItemsSource; } } //------------------------------------------------------ #region ICurrentItem /// /// The ordinal position of the within the (optionally /// sorted and filtered) view. /// public override int CurrentPosition { get { if (!EnsureCollectionView()) return -1; VerifyRefreshNotDeferred(); return _collectionView.CurrentPosition; } } /// /// Return current item. /// public override object CurrentItem { get { if (!EnsureCollectionView()) return null; VerifyRefreshNotDeferred(); return _collectionView.CurrentItem; } } /// /// Return true if is beyond the end (End-Of-File). /// public override bool IsCurrentAfterLast { get { if (!EnsureCollectionView()) return false; VerifyRefreshNotDeferred(); return _collectionView.IsCurrentAfterLast; } } /// /// Return true if is before the beginning (Beginning-Of-File). /// public override bool IsCurrentBeforeFirst { get { if (!EnsureCollectionView()) return false; VerifyRefreshNotDeferred(); return _collectionView.IsCurrentBeforeFirst; } } #endregion ICurrentItem #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. /// NewItemPlaceholderPosition IEditableCollectionView.NewItemPlaceholderPosition { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.NewItemPlaceholderPosition; } else { return NewItemPlaceholderPosition.None; } } set { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.NewItemPlaceholderPosition = value; } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "NewItemPlaceholderPosition")); } } } /// /// Return true if the view supports . /// bool IEditableCollectionView.CanAddNew { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.CanAddNew; } else { return false; } } } /// /// 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. /// object IEditableCollectionView.AddNew() { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.AddNew(); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNew")); } } /// /// 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. /// void IEditableCollectionView.CommitNew() { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.CommitNew(); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "CommitNew")); } } /// /// Complete the transaction started by . The new /// item is removed from the collection. /// void IEditableCollectionView.CancelNew() { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.CancelNew(); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "CancelNew")); } } /// /// Returns true if an transaction is in progress. /// bool IEditableCollectionView.IsAddingNew { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.IsAddingNew; } else { return false; } } } /// /// When an transaction is in progress, this property /// returns the new item. Otherwise it returns null. /// object IEditableCollectionView.CurrentAddItem { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.CurrentAddItem; } else { return null; } } } #endregion Adding new items #region Removing items /// /// Return true if the view supports and /// . /// bool IEditableCollectionView.CanRemove { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.CanRemove; } else { return false; } } } /// /// 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). /// void IEditableCollectionView.RemoveAt(int index) { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.RemoveAt(index); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "RemoveAt")); } } /// /// Remove the given item from the underlying collection. /// void IEditableCollectionView.Remove(object item) { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.Remove(item); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "Remove")); } } #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. /// void IEditableCollectionView.EditItem(object item) { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.EditItem(item); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "EditItem")); } } /// /// Complete the transaction started by . /// The pending changes (if any) to the item are committed. /// void IEditableCollectionView.CommitEdit() { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.CommitEdit(); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "CommitEdit")); } } /// /// Complete the transaction started by . /// The pending changes (if any) to the item are discarded. /// void IEditableCollectionView.CancelEdit() { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { ecv.CancelEdit(); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "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. /// bool IEditableCollectionView.CanCancelEdit { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.CanCancelEdit; } else { return false; } } } /// /// Returns true if an transaction is in progress. /// bool IEditableCollectionView.IsEditingItem { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.IsEditingItem; } else { return false; } } } /// /// When an transaction is in progress, this property /// returns the affected item. Otherwise it returns null. /// object IEditableCollectionView.CurrentEditItem { get { IEditableCollectionView ecv = _collectionView as IEditableCollectionView; if (ecv != null) { return ecv.CurrentEditItem; } else { return null; } } } #endregion Transactional editing of an item #endregion IEditableCollectionView #region IEditableCollectionViewAddNewItem /// /// Return true if the view supports . /// bool IEditableCollectionViewAddNewItem.CanAddNewItem { get { IEditableCollectionViewAddNewItem ani = _collectionView as IEditableCollectionViewAddNewItem; if (ani != null) { return ani.CanAddNewItem; } else { return false; } } } /// /// Add a new item to the underlying collection. Returns the new item. /// After calling AddNewItem and changing the new item as desired, either /// or should be /// called to complete the transaction. /// object IEditableCollectionViewAddNewItem.AddNewItem(object newItem) { IEditableCollectionViewAddNewItem ani = _collectionView as IEditableCollectionViewAddNewItem; if (ani != null) { return ani.AddNewItem(newItem); } else { throw new InvalidOperationException(SR.Get(SRID.MemberNotAllowedForView, "AddNewItem")); } } #endregion IEditableCollectionViewAddNewItem #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. /// ReadOnlyCollection IItemProperties.ItemProperties { get { IItemProperties iip = _collectionView as IItemProperties; if (iip != null) { return iip.ItemProperties; } else { return null; } } } #endregion IItemProperties //----------------------------------------------------- // // Internal API // //----------------------------------------------------- #region Internal API internal DependencyObject ModelParent { get { return (DependencyObject)_modelParent.Target; } } internal FrameworkElement ModelParentFE { get { return ModelParent as FrameworkElement; } } // This puts the ItemCollection into ItemsSource mode. internal void SetItemsSource(IEnumerable value) { // Allow this while refresh is deferred. // If we're switching from Normal mode, first make sure it's legal. if (!IsUsingItemsSource && (_internalView != null) && (_internalView.RawCount > 0)) { throw new InvalidOperationException(SR.Get(SRID.CannotUseItemsSource)); } _itemsSource = value; _isUsingItemsSource = true; SetCollectionView(CollectionViewSource.GetDefaultCollectionView(_itemsSource, ModelParent)); } // This returns ItemCollection to direct mode. internal void ClearItemsSource() { if (IsUsingItemsSource) { // return to normal mode _itemsSource = null; _isUsingItemsSource = false; SetCollectionView(_internalView); // it's ok if _internalView is null; just like uninitialized } else { // already in normal mode - no-op } } // Read-only property used by ItemsControl internal IEnumerable ItemsSource { get { return _itemsSource; } } internal bool IsUsingItemsSource { get { return _isUsingItemsSource; } } internal CollectionView CollectionView { get { return _collectionView; } } internal void BeginInit() { Debug.Assert(_isInitializing == false); _isInitializing = true; if (_collectionView != null) // disconnect from collectionView to cut extraneous events UnhookCollectionView(_collectionView); } internal void EndInit() { Debug.Assert(_isInitializing == true); EnsureCollectionView(); _isInitializing = false; // now we allow collectionView to be hooked up again if (_collectionView != null) { HookCollectionView(_collectionView); Refresh(); // apply any sort or filter for the first time } } internal IEnumerator LogicalChildren { get { EnsureInternalView(); return _internalView.LogicalChildren; } } #endregion Internal API //----------------------------------------------------- // // Private Properties // //------------------------------------------------------ #region Private Properties private new bool IsRefreshDeferred { get { return _deferLevel > 0; } } #endregion //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // ===== Lazy creation of InternalView ===== // When ItemCollection is instantiated, it is uninitialized (_collectionView == null). // It remains so until SetItemsSource() puts it into ItemsSource mode // or a modifying method call such as Add() or Insert() puts it into direct mode. // Several ItemCollection methods check EnsureCollectionView, which returns false if // (_collectionView == null) and (InternalView == null), and it can mean two things: // 1) ItemCollection is uninitialized // 2) ItemsControl is in ItemsSource mode, but the ItemsSource binding returned null // for either of these cases, a reasonable default return value or behavior is provided. // EnsureCollectionView() will set _collectionView to the InternalView if the mode is correct. bool EnsureCollectionView() { if (_collectionView == null && !IsUsingItemsSource && _internalView != null) { // If refresh is not necessary, fake initialization so that SetCollectionView // doesn't raise a refresh event. if (_internalView.IsEmpty) { bool wasInitializing = _isInitializing; _isInitializing = true; SetCollectionView(_internalView); _isInitializing = wasInitializing; } else { SetCollectionView(_internalView); } // If we're not in Begin/End Init, now's a good time to hook up listeners if (!_isInitializing) HookCollectionView(_collectionView); } return (_collectionView != null); } void EnsureInternalView() { if (_internalView == null) { // lazy creation of the InnerItemCollectionView _internalView = new InnerItemCollectionView(_defaultCapacity, this); } } // Change the collection view in use, unhook/hook event handlers void SetCollectionView(CollectionView view) { if (_collectionView == view) return; if (_collectionView != null) { // Unhook events first, to avoid unnecessary refresh while it is still the active view. if (!_isInitializing) UnhookCollectionView(_collectionView); if (IsRefreshDeferred) // we've been deferring refresh on the _collectionView { // end defer refresh on the _collectionView that we're letting go _deferInnerRefresh.Dispose(); _deferInnerRefresh = null; } } bool raiseReset = false; _collectionView = view; InvalidateEnumerableWrapper(); if (_collectionView != null) { _deferInnerRefresh = _collectionView.DeferRefresh(); ApplySortFilterAndGroup(); // delay event hook-up when initializing. see BeginInit() and EndInit(). if (!_isInitializing) HookCollectionView(_collectionView); if (!IsRefreshDeferred) { // make sure we get at least one refresh raiseReset = !_collectionView.NeedsRefresh; _deferInnerRefresh.Dispose(); // This fires refresh event that should reach ItemsControl listeners _deferInnerRefresh = null; } // when refresh is deferred, we hold on to the inner DeferRefresh until EndDefer() } else // ItemsSource binding returned null { if (!IsRefreshDeferred) { raiseReset = true; } } if (raiseReset) { // notify listeners that the view is changed OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } void ApplySortFilterAndGroup() { // Only apply sort/filter/group if new view supports it and ItemCollection has real values if (_collectionView.CanSort) { // if user has added SortDescriptions to this.SortDescriptions, those settings get pushed to // the newly attached collection view // if no SortDescriptions are set on ItemCollection, // the inner collection view's .SortDescriptions gets copied to this.SortDescriptions // when switching back to direct mode and no user-set on this.SortDescriptions // then clear any .SortDescriptions set from previous inner collection view SortDescriptionCollection source = (_isSortingSet) ? _sort : _collectionView.SortDescriptions; SortDescriptionCollection target = (_isSortingSet) ? _collectionView.SortDescriptions : _sort; using (SortDescriptionsMonitor.Enter()) { CloneList(target, source); } } if (_collectionView.CanFilter && _filter != null) _collectionView.Filter = _filter; if (_collectionView.CanGroup) { // if user has added GroupDescriptions to this.GroupDescriptions, those settings get pushed to // the newly attached collection view // if no GroupDescriptions are set on ItemCollection, // the inner collection view's .GroupDescriptions gets copied to this.GroupDescriptions // when switching back to direct mode and no user-set on this.GroupDescriptions // then clear any .GroupDescriptions set from previous inner collection view ObservableCollection source = (_isGroupingSet) ? _groupBy : _collectionView.GroupDescriptions; ObservableCollection target = (_isGroupingSet) ? _collectionView.GroupDescriptions : _groupBy; using (GroupDescriptionsMonitor.Enter()) { CloneList(target, source); } } } void HookCollectionView(CollectionView view) { CollectionChangedEventManager.AddListener(view, this); CurrentChangingEventManager.AddListener(view, this); CurrentChangedEventManager.AddListener(view, this); PropertyChangedEventManager.AddListener(view, this, String.Empty); SortDescriptionCollection sort = view.SortDescriptions; if (sort != null && sort != SortDescriptionCollection.Empty) { CollectionChangedEventManager.AddListener(sort, this); } ObservableCollection group = view.GroupDescriptions; if (group != null) { CollectionChangedEventManager.AddListener(group, this); } } void UnhookCollectionView(CollectionView view) { CollectionChangedEventManager.RemoveListener(view, this); CurrentChangingEventManager.RemoveListener(view, this); CurrentChangedEventManager.RemoveListener(view, this); PropertyChangedEventManager.RemoveListener(view, this, String.Empty); SortDescriptionCollection sort = view.SortDescriptions; if (sort != null && sort != SortDescriptionCollection.Empty) { CollectionChangedEventManager.RemoveListener(sort, this); } ObservableCollection group = view.GroupDescriptions; if (group != null) { CollectionChangedEventManager.RemoveListener(group, this); } } /// /// Handle events from the centralized event table /// bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { if (managerType == typeof(PropertyChangedEventManager)) { OnPropertyChanged((PropertyChangedEventArgs)e); } else if (managerType == typeof(CollectionChangedEventManager)) { NotifyCollectionChangedEventArgs ncce = (NotifyCollectionChangedEventArgs)e; if (_collectionView != null && sender == _collectionView.SortDescriptions) { OnInnerSortDescriptionsChanged(sender, ncce); } else if (_collectionView != null && sender == _collectionView.GroupDescriptions) { OnInnerGroupDescriptionsChanged(sender, ncce); } else { // when the collection changes, the enumerator is no longer valid. // This should be detected by IndexedEnumerable, but isn't because // of bug 1164689. As a partial remedy (for bug 1163708), discard the // enumerator here. // InvalidateEnumerableWrapper(); // notify listeners on ItemsControl (like ItemContainerGenerator) OnCollectionChanged(ncce); } } else if (managerType == typeof(CurrentChangingEventManager)) { CurrentChangingEventArgs ce = (CurrentChangingEventArgs)e; Debug.Assert(sender == _collectionView); OnCurrentChanging(ce); } else if (managerType == typeof(CurrentChangedEventManager)) { Debug.Assert(sender == _collectionView); OnCurrentChanged(); } else { return false; // unrecognized event } return true; } // Before any modifying access, first call CheckIsUsingInnerView() because // a) InternalView is lazily created // b) modifying access is only allowed when the InnerView is being used // c) modifying access is only allowed when Refresh is not deferred void CheckIsUsingInnerView() { if (IsUsingItemsSource) throw new InvalidOperationException(SR.Get(SRID.ItemsSourceInUse)); EnsureInternalView(); EnsureCollectionView(); Debug.Assert(_collectionView != null); VerifyRefreshNotDeferred(); } void EndDefer() { --_deferLevel; if (_deferLevel == 0) { // if there is a _collectionView, there should be a _deferInnerRefresh Debug.Assert(_collectionView == null || _deferInnerRefresh != null); if (_deferInnerRefresh != null) { _deferInnerRefresh.Dispose(); _deferInnerRefresh = null; } else { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } } // Helper to validate that we are not in the middle of a DeferRefresh // and throw if that is the case. The reason that this *new* version of VerifyRefreshNotDeferred // on ItemCollection is needed is that ItemCollection has its own *new* IsRefreshDeferred // which overrides IsRefreshDeferred on the base class (CollectionView), and we need to // be sure that we reference that member on the derived class. private new void VerifyRefreshNotDeferred() { #pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C# #pragma warning disable 6503 // If the Refresh is being deferred to change filtering or sorting of the // data by this CollectionView, then CollectionView will not reflect the correct // state of the underlying data. if (IsRefreshDeferred) throw new InvalidOperationException(SR.Get(SRID.NoCheckOrChangeWhenDeferred)); #pragma warning restore 6503 #pragma warning restore 1634, 1691 } // SortDescription was added/removed to/from this ItemCollection.SortDescriptions, refresh CollView private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) { if (SortDescriptionsMonitor.Busy) return; // if we have an inner collection view, keep its .SortDescriptions collection it up-to-date if (_collectionView != null && _collectionView.CanSort) { using (SortDescriptionsMonitor.Enter()) { SynchronizeSortDescriptions(e, _sort, _collectionView.SortDescriptions); } } _isSortingSet = true; // most recent change came from ItemCollection } // SortDescription was added/removed to/from inner collectionView private void OnInnerSortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) { if (SortDescriptionsMonitor.Busy) return; // keep this ItemColl.SortDescriptions in synch with inner collection view's using (SortDescriptionsMonitor.Enter()) { SynchronizeSortDescriptions(e, _collectionView.SortDescriptions, _sort); } _isSortingSet = false; // most recent change came from inner collection view } // keep inner and outer CollViews' SortDescription collections in synch private void SynchronizeSortDescriptions(NotifyCollectionChangedEventArgs e, SortDescriptionCollection origin, SortDescriptionCollection clone) { if (clone == null) return; // the clone might be lazily-created _sort switch (e.Action) { case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewStartingIndex >= 0); if (clone.Count + e.NewItems.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; for (int i = 0; i < e.NewItems.Count; i++) { clone.Insert(e.NewStartingIndex + i, (SortDescription) e.NewItems[i]); } break; case NotifyCollectionChangedAction.Remove: if (clone.Count - e.OldItems.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; Debug.Assert(e.OldStartingIndex >= 0); for (int i = 0; i < e.OldItems.Count; i++) { clone.RemoveAt(e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: Debug.Assert(e.OldStartingIndex >= 0); if (clone.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; for (int i = 0; i < e.OldItems.Count; i++) { clone[e.OldStartingIndex + i] = (SortDescription) e.NewItems[i]; } break; case NotifyCollectionChangedAction.Move: Debug.Assert(e.OldStartingIndex >= 0); if (clone.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; if (e.NewItems.Count == 1) { clone.RemoveAt(e.OldStartingIndex); clone.Insert(e.NewStartingIndex, (SortDescription) e.NewItems[0]); } else { for (int i = 0; i < e.OldItems.Count; i++) { clone.RemoveAt(e.OldStartingIndex); } for (int i = 0; i < e.NewItems.Count; i++) { clone.Insert(e.NewStartingIndex + i, (SortDescription) e.NewItems[i]); } } break; // this arm also handles cases where the two collections have gotten // out of [....] (typically because exceptions prevented a previous [....] // from happening) case NotifyCollectionChangedAction.Reset: CloneList(clone, origin); break; default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } } // GroupDescription was added/removed to/from this ItemCollection.GroupDescriptions, refresh CollView private void GroupDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) { if (GroupDescriptionsMonitor.Busy) return; // if we have an inner collection view, keep its .SortDescriptions collection it up-to-date if (_collectionView != null && _collectionView.CanGroup) { using (GroupDescriptionsMonitor.Enter()) { SynchronizeGroupDescriptions(e, _groupBy, _collectionView.GroupDescriptions); } } _isGroupingSet = true; // most recent change came from ItemCollection } // GroupDescription was added/removed to/from inner collectionView private void OnInnerGroupDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) { if (GroupDescriptionsMonitor.Busy) return; // keep this ItemColl.GroupDescriptions in synch with inner collection view's using (GroupDescriptionsMonitor.Enter()) { SynchronizeGroupDescriptions(e, _collectionView.GroupDescriptions, _groupBy); } _isGroupingSet = false; // most recent change came from inner collection view } // keep inner and outer CollViews' GroupDescription collections in synch private void SynchronizeGroupDescriptions(NotifyCollectionChangedEventArgs e, ObservableCollection origin, ObservableCollection clone) { if (clone == null) return; // the clone might be lazily-created _groupBy int i; switch (e.Action) { case NotifyCollectionChangedAction.Add: Debug.Assert(e.NewStartingIndex >= 0); if (clone.Count + e.NewItems.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; for (i = 0; i < e.NewItems.Count; i++) { clone.Insert(e.NewStartingIndex + i, (GroupDescription) e.NewItems[i]); } break; case NotifyCollectionChangedAction.Remove: Debug.Assert(e.OldStartingIndex >= 0); if (clone.Count - e.OldItems.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; for (i = 0; i < e.OldItems.Count; i++) { clone.RemoveAt(e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: Debug.Assert(e.OldStartingIndex >= 0); if (clone.Count + e.NewItems.Count - e.OldItems.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; // If there are as many new items as old items, then // this is a straight replace. if (e.OldItems.Count == e.NewItems.Count) { for (i = 0; i < e.OldItems.Count; i++) { clone[e.OldStartingIndex + i] = (GroupDescription) e.NewItems[i]; } } else { for (i = 0; i < e.OldItems.Count; i++) { clone.RemoveAt(e.OldStartingIndex); } for (i = 0; i < e.NewItems.Count; i++) { clone.Insert(e.NewStartingIndex + i, (GroupDescription) e.NewItems[i]); } } break; case NotifyCollectionChangedAction.Move: Debug.Assert(e.OldStartingIndex >= 0); if (clone.Count != origin.Count) goto case NotifyCollectionChangedAction.Reset; if (e.OldItems.Count == 1) { clone.Move(e.OldStartingIndex, e.NewStartingIndex); } else { if (e.NewStartingIndex < e.OldStartingIndex) { for (i = 0; i < e.OldItems.Count; i++) { clone.Move(e.OldStartingIndex + i, e.NewStartingIndex + i); } } else if (e.NewStartingIndex > e.OldStartingIndex) { for (i = e.OldItems.Count - 1; i >= 0; i--) { clone.Move(e.OldStartingIndex + i, e.NewStartingIndex + i); } } } break; // this arm also handles cases where the two collections have gotten // out of [....] (typically because exceptions prevented a previous [....] // from happening) case NotifyCollectionChangedAction.Reset: CloneList(clone, origin); break; default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } } private void CloneList(IList clone, IList master) { // if either party is null, do nothing. Allowing null lets the caller // avoid a lazy instantiation of the Sort/Group description collection. if (clone == null || master == null) return; if (clone.Count > 0) { clone.Clear(); } for (int i = 0, n = master.Count; i < n; ++i) { clone.Add(master[i]); } } #endregion Private Methods private MonitorWrapper SortDescriptionsMonitor { get { if (_syncMonitor == null) _syncMonitor = new MonitorWrapper(); return _syncMonitor; } } private MonitorWrapper GroupDescriptionsMonitor { get { if (_groupByMonitor == null) _groupByMonitor = new MonitorWrapper(); return _groupByMonitor; } } //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields private InnerItemCollectionView _internalView; // direct-mode list and view private IEnumerable _itemsSource; // ItemsControl.ItemsSource property private CollectionView _collectionView; // delegate ICollectionView private int _defaultCapacity = 16; private bool _isUsingItemsSource; // true when using ItemsSource private bool _isInitializing; // when true, ItemCollection does not listen to events of _collectionView private bool _isSortingSet; // true when user has added to this.SortDescriptions private bool _isGroupingSet; // true when user has added to this.GroupDescriptions private int _deferLevel; private IDisposable _deferInnerRefresh; private SortDescriptionCollection _sort; // storage for SortDescriptions; will forward to _collectionView.SortDescriptions when available private Predicate _filter; // storage for Filter when _collectionView is not available private ObservableCollection _groupBy; // storage for GroupDescriptions; will forward to _collectionView.GroupDescriptions when available private WeakReference _modelParent; // use WeakRef to avoid leaking the parent private static object s_syncRoot = new Object(); private MonitorWrapper _syncMonitor; private MonitorWrapper _groupByMonitor; #endregion Private Fields //------------------------------------------------------ // // Private Types // //----------------------------------------------------- #region Private Types private class DeferHelper : IDisposable { public DeferHelper(ItemCollection itemCollection) { _itemCollection = itemCollection; } public void Dispose() { if (_itemCollection != null) { _itemCollection.EndDefer(); _itemCollection = null; } GC.SuppressFinalize(this); } private ItemCollection _itemCollection; } #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK