File: MS\Internal\Ink\InkCanvasSelection.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description:
//      Element that represents InkCanvas selection
//
 
 
using MS.Utility;
using MS.Internal;
using MS.Internal.Controls;
using MS.Internal.Ink;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Threading;
 
namespace MS.Internal.Ink
{
    /// <summary>
    /// InkCanvasSelection
    /// </summary>
    internal sealed class InkCanvasSelection
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors
        //
        //-------------------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// InkCanvasSelection has an internal constructor to prevent direct instantiation
        /// </summary>
        /// <param name="inkCanvas">inkCanvas</param>
        internal InkCanvasSelection(InkCanvas inkCanvas)
        {
            //validate
            ArgumentNullException.ThrowIfNull(inkCanvas);
            _inkCanvas = inkCanvas;
 
            _inkCanvas.FeedbackAdorner.UpdateBounds(Rect.Empty);
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------------------
        //
        // Internal Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Internal Properties
 
        /// <summary>
        /// Returns the collection of selected strokes
        /// </summary>
        internal StrokeCollection SelectedStrokes
        {
            get
            {
                if ( _selectedStrokes == null )
                {
                    _selectedStrokes = new StrokeCollection();
                    _areStrokesChanged = true;
                }
 
                return _selectedStrokes;
            }
        }
 
        /// <summary>
        /// Returns the collection of selected elements
        /// </summary>
        internal ReadOnlyCollection<UIElement> SelectedElements
        {
            get
            {
                if ( _selectedElements == null )
                {
                    _selectedElements = new List<UIElement>();
                }
 
                return new ReadOnlyCollection<UIElement>(_selectedElements);
            }
        }
 
        /// <summary>
        /// Indicates whether there is a selection in the current InkCanvas.
        /// </summary>
        internal bool HasSelection
        {
            get
            {
                return SelectedStrokes.Count != 0 || SelectedElements.Count != 0;
            }
        }
 
        /// <summary>
        /// Returns the selection bounds
        /// </summary>
        internal Rect SelectionBounds
        {
            get
            {
                return Rect.Union(GetStrokesBounds(), GetElementsUnionBounds());
            }
        }
 
        #endregion Internal Properties
 
        //-------------------------------------------------------------------------------
        //
        // Internal Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Start a feedback rubberband.
        /// This method is called from SelectionEditingBehavior.OnActivated
        /// </summary>
        /// <param name="feedbackRect"></param>
        /// <param name="activeSelectionHitResult"></param>
        internal void StartFeedbackAdorner(Rect feedbackRect, InkCanvasSelectionHitResult activeSelectionHitResult)
        {
            Debug.Assert( _inkCanvas.EditingCoordinator.UserIsEditing == true );
            Debug.Assert(activeSelectionHitResult != InkCanvasSelectionHitResult.None, "activeSelectionHitResult cannot be InkCanvasSelectionHitResult.None.");
 
            _activeSelectionHitResult = activeSelectionHitResult;
 
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(_inkCanvas.InnerCanvas);
 
            // Until the next start, we are going to use this adorner regardless user will change feedback adorner or not during the editing.
            InkCanvasFeedbackAdorner feedbackAdorner = _inkCanvas.FeedbackAdorner;
 
            // The feedback adorner shouldn't have been connected to the adorner layer yet.
            Debug.Assert(VisualTreeHelper.GetParent(feedbackAdorner) == null,
                "feedbackAdorner shouldn't be added to tree.");
 
            // Now, attach the feedback adorner to the adorner layer. Then update its bounds
            adornerLayer.Add(feedbackAdorner);
            feedbackAdorner.UpdateBounds(feedbackRect);
        }
 
        /// <summary>
        /// This method updates the feedback rubberband.
        /// This method is called from SelectionEditingBehavior.OnMouseMove.
        /// </summary>
        /// <param name="feedbackRect"></param>
        internal void UpdateFeedbackAdorner(Rect feedbackRect)
        {
            Debug.Assert(VisualTreeHelper.GetParent(_inkCanvas.FeedbackAdorner)
                == AdornerLayer.GetAdornerLayer(_inkCanvas.InnerCanvas),
                "feedbackAdorner should have been added to tree.");
 
            // Update the feedback bounds
            _inkCanvas.FeedbackAdorner.UpdateBounds(feedbackRect);
        }
 
        /// <summary>
        /// Ends a feedback rubberband
        /// This method is called from SelectionEditingBehavior.OnMoveUp
        /// </summary>
        /// <param name="finalRectangle"></param>
        internal void EndFeedbackAdorner(Rect finalRectangle)
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(_inkCanvas.InnerCanvas);
            InkCanvasFeedbackAdorner feedbackAdorner = _inkCanvas.FeedbackAdorner;
 
            Debug.Assert(VisualTreeHelper.GetParent(feedbackAdorner) == adornerLayer,
                "feedbackAdorner should have been added to tree.");
 
            // Reset the feedback bounds and detach it from the adorner layer.
            feedbackAdorner.UpdateBounds(Rect.Empty);
            adornerLayer.Remove(feedbackAdorner);
 
            // Commit the new rectange of the selection.
            CommitChanges(finalRectangle, true);
 
            _activeSelectionHitResult = null;
}
 
        /// <summary>
        /// Set the new selection to this helper class.
        /// </summary>
        /// <param name="strokes"></param>
        /// <param name="elements"></param>
        /// <param name="raiseSelectionChanged"></param>
        internal void Select(StrokeCollection strokes, IList<UIElement> elements, bool raiseSelectionChanged)
        {
            // Can't be null.
            Debug.Assert(strokes != null && elements != null);
 
            //
            // check to see if we're different.  If not, there is no need to raise the changed event
            //
            bool strokesAreDifferent;
            bool elementsAreDifferent;
            int count = 0;
            SelectionIsDifferentThanCurrent(strokes, out strokesAreDifferent, elements, out elementsAreDifferent);
 
            // Besides the differences of both the stroks and the elements, there is a chance that the selected strokes and elements
            // have been removed from the InkCanvas. Meanwhile, we may still hold the reference of _inkCanvasSelection which is empty.
            // So, we should make sure to remove _inkCanvasSelection in this case.
            if ( strokesAreDifferent || elementsAreDifferent )
            {
                if ( strokesAreDifferent && SelectedStrokes.Count != 0 )
                {
                    // PERF-2006/05/02-WAYNEZEN,
                    // Unsubscribe the event fisrt so that reseting IsSelected won't call into our handler.
                    //
                    // quit listening to changes
                    //
                    QuitListeningToStrokeChanges();
 
                    count = SelectedStrokes.Count;
                    for ( int i = 0; i < count; i++ )
                    {
                        SelectedStrokes[i].IsSelected = false;
                    }
                }
 
                _selectedStrokes = strokes;
                _areStrokesChanged = true;
                _selectedElements = new List<UIElement>(elements);
 
 
                // PERF-2006/05/02-WAYNEZEN,
                // Update IsSelected property before adding the event handlers.
                // 
                // We don't have to render hollow strokes if the current mode isn't Select mode
                if ( _inkCanvas.ActiveEditingMode == InkCanvasEditingMode.Select )
                {
                    // 
                    // Updating the visuals of the hitted strokes.
                    count = strokes.Count;
                    for ( int i = 0; i < count; i++ )
                    {
                        strokes[i].IsSelected = true;
                    }
                }
 
                // Add a LayoutUpdated handler if it's necessary.
                UpdateCanvasLayoutUpdatedHandler();
 
                // Update our selection adorner manually
                UpdateSelectionAdorner();
 
                ListenToStrokeChanges();
 
                //
                // And finally... raise the SelectionChanged event
                //
                if ( raiseSelectionChanged )
                {
                    _inkCanvas.RaiseSelectionChanged(EventArgs.Empty);
                }
            }
        }
 
        /// <summary>
        /// Our internal method to commit the current editing.
        /// Called by EndFeedbackAdorner or InkCanvas.PasteFromDataObject
        /// </summary>
        internal void CommitChanges(Rect finalRectangle, bool raiseEvent)
        {
            // The follow moving or resizing raises SelectionMove or SelectionResize event
            // The out-side code could throw exception in the their handlers. We use try/finally block to protect our status.
            Rect selectionBounds = SelectionBounds;
 
            // 
            // If the selection is cleared programmatically somehow during user interaction, we will get an empty source rect.
            // We should check if the source still valid here. If not, no selection needs to be changed.
            if ( selectionBounds.IsEmpty )
            {
                return;
            }
 
            try
            {
                QuitListeningToStrokeChanges( );
 
                if ( raiseEvent )
                {
                    //
                    // see if we resized
                    //
                    if ( !DoubleUtil.AreClose(finalRectangle.Height, selectionBounds.Height)
                        || !DoubleUtil.AreClose(finalRectangle.Width, selectionBounds.Width) )
                    {
                        //
                        // we resized
                        //
                        CommitResizeChange(finalRectangle);
                    }
                    else if ( !DoubleUtil.AreClose(finalRectangle.Top, selectionBounds.Top)
                        || !DoubleUtil.AreClose(finalRectangle.Left, selectionBounds.Left) )
                    {
                        //
                        // we moved
                        //
                        CommitMoveChange(finalRectangle);
                    }
                }
                else
                {
                    //
                    // Just perform the move without raising any events.
                    //
                    MoveSelection(selectionBounds, finalRectangle);
                }
            }
            finally
            {
                ListenToStrokeChanges( );
            }
        }
 
        /// <summary>
        /// Try to remove an element from the selected elements list.
        /// Called by:
        ///             OnCanvasLayoutUpdated
        /// </summary>
        /// <param name="removedElement"></param>
        internal void RemoveElement(UIElement removedElement)
        {
            // No selected element. Bail out
            if ( _selectedElements == null || _selectedElements.Count == 0 )
            {
                return;
            }
 
            if ( _selectedElements.Remove(removedElement) )
            {
                // The element existed and was removed successfully.
                // Now if there is no selected element, we should remove our LayoutUpdated handler.
                if ( _selectedElements.Count == 0 )
                {
                    UpdateCanvasLayoutUpdatedHandler();
                    UpdateSelectionAdorner();
                }
            }
        }
 
        /// <summary>
        /// UpdateElementBounds:
        ///     Called by InkCanvasSelection.MoveSelection
        /// </summary>
        /// <param name="element"></param>
        /// <param name="transform"></param>
        internal void UpdateElementBounds(UIElement element, Matrix transform)
        {
            UpdateElementBounds(element, element, transform);
        }
 
        /// <summary>
        /// UpdateElementBounds:
        ///     Called by   InkCanvasSelection.UpdateElementBounds
        ///                 ClipboardProcessor.CopySelectionInXAML
        /// </summary>
        /// <param name="originalElement"></param>
        /// <param name="updatedElement"></param>
        /// <param name="transform"></param>
        internal void UpdateElementBounds(UIElement originalElement, UIElement updatedElement, Matrix transform)
        {
            if ( originalElement.DependencyObjectType.Id == updatedElement.DependencyObjectType.Id )
            {
                // Get the transform from element to Canvas
                GeneralTransform elementToCanvas = originalElement.TransformToAncestor(_inkCanvas.InnerCanvas);
 
                //cast to a FrameworkElement, nothing inherits from UIElement besides it right now
                FrameworkElement frameworkElement = originalElement as FrameworkElement;
                Size size;
                Thickness thickness = new Thickness();
                if ( frameworkElement == null )
                {
                    // Get the element's render size.
                    size = originalElement.RenderSize;
                }
                else
                {
                    size = new Size(frameworkElement.ActualWidth, frameworkElement.ActualHeight);
                    thickness = frameworkElement.Margin;
                }
 
                Rect elementBounds = new Rect(0, 0, size.Width, size.Height);   // Rect in element space
                elementBounds = elementToCanvas.TransformBounds(elementBounds); // Rect in Canvas space
 
                // Now apply the matrix to the element bounds
                Rect newBounds = Rect.Transform(elementBounds, transform);
 
                if ( !DoubleUtil.AreClose(elementBounds.Width, newBounds.Width) )
                {
                    if ( frameworkElement == null )
                    {
                        Size newSize = originalElement.RenderSize;
                        newSize.Width = newBounds.Width;
                        updatedElement.RenderSize = newSize;
                    }
                    else
                    {
                        ((FrameworkElement)updatedElement).Width = newBounds.Width;
                    }
                }
 
                if ( !DoubleUtil.AreClose(elementBounds.Height, newBounds.Height) )
                {
                    if ( frameworkElement == null )
                    {
                        Size newSize = originalElement.RenderSize;
                        newSize.Height = newBounds.Height;
                        updatedElement.RenderSize = newSize;
                    }
                    else
                    {
                        ( (FrameworkElement)updatedElement ).Height = newBounds.Height;
                    }
                }
 
                double left = InkCanvas.GetLeft(originalElement);
                double top = InkCanvas.GetTop(originalElement);
                double right = InkCanvas.GetRight(originalElement);
                double bottom = InkCanvas.GetBottom(originalElement);
 
                Point originalPosition = new Point(); // Default as (0, 0)
                if ( !double.IsNaN(left) )
                {
                    originalPosition.X = left;
                }
                else if ( !double.IsNaN(right) )
                {
                    originalPosition.X = right;
                }
 
                if ( !double.IsNaN(top) )
                {
                    originalPosition.Y = top;
                }
                else if ( !double.IsNaN(bottom) )
                {
                    originalPosition.Y = bottom;
                }
 
                Point newPosition = originalPosition * transform;
 
                if ( !double.IsNaN(left) )
                {
                    InkCanvas.SetLeft(updatedElement, newPosition.X - thickness.Left);           // Left wasn't auto
                }
                else if ( !double.IsNaN(right) )
                {
                    // NOTICE-2005/05/05-WAYNEZEN
                    // Canvas.RightProperty means the distance between element right side and its parent Canvas
                    // right side. The same definition is applied to Canvas.BottomProperty
                    InkCanvas.SetRight(updatedElement, ( right - ( newPosition.X - originalPosition.X ) )); // Right wasn't not auto
                }
                else
                {
                    InkCanvas.SetLeft(updatedElement, newPosition.X - thickness.Left);           // Both Left and Right were aut. Modify Left by default.
                }
 
                if ( !double.IsNaN(top) )
                {
                    InkCanvas.SetTop(updatedElement, newPosition.Y - thickness.Top);           // Top wasn't auto
                }
                else if ( !double.IsNaN(bottom) )
                {
                    InkCanvas.SetBottom(updatedElement, ( bottom - ( newPosition.Y - originalPosition.Y ) ));      // Bottom wasn't not auto
                }
                else
                {
                    InkCanvas.SetTop(updatedElement, newPosition.Y - thickness.Top);           // Both Top and Bottom were aut. Modify Left by default.
                }
            }
            else
            {
                Debug.Assert(false, "The updatedElement has to be the same type as the originalElement.");
            }
        }
 
        internal void TransformStrokes(StrokeCollection strokes, Matrix matrix)
        {
            strokes.Transform(matrix, false /*Don't apply the transform to StylusTip*/);
        }
 
        internal InkCanvasSelectionHitResult HitTestSelection(Point pointOnInkCanvas)
        {
            // If Selection is moving or resizing now, we should returns the active hit result.
            if ( _activeSelectionHitResult.HasValue )
            {
                return _activeSelectionHitResult.Value;
            }
 
            // No selection at all. Just return None.
            if ( !HasSelection )
            {
                return InkCanvasSelectionHitResult.None;
            }
 
            // Get the transform from InkCanvas to SelectionAdorner
            GeneralTransform inkCanvasToSelectionAdorner = _inkCanvas.TransformToDescendant(_inkCanvas.SelectionAdorner);
            Point pointOnSelectionAdorner = inkCanvasToSelectionAdorner.Transform(pointOnInkCanvas);
 
            InkCanvasSelectionHitResult hitResult = _inkCanvas.SelectionAdorner.SelectionHandleHitTest(pointOnSelectionAdorner);
 
            // If the hit test returns Selection and there is one and only one element is selected.
            // We have to check whether we hit on inside the element.
            if ( hitResult == InkCanvasSelectionHitResult.Selection
                && SelectedElements.Count == 1
                && SelectedStrokes.Count == 0 )
            {
                GeneralTransform transformToInnerCanvas = _inkCanvas.TransformToDescendant(_inkCanvas.InnerCanvas);
                Point pointOnInnerCanvas = transformToInnerCanvas.Transform(pointOnInkCanvas);
 
                // Try to find out whether we hit the single selement. If so, just return None.
                if ( HasHitSingleSelectedElement(pointOnInnerCanvas) )
                {
                    hitResult = InkCanvasSelectionHitResult.None;
                }
            }
 
            return hitResult;
        }
 
        /// <summary>
        /// Private helper to used to determine if the current selection
        /// is different than the selection passed
        /// </summary>
        /// <param name="strokes">strokes</param>
        /// <param name="strokesAreDifferent">strokesAreDifferent</param>
        /// <param name="elements">elements</param>
        /// <param name="elementsAreDifferent">elementsAreDifferent</param>
        internal void SelectionIsDifferentThanCurrent(StrokeCollection strokes,
                                                    out bool strokesAreDifferent,
                                                    IList<UIElement> elements,
                                                    out bool elementsAreDifferent)
        {
            strokesAreDifferent = false;
            elementsAreDifferent = false;
 
            if ( SelectedStrokes.Count == 0 )
            {
                if ( strokes.Count > 0 )
                {
                    strokesAreDifferent = true;
                }
            }
            else if ( !InkCanvasSelection.StrokesAreEqual(SelectedStrokes, strokes) )
            {
                strokesAreDifferent = true;
            }
 
            if ( SelectedElements.Count == 0 )
            {
                if ( elements.Count > 0 )
                {
                    elementsAreDifferent = true;
                }
            }
            else if ( !InkCanvasSelection.FrameworkElementArraysAreEqual(elements, SelectedElements) )
            {
                elementsAreDifferent = true;
            }
        }
 
        #endregion Internal Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// HasHitSingleSelectedElement
        /// This method hits test against the current selection. If the point is inside the single selected element,
        /// it will return true. Otherwise, return false.
        /// </summary>
        /// <param name="pointOnInnerCanvas"></param>
        /// <returns></returns>
        private bool HasHitSingleSelectedElement(Point pointOnInnerCanvas)
        {
            bool hasHit = false;
 
            if ( SelectedElements.Count == 1 )
            {
                IEnumerator<Rect> enumerator = SelectedElementsBoundsEnumerator.GetEnumerator();
                if ( enumerator.MoveNext() )
                {
                    Rect elementRect = enumerator.Current;
                    hasHit = elementRect.Contains(pointOnInnerCanvas);
                }
                else
                {
                    Debug.Assert(false, "An unexpected single selected Element");
                }
            }
 
            return hasHit;
        }
 
        /// <summary>
        /// Private helper to stop listening to stroke changes
        /// </summary>
        private void QuitListeningToStrokeChanges()
        {
            if ( _inkCanvas.Strokes != null )
            {
                _inkCanvas.Strokes.StrokesChanged -= new StrokeCollectionChangedEventHandler(this.OnStrokeCollectionChanged);
            }
 
            foreach ( Stroke s in SelectedStrokes )
            {
                s.Invalidated -= new EventHandler(this.OnStrokeInvalidated);
            }
        }
 
        /// <summary>
        /// Private helper to listen to stroke changes
        /// </summary>
        private void ListenToStrokeChanges()
        {
            if ( _inkCanvas.Strokes != null )
            {
                _inkCanvas.Strokes.StrokesChanged += new StrokeCollectionChangedEventHandler(this.OnStrokeCollectionChanged);
            }
 
            foreach ( Stroke s in SelectedStrokes )
            {
                s.Invalidated += new EventHandler(this.OnStrokeInvalidated);
            }
        }
 
        /// <summary>
        /// Helper method that take a finalRectangle and raised the appropriate
        /// events on the InkCanvas.  Handles cancellation
        /// </summary>
        private void CommitMoveChange(Rect finalRectangle)
        {
            Rect selectionBounds = SelectionBounds;
            //
            //*** MOVED ***
            //
            InkCanvasSelectionEditingEventArgs args =
                new InkCanvasSelectionEditingEventArgs(selectionBounds, finalRectangle);
 
            _inkCanvas.RaiseSelectionMoving(args);
 
            if ( !args.Cancel )
            {
                if ( finalRectangle != args.NewRectangle )
                {
                    finalRectangle = args.NewRectangle;
                }
 
                //
                // perform the move
                //
                MoveSelection(selectionBounds, finalRectangle);
 
                //
                // raise the 'changed' event
                //
                _inkCanvas.RaiseSelectionMoved(EventArgs.Empty);
            }
        }
 
        /// <summary>
        /// Helper method that take a finalRectangle and raised the appropriate
        /// events on the InkCanvas.  Handles cancellation
        /// </summary>
        private void CommitResizeChange(Rect finalRectangle)
        {
            Rect selectionBounds = SelectionBounds;
            //
            // *** RESIZED ***
            //
            InkCanvasSelectionEditingEventArgs args =
                new InkCanvasSelectionEditingEventArgs(selectionBounds, finalRectangle);
 
            _inkCanvas.RaiseSelectionResizing(args);
 
            if ( !args.Cancel )
            {
                if ( finalRectangle != args.NewRectangle )
                {
                    finalRectangle = args.NewRectangle;
                }
 
                //
                // perform the move
                //
                MoveSelection(selectionBounds, finalRectangle);
 
                //
                // raise the 'changed' event
                //
                _inkCanvas.RaiseSelectionResized(EventArgs.Empty);
            }
        }
 
        /// <summary>
        /// Private helper that moves all selected elements from the previous location
        /// to the new one
        /// </summary>
        /// <param name="previousRect"></param>
        /// <param name="newRect"></param>
        private void MoveSelection(Rect previousRect, Rect newRect)
        {
            //
            // compute our transform, but first remove our artificial border
            //
            Matrix matrix = InkCanvasSelection.MapRectToRect(newRect, previousRect);
 
            //
            // transform the elements
            //
            int count = SelectedElements.Count;
            IList<UIElement> elements = SelectedElements;
            for ( int i = 0; i < count ; i++ )
            {
                UpdateElementBounds(elements[i], matrix);
            }
 
            //
            // transform the strokes
            //
            if ( SelectedStrokes.Count > 0 )
            {
                TransformStrokes(SelectedStrokes, matrix);
                // Flag the change
                _areStrokesChanged = true;
            }
 
            if ( SelectedElements.Count == 0 )
            {
                // If there is no element in the current selection, the inner canvas won't have layout changes.
                // So there is no LayoutUpdated event. We have to manually update the selection bounds.
                UpdateSelectionAdorner();
            }
 
            // 
            // Always bring the new selection rectangle into the current view when the InkCanvas is hosted inside a ScrollViewer.
            _inkCanvas.BringIntoView(newRect);
        }
 
        /// <summary>
        /// A handler which receives LayoutUpdated from the layout manager. Since the selected elements can be animated,
        /// moved, resized or even removed programmatically from the InkCanvas, we have to monitor
        /// the any layout changes caused by those actions.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnCanvasLayoutUpdated(object sender, EventArgs e)
        {
            Debug.Assert( SelectedElements.Count != 0, 
                "The handler only can be hooked up when there are elements being selected" );
 
            // Update the selection adorner when receive the layout changed
            UpdateSelectionAdorner();
        }
 
        /// <summary>
        /// Our own internal listener for packet changes
        /// </summary>
        private void OnStrokeInvalidated(object sender, EventArgs e)
        {
            OnStrokeCollectionChanged(sender, 
                new StrokeCollectionChangedEventArgs(new StrokeCollection(), new StrokeCollection()));
        }
 
        /// <summary>
        /// Our own internal listener for strokes changed.
        /// This is used so that if someone deletes or modifies a stroke
        /// we are currently displaying for selection, we can update our size
        /// </summary>
        private void OnStrokeCollectionChanged(object target, StrokeCollectionChangedEventArgs e)
        {
            // If the strokes only get added to the InkCanvas, we don't have to update our internal selected strokes.
            if ( e.Added.Count != 0 && e.Removed.Count == 0 )
            {
                return;
            }
 
            foreach (Stroke s in e.Removed)
            {
                if ( SelectedStrokes.Contains(s) )
                {
                    s.Invalidated -= new EventHandler(this.OnStrokeInvalidated);
                    s.IsSelected = false;
 
                    // Now remove the stroke from our private collection.
                    SelectedStrokes.Remove(s);
                }
            }
 
            // Mark the strokes change
            _areStrokesChanged = true;
            UpdateSelectionAdorner();
        }
 
        /// <summary>
        /// Get the cached bounding boxes to be updated.
        /// </summary>
        private Rect GetStrokesBounds()
        {
            // Update the strokes bounds if they are changed.
            if ( _areStrokesChanged )
            {
                _cachedStrokesBounds = SelectedStrokes.Count != 0 ? 
                                        SelectedStrokes.GetBounds( ) : Rect.Empty;
                _areStrokesChanged = false;
            }
 
            return _cachedStrokesBounds;
        }
 
        /// <summary>
        /// Get the bounding boxes of the selected elements.
        /// </summary>
        /// <returns></returns>
        private List<Rect> GetElementsBounds()
        {
            List<Rect> rects = new List<Rect>();
 
            if ( SelectedElements.Count != 0 )
            {
                // The private SelectedElementsBounds property will ensure the size is got after rendering's done.
                foreach ( Rect elementRect in SelectedElementsBoundsEnumerator )
                {
                    rects.Add(elementRect);
                }
            }
 
            return rects;
        }
 
        /// <summary>
        /// Get the union box of the selected elements.
        /// </summary>
        /// <returns></returns>
        private Rect GetElementsUnionBounds()
        {
            if ( SelectedElements.Count == 0 )
            {
                return Rect.Empty;
            }
 
            Rect elementsBounds = Rect.Empty;
 
            // The private SelectedElementsBounds property will ensure the size is got after rendering's done.
            foreach ( Rect elementRect in SelectedElementsBoundsEnumerator )
            {
                elementsBounds.Union(elementRect);
            }
 
            return elementsBounds;
        }
 
        /// <summary>
        /// Update the selection adorners state.
        /// The method is called by:
        ///         MoveSelection
        ///         OnCanvasLayoutUpdated
        ///         OnStrokeCollectionChanged
        ///         RemoveElement
        ///         Select
        /// </summary>
        private void UpdateSelectionAdorner()
        {
            // The Selection Adorner will be visible all the time unless the ActiveEditingMode is None.
            if ( _inkCanvas.ActiveEditingMode != InkCanvasEditingMode.None )
            {
                Debug.Assert(_inkCanvas.SelectionAdorner.Visibility == Visibility.Visible,
                    "SelectionAdorner should be always visible except under the None ActiveEditingMode");
 
                _inkCanvas.SelectionAdorner.UpdateSelectionWireFrame(GetStrokesBounds(), GetElementsBounds());
            }
            else
            {
                Debug.Assert(_inkCanvas.SelectionAdorner.Visibility == Visibility.Collapsed,
                    "SelectionAdorner should be collapsed except if the ActiveEditingMode is None");
            }
}
 
        /// <summary>
        /// Ensure the rendering is valid.
        /// Called by:
        ///         SelectedElementsBounds
        /// </summary>
        private void EnusreElementsBounds()
        {
            InkCanvasInnerCanvas innerCanvas = _inkCanvas.InnerCanvas;
 
            // Make sure the arrange is valid. If not, we invoke UpdateLayout now.
            if ( !innerCanvas.IsMeasureValid || !innerCanvas.IsArrangeValid )
            {
                innerCanvas.UpdateLayout();
            }
        }
 
        /// <summary>
        /// Utility for computing a transformation that maps one rect to another
        /// </summary>
        /// <param name="target">Destination Rectangle</param>
        /// <param name="source">Source Rectangle</param>
        /// <returns>Transform that maps source rectangle to destination rectangle</returns>
        private static Matrix MapRectToRect(Rect target, Rect source)
        {
            if(source.IsEmpty)
                throw new ArgumentOutOfRangeException("source", SR.InvalidDiameter);
            /*
            In the horizontal direction:
 
                    M11*source.left  + Dx = target.left
                    M11*source.right + Dx = target.right
 
            Subtracting the equations yields:
 
                    M11*(source.right - source.left) = target.right - target.left
            hence:
                    M11 = (target.right - target.left) / (source.right - source.left)
 
            Having computed M11, solve the first equation for Dx:
 
                    Dx = target.left - M11*source.left
            */
            double m11 = target.Width / source.Width;
            double dx = target.Left - m11 * source.Left;
 
            // Now do the same thing in the vertical direction
            double m22 = target.Height / source.Height;
            double dy = target.Top - m22 * source.Top;
            /*
            We are looking for the transformation that takes one upright rectangle to
            another.  This involves neither rotation nor shear, so:
            */
            return new Matrix(m11, 0, 0, m22, dx, dy);
        }
 
        /// <summary>
        /// Adds/Removes the LayoutUpdated handler according to whether there is selected elements or not.
        /// </summary>
        private void UpdateCanvasLayoutUpdatedHandler()
        {
            // If there are selected elements, we should create a LayoutUpdated handler in order to monitor their arrange changes.
            // Otherwise, we don't have to listen the LayoutUpdated if there is no selected element at all.
            if ( SelectedElements.Count != 0 )
            {
                // Make sure we hook up the event handler.
                if ( _layoutUpdatedHandler == null )
                {
                    _layoutUpdatedHandler = new EventHandler(OnCanvasLayoutUpdated);
                    _inkCanvas.InnerCanvas.LayoutUpdated += _layoutUpdatedHandler;
                }
            }
            else
            {
                // Remove the handler if there is one.
                if ( _layoutUpdatedHandler != null )
                {
                    _inkCanvas.InnerCanvas.LayoutUpdated -= _layoutUpdatedHandler;
                    _layoutUpdatedHandler = null;
                }
            }
        }
 
        /// <summary>
        /// Private helper method used to determine if two stroke collection either
        /// 1) are both null
        /// or
        /// 2) both refer to the identical set of strokes
        /// </summary>
        private static bool StrokesAreEqual(StrokeCollection strokes1, StrokeCollection strokes2)
        {
            if ( strokes1 == null && strokes2 == null )
            {
                return true;
            }
            //
            // we know that one of the stroke collections is
            // not null.  If the other one is, they're not
            // equal
            //
            if ( strokes1 == null || strokes2 == null )
            {
                return false;
            }
            if ( strokes1.Count != strokes2.Count )
            {
                return false;
            }
            foreach ( Stroke s in strokes1 )
            {
                if ( !strokes2.Contains(s) )
                {
                    return false;
                }
            }
            return true;
        }
 
        /// <summary>
        /// Private helper method used to determine if two collection either
        /// 1) are both null
        /// or
        /// 2) both refer to the identical set of elements
        /// </summary>
        private static bool FrameworkElementArraysAreEqual(IList<UIElement> elements1, IList<UIElement> elements2)
        {
            if ( elements1 == null && elements2 == null )
            {
                return true;
            }
            //
            // we know that one of the stroke collections is
            // not null.  If the other one is, they're not
            // equal
            //
            if ( elements1 == null || elements2 == null )
            {
                return false;
            }
            if ( elements1.Count != elements2.Count )
            {
                return false;
            }
            foreach ( UIElement e in elements1 )
            {
                if ( !elements2.Contains(e) )
                {
                    return false;
                }
            }
            return true;
        }
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Private Properties
 
        /// <summary>
        /// Iterates the bounding boxes of the selected elements relevent to their parent (InnerCanvas)   
        /// </summary>
        private IEnumerable<Rect> SelectedElementsBoundsEnumerator
        {
            get
            {
                // Ensure the elements have been rendered completely.
                EnusreElementsBounds();
 
                InkCanvasInnerCanvas innerCanvas = _inkCanvas.InnerCanvas;
                foreach ( UIElement element in SelectedElements )
                {
                    // Get the transform from element to Canvas
                    GeneralTransform elementToCanvas = element.TransformToAncestor(innerCanvas);
 
                    // Get the element's render size.
                    Size size = element.RenderSize;
 
                    Rect rect = new Rect(0, 0, size.Width, size.Height);   // Rect in element space
                    rect = elementToCanvas.TransformBounds(rect);          // Rect in Canvas space
 
                    yield return rect;
                }
            }
        }
 
        #endregion Private Properties
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        /// Our logical parent.
        /// </summary>
        private InkCanvas                   _inkCanvas;
 
        /// <summary>
        /// The collection of strokes this selectedinkelement represents
        /// </summary>
        private StrokeCollection            _selectedStrokes;
        /// <summary>
        /// The last known rectangle before the current edit.  used to revert
        /// when events are cancelled and expose the value to the InkCanvas.GetSelectionBounds
        /// </summary>
        private Rect                        _cachedStrokesBounds;
        private bool                        _areStrokesChanged;
 
        /// <summary>
        /// The collection of elements this selectedinkelement represents
        /// </summary>
        private List<UIElement>             _selectedElements;
        private EventHandler                _layoutUpdatedHandler;
 
        private Nullable<InkCanvasSelectionHitResult> _activeSelectionHitResult;
 
        #endregion Private Fields
    }
}