LassoSelectionBehavior.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 / Framework / MS / Internal / Ink / LassoSelectionBehavior.cs / 1305600 / LassoSelectionBehavior.cs

                            //#define DEBUG_LASSO_FEEDBACK // DO NOT LEAVE ENABLED IN CHECKED IN CODE 
//----------------------------------------------------------------------------
//
// File: LassoSelectionBehavior.cs
// 
// Description:
//      LassoSelectionBehavior 
// 
// Authors: samgeo
// 
// Copyright (C) 2003 by Microsoft Corporation.  All rights reserved.
//
//---------------------------------------------------------------------------
 
using MS.Internal.Controls;
using System; 
using System.ComponentModel; 
using System.ComponentModel.Design;
using System.Collections; 
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows; 
using System.Windows.Controls;
using System.Windows.Controls.Primitives; 
using System.Windows.Data; 
using System.Windows.Ink;
using System.Windows.Interop; 
using System.Windows.Navigation;
using System.Windows.Media;

namespace MS.Internal.Ink 
{
    ///  
    /// Eraser Behavior 
    /// 
    internal sealed class LassoSelectionBehavior : StylusEditingBehavior 
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors 
        //
        //------------------------------------------------------------------------------- 
 
        #region Constructors
 
        internal LassoSelectionBehavior(EditingCoordinator editingCoordinator, InkCanvas inkCanvas)
            : base(editingCoordinator, inkCanvas)
        {
        } 

        #endregion Constructors 
 
        //--------------------------------------------------------------------------------
        // 
        // Protected Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Protected Methods
 
        ///  
        /// Overrides SwitchToMode as the following expectations
        /// 31. From Select To InkAndGesture 
        ///     Lasso is discarded. After mode change ink is being collected, gesture event fires. If its not a gesture, StrokeCollected event fires.
        /// 32. From Select To GestureOnly
        ///     Lasso is discarded. After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if its not a gesture.
        /// 33. From Select To EraseByPoint 
        ///     Lasso is discarded. PointErasing is performed after changing the mode.
        /// 34. From Select To EraseByStroke 
        ///     Lasso is discarded. StrokeErasing is performed after changing the mode. 
        /// 35. From Select To Ink
        ///     Ink collection starts when changing the mode. 
        /// 36. From Select To None
        ///     Nothing gets selected.
        /// 
        ///  
        protected override void OnSwitchToMode(InkCanvasEditingMode mode)
        { 
            Debug.Assert(EditingCoordinator.IsInMidStroke, "SwitchToMode should only be called in a mid-stroke"); 

            switch ( mode ) 
            {
                case InkCanvasEditingMode.Ink:
                case InkCanvasEditingMode.InkAndGesture:
                case InkCanvasEditingMode.GestureOnly: 
                    {
                        // Discard the lasso 
                        Commit(false); 

                        // Change the mode. The dynamic renderer will be reset automatically. 
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break;
                    }
                case InkCanvasEditingMode.EraseByPoint: 
                case InkCanvasEditingMode.EraseByStroke:
                    { 
                        // Discard the lasso 
                        Commit(false);
 
                        // Change the mode
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);

                        break; 
                    }
                case InkCanvasEditingMode.Select: 
                    { 
                        Debug.Assert(false, "Cannot switch from Select to Select in mid-stroke");
                        break; 
                    }
                case InkCanvasEditingMode.None:
                    {
                        // Discard the lasso. 
                        Commit(false);
 
                        // Change to the None mode 
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break; 
                    }
                default:
                    Debug.Assert(false, "Unknown InkCanvasEditingMode!");
                    break; 
            }
        } 
 
        /// 
        /// StylusInputBegin 
        /// 
        /// stylusPoints
        /// true if the source eventArgs.UserInitiated flag was set to true
        protected override void StylusInputBegin(StylusPointCollection stylusPoints, bool userInitiated) 
        {
            Debug.Assert(stylusPoints.Count != 0, "An empty stylusPoints has been passed in."); 
 
            _disableLasso = false;
 
            bool startLasso = false;

            List points = new List();
            for ( int x = 0; x < stylusPoints.Count; x++ ) 
            {
                Point point = (Point)( stylusPoints[x] ); 
                if ( x == 0 ) 
                {
                    _startPoint = point; 
                    points.Add(point);
                    continue;
                }
 
                if ( !startLasso )
                { 
                    // If startLasso hasn't be flagged, we should check if the distance between two points is greater than 
                    // our tolerance. If so, we should flag startLasso.
                    Vector vector = point - _startPoint; 
                    double distanceSquared = vector.LengthSquared;

                    if ( DoubleUtil.GreaterThan(distanceSquared, LassoHelper.MinDistanceSquared) )
                    { 
                        points.Add(point);
                        startLasso = true; 
                    } 
                }
                else 
                {
                    // The flag is set. We just add the point.
                    points.Add(point);
                } 
            }
 
            // Start Lasso if it isn't a tap selection. 
            if ( startLasso )
            { 
                StartLasso(points);
            }
        }
 
        /// 
        /// StylusInputContinue 
        ///  
        /// stylusPoints
        /// true if the source eventArgs.UserInitiated flag was set to true 
        protected override void StylusInputContinue(StylusPointCollection stylusPoints, bool userInitiated)
        {
            // Check whether Lasso has started.
            if ( _lassoHelper != null ) 
            {
                // 
                // pump packets to the LassoHelper, it will convert them into an array of equidistant 
                // lasso points, render those lasso point and return them to hit test against.
                // 
                List points = new List();
                for ( int x = 0; x < stylusPoints.Count; x++ )
                {
                    points.Add((Point)stylusPoints[x]); 
                }
                Point[] lassoPoints = _lassoHelper.AddPoints(points); 
                if ( 0 != lassoPoints.Length ) 
                {
                    _incrementalLassoHitTester.AddPoints(lassoPoints); 
                }
            }
            else if ( !_disableLasso )
            { 
                // If Lasso hasn't start and been disabled, we should try to start it when it is needed.
                bool startLasso = false; 
 
                List points = new List();
                for ( int x = 0; x < stylusPoints.Count; x++ ) 
                {
                    Point point = (Point)( stylusPoints[x] );

                    if ( !startLasso ) 
                    {
                        // If startLasso hasn't be flagged, we should check if the distance between two points is greater than 
                        // our tolerance. If so, we should flag startLasso. 
                        Vector vector = point - _startPoint;
                        double distanceSquared = vector.LengthSquared; 

                        if ( DoubleUtil.GreaterThan(distanceSquared, LassoHelper.MinDistanceSquared) )
                        {
                            points.Add(point); 
                            startLasso = true;
                        } 
                    } 
                    else
                    { 
                        // The flag is set. We just add the point.
                        points.Add(point);
                    }
                } 

                // Start Lasso if it isn't a tap selection. 
                if ( startLasso ) 
                {
                    StartLasso(points); 
                }
            }
        }
 

        ///  
        /// StylusInputEnd 
        /// 
        /// commit 
        protected override void StylusInputEnd(bool commit)
        {
            // Initialize with empty selection
            StrokeCollection selectedStrokes = new StrokeCollection(); 
            List elementsToSelect = new List();
 
            if ( _lassoHelper != null ) 
            {
                // This is a lasso selection. 

                //
                // end dynamic rendering
                // 
                selectedStrokes = InkCanvas.EndDynamicSelection(_lassoHelper.Visual);
 
                // 
                // hit test for elements
                // 
                // NOTE: HitTestForElements uses the _lassoHelper so it must be alive at this point
                elementsToSelect = HitTestForElements();

                _incrementalLassoHitTester.SelectionChanged -= new LassoSelectionChangedEventHandler(OnSelectionChanged); 
                _incrementalLassoHitTester.EndHitTesting();
                _incrementalLassoHitTester = null; 
                _lassoHelper = null; 
            }
            else 
            {
                // This is a tap selection.

                // Now try the tap selection 
                Stroke tappedStroke;
                UIElement tappedElement; 
 
                TapSelectObject(_startPoint, out tappedStroke, out tappedElement);
 
                // If we have a pre-selected object, we should select it now.
                if ( tappedStroke != null )
                {
                    Debug.Assert(tappedElement == null); 
                    selectedStrokes = new StrokeCollection();
                    selectedStrokes.Add(tappedStroke); 
                } 
                else if ( tappedElement != null )
                { 
                    Debug.Assert(tappedStroke == null);
                    elementsToSelect.Add(tappedElement);
                }
            } 

            SelfDeactivate(); 
 
            if ( commit )
            { 
                InkCanvas.ChangeInkCanvasSelection(selectedStrokes, elementsToSelect.ToArray());
            }
        }
 
        /// 
        /// OnCommitWithoutStylusInput 
        ///  
        /// 
        protected override void OnCommitWithoutStylusInput(bool commit) 
        {
            // We only deactivate LSB in this case.
            SelfDeactivate();
        } 

        ///  
        /// Get the current cursor for this editing mode 
        /// 
        ///  
        protected override Cursor GetCurrentCursor()
        {
            // By default return cross cursor.
            return Cursors.Cross; 
        }
 
        #endregion Protected Methods 

        //-------------------------------------------------------------------------------- 
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------- 

        #region Private Methods 
 
        /// 
        /// Private event handler that updates which strokes are actually selected 
        /// 
        /// 
        /// 
        private void OnSelectionChanged(object sender, LassoSelectionChangedEventArgs e) 
        {
            this.InkCanvas.UpdateDynamicSelection(e.SelectedStrokes, e.DeselectedStrokes); 
        } 

 
        /// 
        /// Private helper that will hit test for elements
        /// 
        private List HitTestForElements() 
        {
            List elementsToSelect = new List(); 
 
            if ( this.InkCanvas.Children.Count == 0 )
            { 
                return elementsToSelect;
            }

            for (int x = 0; x < this.InkCanvas.Children.Count; x++) 
            {
                UIElement uiElement = this.InkCanvas.Children[x]; 
                HitTestElement(InkCanvas.InnerCanvas, uiElement, elementsToSelect); 
            }
 
            return elementsToSelect;
        }

        ///  
        /// Private helper that will turn an element in any nesting level into a stroke
        /// in the InkCanvas's coordinate space.  This method calls itself recursively 
        ///  
        private void HitTestElement(InkCanvasInnerCanvas parent, UIElement uiElement, List elementsToSelect)
        { 
            ElementCornerPoints elementPoints = LassoSelectionBehavior.GetTransformedElementCornerPoints(parent, uiElement);
            if (elementPoints.Set != false)
            {
                Point[] points = GeneratePointGrid(elementPoints); 

                // 
                // perform hit testing against our lasso 
                //
                System.Diagnostics.Debug.Assert(null != _lassoHelper); 
                if (_lassoHelper.ArePointsInLasso(points, _percentIntersectForElements))
                {
                    elementsToSelect.Add(uiElement);
                } 
            }
            // 
            // we used to recurse into the childrens children.  That is no longer necessary 
            //
        } 

        /// 
        /// Private helper that takes an element and transforms it's 4 points
        /// into the InkCanvas's space 
        /// 
        private static ElementCornerPoints GetTransformedElementCornerPoints(InkCanvasInnerCanvas canvas, UIElement childElement) 
        { 
            Debug.Assert(canvas != null);
            Debug.Assert(childElement != null); 

            Debug.Assert(canvas.CheckAccess());

            ElementCornerPoints elementPoints = new ElementCornerPoints(); 
            elementPoints.Set = false;
 
            if (childElement.Visibility != Visibility.Visible) 
            {
                // 
                // this little one's not worth it...
                //
                return elementPoints;
            } 

            // 
            // get the transform from us to our parent InkCavas 
            //
            GeneralTransform parentTransform = childElement.TransformToAncestor(canvas); 

            //

            parentTransform.TryTransform(new Point(0, 0), out elementPoints.UpperLeft); 
            parentTransform.TryTransform(new Point(childElement.RenderSize.Width, 0), out elementPoints.UpperRight);
            parentTransform.TryTransform(new Point(0, childElement.RenderSize.Height), out elementPoints.LowerLeft); 
            parentTransform.TryTransform(new Point(childElement.RenderSize.Width, childElement.RenderSize.Height), out elementPoints.LowerRight); 

            elementPoints.Set = true; 
            return elementPoints;
        }

        ///  
        /// Private helper that will generate a grid of points 5 px apart given the elements bounding points
        /// this works with any affline transformed points 
        ///  
        private Point[] GeneratePointGrid(ElementCornerPoints elementPoints)
        { 
            if (!elementPoints.Set)
            {
                return new Point[]{};
            } 
            ArrayList pointArray = new ArrayList();
 
            UpdatePointDistances(elementPoints); 

            // 
            // add our original points
            //
            pointArray.Add(elementPoints.UpperLeft);
            pointArray.Add(elementPoints.UpperRight); 
            FillInPoints(pointArray, elementPoints.UpperLeft, elementPoints.UpperRight);
 
            pointArray.Add(elementPoints.LowerLeft); 
            pointArray.Add(elementPoints.LowerRight);
            FillInPoints(pointArray, elementPoints.LowerLeft, elementPoints.LowerRight); 

            FillInGrid( pointArray,
                        elementPoints.UpperLeft,
                        elementPoints.UpperRight, 
                        elementPoints.LowerRight,
                        elementPoints.LowerLeft); 
 
            Point[] retPointArray = new Point[pointArray.Count];
            pointArray.CopyTo(retPointArray); 
            return retPointArray;
        }

        ///  
        /// Private helper that fills in the points between two points by calling itself
        /// recursively in a divide and conquer fashion 
        ///  
        private void FillInPoints(ArrayList pointArray, Point point1, Point point2)
        { 
            // this algorithm improves perf by 20%
            if(!PointsAreCloseEnough(point1, point2))
            {
                Point midPoint = LassoSelectionBehavior.GeneratePointBetweenPoints(point1, point2); 
                pointArray.Add(midPoint);
 
                if(!PointsAreCloseEnough(point1, midPoint)) 
                {
                    FillInPoints(pointArray, point1, midPoint); 
                }

                //sort the right
                if(!PointsAreCloseEnough(midPoint, point2)) 
                {
                    FillInPoints(pointArray, midPoint, point2); 
                } 
            }
        } 

        /// 
        /// Private helper that fills in the points between four points by calling itself
        /// recursively in a divide and conquer fashion 
        /// 
        private void FillInGrid(ArrayList pointArray, 
                                Point upperLeft, 
                                Point upperRight,
                                Point lowerRight, 
                                Point lowerLeft)
        {
            // this algorithm improves perf by 20%
            if(!PointsAreCloseEnough(upperLeft, lowerLeft)) 
            {
                Point midPointLeft = LassoSelectionBehavior.GeneratePointBetweenPoints(upperLeft, lowerLeft); 
                Point midPointRight = LassoSelectionBehavior.GeneratePointBetweenPoints(upperRight, lowerRight); 
                pointArray.Add(midPointLeft);
                pointArray.Add(midPointRight); 
                FillInPoints(pointArray, midPointLeft, midPointRight);

                if(!PointsAreCloseEnough(upperLeft, midPointLeft))
                { 
                    FillInGrid(pointArray, upperLeft, upperRight, midPointRight, midPointLeft);
                } 
 
                //sort the right
                if(!PointsAreCloseEnough(midPointLeft, lowerLeft)) 
                {
                    FillInGrid(pointArray, midPointLeft, midPointRight, lowerRight, lowerLeft);
                }
            } 
        }
 
        ///  
        /// Private helper that will generate a new point between two points
        ///  
        private static Point GeneratePointBetweenPoints(Point point1, Point point2)
        {
            //
            // compute the new point in the middle of the previous two 
            //
            double maxX = point1.X > point2.X ? point1.X : point2.X; 
            double minX = point1.X < point2.X ? point1.X : point2.X; 
            double maxY = point1.Y > point2.Y ? point1.Y : point2.Y;
            double minY = point1.Y < point2.Y ? point1.Y : point2.Y; 

            return new Point( (minX + ((maxX - minX) * 0.5f)),
                                (minY + ((maxY - minY) * 0.5f)));
        } 

        ///  
        /// Private helper used to determine if we're close enough between two points 
        /// 
        private bool PointsAreCloseEnough(Point point1, Point point2) 
        {
            double x = point1.X - point2.X;
            double y = point1.Y - point2.Y;
            if ((x < _xDiff && x > -_xDiff) && (y < _yDiff && y > -_yDiff)) 
            {
                return true; 
            } 
            return false;
        } 

        /// 
        /// Used to calc the diff between points on the x and y axis
        ///  
        private void UpdatePointDistances(ElementCornerPoints elementPoints)
        { 
            // 
            // calc the x and y diffs
            // 
            double width = elementPoints.UpperLeft.X - elementPoints.UpperRight.X;
            if (width < 0)
            {
                width = -width; 
            }
 
            double height = elementPoints.UpperLeft.Y - elementPoints.LowerLeft.Y; 
            if (height < 0)
            { 
                height = -height;
            }

            _xDiff = width * 0.25f; 
            if (_xDiff > _maxThreshold)
            { 
                _xDiff = _maxThreshold; 
            }
            else if (_xDiff < _minThreshold) 
            {
                _xDiff = _minThreshold;
            }
 
            _yDiff = height * 0.25f;
            if (_yDiff > _maxThreshold) 
            { 
                _yDiff = _maxThreshold;
            } 
            else if (_yDiff < _minThreshold)
            {
                _yDiff = _minThreshold;
            } 
        }
 
        ///  
        /// StartLasso
        ///  
        /// 
        private void StartLasso(List points)
        {
            Debug.Assert(!_disableLasso && _lassoHelper == null, "StartLasso is called unexpectedly."); 

            if ( InkCanvas.ClearSelectionRaiseSelectionChanging() // If user cancels clearing the selection, we shouldn't initiate Lasso. 
                // NTRAID:WINDOWS#1534264-2006/02/28-WAYNEZEN 
                // If the active editng mode is no longer as Select, we shouldn't activate LassoSelectionBehavior.
                // Note the order really matters here. This checking has to be done 
                // after ClearSelectionRaiseSelectionChanging is invoked.
                && EditingCoordinator.ActiveEditingMode == InkCanvasEditingMode.Select )
            {
 
                //
                // obtain a dynamic hit-tester for selecting with lasso 
                // 
                _incrementalLassoHitTester =
                                    this.InkCanvas.Strokes.GetIncrementalLassoHitTester(_percentIntersectForInk); 
                //
                // add event handler
                //
                _incrementalLassoHitTester.SelectionChanged += new LassoSelectionChangedEventHandler(OnSelectionChanged); 

                // 
                // start dynamic rendering 
                //
 
                _lassoHelper = new LassoHelper();
                InkCanvas.BeginDynamicSelection(_lassoHelper.Visual);

                Point[] lassoPoints = _lassoHelper.AddPoints(points); 
                if ( 0 != lassoPoints.Length )
                { 
                    _incrementalLassoHitTester.AddPoints(lassoPoints); 
                }
            } 
            else
            {
                // If we fail on clearing the selection or switching to Select mode, we should just disable lasso.
                _disableLasso = true; 
            }
        } 
 
        /// 
        /// Pre-Select the object which user taps on. 
        /// 
        /// 
        /// 
        ///  
        private void TapSelectObject(Point point, out Stroke tappedStroke, out UIElement tappedElement)
        { 
            tappedStroke = null; 
            tappedElement = null;
 
            StrokeCollection hitTestStrokes = InkCanvas.Strokes.HitTest(point, 5.0d);
            if ( hitTestStrokes.Count > 0 )
            {
                tappedStroke = hitTestStrokes[hitTestStrokes.Count - 1]; 
            }
            else 
            { 
                GeneralTransform transformToInnerCanvas = InkCanvas.TransformToVisual(InkCanvas.InnerCanvas);
                Point pointOnInnerCanvas = transformToInnerCanvas.Transform(point); 

                // Try to find out whether we have a pre-select object.
                tappedElement = InkCanvas.InnerCanvas.HitTestOnElements(pointOnInnerCanvas);
            } 

        } 
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        // 
        //--------------------------------------------------------------------------------
 
        #region Private Fields 

        private Point                       _startPoint; 
        private bool                        _disableLasso;

        private LassoHelper                 _lassoHelper = null;
        private IncrementalLassoHitTester   _incrementalLassoHitTester; 
        private double                  _xDiff;
        private double                  _yDiff; 
        private const double            _maxThreshold = 50f; 
        private const double            _minThreshold = 15f;
        private const int               _percentIntersectForInk = 80; 
        private const int               _percentIntersectForElements = 60;


#if DEBUG_LASSO_FEEDBACK 
        private ContainerVisual _tempVisual;
#endif 
        ///  
        /// Private struct
        ///  
        private struct ElementCornerPoints
        {
            internal Point UpperLeft;
            internal Point UpperRight; 
            internal Point LowerRight;
            internal Point LowerLeft; 
            internal bool Set; 
        }
 
        #endregion Private Fields

    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//#define DEBUG_LASSO_FEEDBACK // DO NOT LEAVE ENABLED IN CHECKED IN CODE 
//----------------------------------------------------------------------------
//
// File: LassoSelectionBehavior.cs
// 
// Description:
//      LassoSelectionBehavior 
// 
// Authors: samgeo
// 
// Copyright (C) 2003 by Microsoft Corporation.  All rights reserved.
//
//---------------------------------------------------------------------------
 
using MS.Internal.Controls;
using System; 
using System.ComponentModel; 
using System.ComponentModel.Design;
using System.Collections; 
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows; 
using System.Windows.Controls;
using System.Windows.Controls.Primitives; 
using System.Windows.Data; 
using System.Windows.Ink;
using System.Windows.Interop; 
using System.Windows.Navigation;
using System.Windows.Media;

namespace MS.Internal.Ink 
{
    ///  
    /// Eraser Behavior 
    /// 
    internal sealed class LassoSelectionBehavior : StylusEditingBehavior 
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors 
        //
        //------------------------------------------------------------------------------- 
 
        #region Constructors
 
        internal LassoSelectionBehavior(EditingCoordinator editingCoordinator, InkCanvas inkCanvas)
            : base(editingCoordinator, inkCanvas)
        {
        } 

        #endregion Constructors 
 
        //--------------------------------------------------------------------------------
        // 
        // Protected Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Protected Methods
 
        ///  
        /// Overrides SwitchToMode as the following expectations
        /// 31. From Select To InkAndGesture 
        ///     Lasso is discarded. After mode change ink is being collected, gesture event fires. If its not a gesture, StrokeCollected event fires.
        /// 32. From Select To GestureOnly
        ///     Lasso is discarded. After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if its not a gesture.
        /// 33. From Select To EraseByPoint 
        ///     Lasso is discarded. PointErasing is performed after changing the mode.
        /// 34. From Select To EraseByStroke 
        ///     Lasso is discarded. StrokeErasing is performed after changing the mode. 
        /// 35. From Select To Ink
        ///     Ink collection starts when changing the mode. 
        /// 36. From Select To None
        ///     Nothing gets selected.
        /// 
        ///  
        protected override void OnSwitchToMode(InkCanvasEditingMode mode)
        { 
            Debug.Assert(EditingCoordinator.IsInMidStroke, "SwitchToMode should only be called in a mid-stroke"); 

            switch ( mode ) 
            {
                case InkCanvasEditingMode.Ink:
                case InkCanvasEditingMode.InkAndGesture:
                case InkCanvasEditingMode.GestureOnly: 
                    {
                        // Discard the lasso 
                        Commit(false); 

                        // Change the mode. The dynamic renderer will be reset automatically. 
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break;
                    }
                case InkCanvasEditingMode.EraseByPoint: 
                case InkCanvasEditingMode.EraseByStroke:
                    { 
                        // Discard the lasso 
                        Commit(false);
 
                        // Change the mode
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);

                        break; 
                    }
                case InkCanvasEditingMode.Select: 
                    { 
                        Debug.Assert(false, "Cannot switch from Select to Select in mid-stroke");
                        break; 
                    }
                case InkCanvasEditingMode.None:
                    {
                        // Discard the lasso. 
                        Commit(false);
 
                        // Change to the None mode 
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break; 
                    }
                default:
                    Debug.Assert(false, "Unknown InkCanvasEditingMode!");
                    break; 
            }
        } 
 
        /// 
        /// StylusInputBegin 
        /// 
        /// stylusPoints
        /// true if the source eventArgs.UserInitiated flag was set to true
        protected override void StylusInputBegin(StylusPointCollection stylusPoints, bool userInitiated) 
        {
            Debug.Assert(stylusPoints.Count != 0, "An empty stylusPoints has been passed in."); 
 
            _disableLasso = false;
 
            bool startLasso = false;

            List points = new List();
            for ( int x = 0; x < stylusPoints.Count; x++ ) 
            {
                Point point = (Point)( stylusPoints[x] ); 
                if ( x == 0 ) 
                {
                    _startPoint = point; 
                    points.Add(point);
                    continue;
                }
 
                if ( !startLasso )
                { 
                    // If startLasso hasn't be flagged, we should check if the distance between two points is greater than 
                    // our tolerance. If so, we should flag startLasso.
                    Vector vector = point - _startPoint; 
                    double distanceSquared = vector.LengthSquared;

                    if ( DoubleUtil.GreaterThan(distanceSquared, LassoHelper.MinDistanceSquared) )
                    { 
                        points.Add(point);
                        startLasso = true; 
                    } 
                }
                else 
                {
                    // The flag is set. We just add the point.
                    points.Add(point);
                } 
            }
 
            // Start Lasso if it isn't a tap selection. 
            if ( startLasso )
            { 
                StartLasso(points);
            }
        }
 
        /// 
        /// StylusInputContinue 
        ///  
        /// stylusPoints
        /// true if the source eventArgs.UserInitiated flag was set to true 
        protected override void StylusInputContinue(StylusPointCollection stylusPoints, bool userInitiated)
        {
            // Check whether Lasso has started.
            if ( _lassoHelper != null ) 
            {
                // 
                // pump packets to the LassoHelper, it will convert them into an array of equidistant 
                // lasso points, render those lasso point and return them to hit test against.
                // 
                List points = new List();
                for ( int x = 0; x < stylusPoints.Count; x++ )
                {
                    points.Add((Point)stylusPoints[x]); 
                }
                Point[] lassoPoints = _lassoHelper.AddPoints(points); 
                if ( 0 != lassoPoints.Length ) 
                {
                    _incrementalLassoHitTester.AddPoints(lassoPoints); 
                }
            }
            else if ( !_disableLasso )
            { 
                // If Lasso hasn't start and been disabled, we should try to start it when it is needed.
                bool startLasso = false; 
 
                List points = new List();
                for ( int x = 0; x < stylusPoints.Count; x++ ) 
                {
                    Point point = (Point)( stylusPoints[x] );

                    if ( !startLasso ) 
                    {
                        // If startLasso hasn't be flagged, we should check if the distance between two points is greater than 
                        // our tolerance. If so, we should flag startLasso. 
                        Vector vector = point - _startPoint;
                        double distanceSquared = vector.LengthSquared; 

                        if ( DoubleUtil.GreaterThan(distanceSquared, LassoHelper.MinDistanceSquared) )
                        {
                            points.Add(point); 
                            startLasso = true;
                        } 
                    } 
                    else
                    { 
                        // The flag is set. We just add the point.
                        points.Add(point);
                    }
                } 

                // Start Lasso if it isn't a tap selection. 
                if ( startLasso ) 
                {
                    StartLasso(points); 
                }
            }
        }
 

        ///  
        /// StylusInputEnd 
        /// 
        /// commit 
        protected override void StylusInputEnd(bool commit)
        {
            // Initialize with empty selection
            StrokeCollection selectedStrokes = new StrokeCollection(); 
            List elementsToSelect = new List();
 
            if ( _lassoHelper != null ) 
            {
                // This is a lasso selection. 

                //
                // end dynamic rendering
                // 
                selectedStrokes = InkCanvas.EndDynamicSelection(_lassoHelper.Visual);
 
                // 
                // hit test for elements
                // 
                // NOTE: HitTestForElements uses the _lassoHelper so it must be alive at this point
                elementsToSelect = HitTestForElements();

                _incrementalLassoHitTester.SelectionChanged -= new LassoSelectionChangedEventHandler(OnSelectionChanged); 
                _incrementalLassoHitTester.EndHitTesting();
                _incrementalLassoHitTester = null; 
                _lassoHelper = null; 
            }
            else 
            {
                // This is a tap selection.

                // Now try the tap selection 
                Stroke tappedStroke;
                UIElement tappedElement; 
 
                TapSelectObject(_startPoint, out tappedStroke, out tappedElement);
 
                // If we have a pre-selected object, we should select it now.
                if ( tappedStroke != null )
                {
                    Debug.Assert(tappedElement == null); 
                    selectedStrokes = new StrokeCollection();
                    selectedStrokes.Add(tappedStroke); 
                } 
                else if ( tappedElement != null )
                { 
                    Debug.Assert(tappedStroke == null);
                    elementsToSelect.Add(tappedElement);
                }
            } 

            SelfDeactivate(); 
 
            if ( commit )
            { 
                InkCanvas.ChangeInkCanvasSelection(selectedStrokes, elementsToSelect.ToArray());
            }
        }
 
        /// 
        /// OnCommitWithoutStylusInput 
        ///  
        /// 
        protected override void OnCommitWithoutStylusInput(bool commit) 
        {
            // We only deactivate LSB in this case.
            SelfDeactivate();
        } 

        ///  
        /// Get the current cursor for this editing mode 
        /// 
        ///  
        protected override Cursor GetCurrentCursor()
        {
            // By default return cross cursor.
            return Cursors.Cross; 
        }
 
        #endregion Protected Methods 

        //-------------------------------------------------------------------------------- 
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------- 

        #region Private Methods 
 
        /// 
        /// Private event handler that updates which strokes are actually selected 
        /// 
        /// 
        /// 
        private void OnSelectionChanged(object sender, LassoSelectionChangedEventArgs e) 
        {
            this.InkCanvas.UpdateDynamicSelection(e.SelectedStrokes, e.DeselectedStrokes); 
        } 

 
        /// 
        /// Private helper that will hit test for elements
        /// 
        private List HitTestForElements() 
        {
            List elementsToSelect = new List(); 
 
            if ( this.InkCanvas.Children.Count == 0 )
            { 
                return elementsToSelect;
            }

            for (int x = 0; x < this.InkCanvas.Children.Count; x++) 
            {
                UIElement uiElement = this.InkCanvas.Children[x]; 
                HitTestElement(InkCanvas.InnerCanvas, uiElement, elementsToSelect); 
            }
 
            return elementsToSelect;
        }

        ///  
        /// Private helper that will turn an element in any nesting level into a stroke
        /// in the InkCanvas's coordinate space.  This method calls itself recursively 
        ///  
        private void HitTestElement(InkCanvasInnerCanvas parent, UIElement uiElement, List elementsToSelect)
        { 
            ElementCornerPoints elementPoints = LassoSelectionBehavior.GetTransformedElementCornerPoints(parent, uiElement);
            if (elementPoints.Set != false)
            {
                Point[] points = GeneratePointGrid(elementPoints); 

                // 
                // perform hit testing against our lasso 
                //
                System.Diagnostics.Debug.Assert(null != _lassoHelper); 
                if (_lassoHelper.ArePointsInLasso(points, _percentIntersectForElements))
                {
                    elementsToSelect.Add(uiElement);
                } 
            }
            // 
            // we used to recurse into the childrens children.  That is no longer necessary 
            //
        } 

        /// 
        /// Private helper that takes an element and transforms it's 4 points
        /// into the InkCanvas's space 
        /// 
        private static ElementCornerPoints GetTransformedElementCornerPoints(InkCanvasInnerCanvas canvas, UIElement childElement) 
        { 
            Debug.Assert(canvas != null);
            Debug.Assert(childElement != null); 

            Debug.Assert(canvas.CheckAccess());

            ElementCornerPoints elementPoints = new ElementCornerPoints(); 
            elementPoints.Set = false;
 
            if (childElement.Visibility != Visibility.Visible) 
            {
                // 
                // this little one's not worth it...
                //
                return elementPoints;
            } 

            // 
            // get the transform from us to our parent InkCavas 
            //
            GeneralTransform parentTransform = childElement.TransformToAncestor(canvas); 

            //

            parentTransform.TryTransform(new Point(0, 0), out elementPoints.UpperLeft); 
            parentTransform.TryTransform(new Point(childElement.RenderSize.Width, 0), out elementPoints.UpperRight);
            parentTransform.TryTransform(new Point(0, childElement.RenderSize.Height), out elementPoints.LowerLeft); 
            parentTransform.TryTransform(new Point(childElement.RenderSize.Width, childElement.RenderSize.Height), out elementPoints.LowerRight); 

            elementPoints.Set = true; 
            return elementPoints;
        }

        ///  
        /// Private helper that will generate a grid of points 5 px apart given the elements bounding points
        /// this works with any affline transformed points 
        ///  
        private Point[] GeneratePointGrid(ElementCornerPoints elementPoints)
        { 
            if (!elementPoints.Set)
            {
                return new Point[]{};
            } 
            ArrayList pointArray = new ArrayList();
 
            UpdatePointDistances(elementPoints); 

            // 
            // add our original points
            //
            pointArray.Add(elementPoints.UpperLeft);
            pointArray.Add(elementPoints.UpperRight); 
            FillInPoints(pointArray, elementPoints.UpperLeft, elementPoints.UpperRight);
 
            pointArray.Add(elementPoints.LowerLeft); 
            pointArray.Add(elementPoints.LowerRight);
            FillInPoints(pointArray, elementPoints.LowerLeft, elementPoints.LowerRight); 

            FillInGrid( pointArray,
                        elementPoints.UpperLeft,
                        elementPoints.UpperRight, 
                        elementPoints.LowerRight,
                        elementPoints.LowerLeft); 
 
            Point[] retPointArray = new Point[pointArray.Count];
            pointArray.CopyTo(retPointArray); 
            return retPointArray;
        }

        ///  
        /// Private helper that fills in the points between two points by calling itself
        /// recursively in a divide and conquer fashion 
        ///  
        private void FillInPoints(ArrayList pointArray, Point point1, Point point2)
        { 
            // this algorithm improves perf by 20%
            if(!PointsAreCloseEnough(point1, point2))
            {
                Point midPoint = LassoSelectionBehavior.GeneratePointBetweenPoints(point1, point2); 
                pointArray.Add(midPoint);
 
                if(!PointsAreCloseEnough(point1, midPoint)) 
                {
                    FillInPoints(pointArray, point1, midPoint); 
                }

                //sort the right
                if(!PointsAreCloseEnough(midPoint, point2)) 
                {
                    FillInPoints(pointArray, midPoint, point2); 
                } 
            }
        } 

        /// 
        /// Private helper that fills in the points between four points by calling itself
        /// recursively in a divide and conquer fashion 
        /// 
        private void FillInGrid(ArrayList pointArray, 
                                Point upperLeft, 
                                Point upperRight,
                                Point lowerRight, 
                                Point lowerLeft)
        {
            // this algorithm improves perf by 20%
            if(!PointsAreCloseEnough(upperLeft, lowerLeft)) 
            {
                Point midPointLeft = LassoSelectionBehavior.GeneratePointBetweenPoints(upperLeft, lowerLeft); 
                Point midPointRight = LassoSelectionBehavior.GeneratePointBetweenPoints(upperRight, lowerRight); 
                pointArray.Add(midPointLeft);
                pointArray.Add(midPointRight); 
                FillInPoints(pointArray, midPointLeft, midPointRight);

                if(!PointsAreCloseEnough(upperLeft, midPointLeft))
                { 
                    FillInGrid(pointArray, upperLeft, upperRight, midPointRight, midPointLeft);
                } 
 
                //sort the right
                if(!PointsAreCloseEnough(midPointLeft, lowerLeft)) 
                {
                    FillInGrid(pointArray, midPointLeft, midPointRight, lowerRight, lowerLeft);
                }
            } 
        }
 
        ///  
        /// Private helper that will generate a new point between two points
        ///  
        private static Point GeneratePointBetweenPoints(Point point1, Point point2)
        {
            //
            // compute the new point in the middle of the previous two 
            //
            double maxX = point1.X > point2.X ? point1.X : point2.X; 
            double minX = point1.X < point2.X ? point1.X : point2.X; 
            double maxY = point1.Y > point2.Y ? point1.Y : point2.Y;
            double minY = point1.Y < point2.Y ? point1.Y : point2.Y; 

            return new Point( (minX + ((maxX - minX) * 0.5f)),
                                (minY + ((maxY - minY) * 0.5f)));
        } 

        ///  
        /// Private helper used to determine if we're close enough between two points 
        /// 
        private bool PointsAreCloseEnough(Point point1, Point point2) 
        {
            double x = point1.X - point2.X;
            double y = point1.Y - point2.Y;
            if ((x < _xDiff && x > -_xDiff) && (y < _yDiff && y > -_yDiff)) 
            {
                return true; 
            } 
            return false;
        } 

        /// 
        /// Used to calc the diff between points on the x and y axis
        ///  
        private void UpdatePointDistances(ElementCornerPoints elementPoints)
        { 
            // 
            // calc the x and y diffs
            // 
            double width = elementPoints.UpperLeft.X - elementPoints.UpperRight.X;
            if (width < 0)
            {
                width = -width; 
            }
 
            double height = elementPoints.UpperLeft.Y - elementPoints.LowerLeft.Y; 
            if (height < 0)
            { 
                height = -height;
            }

            _xDiff = width * 0.25f; 
            if (_xDiff > _maxThreshold)
            { 
                _xDiff = _maxThreshold; 
            }
            else if (_xDiff < _minThreshold) 
            {
                _xDiff = _minThreshold;
            }
 
            _yDiff = height * 0.25f;
            if (_yDiff > _maxThreshold) 
            { 
                _yDiff = _maxThreshold;
            } 
            else if (_yDiff < _minThreshold)
            {
                _yDiff = _minThreshold;
            } 
        }
 
        ///  
        /// StartLasso
        ///  
        /// 
        private void StartLasso(List points)
        {
            Debug.Assert(!_disableLasso && _lassoHelper == null, "StartLasso is called unexpectedly."); 

            if ( InkCanvas.ClearSelectionRaiseSelectionChanging() // If user cancels clearing the selection, we shouldn't initiate Lasso. 
                // NTRAID:WINDOWS#1534264-2006/02/28-WAYNEZEN 
                // If the active editng mode is no longer as Select, we shouldn't activate LassoSelectionBehavior.
                // Note the order really matters here. This checking has to be done 
                // after ClearSelectionRaiseSelectionChanging is invoked.
                && EditingCoordinator.ActiveEditingMode == InkCanvasEditingMode.Select )
            {
 
                //
                // obtain a dynamic hit-tester for selecting with lasso 
                // 
                _incrementalLassoHitTester =
                                    this.InkCanvas.Strokes.GetIncrementalLassoHitTester(_percentIntersectForInk); 
                //
                // add event handler
                //
                _incrementalLassoHitTester.SelectionChanged += new LassoSelectionChangedEventHandler(OnSelectionChanged); 

                // 
                // start dynamic rendering 
                //
 
                _lassoHelper = new LassoHelper();
                InkCanvas.BeginDynamicSelection(_lassoHelper.Visual);

                Point[] lassoPoints = _lassoHelper.AddPoints(points); 
                if ( 0 != lassoPoints.Length )
                { 
                    _incrementalLassoHitTester.AddPoints(lassoPoints); 
                }
            } 
            else
            {
                // If we fail on clearing the selection or switching to Select mode, we should just disable lasso.
                _disableLasso = true; 
            }
        } 
 
        /// 
        /// Pre-Select the object which user taps on. 
        /// 
        /// 
        /// 
        ///  
        private void TapSelectObject(Point point, out Stroke tappedStroke, out UIElement tappedElement)
        { 
            tappedStroke = null; 
            tappedElement = null;
 
            StrokeCollection hitTestStrokes = InkCanvas.Strokes.HitTest(point, 5.0d);
            if ( hitTestStrokes.Count > 0 )
            {
                tappedStroke = hitTestStrokes[hitTestStrokes.Count - 1]; 
            }
            else 
            { 
                GeneralTransform transformToInnerCanvas = InkCanvas.TransformToVisual(InkCanvas.InnerCanvas);
                Point pointOnInnerCanvas = transformToInnerCanvas.Transform(point); 

                // Try to find out whether we have a pre-select object.
                tappedElement = InkCanvas.InnerCanvas.HitTestOnElements(pointOnInnerCanvas);
            } 

        } 
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        // 
        //--------------------------------------------------------------------------------
 
        #region Private Fields 

        private Point                       _startPoint; 
        private bool                        _disableLasso;

        private LassoHelper                 _lassoHelper = null;
        private IncrementalLassoHitTester   _incrementalLassoHitTester; 
        private double                  _xDiff;
        private double                  _yDiff; 
        private const double            _maxThreshold = 50f; 
        private const double            _minThreshold = 15f;
        private const int               _percentIntersectForInk = 80; 
        private const int               _percentIntersectForElements = 60;


#if DEBUG_LASSO_FEEDBACK 
        private ContainerVisual _tempVisual;
#endif 
        ///  
        /// Private struct
        ///  
        private struct ElementCornerPoints
        {
            internal Point UpperLeft;
            internal Point UpperRight; 
            internal Point LowerRight;
            internal Point LowerLeft; 
            internal bool Set; 
        }
 
        #endregion Private Fields

    }
} 

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

                        

Link Menu

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