CollectionView.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Framework / System / Windows / Data / CollectionView.cs / 4 / CollectionView.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) 2003 by Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: Base implementation of ICollectionView that enforces 
// affinity to the UI thread dispatcher.
// 
// See spec at [....]/connecteddata/Specs/CollectionView.mht
//
// History:
//  06/02/2003 : [....] - Ported from DotNet tree 
//
//--------------------------------------------------------------------------- 
 

using System; 
using System.ComponentModel;
using System.Globalization;

using System.Collections; 
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Diagnostics;
using System.Windows; 
using System.Threading;
using System.Windows.Threading;
using MS.Internal.Data;
using MS.Internal;              // Invariant.Assert 
using MS.Internal.Hashing.PresentationFramework;    // HashHelper
 
namespace System.Windows.Data 
{
    ///  
    /// ICollectionView with checks for affinity to the UI thread dispatcher
    /// 
    public class CollectionView : DispatcherObject, ICollectionView, INotifyPropertyChanged
    { 
        //-----------------------------------------------------
        // 
        //  Constructors 
        //
        //----------------------------------------------------- 

        #region Constructors

        ///  
        /// Create a view to given collection.
        ///  
        ///  
        /// Note that this instance of CollectionView is bound to the
        /// UI thread dispatcher of the caller of this constructor. 
        /// 
        /// underlying collection
        public CollectionView(IEnumerable collection)
            : this(collection, 0) 
        {
        } 
 
        internal CollectionView(IEnumerable collection, int moveToFirst)
        { 
            if (collection == null)
                throw new ArgumentNullException("collection");

            // Constructing a CollectionView itself (as opposed to a derived class) 
            // is deprecated in NetFx3.5.  This used to use IndexedEnumerable to
            // support a view over a plain IEnumerable, but this scenario is now 
            // supported by the EnumerableCollectionView derived class.  Internal 
            // code does not create a CollectionView, but it is still
            // possible for an app to call "new CollectionView(myCollection)" 
            // directly;  this is public API that we cannot remove.  Such an app
            // will continue to get the old behavior with IndexedEnumerable, bugs
            // and all.  As a courtesy, we detect this here and warn the user about
            // it through the tracing channel. 
            if (this.GetType() == typeof(CollectionView))
            { 
                if (TraceData.IsEnabled) 
                {
                    TraceData.Trace(TraceEventType.Warning, 
                        TraceData.CollectionViewIsUnsupported);
                }
            }
 
            _sourceCollection = collection;
 
            // forward collection change events from underlying collection to our listeners. 
            INotifyCollectionChanged incc = collection as INotifyCollectionChanged;
            if (incc != null) 
            {
                incc.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                SetFlag(CollectionViewFlags.IsDynamic, true);
            } 

            // set currency to the first item if available 
            object currentItem = null; 
            int currentPosition = -1;
            if (moveToFirst >= 0) 
            {
                IEnumerator e = collection.GetEnumerator();
                if (e.MoveNext())
                { 
                    currentItem = e.Current;
                    currentPosition = 0; 
                } 
            }
 
            _currentItem = currentItem;
            _currentPosition = currentPosition;
            SetFlag(CollectionViewFlags.IsCurrentBeforeFirst,  _currentPosition < 0);
            SetFlag(CollectionViewFlags.IsCurrentAfterLast,  _currentPosition < 0); 
            SetFlag(CollectionViewFlags.CachedIsEmpty, _currentPosition < 0);
        } 
 
        internal CollectionView(IEnumerable collection, bool shouldProcessCollectionChanged)
            : this(collection) 
        {
            SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, shouldProcessCollectionChanged);
        }
 
        #endregion Constructors
 
        //------------------------------------------------------ 
        //
        //  Public Interfaces 
        //
        //-----------------------------------------------------

        #region Public Interfaces 

        #region ICollectionView 
 
        /// 
        /// Culture to use during sorting. 
        /// 
        [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))]
        public virtual CultureInfo Culture
        { 
            get { return _culture; }
            set 
            { 
                if (value == null)
                    throw new ArgumentNullException("value"); 

                if (_culture != value)
                {
                    _culture = value; 
                    OnPropertyChanged(CulturePropertyName);
                } 
            } 
        }
 
        /// 
        /// Return true if the item belongs to this view.  No assumptions are
        /// made about the item. This method will behave similarly to IList.Contains().
        ///  
        /// 
        /// 

If the caller knows that the item belongs to the /// underlying collection, it is more efficient to call PassesFilter. /// If the underlying collection is only of type IEnumerable, this method /// is a O(N) operation

///
public virtual bool Contains(object item) { VerifyRefreshNotDeferred(); return (IndexOf(item) >= 0); } /// /// Returns the underlying collection. /// public virtual IEnumerable SourceCollection { get { return _sourceCollection; } } /// /// Filter is a callback set by the consumer of the ICollectionView /// and used by the implementation of the ICollectionView to determine if an /// item is suitable for inclusion in the view. /// /// /// Simpler implementations do not support filtering and will throw a NotSupportedException. /// Use property to test if filtering is supported before /// assigning a non-null value. /// public virtual Predicate Filter { get { return _filter; } set { if (!CanFilter) throw new NotSupportedException(); _filter = value; RefreshOrDefer(); } } /// /// Indicates whether or not this ICollectionView can do any filtering. /// When false, set will throw an exception. /// public virtual bool CanFilter { get { return true; } } /// /// Collection of Sort criteria to sort items in this view over the SourceCollection. /// /// ///

/// Simpler implementations 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 virtual SortDescriptionCollection SortDescriptions { get { return SortDescriptionCollection.Empty; } } /// /// Test if this ICollectionView supports sorting before adding /// to . /// public virtual bool CanSort { get { return false; } } /// /// Returns true if this view really supports grouping. /// When this returns false, the rest of the interface is ignored. /// public virtual bool CanGroup { get { return false; } } /// /// The description of grouping, indexed by level. /// public virtual ObservableCollection GroupDescriptions { get { return null; } } /// /// The top-level groups, constructed according to the descriptions /// given in GroupDescriptions. /// public virtual ReadOnlyObservableCollection Groups { get { return null; } } /// /// Re-create the view, using any and/or . /// public virtual void Refresh() { RefreshOverride(); SetFlag(CollectionViewFlags.NeedsRefresh, false); } /// /// Enter a Defer Cycle. /// Defer cycles are used to coalesce changes to the ICollectionView. /// public virtual IDisposable DeferRefresh() { ++ _deferLevel; return new DeferHelper(this); } /// /// Return the "current item" for this view /// /// /// Only wrapper classes (those that pass currency handling calls to another internal /// CollectionView) should override CurrentItem; all other derived classes /// should use SetCurrent() to update the current values stored in the base class. /// public virtual object CurrentItem { get { VerifyRefreshNotDeferred(); return _currentItem; } } /// /// The ordinal position of the within the (optionally /// sorted and filtered) view. /// /// /// -1 if the CurrentPosition is unknown, because the collection does not have an /// effective notion of indices, or because CurrentPosition is being forcibly changed /// due to a CollectionChange. /// /// /// Only wrapper classes (those that pass currency handling calls to another internal /// CollectionView) should override CurrenPosition; all other derived classes /// should use SetCurrent() to update the current values stored in the base class. /// public virtual int CurrentPosition { get { VerifyRefreshNotDeferred(); return _currentPosition; } } /// /// Return true if is beyond the end (End-Of-File). /// public virtual bool IsCurrentAfterLast { get { VerifyRefreshNotDeferred(); return CheckFlag(CollectionViewFlags.IsCurrentAfterLast); } } /// /// Return true if is before the beginning (Beginning-Of-File). /// public virtual bool IsCurrentBeforeFirst { get { VerifyRefreshNotDeferred(); return CheckFlag(CollectionViewFlags.IsCurrentBeforeFirst); } } /// /// Move to the first item. /// /// true if points to an item within the view. public virtual bool MoveCurrentToFirst() { VerifyRefreshNotDeferred(); return MoveCurrentToPosition(0); } /// /// Move to the last item. /// /// true if points to an item within the view. public virtual bool MoveCurrentToLast() { VerifyRefreshNotDeferred(); return MoveCurrentToPosition(Count - 1); } /// /// Move to the next item. /// /// true if points to an item within the view. public virtual bool MoveCurrentToNext() { VerifyRefreshNotDeferred(); if (CurrentPosition < Count) { return MoveCurrentToPosition(CurrentPosition + 1); } else { return false; } } /// /// Move to the previous item. /// /// true if points to an item within the view. public virtual bool MoveCurrentToPrevious() { VerifyRefreshNotDeferred(); if (CurrentPosition >= 0) { return MoveCurrentToPosition(CurrentPosition - 1); } else { return false; } } /// /// Move to the given item. /// If the item is not found, move to BeforeFirst. /// /// Move CurrentItem to this item. /// true if points to an item within the view. public virtual bool MoveCurrentTo(object item) { VerifyRefreshNotDeferred(); // if already on item, don't do anything if (Object.Equals(CurrentItem, item)) { // also check that we're not fooled by a false null _currentItem if (item != null || IsCurrentInView) return IsCurrentInView; } int index = -1; if (PassesFilter(item)) { // if the item is not found IndexOf() will return -1, and // the MoveCurrentToPosition() below will move current to BeforeFirst index = IndexOf(item); } return MoveCurrentToPosition(index); } /// /// Move to the item at the given index. /// /// Move CurrentItem to this index /// true if points to an item within the view. public virtual bool MoveCurrentToPosition(int position) { VerifyRefreshNotDeferred(); if (position < -1 || position > Count) throw new ArgumentOutOfRangeException("position"); if ((position != CurrentPosition || !IsCurrentInSync) && OKToChangeCurrent()) { bool oldIsCurrentAfterLast = IsCurrentAfterLast; bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; _MoveCurrentToPosition(position); OnCurrentChanged(); if (IsCurrentAfterLast != oldIsCurrentAfterLast) OnPropertyChanged(IsCurrentAfterLastPropertyName); if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst) OnPropertyChanged(IsCurrentBeforeFirstPropertyName); OnPropertyChanged(CurrentPositionPropertyName); OnPropertyChanged(CurrentItemPropertyName); } return IsCurrentInView; } /// /// Raise this event before changing currency. /// public virtual event CurrentChangingEventHandler CurrentChanging; /// ///Raise this event after changing currency. /// public virtual event EventHandler CurrentChanged; #endregion ICollectionView #region IEnumerable /// /// Returns an object that enumerates the items in this view. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion IEnumerable #endregion Public Interfaces //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// /// 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 virtual bool PassesFilter(object item) { if (CanFilter && Filter != null) return Filter(item); return true; } /// 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 virtual int IndexOf(object item) { VerifyRefreshNotDeferred(); return EnumerableWrapper.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. /// If the underlying collection is only of type IEnumerable, this method /// is a O(N) operation.

///

When deriving from CollectionView, override this method to provide /// a more efficient implementation.

///
/// /// Thrown if index is out of range /// public virtual object GetItemAt(int index) { // only check lower bound because Count could be expensive if (index < 0) throw new ArgumentOutOfRangeException("index"); return EnumerableWrapper[index]; } #endregion Public Methods //----------------------------------------------------- // // Public Properties // //------------------------------------------------------ #region Public Properties /// /// Return the number of items (or -1, meaning "don't know"); /// if a Filter is set, this counts only items that pass the filter. /// /// ///

If the underlying collection is only of type IEnumerable, this count /// is a O(N) operation; this Count value will be cached until the /// collection changes again.

///

When deriving from CollectionView, override this property to provide /// a more efficient implementation.

///
public virtual int Count { get { VerifyRefreshNotDeferred(); return EnumerableWrapper.Count; } } /// /// Returns true if the resulting (filtered) view is emtpy. /// public virtual bool IsEmpty { get { return EnumerableWrapper.IsEmpty; } } /// /// Return an object that compares items in this view. /// public virtual IComparer Comparer { get { return this as IComparer; } } /// /// Returns true if this view needs to be refreshed. /// public virtual bool NeedsRefresh { get { return CheckFlag(CollectionViewFlags.NeedsRefresh); } } #endregion Public Properties //----------------------------------------------------- // // Public Events // //----------------------------------------------------- #region Public Events /// /// Raise this event when the (filtered) view changes /// protected virtual event NotifyCollectionChangedEventHandler CollectionChanged; /// /// CollectionChanged event (per ). /// event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add { CollectionChanged += value; } remove { CollectionChanged -= value; } } //----------------------------------------------------- #region IPropertyChange implementation /// /// PropertyChanged event (per ). /// event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { PropertyChanged += value; } remove { PropertyChanged -= value; } } /// /// Raises a PropertyChanged event (per ). /// protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if (PropertyChanged != null) { PropertyChanged(this, e); } } /// /// PropertyChanged event (per ). /// protected virtual event PropertyChangedEventHandler PropertyChanged; #endregion IPropertyChange implementation #endregion Public Events //------------------------------------------------------ // // Protected Methods // //----------------------------------------------------- #region Protected Methods /// /// Re-create the view, using any and/or . /// protected virtual void RefreshOverride() { if (SortDescriptions.Count > 0) throw new InvalidOperationException(SR.Get(SRID.ImplementOtherMembersWithSort, "Refresh()")); object oldCurrentItem = _currentItem; bool oldIsCurrentAfterLast = CheckFlag(CollectionViewFlags.IsCurrentAfterLast); bool oldIsCurrentBeforeFirst = CheckFlag(CollectionViewFlags.IsCurrentBeforeFirst); int oldCurrentPosition = _currentPosition; // force currency off the collection (gives user a chance to save dirty information) OnCurrentChanging(); InvalidateEnumerableWrapper(); if (IsEmpty || oldIsCurrentBeforeFirst) { _MoveCurrentToPosition(-1); } else if (oldIsCurrentAfterLast) { _MoveCurrentToPosition(Count); } else if (oldCurrentItem != null) // set currency back to old current item, or first if not found { int index = EnumerableWrapper.IndexOf(oldCurrentItem); if (index < 0) { index = 0; } _MoveCurrentToPosition(index); } 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); } /// /// Returns an object that enumerates the items in this view. /// protected virtual IEnumerator GetEnumerator() { VerifyRefreshNotDeferred(); if (SortDescriptions.Count > 0) throw new InvalidOperationException(SR.Get(SRID.ImplementOtherMembersWithSort, "GetEnumerator()")); return EnumerableWrapper.GetEnumerator(); } /// /// Notify listeners that this View has changed /// /// /// CollectionViews (and sub-classes) should take their filter/sort/grouping /// into account before calling this method to forward CollectionChanged events. /// /// /// The NotifyCollectionChangedEventArgs to be passed to the EventHandler /// protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { if (args == null) throw new ArgumentNullException("args"); if (CollectionChanged != null) CollectionChanged(this, args); // Collection changes change the count unless an item is being // replaced or moved within the collection. if (args.Action != NotifyCollectionChangedAction.Replace && args.Action != NotifyCollectionChangedAction.Move) { OnPropertyChanged(CountPropertyName); } bool isEmpty = IsEmpty; if (isEmpty != CheckFlag(CollectionViewFlags.CachedIsEmpty)) { SetFlag(CollectionViewFlags.CachedIsEmpty, isEmpty); OnPropertyChanged(IsEmptyPropertyName); } } /// /// set CurrentItem and CurrentPosition, no questions asked! /// /// /// CollectionViews (and sub-classes) should use this method to update /// the Current__ values. /// protected void SetCurrent(object newItem, int newPosition) { int count = (newItem != null) ? 0 : IsEmpty ? 0 : Count; SetCurrent(newItem, newPosition, count); } /// /// set CurrentItem and CurrentPosition, no questions asked! /// /// /// This method can be called from a constructor - it does not call /// any virtuals. The 'count' parameter is substitute for the real Count, /// used only when newItem is null. /// In that case, this method sets IsCurrentAfterLast to true if and only /// if newPosition >= count. This distinguishes between a null belonging /// to the view and the dummy null when CurrentPosition is past the end. /// protected void SetCurrent(object newItem, int newPosition, int count) { if (newItem != null) { // non-null item implies position is within range. // We ignore count - it's just a placeholder SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, false); SetFlag(CollectionViewFlags.IsCurrentAfterLast, false); } else if (count == 0) { // empty collection - by convention both flags are true and position is -1 SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, true); SetFlag(CollectionViewFlags.IsCurrentAfterLast, true); newPosition = -1; } else { // null item, possibly within range. SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, newPosition < 0); SetFlag(CollectionViewFlags.IsCurrentAfterLast, newPosition >= count); } _currentItem = newItem; _currentPosition = newPosition; } /// /// ask listeners (via event) if it's OK to change currency /// /// false if a listener cancels the change, true otherwise protected bool OKToChangeCurrent() { CurrentChangingEventArgs args = new CurrentChangingEventArgs(); OnCurrentChanging(args); return (!args.Cancel); } /// /// Raise a CurrentChanging event that is not cancelable. /// Internally, CurrentPosition is set to -1. /// This is called by CollectionChanges (Remove and Refresh) that affect the CurrentItem. /// /// /// This CurrentChanging event cannot be canceled. /// protected void OnCurrentChanging() { _currentPosition = -1; OnCurrentChanging(uncancelableCurrentChangingEventArgs); } /// /// Raises the CurrentChanging event /// /// /// CancelEventArgs used by the consumer of the event. args.Cancel will /// be true after this call if the CurrentItem should not be changed for /// any reason. /// /// /// This CurrentChanging event cannot be canceled. /// protected virtual void OnCurrentChanging(CurrentChangingEventArgs args) { if (args == null) throw new ArgumentNullException("args"); if (_currentChangedMonitor.Busy) { if (args.IsCancelable) args.Cancel = true; return; } if (CurrentChanging != null) { CurrentChanging(this, args); } } /// /// Raises the CurrentChanged event /// protected virtual void OnCurrentChanged() { if (CurrentChanged != null && _currentChangedMonitor.Enter()) { using (_currentChangedMonitor) { CurrentChanged(this, EventArgs.Empty); } } } /// /// Must be implemented by the derived classes to process a single change on the /// UI thread. The UI thread will have already been entered by now. /// /// /// The NotifyCollectionChangedEventArgs to be processed. /// protected virtual void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) { // // Steps for ProcessCollectionChanged: // // 1) Validate that the values in the args are acceptable. // 2) Translate the indices if necessary. // 3) Raise CollectionChanged. // 4) Adjust Currency. // 5) Raise any PropertyChanged events that apply. // ValidateCollectionChangedEventArgs(args); object oldCurrentItem = _currentItem; bool oldIsCurrentAfterLast = CheckFlag(CollectionViewFlags.IsCurrentAfterLast); bool oldIsCurrentBeforeFirst = CheckFlag(CollectionViewFlags.IsCurrentBeforeFirst); int oldCurrentPosition = _currentPosition; bool raiseChanged = false; switch (args.Action) { case NotifyCollectionChangedAction.Add: if (PassesFilter(args.NewItems[0])) { raiseChanged = true; AdjustCurrencyForAdd(args.NewStartingIndex); } break; case NotifyCollectionChangedAction.Remove: if (PassesFilter(args.OldItems[0])) { raiseChanged = true; AdjustCurrencyForRemove(args.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: if (PassesFilter(args.OldItems[0]) || PassesFilter(args.NewItems[0])) { raiseChanged = true; AdjustCurrencyForReplace(args.OldStartingIndex); } break; case NotifyCollectionChangedAction.Move: if (PassesFilter(args.NewItems[0])) { raiseChanged = true; AdjustCurrencyForMove(args.OldStartingIndex, args.NewStartingIndex); } break; case NotifyCollectionChangedAction.Reset: // collection has completely changed RefreshOrDefer(); return; // Refresh already raises the event } // we've already returned if (args.Action == NotifyCollectionChangedAction.Reset) above if (raiseChanged) OnCollectionChanged(args); // currency has to change after firing the deletion event, // so event handlers have the right picture if (_currentElementWasRemovedOrReplaced) { MoveCurrencyOffDeletedElement(); _currentElementWasRemovedOrReplaced = false; } // notify that the properties have changed. if (IsCurrentAfterLast != oldIsCurrentAfterLast) OnPropertyChanged(IsCurrentAfterLastPropertyName); if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst) OnPropertyChanged(IsCurrentBeforeFirstPropertyName); if (_currentPosition != oldCurrentPosition) OnPropertyChanged(CurrentPositionPropertyName); if (_currentItem != oldCurrentItem) OnPropertyChanged(CurrentItemPropertyName); } /// /// Handle CollectionChanged events. /// /// Calls ProcessCollectionChanged() or /// posts the change to the Dispatcher to process on the correct thread. /// /// /// User should override /// /// /// /// /// protected void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { if (CheckFlag(CollectionViewFlags.ShouldProcessCollectionChanged)) { bool isDispatcherThread = CheckAccess(); if (!isDispatcherThread && !CheckFlag(CollectionViewFlags.IsMultiThreadCollectionChangeAllowed)) throw new NotSupportedException(SR.Get(SRID.MultiThreadedCollectionChangeNotSupported)); // if we have entered the correct UI thread dispatcher, and have never been // updated from a different thread, then we can just directly process // the change. if (isDispatcherThread && !UpdatedOutsideDispatcher == true) { ProcessCollectionChanged(args); } else { PostChange(args); } } } /// /// 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 virtual void OnBeginChangeLogging(NotifyCollectionChangedEventArgs args) { } /// /// Clear any pending changes from the changelog. /// protected void ClearChangeLog() { lock(_changeLog.SyncRoot) { _changeLog.Clear(); } } /// /// Refresh, or mark that refresh is needed when defer cycle completes. /// protected void RefreshOrDefer() { if (IsRefreshDeferred) { SetFlag(CollectionViewFlags.NeedsRefresh, true); } else { Refresh(); } } #endregion Protected Methods //------------------------------------------------------ // // Protected Properties // //------------------------------------------------------ #region Protected Properties /// /// returns true if the underlying collection provides change notifications /// the collection view is listening to the change events. /// protected bool IsDynamic { get { return CheckFlag(CollectionViewFlags.IsDynamic); } } /// /// Returns true if it has been necessary /// to add a change to the ChangeLog because a CollectionChange /// notification has been received on a different thread without /// first entering the UI thread dispatcher. /// protected bool UpdatedOutsideDispatcher { get { return CheckFlag(CollectionViewFlags.UpdatedOutsideDispatcher); } } /// /// IsRefreshDeferred returns true if there /// is still an outstanding DeferRefresh in /// use. If at all possible, derived classes /// should not call Refresh if IsRefreshDeferred /// is true. /// protected bool IsRefreshDeferred { get { return _deferLevel > 0; } } /// /// IsCurrentInSync returns true if CurrentItem and CurrentPosition are /// up-to-date with the state and content of the collection. /// protected bool IsCurrentInSync { get { if (IsCurrentInView) return GetItemAt(CurrentPosition) == CurrentItem; else return CurrentItem == null; } } #endregion Protected Properties //----------------------------------------------------- // // Internal Methods // //------------------------------------------------------ #region Internal Methods /// /// This method is for use by an agent that manages a set of /// one or more views. Normal applications should not use it directly. /// /// /// It is used to control the lifetime of the view, so that it gets /// garbage-collected at the right time. /// internal void SetViewManagerData(object value) { object[] array; if (_vmData == null) { // 90% case - store a single value directly _vmData = value; } else if ((array = _vmData as object[]) == null) { // BindingListCollectionView appears in the table for both // DataTable and DataView - keep both references (bug 1745899) _vmData = new object[]{_vmData, value}; } else { // in case a view is held by more than two tables, keep all // references. This doesn't happen in current code, but there's // nothing preventing it, either. object[] newArray = new object[array.Length + 1]; array.CopyTo(newArray, 0); newArray[array.Length] = value; _vmData = newArray; } } // determine whether the items have reliable hash codes internal virtual bool HasReliableHashCodes() { // default implementation - sample the first item return (IsEmpty || HashHelper.HasReliableHashCode(GetItemAt(0))); } // helper to validate that we are not in the middle of a DeferRefresh // and throw if that is the case. internal 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 } internal void InvalidateEnumerableWrapper() { IndexedEnumerable wrapper = (IndexedEnumerable) Interlocked.Exchange(ref _enumerableWrapper, null); if (wrapper != null) { wrapper.Invalidate(); } } #endregion Internal Methods //----------------------------------------------------- // // Internal Properties // //----------------------------------------------------- #region Internal Properties internal object SyncRoot { get { ICollection collection = SourceCollection as ICollection; if (collection != null) return collection.SyncRoot; else return SourceCollection; } } #endregion Internal Properties //----------------------------------------------------- // // Private Properties // //------------------------------------------------------ #region Private Properties private bool IsCurrentInView { get { VerifyRefreshNotDeferred(); return (0 <= CurrentPosition && CurrentPosition < Count); } } private IndexedEnumerable EnumerableWrapper { get { if (_enumerableWrapper == null) { IndexedEnumerable newWrapper = new IndexedEnumerable(SourceCollection, new Predicate(this.PassesFilter)); Interlocked.CompareExchange(ref _enumerableWrapper, newWrapper, null); } return _enumerableWrapper; } } #endregion Private Properties //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // Just move it. No argument check, no events, just move current to position. private void _MoveCurrentToPosition(int position) { if (position < 0) { SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, true); SetCurrent(null, -1); } else if (position >= Count) { SetFlag(CollectionViewFlags.IsCurrentAfterLast, true); SetCurrent(null, Count); } else { SetFlag(CollectionViewFlags.IsCurrentBeforeFirst | CollectionViewFlags.IsCurrentAfterLast, false); SetCurrent(EnumerableWrapper[position], position); } } private void MoveCurrencyOffDeletedElement() { int lastPosition = Count - 1; // if position falls beyond last position, move back to last position int newPosition = (_currentPosition < lastPosition) ? _currentPosition : lastPosition; // ignore cancel, there's no choice in this currency change OnCurrentChanging(); _MoveCurrentToPosition(newPosition); OnCurrentChanged(); } private void EndDefer() { -- _deferLevel; if (_deferLevel == 0 && CheckFlag(CollectionViewFlags.NeedsRefresh)) { Refresh(); } } /// /// DeferProcessing is to be called from OnCollectionChanged by derived classes that /// wish to process the remainder of a changeLog after allowing other events to be /// processed. /// /// /// ArrayList of NotifyCollectionChangedEventArgs that could not be precessed. /// private void DeferProcessing(ICollection changeLog) { if (changeLog == null) changeLog = new object[]{}; lock(SyncRoot) { lock(_changeLog.SyncRoot) { if (_changeLog == null) { _changeLog = new ArrayList(changeLog); } else { _changeLog.InsertRange(0, changeLog); } if (_dispatcherOperation != null && _dispatcherOperation.Priority == DispatcherPriority.DataBind) { _dispatcherOperation.Priority = DispatcherPriority.Input; } else { _dispatcherOperation = this.Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(ProcessInvoke), null); } } } } /// /// Must be implemented by the derived classes to process changes on the /// UI thread. Called by ProcessInvoke wich is called by the Dispatcher, so /// the UI thread will have allready been entered by now. /// /// /// List of NotifyCollectionChangedEventArgs that is to be processed. /// private ICollection ProcessChangeLog(ArrayList changeLog) { int currentIndex = 0; bool mustDeferProcessing = false; long beginTime = DateTime.Now.Ticks; for ( ; currentIndex < changeLog.Count && !(mustDeferProcessing); currentIndex++) { NotifyCollectionChangedEventArgs args = changeLog[currentIndex] as NotifyCollectionChangedEventArgs; if (args != null) { ProcessCollectionChanged(args); } mustDeferProcessing = DateTime.Now.Ticks - beginTime > 1000000; // 100 milliseconds } if (mustDeferProcessing) { // create an unprocessed subset of changeLog changeLog.RemoveRange(0,currentIndex); return changeLog; } return null; } // returns true if ANY flag in flags is set. private bool CheckFlag(CollectionViewFlags flags) { return (_flags & flags) != 0; } private void SetFlag(CollectionViewFlags flags, bool value) { if (value) { _flags = _flags | flags; } else { _flags = _flags & ~flags; } } // Post a change on the UI thread Dispatcher and updated the _changeLog. private void PostChange(NotifyCollectionChangedEventArgs args) { lock(SyncRoot) { lock(_changeLog.SyncRoot) { _changeLog.Add(args); if(_changeLog.Count <= 1) { _dispatcherOperation = Dispatcher.BeginInvoke( DispatcherPriority.DataBind, new DispatcherOperationCallback(ProcessInvoke), null); if (!UpdatedOutsideDispatcher) { OnBeginChangeLogging(args); SetFlag(CollectionViewFlags.UpdatedOutsideDispatcher, true); } } } } } // Callback that is passed to Dispatcher.BeginInvoke in PostChange private object ProcessInvoke(object arg) { ArrayList tempChangeLog; ICollection unprocessedChanges; lock(SyncRoot) { lock(_changeLog.SyncRoot) { _dispatcherOperation = null; tempChangeLog = _changeLog; _changeLog = new ArrayList(); } } // if there is a refresh in the changelog, then the refresh // is the only thing that is interesting. foreach (NotifyCollectionChangedEventArgs args in tempChangeLog) { if (args.Action == NotifyCollectionChangedAction.Reset) { tempChangeLog = new ArrayList(1); tempChangeLog.Add(args); break; } } unprocessedChanges = ProcessChangeLog(tempChangeLog); if (unprocessedChanges != null && unprocessedChanges.Count > 0) { DeferProcessing(unprocessedChanges); } return null; } private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); break; case NotifyCollectionChangedAction.Remove: if (e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); if (e.OldStartingIndex < 0) throw new InvalidOperationException(SR.Get(SRID.RemovedItemNotFound)); break; case NotifyCollectionChangedAction.Replace: if (e.NewItems.Count != 1 || e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); break; case NotifyCollectionChangedAction.Move: if (e.NewItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); if (e.NewStartingIndex < 0) throw new InvalidOperationException(SR.Get(SRID.CannotMoveToUnknownPosition)); break; case NotifyCollectionChangedAction.Reset: break; default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } } // fix up CurrentPosition and CurrentItem after a collection change private void AdjustCurrencyForAdd(int index) { // adjust current index if insertion is earlier if (Count == 1) _currentPosition = -1; else if (index <= _currentPosition) { ++_currentPosition; if (_currentPosition < Count) { _currentItem = EnumerableWrapper[_currentPosition]; } } } // Fix up CurrentPosition and CurrentItem after an item was removed. private void AdjustCurrencyForRemove(int index) { // adjust current index if deletion is earlier if (index < _currentPosition) --_currentPosition; // move currency off the deleted element else if (index == _currentPosition) { _currentElementWasRemovedOrReplaced = true; } } // Fix up CurrentPosition and CurrentItem after an item was moved. private void AdjustCurrencyForMove(int oldIndex, int newIndex) { // if entire move was before or after current item, then there // is nothing that needs to be done. if ((oldIndex < CurrentPosition && newIndex < CurrentPosition) || (oldIndex > CurrentPosition && newIndex > CurrentPosition)) return; if (oldIndex <= CurrentPosition) AdjustCurrencyForRemove(oldIndex); else if (newIndex <= CurrentPosition) AdjustCurrencyForAdd(newIndex); } // fix up CurrentPosition and CurrentItem after a collection change private void AdjustCurrencyForReplace(int index) { // CurrentItem was replaced if (index == _currentPosition) { _currentElementWasRemovedOrReplaced = true; } } /// /// Helper to raise a PropertyChanged event />). /// private void OnPropertyChanged(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } #endregion Private Methods //------------------------------------------------------ // // Private Types // //----------------------------------------------------- #region Private Types private class DeferHelper : IDisposable { public DeferHelper(CollectionView collectionView) { _collectionView = collectionView; } public void Dispose() { if (_collectionView != null) { _collectionView.EndDefer(); _collectionView = null; } } private CollectionView _collectionView; } // this class helps prevent reentrant calls private class SimpleMonitor : IDisposable { public bool Enter() { if (_entered) return false; _entered = true; return true; } public void Dispose() { _entered = false; } public bool Busy { get { return _entered; } } bool _entered; } [Flags] private enum CollectionViewFlags { UpdatedOutsideDispatcher = 0x2, ShouldProcessCollectionChanged = 0x4, IsCurrentBeforeFirst = 0x8, IsCurrentAfterLast = 0x10, IsDynamic = 0x20, IsDataInGroupOrder = 0x40, NeedsRefresh = 0x80, IsMultiThreadCollectionChangeAllowed = 0x100, CachedIsEmpty = 0x200, } #endregion Private Types //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields ArrayList _changeLog = new ArrayList(); DispatcherOperation _dispatcherOperation; object _vmData; // view manager's private data IEnumerable _sourceCollection; // the underlying collection CultureInfo _culture; // culture to use when sorting SimpleMonitor _currentChangedMonitor = new SimpleMonitor(); int _deferLevel; IndexedEnumerable _enumerableWrapper; Predicate _filter; object _currentItem; int _currentPosition; CollectionViewFlags _flags = CollectionViewFlags.ShouldProcessCollectionChanged | CollectionViewFlags.NeedsRefresh; bool _currentElementWasRemovedOrReplaced; // since there's nothing in the uncancelable event args that is mutable, // just create one instance to be used universally. static readonly CurrentChangingEventArgs uncancelableCurrentChangingEventArgs = new CurrentChangingEventArgs(false); internal const string CountPropertyName = "Count"; internal const string IsEmptyPropertyName = "IsEmpty"; internal const string CulturePropertyName = "Culture"; internal const string CurrentPositionPropertyName = "CurrentPosition"; internal const string CurrentItemPropertyName = "CurrentItem"; internal const string IsCurrentBeforeFirstPropertyName = "IsCurrentBeforeFirst"; internal const string IsCurrentAfterLastPropertyName = "IsCurrentAfterLast"; #endregion Private Fields } } // 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