StrokeCollection.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / System / Windows / Ink / StrokeCollection.cs / 1305600 / StrokeCollection.cs

                            //------------------------------------------------------------------------ 
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
//----------------------------------------------------------------------- 

using MS.Utility; 
using System; 
using System.ComponentModel;
using System.Collections; 
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics; 
using System.Windows.Media;
using System.Windows.Ink; 
using System.Windows.Input; 
using System.Runtime.Serialization;
using System.IO; 
using System.Reflection;
using MS.Internal.Ink.InkSerializedFormat;
using MS.Internal.Ink;
 
using SR = MS.Internal.PresentationCore.SR;
using SRID = MS.Internal.PresentationCore.SRID; 
 
// Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET
namespace System.Windows.Ink 
{

    /// 
    /// Collection of strokes objects which can be operated on in aggregate. 
    /// 
    [System.ComponentModel.TypeConverter(typeof(StrokeCollectionConverter))] 
    public partial class StrokeCollection : Collection, INotifyPropertyChanged, INotifyCollectionChanged 
    {
 
        /// 
        /// The string used to designate the native persistence format
        ///      for ink data (e.g. used on the clipboard)
        ///  
        public static readonly String InkSerializedFormat = "Ink Serialized Format";
 
        /// Creates an empty stroke collection 
        public StrokeCollection()
        { 
        }

        /// Creates a StrokeCollection based on a collection of existing strokes
        public StrokeCollection(IEnumerable strokes) 
        {
            if ( strokes == null ) 
            { 
                throw new ArgumentNullException("strokes");
            } 

            List items = (List)this.Items;

            //unfortunately we have to check for dupes with this ctor 
            foreach ( Stroke stroke in strokes )
            { 
                if ( items.Contains(stroke) ) 
                {
                    //clear and throw 
                    items.Clear();
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokes");
                }
                items.Add(stroke); 
            }
        } 
 
        /// Creates a collection from ISF data in the specified stream
        /// Stream of ISF data 
        public StrokeCollection(Stream stream)
        {
            if ( stream == null )
            { 
                throw new ArgumentNullException("stream");
            } 
            if ( !stream.CanRead ) 
            {
                throw new ArgumentException(SR.Get(SRID.Image_StreamRead), "stream"); 
            }

            Stream seekableStream = GetSeekableStream(stream);
            if (seekableStream == null) 
            {
                throw new ArgumentException(SR.Get(SRID.Invalid_isfData_Length), "stream"); 
            } 

            //this will init our stroke collection 
            StrokeCollectionSerializer serializer = new StrokeCollectionSerializer(this);
            serializer.DecodeISF(seekableStream);
        }
 

        /// Save the collection of strokes, including any custom attributes to a stream 
        /// The stream to save Ink Serialized Format to 
        /// Flag if set to true the data will be compressed, which can
        /// reduce the output buffer size in exchange for slower Save performance. 
        public virtual void Save(Stream stream, bool compress)
        {
            if ( stream == null )
            { 
                throw new ArgumentNullException("stream");
            } 
            if ( !stream.CanWrite ) 
            {
                throw new ArgumentException(SR.Get(SRID.Image_StreamWrite), "stream"); 
            }
            SaveIsf(stream, compress);
        }
 
        /// Save the collection of strokes uncompressed, including any custom attributes to a stream
        /// The stream to save Ink Serialized Format to 
        public void Save(Stream stream) 
        {
            Save(stream, true); 
        }

        /// 
        /// Internal method for getting just the byte[] out 
        /// 
        internal void SaveIsf(Stream stream, bool compress) 
        { 
            StrokeCollectionSerializer serializer = new StrokeCollectionSerializer(this);
            serializer.CurrentCompressionMode = compress ? CompressionMode.Compressed : CompressionMode.NoCompression; 
            serializer.EncodeISF(stream);
        }

        ///  
        /// Private helper to read from a stream to the end and get a byte[]
        ///  
        private Stream GetSeekableStream(Stream stream) 
        {
            Debug.Assert(stream != null); 
            Debug.Assert(stream.CanRead);
            if ( stream.CanSeek )
            {
                int bytesToRead = (int)( stream.Length - stream.Position ); 
                if ( bytesToRead < 1 )
                { 
                    return null; //nothing to read 
                }
                //we can write to this 
                return stream;
            }
            else
            { 
                //Not all Stream implementations support Length.  Streams derived from
                //NetworkStream and CryptoStream are the primary examples, but there are others 
                //theyll throw NotSupportedException 
                MemoryStream ms = new MemoryStream();
 
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int count = bufferSize;
                while ( count == bufferSize ) 
                {
                    count = stream.Read(buffer, 0, 4096); 
                    ms.Write(buffer, 0, count); 
                }
                ms.Position = 0; 
                return ms;
            }
        }
 
        /// 
        /// Allows addition of objects to the EPC 
        ///  
        /// 
        ///  
        public void AddPropertyData(Guid propertyDataId, object propertyData)
        {
            DrawingAttributes.ValidateStylusTipTransform(propertyDataId, propertyData);
            object oldValue = null; 
            if ( ContainsPropertyData(propertyDataId) )
            { 
                oldValue = GetPropertyData(propertyDataId); 
                this.ExtendedProperties[propertyDataId] = propertyData;
            } 
            else
            {
                this.ExtendedProperties.Add(propertyDataId, propertyData);
            } 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, propertyData, oldValue)); 
        } 

        ///  
        /// Allows removal of objects from the EPC
        /// 
        /// 
        public void RemovePropertyData(Guid propertyDataId) 
        {
            object propertyData = GetPropertyData(propertyDataId); 
            this.ExtendedProperties.Remove(propertyDataId); 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, null, propertyData)); 
        }

        /// 
        /// Allows retrieval of objects from the EPC 
        /// 
        ///  
        public object GetPropertyData(Guid propertyDataId) 
        {
            if ( propertyDataId == Guid.Empty ) 
            {
                throw new ArgumentException(SR.Get(SRID.InvalidGuid), "propertyDataId");
            }
 
            return this.ExtendedProperties[propertyDataId];
        } 
 
        /// 
        /// Allows retrieval of a Array of guids that are contained in the EPC 
        /// 
        public Guid[] GetPropertyDataIds()
        {
            return this.ExtendedProperties.GetGuidArray(); 
        }
 
        ///  
        /// Allows the checking of objects in the EPC
        ///  
        /// 
        public bool ContainsPropertyData(Guid propertyDataId)
        {
            return this.ExtendedProperties.Contains(propertyDataId); 
        }
 
        ///  
        /// Applies the specified transform matrix on every stroke in the collection.
        /// This method composes this transform with the existing 
        /// transform on the stroke.
        /// The transform to compose against each Stroke
        /// Boolean if true the transform matrix will be applied to StylusTip
        /// The StrokeCollection does not maintain a separate transform 
        /// from each Stroke object. Calling Transform on the collection will
        /// cause each individual Stroke to be modified. 
        /// If the StrokesChanged event fires, the changed parameter will be a pointer to 'this' 
        /// collection, so any changes made to the changed event args will affect 'this' collection.
        public void Transform(Matrix transformMatrix, bool applyToStylusTip) 
        {
            // Ensure that the transformMatrix is invertible.
            if ( false == transformMatrix.HasInverse )
                throw new ArgumentException(SR.Get(SRID.MatrixNotInvertible), "transformMatrix"); 

            // if transformMatrix is identity or the StrokeCollection is empty 
            //      then no change will occur anyway 
            if ( transformMatrix.IsIdentity || Count == 0 )
            { 
                return;
            }

            // Apply the transform to each strokes 
            foreach ( Stroke stroke in this )
            { 
                // samgeo - Presharp issue 
                // Presharp gives a warning when get methods might deref a null.  It's complaining
                // here that 'stroke'' could be null, but StrokeCollection never allows nulls to be added 
                // so this is not possible
#pragma warning disable 1634, 1691
#pragma warning suppress 6506
                stroke.Transform(transformMatrix, applyToStylusTip); 
#pragma warning restore 1634, 1691
            } 
        } 

        ///  
        /// Performs a deep copy of the StrokeCollection.
        /// 
        public virtual StrokeCollection Clone()
        { 
            StrokeCollection clone = new StrokeCollection();
            foreach ( Stroke s in this ) 
            { 
                // samgeo - Presharp issue
                // Presharp gives a warning when get methods might deref a null.  It's complaining 
                // here that s could be null, but StrokeCollection never allows nulls to be added
                // so this is not possible
#pragma warning disable 1634, 1691
#pragma warning suppress 6506 
                clone.Add(s.Clone());
#pragma warning restore 1634, 1691 
            } 

            // 
            // clone epc if we have them
            //
            if ( _extendedProperties != null )
            { 
                clone._extendedProperties = _extendedProperties.Clone();
            } 
            return clone; 
        }
 
        /// 
        /// called by base class Collection<T> when the list is being cleared;
        /// raises a CollectionChanged event to any listeners
        ///  
        protected override sealed void ClearItems()
        { 
            if ( this.Count > 0 ) 
            {
                StrokeCollection removed = new StrokeCollection(); 
                for ( int x = 0; x < this.Count; x++ )
                {
                    ( (List)removed.Items ).Add(this[x]);
                } 

                base.ClearItems(); 
 
                RaiseStrokesChanged(null /*added*/, removed, -1);
            } 
        }

        /// 
        /// called by base class RemoveAt or Remove methods 
        /// 
        protected override sealed void RemoveItem(int index) 
        { 
            Stroke removedStroke = this[index];
            base.RemoveItem(index); 

            StrokeCollection removed = new StrokeCollection();
            ( (List)removed.Items ).Add(removedStroke);
            RaiseStrokesChanged(null /*added*/, removed, index); 
        }
 
        ///  
        /// called by base class Insert, Add methods
        ///  
        protected override sealed void InsertItem(int index, Stroke stroke)
        {
            if ( stroke == null )
            { 
                throw new ArgumentNullException("stroke");
            } 
            if ( this.IndexOf(stroke) != -1 ) 
            {
                throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "stroke"); 
            }

            base.InsertItem(index, stroke);
 
            StrokeCollection addedStrokes = new StrokeCollection();
            ( (List)addedStrokes.Items ).Add(stroke); 
            RaiseStrokesChanged(addedStrokes, null /*removed*/, index); 
        }
 
        /// 
        /// called by base class set_Item method
        /// 
        protected override sealed void SetItem(int index, Stroke stroke) 
        {
            if ( stroke == null ) 
            { 
                throw new ArgumentNullException("stroke");
            } 
            if ( IndexOf(stroke) != -1 )
            {
                throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "stroke");
            } 

            Stroke removedStroke = this[index]; 
            base.SetItem(index, stroke); 

            StrokeCollection removed = new StrokeCollection(); 
            ( (List)removed.Items ).Add(removedStroke);

            StrokeCollection added = new StrokeCollection();
            ( (List)added.Items ).Add(stroke); 
            RaiseStrokesChanged(added, removed, index);
        } 
 
        /// 
        /// Gets the index of the stroke, or -1 if it is not found 
        /// 
        /// stroke
        /// 
        public new int IndexOf(Stroke stroke) 
        {
            if (stroke == null) 
            { 
                //we never allow null strokes
                return -1; 
            }
            for (int i = 0; i < Count; i++)
            {
                if (object.ReferenceEquals(this[i], stroke)) 
                {
                    return i; 
                } 
            }
            return -1; 
        }

        /// 
        /// Remove a set of Stroke objects to the collection 
        /// 
        /// The strokes to remove from the collection 
        /// Changes to the collection trigger a StrokesChanged event. 
        public void Remove(StrokeCollection strokes)
        { 
            if ( strokes == null )
            {
                throw new ArgumentNullException("strokes");
            } 
            if ( strokes.Count == 0 )
            { 
                // NOTICE-2004/06/08-WAYNEZEN: 
                // We don't throw if an empty collection is going to be removed. And there is no event either.
                // This rule is also applied to invoking Clear() with an empty StrokeCollection. 
                return;
            }

            int[] indexes = this.GetStrokeIndexes(strokes); 
            if ( indexes == null )
            { 
                // At least one stroke doesn't exist in our collection. We throw. 
                ArgumentException ae = new ArgumentException(SR.Get(SRID.InvalidRemovedStroke), "strokes");
                // 
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged
                // to determine if this method is the origin of an ArgumentException we harden against
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            } 
 
            for ( int x = indexes.Length - 1; x >= 0; x-- )
            { 
                //bypass this.RemoveAt, which calls changed events
                //and call our protected List directly
                //remove from the back so the indexes are correct
                ( (List)this.Items ).RemoveAt(indexes[x]); 
            }
 
            RaiseStrokesChanged(null /*added*/, strokes, indexes[0]); 
        }
 
        /// 
        /// Add a set of Stroke objects to the collection
        /// 
        /// The strokes to add to the collection 
        /// The items are added to the collection at the end of the list.
        /// If the item already exists in the collection, then the item is not added again. 
        public void Add(StrokeCollection strokes) 
        {
            if ( strokes == null ) 
            {
                throw new ArgumentNullException("strokes");
            }
            if ( strokes.Count == 0 ) 
            {
                // NOTICE-2004/06/08-WAYNEZEN: 
                // We don't throw if an empty collection is going to be added. And there is no event either. 
                return;
            } 

            int index = this.Count;

            //validate that none of the strokes exist in the collection 
            for ( int x = 0; x < strokes.Count; x++ )
            { 
                Stroke stroke = strokes[x]; 
                if ( this.IndexOf(stroke) != -1 )
                { 
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokes");
                }
            }
 
            //add the strokes
            //bypass this.AddRange, which calls changed events 
            //and call our protected List directly 
            ( (List)this.Items ).AddRange(strokes);
 
            RaiseStrokesChanged(strokes, null /*removed*/, index);
        }

        ///  
        /// Replace
        ///  
        ///  
        /// 
        public void Replace(Stroke strokeToReplace, StrokeCollection strokesToReplaceWith) 
        {
            if ( strokeToReplace == null )
            {
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplace)); 
            }
 
            StrokeCollection strokesToReplace = new StrokeCollection(); 
            strokesToReplace.Add(strokeToReplace);
            this.Replace(strokesToReplace, strokesToReplaceWith); 
        }

        /// 
        /// Replace 
        /// 
        ///  
        ///  
        public void Replace(StrokeCollection strokesToReplace, StrokeCollection strokesToReplaceWith)
        { 
            if ( strokesToReplace == null )
            {
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplace));
            } 
            if ( strokesToReplaceWith == null )
            { 
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplaceWith)); 
            }
 
            int replaceCount = strokesToReplace.Count;
            if ( replaceCount == 0 )
            {
                ArgumentException ae = new ArgumentException(SR.Get(SRID.EmptyScToReplace), "strokesToReplace"); 
                //
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged 
                // to determine if this method is the origin of an ArgumentException we harden against 
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            }

            int[] indexes = this.GetStrokeIndexes(strokesToReplace); 
            if ( indexes == null )
            { 
                // At least one stroke doesn't exist in our collection. We throw. 
                ArgumentException ae = new ArgumentException(SR.Get(SRID.InvalidRemovedStroke), "strokesToReplace");
                // 
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged
                // to determine if this method is the origin of an ArgumentException we harden against
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            } 
 

            //validate that none of the relplaceWith strokes exist in the collection 
            for ( int x = 0; x < strokesToReplaceWith.Count; x++ )
            {
                Stroke stroke = strokesToReplaceWith[x];
                if ( this.IndexOf(stroke) != -1 ) 
                {
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokesToReplaceWith"); 
                } 
            }
 
            //bypass this.RemoveAt / InsertRange, which calls changed events
            //and call our protected List directly
            for ( int x = indexes.Length - 1; x >= 0; x-- )
            { 
                //bypass this.RemoveAt, which calls changed events
                //and call our protected List directly 
                //remove from the back so the indexes are correct 
                ( (List)this.Items ).RemoveAt(indexes[x]);
            } 

            if ( strokesToReplaceWith.Count > 0 )
            {
                //insert at the 
                ( (List)this.Items ).InsertRange(indexes[0], strokesToReplaceWith);
            } 
 

            RaiseStrokesChanged(strokesToReplaceWith, strokesToReplace, indexes[0]); 
        }

        /// 
        /// called by StrokeCollectionSerializer during Load, bypasses Change notification 
        /// 
        internal void AddWithoutEvent(Stroke stroke) 
        { 
            Debug.Assert(stroke != null && IndexOf(stroke) == -1);
            ( (List)this.Items ).Add(stroke); 
        }


        /// Collection of extended properties on this StrokeCollection 
        internal ExtendedPropertyCollection ExtendedProperties
        { 
            get 
            {
                // 
                // internal getter is used by the serialization code
                //
                if ( _extendedProperties == null )
                { 
                    _extendedProperties = new ExtendedPropertyCollection();
                } 
 
                return _extendedProperties;
            } 
            private set
            {
                //
                // private setter used by copy 
                //
                if ( value != null ) 
                { 
                    _extendedProperties = value;
                } 
            }
        }

        ///  
        /// Event that notifies listeners whenever a change occurs in the set
        /// of stroke objects contained in the collection. 
        ///  
        /// StrokeCollectionChangedEventHandler
        public event StrokeCollectionChangedEventHandler StrokesChanged; 

        /// 
        /// Event that notifies internal listeners whenever a change occurs in the set
        /// of stroke objects contained in the collection. 
        /// 
        /// StrokeCollectionChangedEventHandler 
        internal event StrokeCollectionChangedEventHandler StrokesChangedInternal; 

        ///  
        /// Event that notifies listeners whenever a change occurs in the propertyData
        /// 
        /// PropertyDataChangedEventHandler
        public event PropertyDataChangedEventHandler PropertyDataChanged; 

        ///  
        /// INotifyPropertyChanged.PropertyChanged event, explicitly implemented 
        /// 
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 
        {
            add { _propertyChanged += value; }
            remove { _propertyChanged -= value; }
        } 

        ///  
        /// INotifyCollectionChanged.CollectionChanged event, explicitly implemented 
        /// 
        event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged 
        {
            add { _collectionChanged += value; }
            remove { _collectionChanged -= value; }
        } 

 
        /// Method called on derived classes whenever a drawing attributes 
        /// change has occurred in the stroke references in the collection
        /// The change information for the stroke collection 
        /// StrokesChanged will not be called when drawing attributes or
        /// custom attributes are changed. Changes that trigger StrokesChanged
        /// include packets or points changing, modified tranforms, and stroke objects
        /// being added or removed from the collection. 
        /// To ensure that events fire for event listeners, derived classes
        /// should call this method. 
        protected virtual void OnStrokesChanged(StrokeCollectionChangedEventArgs e) 
        {
            if ( null == e ) 
            {
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            }
 
            //raise our internal event first.  This is used by
            //our Renderer and IncrementalHitTester since if they can assume 
            //they are the first in the delegate chain, they can be optimized 
            //to not have to handle out of order events caused by 3rd party code
            //getting called first 
            if ( this.StrokesChangedInternal != null)
            {
                this.StrokesChangedInternal(this, e);
            } 
            if ( this.StrokesChanged != null )
            { 
                this.StrokesChanged(this, e); 
            }
            if ( _collectionChanged != null ) 
            {
                //raise CollectionChanged.  We support the following
                //NotifyCollectionChangedActions
                NotifyCollectionChangedEventArgs args = null; 
                if ( this.Count == 0 )
                { 
                    //Reset 
                    Debug.Assert(e.Removed.Count > 0);
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 
                }
                else if ( e.Added.Count == 0 )
                {
                    //Remove 
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.Removed, e.Index);
                } 
                else if ( e.Removed.Count == 0 ) 
                {
                    //Add 
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.Added, e.Index);
                }
                else
                { 
                    //Replace
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.Added, e.Removed, e.Index); 
                } 
                _collectionChanged(this, args);
            } 
        }


        ///  
        /// Method called on derived classes whenever a change occurs in
        /// the PropertyData. 
        ///  
        /// Derived classes should call this method (their base class)
        /// to ensure that event listeners are notified 
        protected virtual void OnPropertyDataChanged(PropertyDataChangedEventArgs e)
        {
            if ( null == e )
            { 
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            } 
 
            if ( this.PropertyDataChanged != null )
            { 
                this.PropertyDataChanged(this, e);
            }
        }
 
        /// 
        /// Method called when a property change occurs to the StrokeCollection 
        ///  
        /// The EventArgs specifying the name of the changed property.
        /// To follow the guidelines, this method should take a PropertyChangedEventArgs 
        /// instance, but every other INotifyPropertyChanged implementation follows this pattern.
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if ( _propertyChanged != null ) 
            {
                _propertyChanged(this, e); 
            } 
        }
 

        /// 
        /// Private helper that starts searching for stroke at index,
        /// but will loop around before reporting -1.  This is used for 
        /// Stroke.Remove(StrokeCollection).  For example, if we're removing
        /// strokes, chances are they are in contiguous order.  If so, calling 
        /// IndexOf to validate each stroke is O(n2).  If the strokes are in order 
        /// this produces closer to O(n), if they are not in order, it is no worse
        ///  
        private int OptimisticIndexOf(int startingIndex, Stroke stroke)
        {
            Debug.Assert(startingIndex >= 0);
            for ( int x = startingIndex; x < this.Count; x++ ) 
            {
                if ( this[x] == stroke ) 
                { 
                    return x;
                } 
            }

            //we didn't find anything on the first pass, now search the beginning
            for ( int x = 0; x < startingIndex; x++ ) 
            {
                if ( this[x] == stroke ) 
                { 
                    return x;
                } 
            }
            return -1;
        }
 
        /// 
        /// Private helper that returns an array of indexes where the specified 
        /// strokes exist in this stroke collection.  Returns null if at least one is not found. 
        ///
        /// The indexes are sorted from smallest to largest 
        /// 
        /// 
        private int[] GetStrokeIndexes(StrokeCollection strokes)
        { 
            //to keep from walking the StrokeCollection twice for each stroke, we will maintain an index of
            //strokes to remove as we go 
            int[] indexes = new int[strokes.Count]; 
            for ( int x = 0; x < indexes.Length; x++ )
            { 
                indexes[x] = Int32.MaxValue;
            }

            int currentIndex = 0; 
            int highestIndex = -1;
            int usedIndexCount = 0; 
            for ( int x = 0; x < strokes.Count; x++ ) 
            {
                currentIndex = this.OptimisticIndexOf(currentIndex, strokes[x]); 
                if ( currentIndex == -1 )
                {
                    //stroke doe3sn't exist, bail out.
                    return null; 
                }
 
                // 
                // optimize for the most common case... replace is passes strokes
                // in contiguous order.  Only do the sort if we need to 
                //
                if ( currentIndex > highestIndex )
                {
                    //write current to the next available slot 
                    indexes[usedIndexCount++] = currentIndex;
                    highestIndex = currentIndex; 
                    continue; 
                }
 
                //keep in sorted order (smallest to largest) with a simple insertion sort
                for ( int y = 0; y < indexes.Length; y++ )
                {
                    if ( currentIndex < indexes[y] ) 
                    {
                        if ( indexes[y] != Int32.MaxValue ) 
                        { 
                            //shift from the end
                            for ( int i = indexes.Length - 1; i > y; i-- ) 
                            {
                                indexes[i] = indexes[i - 1];
                            }
                        } 
                        indexes[y] = currentIndex;
                        usedIndexCount++; 
 
                        if ( currentIndex > highestIndex )
                        { 
                            highestIndex = currentIndex;
                        }
                        break;
                    } 
                }
            } 
 
            return indexes;
        } 

        // This function will invoke OnStrokesChanged method.
        //      addedStrokes    -   the collection which contains the added strokes during the previous op.
        //      removedStrokes  -   the collection which contains the removed strokes during the previous op. 
        private void RaiseStrokesChanged(StrokeCollection addedStrokes, StrokeCollection removedStrokes, int index)
        { 
            StrokeCollectionChangedEventArgs eventArgs = 
                new StrokeCollectionChangedEventArgs(addedStrokes, removedStrokes, index);
 
            // Invoke OnPropertyChanged
            OnPropertyChanged(CountName);
            OnPropertyChanged(IndexerName);
 
            // Invoke OnStrokesChanged which will fire the StrokesChanged event AND the CollectionChanged event.
            OnStrokesChanged(eventArgs); 
        } 

        private void OnPropertyChanged(string propertyName) 
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
 
        // Custom 'user-defined' attributes assigned to this collection
        //  In v1, these were called Ink.ExtendedProperties 
        private ExtendedPropertyCollection _extendedProperties = null; 

        // The private PropertyChanged event 
        private PropertyChangedEventHandler _propertyChanged;

        // private CollectionChanged event raiser
        private NotifyCollectionChangedEventHandler _collectionChanged; 

        ///  
        /// Constants for the PropertyChanged event 
        /// 
        private const string IndexerName = "Item[]"; 
        private const string CountName = "Count";

        //
        // Nested types... 
        //
 
        ///  
        /// ReadOnlyStrokeCollection - for StrokeCollection.StrokesChanged event args...
        ///  
        internal class ReadOnlyStrokeCollection : StrokeCollection, ICollection, IList
        {
            internal ReadOnlyStrokeCollection(StrokeCollection strokeCollection)
            { 
                if ( strokeCollection != null )
                { 
                    ( (List)this.Items ).AddRange(strokeCollection); 
                }
            } 

            /// 
            /// Change is not allowed.  We would override SetItem, InsertItem etc but
            /// they need to be sealed on StrokeCollection to prevent dupes from being added 
            /// 
            ///  
            protected override void OnStrokesChanged(StrokeCollectionChangedEventArgs e) 
            {
                throw new NotSupportedException(SR.Get(SRID.StrokeCollectionIsReadOnly)); 
            }

            /// 
            /// IsReadOnly 
            /// 
            bool IList.IsReadOnly 
            { 
                get { return true; }
            } 

            /// 
            /// IsReadOnly
            ///  
            bool ICollection.IsReadOnly
            { 
                get { return true; } 
            }
        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------ 
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
//----------------------------------------------------------------------- 

using MS.Utility; 
using System; 
using System.ComponentModel;
using System.Collections; 
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics; 
using System.Windows.Media;
using System.Windows.Ink; 
using System.Windows.Input; 
using System.Runtime.Serialization;
using System.IO; 
using System.Reflection;
using MS.Internal.Ink.InkSerializedFormat;
using MS.Internal.Ink;
 
using SR = MS.Internal.PresentationCore.SR;
using SRID = MS.Internal.PresentationCore.SRID; 
 
// Primary root namespace for TabletPC/Ink/Handwriting/Recognition in .NET
namespace System.Windows.Ink 
{

    /// 
    /// Collection of strokes objects which can be operated on in aggregate. 
    /// 
    [System.ComponentModel.TypeConverter(typeof(StrokeCollectionConverter))] 
    public partial class StrokeCollection : Collection, INotifyPropertyChanged, INotifyCollectionChanged 
    {
 
        /// 
        /// The string used to designate the native persistence format
        ///      for ink data (e.g. used on the clipboard)
        ///  
        public static readonly String InkSerializedFormat = "Ink Serialized Format";
 
        /// Creates an empty stroke collection 
        public StrokeCollection()
        { 
        }

        /// Creates a StrokeCollection based on a collection of existing strokes
        public StrokeCollection(IEnumerable strokes) 
        {
            if ( strokes == null ) 
            { 
                throw new ArgumentNullException("strokes");
            } 

            List items = (List)this.Items;

            //unfortunately we have to check for dupes with this ctor 
            foreach ( Stroke stroke in strokes )
            { 
                if ( items.Contains(stroke) ) 
                {
                    //clear and throw 
                    items.Clear();
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokes");
                }
                items.Add(stroke); 
            }
        } 
 
        /// Creates a collection from ISF data in the specified stream
        /// Stream of ISF data 
        public StrokeCollection(Stream stream)
        {
            if ( stream == null )
            { 
                throw new ArgumentNullException("stream");
            } 
            if ( !stream.CanRead ) 
            {
                throw new ArgumentException(SR.Get(SRID.Image_StreamRead), "stream"); 
            }

            Stream seekableStream = GetSeekableStream(stream);
            if (seekableStream == null) 
            {
                throw new ArgumentException(SR.Get(SRID.Invalid_isfData_Length), "stream"); 
            } 

            //this will init our stroke collection 
            StrokeCollectionSerializer serializer = new StrokeCollectionSerializer(this);
            serializer.DecodeISF(seekableStream);
        }
 

        /// Save the collection of strokes, including any custom attributes to a stream 
        /// The stream to save Ink Serialized Format to 
        /// Flag if set to true the data will be compressed, which can
        /// reduce the output buffer size in exchange for slower Save performance. 
        public virtual void Save(Stream stream, bool compress)
        {
            if ( stream == null )
            { 
                throw new ArgumentNullException("stream");
            } 
            if ( !stream.CanWrite ) 
            {
                throw new ArgumentException(SR.Get(SRID.Image_StreamWrite), "stream"); 
            }
            SaveIsf(stream, compress);
        }
 
        /// Save the collection of strokes uncompressed, including any custom attributes to a stream
        /// The stream to save Ink Serialized Format to 
        public void Save(Stream stream) 
        {
            Save(stream, true); 
        }

        /// 
        /// Internal method for getting just the byte[] out 
        /// 
        internal void SaveIsf(Stream stream, bool compress) 
        { 
            StrokeCollectionSerializer serializer = new StrokeCollectionSerializer(this);
            serializer.CurrentCompressionMode = compress ? CompressionMode.Compressed : CompressionMode.NoCompression; 
            serializer.EncodeISF(stream);
        }

        ///  
        /// Private helper to read from a stream to the end and get a byte[]
        ///  
        private Stream GetSeekableStream(Stream stream) 
        {
            Debug.Assert(stream != null); 
            Debug.Assert(stream.CanRead);
            if ( stream.CanSeek )
            {
                int bytesToRead = (int)( stream.Length - stream.Position ); 
                if ( bytesToRead < 1 )
                { 
                    return null; //nothing to read 
                }
                //we can write to this 
                return stream;
            }
            else
            { 
                //Not all Stream implementations support Length.  Streams derived from
                //NetworkStream and CryptoStream are the primary examples, but there are others 
                //theyll throw NotSupportedException 
                MemoryStream ms = new MemoryStream();
 
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int count = bufferSize;
                while ( count == bufferSize ) 
                {
                    count = stream.Read(buffer, 0, 4096); 
                    ms.Write(buffer, 0, count); 
                }
                ms.Position = 0; 
                return ms;
            }
        }
 
        /// 
        /// Allows addition of objects to the EPC 
        ///  
        /// 
        ///  
        public void AddPropertyData(Guid propertyDataId, object propertyData)
        {
            DrawingAttributes.ValidateStylusTipTransform(propertyDataId, propertyData);
            object oldValue = null; 
            if ( ContainsPropertyData(propertyDataId) )
            { 
                oldValue = GetPropertyData(propertyDataId); 
                this.ExtendedProperties[propertyDataId] = propertyData;
            } 
            else
            {
                this.ExtendedProperties.Add(propertyDataId, propertyData);
            } 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, propertyData, oldValue)); 
        } 

        ///  
        /// Allows removal of objects from the EPC
        /// 
        /// 
        public void RemovePropertyData(Guid propertyDataId) 
        {
            object propertyData = GetPropertyData(propertyDataId); 
            this.ExtendedProperties.Remove(propertyDataId); 
            // fire notification
            OnPropertyDataChanged(new PropertyDataChangedEventArgs(propertyDataId, null, propertyData)); 
        }

        /// 
        /// Allows retrieval of objects from the EPC 
        /// 
        ///  
        public object GetPropertyData(Guid propertyDataId) 
        {
            if ( propertyDataId == Guid.Empty ) 
            {
                throw new ArgumentException(SR.Get(SRID.InvalidGuid), "propertyDataId");
            }
 
            return this.ExtendedProperties[propertyDataId];
        } 
 
        /// 
        /// Allows retrieval of a Array of guids that are contained in the EPC 
        /// 
        public Guid[] GetPropertyDataIds()
        {
            return this.ExtendedProperties.GetGuidArray(); 
        }
 
        ///  
        /// Allows the checking of objects in the EPC
        ///  
        /// 
        public bool ContainsPropertyData(Guid propertyDataId)
        {
            return this.ExtendedProperties.Contains(propertyDataId); 
        }
 
        ///  
        /// Applies the specified transform matrix on every stroke in the collection.
        /// This method composes this transform with the existing 
        /// transform on the stroke.
        /// The transform to compose against each Stroke
        /// Boolean if true the transform matrix will be applied to StylusTip
        /// The StrokeCollection does not maintain a separate transform 
        /// from each Stroke object. Calling Transform on the collection will
        /// cause each individual Stroke to be modified. 
        /// If the StrokesChanged event fires, the changed parameter will be a pointer to 'this' 
        /// collection, so any changes made to the changed event args will affect 'this' collection.
        public void Transform(Matrix transformMatrix, bool applyToStylusTip) 
        {
            // Ensure that the transformMatrix is invertible.
            if ( false == transformMatrix.HasInverse )
                throw new ArgumentException(SR.Get(SRID.MatrixNotInvertible), "transformMatrix"); 

            // if transformMatrix is identity or the StrokeCollection is empty 
            //      then no change will occur anyway 
            if ( transformMatrix.IsIdentity || Count == 0 )
            { 
                return;
            }

            // Apply the transform to each strokes 
            foreach ( Stroke stroke in this )
            { 
                // samgeo - Presharp issue 
                // Presharp gives a warning when get methods might deref a null.  It's complaining
                // here that 'stroke'' could be null, but StrokeCollection never allows nulls to be added 
                // so this is not possible
#pragma warning disable 1634, 1691
#pragma warning suppress 6506
                stroke.Transform(transformMatrix, applyToStylusTip); 
#pragma warning restore 1634, 1691
            } 
        } 

        ///  
        /// Performs a deep copy of the StrokeCollection.
        /// 
        public virtual StrokeCollection Clone()
        { 
            StrokeCollection clone = new StrokeCollection();
            foreach ( Stroke s in this ) 
            { 
                // samgeo - Presharp issue
                // Presharp gives a warning when get methods might deref a null.  It's complaining 
                // here that s could be null, but StrokeCollection never allows nulls to be added
                // so this is not possible
#pragma warning disable 1634, 1691
#pragma warning suppress 6506 
                clone.Add(s.Clone());
#pragma warning restore 1634, 1691 
            } 

            // 
            // clone epc if we have them
            //
            if ( _extendedProperties != null )
            { 
                clone._extendedProperties = _extendedProperties.Clone();
            } 
            return clone; 
        }
 
        /// 
        /// called by base class Collection<T> when the list is being cleared;
        /// raises a CollectionChanged event to any listeners
        ///  
        protected override sealed void ClearItems()
        { 
            if ( this.Count > 0 ) 
            {
                StrokeCollection removed = new StrokeCollection(); 
                for ( int x = 0; x < this.Count; x++ )
                {
                    ( (List)removed.Items ).Add(this[x]);
                } 

                base.ClearItems(); 
 
                RaiseStrokesChanged(null /*added*/, removed, -1);
            } 
        }

        /// 
        /// called by base class RemoveAt or Remove methods 
        /// 
        protected override sealed void RemoveItem(int index) 
        { 
            Stroke removedStroke = this[index];
            base.RemoveItem(index); 

            StrokeCollection removed = new StrokeCollection();
            ( (List)removed.Items ).Add(removedStroke);
            RaiseStrokesChanged(null /*added*/, removed, index); 
        }
 
        ///  
        /// called by base class Insert, Add methods
        ///  
        protected override sealed void InsertItem(int index, Stroke stroke)
        {
            if ( stroke == null )
            { 
                throw new ArgumentNullException("stroke");
            } 
            if ( this.IndexOf(stroke) != -1 ) 
            {
                throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "stroke"); 
            }

            base.InsertItem(index, stroke);
 
            StrokeCollection addedStrokes = new StrokeCollection();
            ( (List)addedStrokes.Items ).Add(stroke); 
            RaiseStrokesChanged(addedStrokes, null /*removed*/, index); 
        }
 
        /// 
        /// called by base class set_Item method
        /// 
        protected override sealed void SetItem(int index, Stroke stroke) 
        {
            if ( stroke == null ) 
            { 
                throw new ArgumentNullException("stroke");
            } 
            if ( IndexOf(stroke) != -1 )
            {
                throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "stroke");
            } 

            Stroke removedStroke = this[index]; 
            base.SetItem(index, stroke); 

            StrokeCollection removed = new StrokeCollection(); 
            ( (List)removed.Items ).Add(removedStroke);

            StrokeCollection added = new StrokeCollection();
            ( (List)added.Items ).Add(stroke); 
            RaiseStrokesChanged(added, removed, index);
        } 
 
        /// 
        /// Gets the index of the stroke, or -1 if it is not found 
        /// 
        /// stroke
        /// 
        public new int IndexOf(Stroke stroke) 
        {
            if (stroke == null) 
            { 
                //we never allow null strokes
                return -1; 
            }
            for (int i = 0; i < Count; i++)
            {
                if (object.ReferenceEquals(this[i], stroke)) 
                {
                    return i; 
                } 
            }
            return -1; 
        }

        /// 
        /// Remove a set of Stroke objects to the collection 
        /// 
        /// The strokes to remove from the collection 
        /// Changes to the collection trigger a StrokesChanged event. 
        public void Remove(StrokeCollection strokes)
        { 
            if ( strokes == null )
            {
                throw new ArgumentNullException("strokes");
            } 
            if ( strokes.Count == 0 )
            { 
                // NOTICE-2004/06/08-WAYNEZEN: 
                // We don't throw if an empty collection is going to be removed. And there is no event either.
                // This rule is also applied to invoking Clear() with an empty StrokeCollection. 
                return;
            }

            int[] indexes = this.GetStrokeIndexes(strokes); 
            if ( indexes == null )
            { 
                // At least one stroke doesn't exist in our collection. We throw. 
                ArgumentException ae = new ArgumentException(SR.Get(SRID.InvalidRemovedStroke), "strokes");
                // 
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged
                // to determine if this method is the origin of an ArgumentException we harden against
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            } 
 
            for ( int x = indexes.Length - 1; x >= 0; x-- )
            { 
                //bypass this.RemoveAt, which calls changed events
                //and call our protected List directly
                //remove from the back so the indexes are correct
                ( (List)this.Items ).RemoveAt(indexes[x]); 
            }
 
            RaiseStrokesChanged(null /*added*/, strokes, indexes[0]); 
        }
 
        /// 
        /// Add a set of Stroke objects to the collection
        /// 
        /// The strokes to add to the collection 
        /// The items are added to the collection at the end of the list.
        /// If the item already exists in the collection, then the item is not added again. 
        public void Add(StrokeCollection strokes) 
        {
            if ( strokes == null ) 
            {
                throw new ArgumentNullException("strokes");
            }
            if ( strokes.Count == 0 ) 
            {
                // NOTICE-2004/06/08-WAYNEZEN: 
                // We don't throw if an empty collection is going to be added. And there is no event either. 
                return;
            } 

            int index = this.Count;

            //validate that none of the strokes exist in the collection 
            for ( int x = 0; x < strokes.Count; x++ )
            { 
                Stroke stroke = strokes[x]; 
                if ( this.IndexOf(stroke) != -1 )
                { 
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokes");
                }
            }
 
            //add the strokes
            //bypass this.AddRange, which calls changed events 
            //and call our protected List directly 
            ( (List)this.Items ).AddRange(strokes);
 
            RaiseStrokesChanged(strokes, null /*removed*/, index);
        }

        ///  
        /// Replace
        ///  
        ///  
        /// 
        public void Replace(Stroke strokeToReplace, StrokeCollection strokesToReplaceWith) 
        {
            if ( strokeToReplace == null )
            {
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplace)); 
            }
 
            StrokeCollection strokesToReplace = new StrokeCollection(); 
            strokesToReplace.Add(strokeToReplace);
            this.Replace(strokesToReplace, strokesToReplaceWith); 
        }

        /// 
        /// Replace 
        /// 
        ///  
        ///  
        public void Replace(StrokeCollection strokesToReplace, StrokeCollection strokesToReplaceWith)
        { 
            if ( strokesToReplace == null )
            {
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplace));
            } 
            if ( strokesToReplaceWith == null )
            { 
                throw new ArgumentNullException(SR.Get(SRID.EmptyScToReplaceWith)); 
            }
 
            int replaceCount = strokesToReplace.Count;
            if ( replaceCount == 0 )
            {
                ArgumentException ae = new ArgumentException(SR.Get(SRID.EmptyScToReplace), "strokesToReplace"); 
                //
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged 
                // to determine if this method is the origin of an ArgumentException we harden against 
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            }

            int[] indexes = this.GetStrokeIndexes(strokesToReplace); 
            if ( indexes == null )
            { 
                // At least one stroke doesn't exist in our collection. We throw. 
                ArgumentException ae = new ArgumentException(SR.Get(SRID.InvalidRemovedStroke), "strokesToReplace");
                // 
                // we add a tag here so we can check for this in EraserBehavior.OnPointEraseResultChanged
                // to determine if this method is the origin of an ArgumentException we harden against
                //
                ae.Data.Add("System.Windows.Ink.StrokeCollection", ""); 
                throw ae;
            } 
 

            //validate that none of the relplaceWith strokes exist in the collection 
            for ( int x = 0; x < strokesToReplaceWith.Count; x++ )
            {
                Stroke stroke = strokesToReplaceWith[x];
                if ( this.IndexOf(stroke) != -1 ) 
                {
                    throw new ArgumentException(SR.Get(SRID.StrokeIsDuplicated), "strokesToReplaceWith"); 
                } 
            }
 
            //bypass this.RemoveAt / InsertRange, which calls changed events
            //and call our protected List directly
            for ( int x = indexes.Length - 1; x >= 0; x-- )
            { 
                //bypass this.RemoveAt, which calls changed events
                //and call our protected List directly 
                //remove from the back so the indexes are correct 
                ( (List)this.Items ).RemoveAt(indexes[x]);
            } 

            if ( strokesToReplaceWith.Count > 0 )
            {
                //insert at the 
                ( (List)this.Items ).InsertRange(indexes[0], strokesToReplaceWith);
            } 
 

            RaiseStrokesChanged(strokesToReplaceWith, strokesToReplace, indexes[0]); 
        }

        /// 
        /// called by StrokeCollectionSerializer during Load, bypasses Change notification 
        /// 
        internal void AddWithoutEvent(Stroke stroke) 
        { 
            Debug.Assert(stroke != null && IndexOf(stroke) == -1);
            ( (List)this.Items ).Add(stroke); 
        }


        /// Collection of extended properties on this StrokeCollection 
        internal ExtendedPropertyCollection ExtendedProperties
        { 
            get 
            {
                // 
                // internal getter is used by the serialization code
                //
                if ( _extendedProperties == null )
                { 
                    _extendedProperties = new ExtendedPropertyCollection();
                } 
 
                return _extendedProperties;
            } 
            private set
            {
                //
                // private setter used by copy 
                //
                if ( value != null ) 
                { 
                    _extendedProperties = value;
                } 
            }
        }

        ///  
        /// Event that notifies listeners whenever a change occurs in the set
        /// of stroke objects contained in the collection. 
        ///  
        /// StrokeCollectionChangedEventHandler
        public event StrokeCollectionChangedEventHandler StrokesChanged; 

        /// 
        /// Event that notifies internal listeners whenever a change occurs in the set
        /// of stroke objects contained in the collection. 
        /// 
        /// StrokeCollectionChangedEventHandler 
        internal event StrokeCollectionChangedEventHandler StrokesChangedInternal; 

        ///  
        /// Event that notifies listeners whenever a change occurs in the propertyData
        /// 
        /// PropertyDataChangedEventHandler
        public event PropertyDataChangedEventHandler PropertyDataChanged; 

        ///  
        /// INotifyPropertyChanged.PropertyChanged event, explicitly implemented 
        /// 
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 
        {
            add { _propertyChanged += value; }
            remove { _propertyChanged -= value; }
        } 

        ///  
        /// INotifyCollectionChanged.CollectionChanged event, explicitly implemented 
        /// 
        event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged 
        {
            add { _collectionChanged += value; }
            remove { _collectionChanged -= value; }
        } 

 
        /// Method called on derived classes whenever a drawing attributes 
        /// change has occurred in the stroke references in the collection
        /// The change information for the stroke collection 
        /// StrokesChanged will not be called when drawing attributes or
        /// custom attributes are changed. Changes that trigger StrokesChanged
        /// include packets or points changing, modified tranforms, and stroke objects
        /// being added or removed from the collection. 
        /// To ensure that events fire for event listeners, derived classes
        /// should call this method. 
        protected virtual void OnStrokesChanged(StrokeCollectionChangedEventArgs e) 
        {
            if ( null == e ) 
            {
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            }
 
            //raise our internal event first.  This is used by
            //our Renderer and IncrementalHitTester since if they can assume 
            //they are the first in the delegate chain, they can be optimized 
            //to not have to handle out of order events caused by 3rd party code
            //getting called first 
            if ( this.StrokesChangedInternal != null)
            {
                this.StrokesChangedInternal(this, e);
            } 
            if ( this.StrokesChanged != null )
            { 
                this.StrokesChanged(this, e); 
            }
            if ( _collectionChanged != null ) 
            {
                //raise CollectionChanged.  We support the following
                //NotifyCollectionChangedActions
                NotifyCollectionChangedEventArgs args = null; 
                if ( this.Count == 0 )
                { 
                    //Reset 
                    Debug.Assert(e.Removed.Count > 0);
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 
                }
                else if ( e.Added.Count == 0 )
                {
                    //Remove 
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.Removed, e.Index);
                } 
                else if ( e.Removed.Count == 0 ) 
                {
                    //Add 
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.Added, e.Index);
                }
                else
                { 
                    //Replace
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.Added, e.Removed, e.Index); 
                } 
                _collectionChanged(this, args);
            } 
        }


        ///  
        /// Method called on derived classes whenever a change occurs in
        /// the PropertyData. 
        ///  
        /// Derived classes should call this method (their base class)
        /// to ensure that event listeners are notified 
        protected virtual void OnPropertyDataChanged(PropertyDataChangedEventArgs e)
        {
            if ( null == e )
            { 
                throw new ArgumentNullException("e", SR.Get(SRID.EventArgIsNull));
            } 
 
            if ( this.PropertyDataChanged != null )
            { 
                this.PropertyDataChanged(this, e);
            }
        }
 
        /// 
        /// Method called when a property change occurs to the StrokeCollection 
        ///  
        /// The EventArgs specifying the name of the changed property.
        /// To follow the guidelines, this method should take a PropertyChangedEventArgs 
        /// instance, but every other INotifyPropertyChanged implementation follows this pattern.
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if ( _propertyChanged != null ) 
            {
                _propertyChanged(this, e); 
            } 
        }
 

        /// 
        /// Private helper that starts searching for stroke at index,
        /// but will loop around before reporting -1.  This is used for 
        /// Stroke.Remove(StrokeCollection).  For example, if we're removing
        /// strokes, chances are they are in contiguous order.  If so, calling 
        /// IndexOf to validate each stroke is O(n2).  If the strokes are in order 
        /// this produces closer to O(n), if they are not in order, it is no worse
        ///  
        private int OptimisticIndexOf(int startingIndex, Stroke stroke)
        {
            Debug.Assert(startingIndex >= 0);
            for ( int x = startingIndex; x < this.Count; x++ ) 
            {
                if ( this[x] == stroke ) 
                { 
                    return x;
                } 
            }

            //we didn't find anything on the first pass, now search the beginning
            for ( int x = 0; x < startingIndex; x++ ) 
            {
                if ( this[x] == stroke ) 
                { 
                    return x;
                } 
            }
            return -1;
        }
 
        /// 
        /// Private helper that returns an array of indexes where the specified 
        /// strokes exist in this stroke collection.  Returns null if at least one is not found. 
        ///
        /// The indexes are sorted from smallest to largest 
        /// 
        /// 
        private int[] GetStrokeIndexes(StrokeCollection strokes)
        { 
            //to keep from walking the StrokeCollection twice for each stroke, we will maintain an index of
            //strokes to remove as we go 
            int[] indexes = new int[strokes.Count]; 
            for ( int x = 0; x < indexes.Length; x++ )
            { 
                indexes[x] = Int32.MaxValue;
            }

            int currentIndex = 0; 
            int highestIndex = -1;
            int usedIndexCount = 0; 
            for ( int x = 0; x < strokes.Count; x++ ) 
            {
                currentIndex = this.OptimisticIndexOf(currentIndex, strokes[x]); 
                if ( currentIndex == -1 )
                {
                    //stroke doe3sn't exist, bail out.
                    return null; 
                }
 
                // 
                // optimize for the most common case... replace is passes strokes
                // in contiguous order.  Only do the sort if we need to 
                //
                if ( currentIndex > highestIndex )
                {
                    //write current to the next available slot 
                    indexes[usedIndexCount++] = currentIndex;
                    highestIndex = currentIndex; 
                    continue; 
                }
 
                //keep in sorted order (smallest to largest) with a simple insertion sort
                for ( int y = 0; y < indexes.Length; y++ )
                {
                    if ( currentIndex < indexes[y] ) 
                    {
                        if ( indexes[y] != Int32.MaxValue ) 
                        { 
                            //shift from the end
                            for ( int i = indexes.Length - 1; i > y; i-- ) 
                            {
                                indexes[i] = indexes[i - 1];
                            }
                        } 
                        indexes[y] = currentIndex;
                        usedIndexCount++; 
 
                        if ( currentIndex > highestIndex )
                        { 
                            highestIndex = currentIndex;
                        }
                        break;
                    } 
                }
            } 
 
            return indexes;
        } 

        // This function will invoke OnStrokesChanged method.
        //      addedStrokes    -   the collection which contains the added strokes during the previous op.
        //      removedStrokes  -   the collection which contains the removed strokes during the previous op. 
        private void RaiseStrokesChanged(StrokeCollection addedStrokes, StrokeCollection removedStrokes, int index)
        { 
            StrokeCollectionChangedEventArgs eventArgs = 
                new StrokeCollectionChangedEventArgs(addedStrokes, removedStrokes, index);
 
            // Invoke OnPropertyChanged
            OnPropertyChanged(CountName);
            OnPropertyChanged(IndexerName);
 
            // Invoke OnStrokesChanged which will fire the StrokesChanged event AND the CollectionChanged event.
            OnStrokesChanged(eventArgs); 
        } 

        private void OnPropertyChanged(string propertyName) 
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
 
        // Custom 'user-defined' attributes assigned to this collection
        //  In v1, these were called Ink.ExtendedProperties 
        private ExtendedPropertyCollection _extendedProperties = null; 

        // The private PropertyChanged event 
        private PropertyChangedEventHandler _propertyChanged;

        // private CollectionChanged event raiser
        private NotifyCollectionChangedEventHandler _collectionChanged; 

        ///  
        /// Constants for the PropertyChanged event 
        /// 
        private const string IndexerName = "Item[]"; 
        private const string CountName = "Count";

        //
        // Nested types... 
        //
 
        ///  
        /// ReadOnlyStrokeCollection - for StrokeCollection.StrokesChanged event args...
        ///  
        internal class ReadOnlyStrokeCollection : StrokeCollection, ICollection, IList
        {
            internal ReadOnlyStrokeCollection(StrokeCollection strokeCollection)
            { 
                if ( strokeCollection != null )
                { 
                    ( (List)this.Items ).AddRange(strokeCollection); 
                }
            } 

            /// 
            /// Change is not allowed.  We would override SetItem, InsertItem etc but
            /// they need to be sealed on StrokeCollection to prevent dupes from being added 
            /// 
            ///  
            protected override void OnStrokesChanged(StrokeCollectionChangedEventArgs e) 
            {
                throw new NotSupportedException(SR.Get(SRID.StrokeCollectionIsReadOnly)); 
            }

            /// 
            /// IsReadOnly 
            /// 
            bool IList.IsReadOnly 
            { 
                get { return true; }
            } 

            /// 
            /// IsReadOnly
            ///  
            bool ICollection.IsReadOnly
            { 
                get { return true; } 
            }
        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

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