Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / System / Windows / Ink / IncrementalHitTester.cs / 1 / IncrementalHitTester.cs
//------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- //#define TRACE using System; using System.Windows; using System.Windows.Input; using System.Windows.Ink; using System.Windows.Media; using System.Collections.Generic; using System.Collections.ObjectModel; using MS.Internal.Ink; using MS.Utility; using MS.Internal; using System.Diagnostics; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Ink { #region IncrementalHitTester Abstract Base Class ////// This class serves as both the base class and public interface for /// incremental hit-testing implementaions. /// public abstract class IncrementalHitTester { #region Public API ////// Adds a point representing an incremental move of the hit-testing tool /// /// a point that represents an incremental move of the hitting tool public void AddPoint(Point point) { AddPoints(new Point[1] { point }); } ////// Adds an array of points representing an incremental move of the hit-testing tool /// /// points representing an incremental move of the hitting tool public void AddPoints(IEnumerablepoints) { if (points == null) { throw new System.ArgumentNullException("points"); } if (IEnumerablePointHelper.GetCount(points) == 0) { throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "points"); } if (false == _fValid) { throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); } System.Diagnostics.Debug.Assert(_strokes != null); AddPointsCore(points); } /// /// Adds a StylusPacket representing an incremental move of the hit-testing tool /// /// stylusPoints public void AddPoints(StylusPointCollection stylusPoints) { if (stylusPoints == null) { throw new System.ArgumentNullException("stylusPoints"); } if (stylusPoints.Count == 0) { throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "stylusPoints"); } if (false == _fValid) { throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); } System.Diagnostics.Debug.Assert(_strokes != null); Point[] points = new Point[stylusPoints.Count]; for (int x = 0; x < stylusPoints.Count; x++) { points[x] = (Point)stylusPoints[x]; } AddPointsCore(points); } ////// Release as many resources as possible for this enumerator /// public void EndHitTesting() { if (_strokes != null) { // Detach the event handler _strokes.StrokesChangedInternal -= new StrokeCollectionChangedEventHandler(OnStrokesChanged); _strokes = null; int count = _strokeInfos.Count; for ( int i = 0; i < count; i++) { #if TRACE if (_strokeInfos[i].HitWeight != _strokeInfos[i].TotalWeight) { System.Diagnostics.Debug.WriteLine(String.Format("IsHit: {0}; TotalWeight: {1}; HitWeight: {2}, Percent hit: {3}", _strokeInfos[i].IsHit, _strokeInfos[i].TotalWeight, _strokeInfos[i].HitWeight, 100f * _strokeInfos[i].HitWeight / _strokeInfos[i].TotalWeight)); } #endif _strokeInfos[i].Detach(); } _strokeInfos = null; } _fValid = false; } ////// Accessor to see if the Hit Tester is still valid /// public bool IsValid { get { return _fValid; } } #endregion #region Internal ////// C-tor. /// /// strokes to hit-test internal IncrementalHitTester(StrokeCollection strokes) { System.Diagnostics.Debug.Assert(strokes != null); // Create a StrokeInfo object for each stroke. _strokeInfos = new List(strokes.Count); for (int x = 0; x < strokes.Count; x++) { Stroke stroke = strokes[x]; _strokeInfos.Add(new StrokeInfo(stroke)); } _strokes = strokes; // Attach an event handler to the strokes' changed event _strokes.StrokesChangedInternal += new StrokeCollectionChangedEventHandler(OnStrokesChanged); } /// /// The implementation behind AddPoint/AddPoints. /// Derived classes are supposed to override this method. /// protected abstract void AddPointsCore(IEnumerablepoints); /// /// Accessor to the internal collection of StrokeInfo objects /// internal ListStrokeInfos { get { return _strokeInfos; } } #endregion #region Private /// /// Event handler associated with the stroke collection. /// /// Stroke collection that was modified /// Modification that occurred ////// Update our _strokeInfos cache. We get notified on StrokeCollection.StrokesChangedInternal which /// is raised first so we can assume we're the first delegate in the call chain /// private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs args) { System.Diagnostics.Debug.Assert((_strokes != null) && (_strokeInfos != null) && (_strokes == sender)); StrokeCollection added = args.Added; StrokeCollection removed = args.Removed; if (added.Count > 0) { int firstIndex = _strokes.IndexOf(added[0]); for (int i = 0; i < added.Count; i++) { _strokeInfos.Insert(firstIndex, new StrokeInfo(added[i])); firstIndex++; } } if (removed.Count > 0) { StrokeCollection localRemoved = new StrokeCollection(removed); //we have to assume that removed strokes can be in any order in _strokes for (int i = 0; i < _strokeInfos.Count && localRemoved.Count > 0; ) { bool found = false; for (int j = 0; j < localRemoved.Count; j++) { if (localRemoved[j] == _strokeInfos[i].Stroke) { _strokeInfos.RemoveAt(i); localRemoved.RemoveAt(j); found = true; } } //we didn't find a removed stroke at index i in _strokeInfos, so advance i if (!found) { i++; } } } //validate our cache if (_strokes.Count != _strokeInfos.Count) { Debug.Assert(false, "Benign assert. IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); RebuildStrokeInfoCache(); return; } for (int i = 0; i < _strokeInfos.Count; i++) { if (_strokeInfos[i].Stroke != _strokes[i]) { Debug.Assert(false, "Benign assert. IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); RebuildStrokeInfoCache(); return; } } } ////// IHT's can get into a state where their StrokeInfo cache is too /// out of [....] with the StrokeCollection to incrementally update it. /// When we detect this has happened, we just rebuild the entire cache. /// private void RebuildStrokeInfoCache() { ListnewStrokeInfos = new List (_strokes.Count); foreach (Stroke stroke in _strokes) { bool found = false; for (int x = 0; x < _strokeInfos.Count; x++) { StrokeInfo strokeInfo = _strokeInfos[x]; if (strokeInfo != null && stroke == strokeInfo.Stroke) { newStrokeInfos.Add(strokeInfo); //just set to null instead of removing and shifting //we're about to GC _strokeInfos _strokeInfos[x] = null; found = true; break; } } if (!found) { //we didn't find an existing strokeInfo newStrokeInfos.Add(new StrokeInfo(stroke)); } } //detach the remaining strokeInfo's from their strokes for (int x = 0; x < _strokeInfos.Count; x++) { StrokeInfo strokeInfo = _strokeInfos[x]; if (strokeInfo != null) { strokeInfo.Detach(); } } _strokeInfos = newStrokeInfos; #if DEBUG Debug.Assert(_strokeInfos.Count == _strokes.Count); for (int x = 0; x < _strokeInfos.Count; x++) { Debug.Assert(_strokeInfos[x].Stroke == _strokes[x]); } #endif } #endregion #region Fields /// Reference to the stroke collection under test private StrokeCollection _strokes; ///A collection of helper objects mapped to the stroke colection private List_strokeInfos; private bool _fValid = true; #endregion } #endregion #region IncrementalLassoHitTester /// /// IncrementalHitTester implementation for hit-testing with lasso /// public class IncrementalLassoHitTester : IncrementalHitTester { #region public APIs ////// Event /// public event LassoSelectionChangedEventHandler SelectionChanged; #endregion #region C-tor and the overrides ////// C-tor. /// /// strokes to hit-test /// a hit-testing parameter that defines the minimal /// percent of nodes of a stroke to be inside the lasso to consider the stroke hit internal IncrementalLassoHitTester(StrokeCollection strokes, int percentageWithinLasso) : base(strokes) { System.Diagnostics.Debug.Assert((percentageWithinLasso >= 0) && (percentageWithinLasso <= 100)); _lasso = new SingleLoopLasso(); _percentIntersect = percentageWithinLasso; } ////// The implementation behind the public methods AddPoint/AddPoints /// /// new points to add to the lasso protected override void AddPointsCore(IEnumerablepoints) { System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points)!= 0)); // Add the new points to the lasso int lastPointIndex = (0 != _lasso.PointCount) ? (_lasso.PointCount - 1) : 0; _lasso.AddPoints(points); // Do nothing if there's not enough points, or there's nobody listening // The points may be filtered out, so if all the points are filtered out, (lastPointIndex == (_lasso.PointCount - 1). // For this case, check if the incremental lasso is disabled (i.e., points modified). if ((_lasso.IsEmpty) || (lastPointIndex == (_lasso.PointCount - 1) && false == _lasso.IsIncrementalLassoDirty) || (SelectionChanged == null)) { return; } // Variables for possible HitChanged events to fire StrokeCollection strokesHit = null; StrokeCollection strokesUnhit = null; // Create a lasso that represents the current increment Lasso lassoUpdate = new Lasso(); if (false == _lasso.IsIncrementalLassoDirty) { if (0 < lastPointIndex) { lassoUpdate.AddPoint(_lasso[0]); } // Only the points the have been successfully added to _lasso will be added to // lassoUpdate. for (; lastPointIndex < _lasso.PointCount; lastPointIndex++) { lassoUpdate.AddPoint(_lasso[lastPointIndex]); } } // Enumerate through the strokes and update their hit-test results foreach (StrokeInfo strokeInfo in this.StrokeInfos) { Lasso lasso; if (true == strokeInfo.IsDirty || true == _lasso.IsIncrementalLassoDirty) { // If this is the first time this stroke gets hit-tested with this lasso, // or if the stroke (or its DAs) has changed since the last hit-testing, // or if the lasso points have been modified, // then (re)hit-test this stroke against the entire lasso. lasso = _lasso; strokeInfo.IsDirty = false; } else { // Otherwise, hit-test it against the lasso increment first and then only // those ink points that are in that small lasso need to be hit-tested // against the big (entire) lasso. // This is supposed to be a significant piece of optimization, since // lasso increments are usually very small, they are defined by just // a few points and they don't capture and/or release too many ink nodes. lasso = lassoUpdate; } // Skip those stroke which bounding box doesn't even intersects with the lasso bounds double hitWeightChange = 0f; if (lasso.Bounds.IntersectsWith(strokeInfo.StrokeBounds)) { // Get the stroke node points for the hit-testing. StylusPointCollection stylusPoints = strokeInfo.StylusPoints; // Find out if the lasso update has changed the hit count of the stroke. for (int i = 0; i < stylusPoints.Count; i++) { // Consider only the points that become captured/released with this particular update if (true == lasso.Contains((Point)stylusPoints[i])) { double weight = strokeInfo.GetPointWeight(i); if (lasso == _lasso || _lasso.Contains((Point)stylusPoints[i])) { hitWeightChange += weight; } else { hitWeightChange -= weight; } } } } // Update the stroke hit weight and check whether it has crossed the margin // in either direction since the last update. if ((hitWeightChange != 0) || (lasso == _lasso)) { strokeInfo.HitWeight = (lasso == _lasso) ? hitWeightChange : (strokeInfo.HitWeight + hitWeightChange); bool isHit = DoubleUtil.GreaterThanOrClose(strokeInfo.HitWeight, strokeInfo.TotalWeight * _percentIntersect / 100f - Stroke.PercentageTolerance); if (strokeInfo.IsHit != isHit) { strokeInfo.IsHit = isHit; if (isHit) { // The hit count became greater than the margin percentage, the stroke // needs to be reported for selection if (null == strokesHit) { strokesHit = new StrokeCollection(); } strokesHit.Add(strokeInfo.Stroke); #if TRACE System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the hit list. Current hit count = {0}", strokesHit.Count)); #endif } else { // The hit count just became less than the margin percentage, // the stroke needs to be reported for de-selection if (null == strokesUnhit) { strokesUnhit = new StrokeCollection(); } strokesUnhit.Add(strokeInfo.Stroke); #if TRACE System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the unhit list")); #endif } } } } _lasso.IsIncrementalLassoDirty = false; // Raise StrokesHitChanged event if any strokes has changed thier // hit status and there're the event subscribers. if ((null != strokesHit) || (null != strokesUnhit)) { OnSelectionChanged(new LassoSelectionChangedEventArgs (strokesHit, strokesUnhit)); } } /// /// SelectionChanged event raiser /// /// protected void OnSelectionChanged(LassoSelectionChangedEventArgs eventArgs) { System.Diagnostics.Debug.Assert(eventArgs != null); if (SelectionChanged != null) { SelectionChanged(this, eventArgs); } } #endregion #region Fields private Lasso _lasso; private int _percentIntersect; #endregion } #endregion #region IncrementalStrokeHitTester ////// IncrementalHitTester implementation for hit-testing with a shape, PointErasing . /// public class IncrementalStrokeHitTester : IncrementalHitTester { ////// /// public event StrokeHitEventHandler StrokeHit; #region C-tor and the overrides ////// C-tor /// /// strokes to hit-test for erasing /// erasing shape internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) : base(strokes) { System.Diagnostics.Debug.Assert(eraserShape != null); // Create an ErasingStroke objects that implements the actual hit-testing _erasingStroke = new ErasingStroke(eraserShape); } ////// The implementation behind the public methods AddPoint/AddPoints /// /// a set of points representing the last increment /// in the moving of the erasing shape protected override void AddPointsCore(IEnumerablepoints) { System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points) != 0)); System.Diagnostics.Debug.Assert(_erasingStroke != null); // Move the shape through the new points and build the contour of the move. _erasingStroke.MoveTo(points); Rect erasingBounds = _erasingStroke.Bounds; if (erasingBounds.IsEmpty) { return; } List strokeHitEventArgCollection = null; // Do nothing if there's nobody listening to the events if (StrokeHit != null) { List eraseAt = new List (); // Test stroke by stroke and collect the results. for (int x = 0; x < this.StrokeInfos.Count; x++) { StrokeInfo strokeInfo = this.StrokeInfos[x]; // Skip the stroke if its bounding box doesn't intersect with the one of the hitting shape. if ((erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) == false) || (_erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Stroke.DrawingAttributes), eraseAt) == false)) { continue; } // Create an event args to raise after done with hit-testing // We don't fire these events right away because user is expected to // modify the stroke collection in her event handler, and that would // invalidate this foreach loop. if (strokeHitEventArgCollection == null) { strokeHitEventArgCollection = new List (); } strokeHitEventArgCollection.Add(new StrokeHitEventArgs(strokeInfo.Stroke, eraseAt.ToArray())); // We must clear eraseAt or it will contain invalid results for the next strokes eraseAt.Clear(); } } // Raise StrokeHit event if needed. if (strokeHitEventArgCollection != null) { System.Diagnostics.Debug.Assert(strokeHitEventArgCollection.Count != 0); for (int x = 0; x < strokeHitEventArgCollection.Count; x++) { StrokeHitEventArgs eventArgs = strokeHitEventArgCollection[x]; System.Diagnostics.Debug.Assert(eventArgs.HitStroke != null); OnStrokeHit(eventArgs); } } } /// /// Event raiser for StrokeHit /// protected void OnStrokeHit(StrokeHitEventArgs eventArgs) { System.Diagnostics.Debug.Assert(eventArgs != null); if (StrokeHit != null) { StrokeHit(this, eventArgs); } } #endregion #region Fields private ErasingStroke _erasingStroke; #endregion } #endregion #region EventArgs and delegates ////// Declaration for LassoSelectionChanged event handler. Used in lasso-selection /// public delegate void LassoSelectionChangedEventHandler(object sender, LassoSelectionChangedEventArgs e); ////// Declaration for StrokeHit event handler. Used in point-erasing /// public delegate void StrokeHitEventHandler(object sender, StrokeHitEventArgs e); ////// Event arguments for LassoSelectionChanged event /// public class LassoSelectionChangedEventArgs : EventArgs { internal LassoSelectionChangedEventArgs(StrokeCollection selectedStrokes, StrokeCollection deselectedStrokes) { _selectedStrokes = selectedStrokes; _deselectedStrokes = deselectedStrokes; } ////// Collection of strokes which were hit with the last increment /// public StrokeCollection SelectedStrokes { get { if (_selectedStrokes != null) { StrokeCollection sc = new StrokeCollection(); sc.Add(_selectedStrokes); return sc; } else { return new StrokeCollection(); } } } ////// Collection of strokes which were unhit with the last increment /// public StrokeCollection DeselectedStrokes { get { if (_deselectedStrokes != null) { StrokeCollection sc = new StrokeCollection(); sc.Add(_deselectedStrokes); return sc; } else { return new StrokeCollection(); } } } private StrokeCollection _selectedStrokes; private StrokeCollection _deselectedStrokes; } ////// Event arguments for StrokeHit event /// public class StrokeHitEventArgs : EventArgs { ////// C-tor /// internal StrokeHitEventArgs(Stroke stroke, StrokeIntersection[] hitFragments) { System.Diagnostics.Debug.Assert(stroke != null && hitFragments != null && hitFragments.Length > 0); _stroke = stroke; _hitFragments = hitFragments; } ///Stroke that was hit public Stroke HitStroke { get { return _stroke; } } ////// /// ///public StrokeCollection GetPointEraseResults() { return _stroke.Erase(_hitFragments); } private Stroke _stroke; private StrokeIntersection[] _hitFragments; } #endregion } namespace MS.Internal.Ink { #region StrokeInfo /// /// A helper class associated with a stroke. Used for caching the stroke's /// bounding box, hit-testing results, and for keeping an eye on the stroke changes /// internal class StrokeInfo { #region API (used by incremental hit-testers) ////// StrokeInfo /// internal StrokeInfo(Stroke stroke) { System.Diagnostics.Debug.Assert(stroke != null); _stroke = stroke; _bounds = stroke.GetBounds(); // Start listening to the stroke events _stroke.DrawingAttributesChanged += new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); _stroke.StylusPointsReplaced += new StylusPointsReplacedEventHandler(OnStylusPointsReplaced); _stroke.StylusPoints.Changed += new EventHandler(OnStylusPointsChanged); _stroke.DrawingAttributesReplaced += new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced); } ///The stroke object associated with this helper structure internal Stroke Stroke { get { return _stroke; } } ///Pre-calculated bounds of the stroke internal Rect StrokeBounds { get { return _bounds; } } ///Tells whether the stroke or its drawing attributes have been modified /// since the last use (hit-testing) internal bool IsDirty { get { return _isDirty; } set { _isDirty = value; } } ///Tells whether the stroke was found (and reported) as hit internal bool IsHit { get { return _isHit; } set { _isHit = value; } } ////// Cache teh stroke points /// internal StylusPointCollection StylusPoints { get { if (_stylusPoints == null) { if (_stroke.DrawingAttributes.FitToCurve) { _stylusPoints = _stroke.GetBezierStylusPoints(); } else { _stylusPoints = _stroke.StylusPoints; } } return _stylusPoints; } } ////// Holds the current hit-testing result for the stroke. Represents the length of /// the stroke "inside" and "hit" by the lasso /// internal double HitWeight { get { return _hitWeight; } set { // it is ok to clamp this off, rounding error sends it over or under by a minimal amount. if (DoubleUtil.GreaterThan(value, TotalWeight)) { _hitWeight = TotalWeight; } else if (DoubleUtil.LessThan(value, 0f)) { _hitWeight = 0f; } else { _hitWeight = value; } } } ////// Get the total weight of the stroke. For this implementation, it is the total length of the stroke. /// ///internal double TotalWeight { get { if (!_totalWeightCached) { _totalWeight= 0; for (int i = 0; i < StylusPoints.Count; i++) { _totalWeight += this.GetPointWeight(i); } _totalWeightCached = true; } return _totalWeight; } } /// /// Calculate the weight of a point. /// internal double GetPointWeight(int index) { StylusPointCollection stylusPoints = this.StylusPoints; DrawingAttributes da = this.Stroke.DrawingAttributes; System.Diagnostics.Debug.Assert(stylusPoints != null && index >= 0 && index < stylusPoints.Count); double weight = 0f; if (index == 0) { weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; } else { Vector spine = (Point)stylusPoints[index] - (Point)stylusPoints[index - 1]; weight += Math.Sqrt(spine.LengthSquared) / 2.0f; } if (index == stylusPoints.Count - 1) { weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; } else { Vector spine = (Point)stylusPoints[index + 1] - (Point)stylusPoints[index]; weight += Math.Sqrt(spine.LengthSquared) / 2.0f; } return weight; } ////// A kind of disposing method /// internal void Detach() { if (_stroke != null) { // Detach the event handlers _stroke.DrawingAttributesChanged -= new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); _stroke.StylusPointsReplaced -= new StylusPointsReplacedEventHandler(OnStylusPointsReplaced); _stroke.StylusPoints.Changed -= new EventHandler(OnStylusPointsChanged); _stroke.DrawingAttributesReplaced -= new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced); _stroke = null; } } #endregion #region Stroke event handlers (Private) ///Event handler for stroke data changed events private void OnStylusPointsChanged(object sender, EventArgs args) { Invalidate(); } ///Event handler for stroke data changed events private void OnStylusPointsReplaced(object sender, StylusPointsReplacedEventArgs args) { Invalidate(); } ////// Event handler for stroke's drawing attributes changes. /// /// /// private void OnStrokeDrawingAttributesChanged(object sender, PropertyDataChangedEventArgs args) { // Only enforce rehittesting of the whole stroke when the DrawingAttribute change may affect hittesting if(DrawingAttributes.IsGeometricalDaGuid(args.PropertyGuid)) { Invalidate(); } } private void OnDrawingAttributesReplaced(Object sender, DrawingAttributesReplacedEventArgs args) { // If the drawing attributes change involves Width, Height, StylusTipTransform, IgnorePressure, or FitToCurve, // we need to invalidate if (false == DrawingAttributes.GeometricallyEqual(args.NewDrawingAttributes, args.PreviousDrawingAttributes)) { Invalidate(); } } ///Implementation for the event handlers above private void Invalidate() { _totalWeightCached = false; _stylusPoints = null; _hitWeight = 0; // Let the hit-tester know that it should not use incremental hit-testing _isDirty = true; // The Stroke.GetBounds may be overriden in the 3rd party code. // The out-side code could throw exception. If an exception is thrown, _bounds will keep the original value. // Re-compute the stroke bounds _bounds = _stroke.GetBounds(); } #endregion #region Fields private Stroke _stroke; private Rect _bounds; private double _hitWeight = 0f; private bool _isHit = false; private bool _isDirty = true; private StylusPointCollection _stylusPoints; // Cache the stroke rendering points private double _totalWeight = 0f; private bool _totalWeightCached = false; #endregion } #endregion // StrokeInfo } // The following code is for Stroke-Erasing scenario. Currently the IncrementalStrokeHitTester // can be used for Stroke-erasing but the following class is faster. If in the future there's a // perf issue with Stroke-Erasing, consider adding the following code. //#region Commented Code for IncrementalStrokeHitTester //#region IncrementalStrokeHitTester ////////// IncrementalHitTester implementation for hit-testing with a shape, StrokeErasing . ///// //public class IncrementalStrokeHitTester : IncrementalHitTester //{ // ///// /// event // /// // public event StrokesHitEventHandler StrokesHit; // #region C-tor and the overrides // ///// /// C-tor // /// // /// strokes to hit-test for erasing // /// erasing shape // internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) // : base(strokes) // { // System.Diagnostics.Debug.Assert(eraserShape != null); // // Create an ErasingStroke objects that implements the actual hit-testing // _erasingStroke = new ErasingStroke(eraserShape); // } // ///// /// // /// // /// // internal protected void OnStrokesHit(StrokesHitEventArgs eventArgs) // { // if (StrokesHit != null) // { // StrokesHit(this, eventArgs); // } // } // ///// /// The implementation behind the public methods AddPoint/AddPoints // /// // /// a set of points representing the last increment // /// in the moving of the erasing shape // internal protected override void AddPointsCore(Point[] points) // { // System.Diagnostics.Debug.Assert((points != null) && (points.Length != 0)); // System.Diagnostics.Debug.Assert(_erasingStroke != null); // // Move the shape through the new points and build the contour of the move. // _erasingStroke.MoveTo(points); // Rect erasingBounds = _erasingStroke.Bounds; // if (erasingBounds.IsEmpty) // { // return; // } // StrokeCollection strokesHit = null; // if (StrokesHit != null) // { // // Test stroke by stroke and collect hits. // foreach (StrokeInfo strokeInfo in StrokeInfos) // { // // Skip strokes that have already been reported hit or which bounds // // don't intersect with the bounds of the erasing stroke. // if ((strokeInfo.IsHit == false) && erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) // && _erasingStroke.HitTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Overrides))) // { // if (strokesHit == null) // { // strokesHit = new StrokeCollection(); // } // strokesHit.Add(strokeInfo.Stroke); // strokeInfo.IsHit = true; // } // } // } // // Raise StrokesHitChanged event if any strokes have been hit and there're listeners to the event. // if (strokesHit != null) // { // System.Diagnostics.Debug.Assert(strokesHit.Count != 0); // OnStrokesHit(new StrokesHitEventArgs(strokesHit)); // } // } // #endregion // #region Fields // private ErasingStroke _erasingStroke; // #endregion //} //#endregion ////////// Declaration for StrokesHit event handler. Used in stroke-erasing ///// //public delegate void StrokesHitEventHandler(object sender, StrokesHitEventArgs e); ////////// Event arguments for StrokesHit event ///// //public class StrokesHitEventArgs : EventArgs //{ // internal StrokesHitEventArgs(StrokeCollection hitStrokes) // { // System.Diagnostics.Debug.Assert(hitStrokes != null && hitStrokes.Count > 0); // _hitStrokes = hitStrokes; // } // ///// /// // /// // public StrokeCollection HitStrokes // { // get { return _hitStrokes; } // } // private StrokeCollection _hitStrokes; //} //#endregion // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- //#define TRACE using System; using System.Windows; using System.Windows.Input; using System.Windows.Ink; using System.Windows.Media; using System.Collections.Generic; using System.Collections.ObjectModel; using MS.Internal.Ink; using MS.Utility; using MS.Internal; using System.Diagnostics; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Ink { #region IncrementalHitTester Abstract Base Class ////// This class serves as both the base class and public interface for /// incremental hit-testing implementaions. /// public abstract class IncrementalHitTester { #region Public API ////// Adds a point representing an incremental move of the hit-testing tool /// /// a point that represents an incremental move of the hitting tool public void AddPoint(Point point) { AddPoints(new Point[1] { point }); } ////// Adds an array of points representing an incremental move of the hit-testing tool /// /// points representing an incremental move of the hitting tool public void AddPoints(IEnumerablepoints) { if (points == null) { throw new System.ArgumentNullException("points"); } if (IEnumerablePointHelper.GetCount(points) == 0) { throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "points"); } if (false == _fValid) { throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); } System.Diagnostics.Debug.Assert(_strokes != null); AddPointsCore(points); } /// /// Adds a StylusPacket representing an incremental move of the hit-testing tool /// /// stylusPoints public void AddPoints(StylusPointCollection stylusPoints) { if (stylusPoints == null) { throw new System.ArgumentNullException("stylusPoints"); } if (stylusPoints.Count == 0) { throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "stylusPoints"); } if (false == _fValid) { throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); } System.Diagnostics.Debug.Assert(_strokes != null); Point[] points = new Point[stylusPoints.Count]; for (int x = 0; x < stylusPoints.Count; x++) { points[x] = (Point)stylusPoints[x]; } AddPointsCore(points); } ////// Release as many resources as possible for this enumerator /// public void EndHitTesting() { if (_strokes != null) { // Detach the event handler _strokes.StrokesChangedInternal -= new StrokeCollectionChangedEventHandler(OnStrokesChanged); _strokes = null; int count = _strokeInfos.Count; for ( int i = 0; i < count; i++) { #if TRACE if (_strokeInfos[i].HitWeight != _strokeInfos[i].TotalWeight) { System.Diagnostics.Debug.WriteLine(String.Format("IsHit: {0}; TotalWeight: {1}; HitWeight: {2}, Percent hit: {3}", _strokeInfos[i].IsHit, _strokeInfos[i].TotalWeight, _strokeInfos[i].HitWeight, 100f * _strokeInfos[i].HitWeight / _strokeInfos[i].TotalWeight)); } #endif _strokeInfos[i].Detach(); } _strokeInfos = null; } _fValid = false; } ////// Accessor to see if the Hit Tester is still valid /// public bool IsValid { get { return _fValid; } } #endregion #region Internal ////// C-tor. /// /// strokes to hit-test internal IncrementalHitTester(StrokeCollection strokes) { System.Diagnostics.Debug.Assert(strokes != null); // Create a StrokeInfo object for each stroke. _strokeInfos = new List(strokes.Count); for (int x = 0; x < strokes.Count; x++) { Stroke stroke = strokes[x]; _strokeInfos.Add(new StrokeInfo(stroke)); } _strokes = strokes; // Attach an event handler to the strokes' changed event _strokes.StrokesChangedInternal += new StrokeCollectionChangedEventHandler(OnStrokesChanged); } /// /// The implementation behind AddPoint/AddPoints. /// Derived classes are supposed to override this method. /// protected abstract void AddPointsCore(IEnumerablepoints); /// /// Accessor to the internal collection of StrokeInfo objects /// internal ListStrokeInfos { get { return _strokeInfos; } } #endregion #region Private /// /// Event handler associated with the stroke collection. /// /// Stroke collection that was modified /// Modification that occurred ////// Update our _strokeInfos cache. We get notified on StrokeCollection.StrokesChangedInternal which /// is raised first so we can assume we're the first delegate in the call chain /// private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs args) { System.Diagnostics.Debug.Assert((_strokes != null) && (_strokeInfos != null) && (_strokes == sender)); StrokeCollection added = args.Added; StrokeCollection removed = args.Removed; if (added.Count > 0) { int firstIndex = _strokes.IndexOf(added[0]); for (int i = 0; i < added.Count; i++) { _strokeInfos.Insert(firstIndex, new StrokeInfo(added[i])); firstIndex++; } } if (removed.Count > 0) { StrokeCollection localRemoved = new StrokeCollection(removed); //we have to assume that removed strokes can be in any order in _strokes for (int i = 0; i < _strokeInfos.Count && localRemoved.Count > 0; ) { bool found = false; for (int j = 0; j < localRemoved.Count; j++) { if (localRemoved[j] == _strokeInfos[i].Stroke) { _strokeInfos.RemoveAt(i); localRemoved.RemoveAt(j); found = true; } } //we didn't find a removed stroke at index i in _strokeInfos, so advance i if (!found) { i++; } } } //validate our cache if (_strokes.Count != _strokeInfos.Count) { Debug.Assert(false, "Benign assert. IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); RebuildStrokeInfoCache(); return; } for (int i = 0; i < _strokeInfos.Count; i++) { if (_strokeInfos[i].Stroke != _strokes[i]) { Debug.Assert(false, "Benign assert. IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); RebuildStrokeInfoCache(); return; } } } ////// IHT's can get into a state where their StrokeInfo cache is too /// out of [....] with the StrokeCollection to incrementally update it. /// When we detect this has happened, we just rebuild the entire cache. /// private void RebuildStrokeInfoCache() { ListnewStrokeInfos = new List (_strokes.Count); foreach (Stroke stroke in _strokes) { bool found = false; for (int x = 0; x < _strokeInfos.Count; x++) { StrokeInfo strokeInfo = _strokeInfos[x]; if (strokeInfo != null && stroke == strokeInfo.Stroke) { newStrokeInfos.Add(strokeInfo); //just set to null instead of removing and shifting //we're about to GC _strokeInfos _strokeInfos[x] = null; found = true; break; } } if (!found) { //we didn't find an existing strokeInfo newStrokeInfos.Add(new StrokeInfo(stroke)); } } //detach the remaining strokeInfo's from their strokes for (int x = 0; x < _strokeInfos.Count; x++) { StrokeInfo strokeInfo = _strokeInfos[x]; if (strokeInfo != null) { strokeInfo.Detach(); } } _strokeInfos = newStrokeInfos; #if DEBUG Debug.Assert(_strokeInfos.Count == _strokes.Count); for (int x = 0; x < _strokeInfos.Count; x++) { Debug.Assert(_strokeInfos[x].Stroke == _strokes[x]); } #endif } #endregion #region Fields /// Reference to the stroke collection under test private StrokeCollection _strokes; ///A collection of helper objects mapped to the stroke colection private List_strokeInfos; private bool _fValid = true; #endregion } #endregion #region IncrementalLassoHitTester /// /// IncrementalHitTester implementation for hit-testing with lasso /// public class IncrementalLassoHitTester : IncrementalHitTester { #region public APIs ////// Event /// public event LassoSelectionChangedEventHandler SelectionChanged; #endregion #region C-tor and the overrides ////// C-tor. /// /// strokes to hit-test /// a hit-testing parameter that defines the minimal /// percent of nodes of a stroke to be inside the lasso to consider the stroke hit internal IncrementalLassoHitTester(StrokeCollection strokes, int percentageWithinLasso) : base(strokes) { System.Diagnostics.Debug.Assert((percentageWithinLasso >= 0) && (percentageWithinLasso <= 100)); _lasso = new SingleLoopLasso(); _percentIntersect = percentageWithinLasso; } ////// The implementation behind the public methods AddPoint/AddPoints /// /// new points to add to the lasso protected override void AddPointsCore(IEnumerablepoints) { System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points)!= 0)); // Add the new points to the lasso int lastPointIndex = (0 != _lasso.PointCount) ? (_lasso.PointCount - 1) : 0; _lasso.AddPoints(points); // Do nothing if there's not enough points, or there's nobody listening // The points may be filtered out, so if all the points are filtered out, (lastPointIndex == (_lasso.PointCount - 1). // For this case, check if the incremental lasso is disabled (i.e., points modified). if ((_lasso.IsEmpty) || (lastPointIndex == (_lasso.PointCount - 1) && false == _lasso.IsIncrementalLassoDirty) || (SelectionChanged == null)) { return; } // Variables for possible HitChanged events to fire StrokeCollection strokesHit = null; StrokeCollection strokesUnhit = null; // Create a lasso that represents the current increment Lasso lassoUpdate = new Lasso(); if (false == _lasso.IsIncrementalLassoDirty) { if (0 < lastPointIndex) { lassoUpdate.AddPoint(_lasso[0]); } // Only the points the have been successfully added to _lasso will be added to // lassoUpdate. for (; lastPointIndex < _lasso.PointCount; lastPointIndex++) { lassoUpdate.AddPoint(_lasso[lastPointIndex]); } } // Enumerate through the strokes and update their hit-test results foreach (StrokeInfo strokeInfo in this.StrokeInfos) { Lasso lasso; if (true == strokeInfo.IsDirty || true == _lasso.IsIncrementalLassoDirty) { // If this is the first time this stroke gets hit-tested with this lasso, // or if the stroke (or its DAs) has changed since the last hit-testing, // or if the lasso points have been modified, // then (re)hit-test this stroke against the entire lasso. lasso = _lasso; strokeInfo.IsDirty = false; } else { // Otherwise, hit-test it against the lasso increment first and then only // those ink points that are in that small lasso need to be hit-tested // against the big (entire) lasso. // This is supposed to be a significant piece of optimization, since // lasso increments are usually very small, they are defined by just // a few points and they don't capture and/or release too many ink nodes. lasso = lassoUpdate; } // Skip those stroke which bounding box doesn't even intersects with the lasso bounds double hitWeightChange = 0f; if (lasso.Bounds.IntersectsWith(strokeInfo.StrokeBounds)) { // Get the stroke node points for the hit-testing. StylusPointCollection stylusPoints = strokeInfo.StylusPoints; // Find out if the lasso update has changed the hit count of the stroke. for (int i = 0; i < stylusPoints.Count; i++) { // Consider only the points that become captured/released with this particular update if (true == lasso.Contains((Point)stylusPoints[i])) { double weight = strokeInfo.GetPointWeight(i); if (lasso == _lasso || _lasso.Contains((Point)stylusPoints[i])) { hitWeightChange += weight; } else { hitWeightChange -= weight; } } } } // Update the stroke hit weight and check whether it has crossed the margin // in either direction since the last update. if ((hitWeightChange != 0) || (lasso == _lasso)) { strokeInfo.HitWeight = (lasso == _lasso) ? hitWeightChange : (strokeInfo.HitWeight + hitWeightChange); bool isHit = DoubleUtil.GreaterThanOrClose(strokeInfo.HitWeight, strokeInfo.TotalWeight * _percentIntersect / 100f - Stroke.PercentageTolerance); if (strokeInfo.IsHit != isHit) { strokeInfo.IsHit = isHit; if (isHit) { // The hit count became greater than the margin percentage, the stroke // needs to be reported for selection if (null == strokesHit) { strokesHit = new StrokeCollection(); } strokesHit.Add(strokeInfo.Stroke); #if TRACE System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the hit list. Current hit count = {0}", strokesHit.Count)); #endif } else { // The hit count just became less than the margin percentage, // the stroke needs to be reported for de-selection if (null == strokesUnhit) { strokesUnhit = new StrokeCollection(); } strokesUnhit.Add(strokeInfo.Stroke); #if TRACE System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the unhit list")); #endif } } } } _lasso.IsIncrementalLassoDirty = false; // Raise StrokesHitChanged event if any strokes has changed thier // hit status and there're the event subscribers. if ((null != strokesHit) || (null != strokesUnhit)) { OnSelectionChanged(new LassoSelectionChangedEventArgs (strokesHit, strokesUnhit)); } } /// /// SelectionChanged event raiser /// /// protected void OnSelectionChanged(LassoSelectionChangedEventArgs eventArgs) { System.Diagnostics.Debug.Assert(eventArgs != null); if (SelectionChanged != null) { SelectionChanged(this, eventArgs); } } #endregion #region Fields private Lasso _lasso; private int _percentIntersect; #endregion } #endregion #region IncrementalStrokeHitTester ////// IncrementalHitTester implementation for hit-testing with a shape, PointErasing . /// public class IncrementalStrokeHitTester : IncrementalHitTester { ////// /// public event StrokeHitEventHandler StrokeHit; #region C-tor and the overrides ////// C-tor /// /// strokes to hit-test for erasing /// erasing shape internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) : base(strokes) { System.Diagnostics.Debug.Assert(eraserShape != null); // Create an ErasingStroke objects that implements the actual hit-testing _erasingStroke = new ErasingStroke(eraserShape); } ////// The implementation behind the public methods AddPoint/AddPoints /// /// a set of points representing the last increment /// in the moving of the erasing shape protected override void AddPointsCore(IEnumerablepoints) { System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points) != 0)); System.Diagnostics.Debug.Assert(_erasingStroke != null); // Move the shape through the new points and build the contour of the move. _erasingStroke.MoveTo(points); Rect erasingBounds = _erasingStroke.Bounds; if (erasingBounds.IsEmpty) { return; } List strokeHitEventArgCollection = null; // Do nothing if there's nobody listening to the events if (StrokeHit != null) { List eraseAt = new List (); // Test stroke by stroke and collect the results. for (int x = 0; x < this.StrokeInfos.Count; x++) { StrokeInfo strokeInfo = this.StrokeInfos[x]; // Skip the stroke if its bounding box doesn't intersect with the one of the hitting shape. if ((erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) == false) || (_erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Stroke.DrawingAttributes), eraseAt) == false)) { continue; } // Create an event args to raise after done with hit-testing // We don't fire these events right away because user is expected to // modify the stroke collection in her event handler, and that would // invalidate this foreach loop. if (strokeHitEventArgCollection == null) { strokeHitEventArgCollection = new List (); } strokeHitEventArgCollection.Add(new StrokeHitEventArgs(strokeInfo.Stroke, eraseAt.ToArray())); // We must clear eraseAt or it will contain invalid results for the next strokes eraseAt.Clear(); } } // Raise StrokeHit event if needed. if (strokeHitEventArgCollection != null) { System.Diagnostics.Debug.Assert(strokeHitEventArgCollection.Count != 0); for (int x = 0; x < strokeHitEventArgCollection.Count; x++) { StrokeHitEventArgs eventArgs = strokeHitEventArgCollection[x]; System.Diagnostics.Debug.Assert(eventArgs.HitStroke != null); OnStrokeHit(eventArgs); } } } /// /// Event raiser for StrokeHit /// protected void OnStrokeHit(StrokeHitEventArgs eventArgs) { System.Diagnostics.Debug.Assert(eventArgs != null); if (StrokeHit != null) { StrokeHit(this, eventArgs); } } #endregion #region Fields private ErasingStroke _erasingStroke; #endregion } #endregion #region EventArgs and delegates ////// Declaration for LassoSelectionChanged event handler. Used in lasso-selection /// public delegate void LassoSelectionChangedEventHandler(object sender, LassoSelectionChangedEventArgs e); ////// Declaration for StrokeHit event handler. Used in point-erasing /// public delegate void StrokeHitEventHandler(object sender, StrokeHitEventArgs e); ////// Event arguments for LassoSelectionChanged event /// public class LassoSelectionChangedEventArgs : EventArgs { internal LassoSelectionChangedEventArgs(StrokeCollection selectedStrokes, StrokeCollection deselectedStrokes) { _selectedStrokes = selectedStrokes; _deselectedStrokes = deselectedStrokes; } ////// Collection of strokes which were hit with the last increment /// public StrokeCollection SelectedStrokes { get { if (_selectedStrokes != null) { StrokeCollection sc = new StrokeCollection(); sc.Add(_selectedStrokes); return sc; } else { return new StrokeCollection(); } } } ////// Collection of strokes which were unhit with the last increment /// public StrokeCollection DeselectedStrokes { get { if (_deselectedStrokes != null) { StrokeCollection sc = new StrokeCollection(); sc.Add(_deselectedStrokes); return sc; } else { return new StrokeCollection(); } } } private StrokeCollection _selectedStrokes; private StrokeCollection _deselectedStrokes; } ////// Event arguments for StrokeHit event /// public class StrokeHitEventArgs : EventArgs { ////// C-tor /// internal StrokeHitEventArgs(Stroke stroke, StrokeIntersection[] hitFragments) { System.Diagnostics.Debug.Assert(stroke != null && hitFragments != null && hitFragments.Length > 0); _stroke = stroke; _hitFragments = hitFragments; } ///Stroke that was hit public Stroke HitStroke { get { return _stroke; } } ////// /// ///public StrokeCollection GetPointEraseResults() { return _stroke.Erase(_hitFragments); } private Stroke _stroke; private StrokeIntersection[] _hitFragments; } #endregion } namespace MS.Internal.Ink { #region StrokeInfo /// /// A helper class associated with a stroke. Used for caching the stroke's /// bounding box, hit-testing results, and for keeping an eye on the stroke changes /// internal class StrokeInfo { #region API (used by incremental hit-testers) ////// StrokeInfo /// internal StrokeInfo(Stroke stroke) { System.Diagnostics.Debug.Assert(stroke != null); _stroke = stroke; _bounds = stroke.GetBounds(); // Start listening to the stroke events _stroke.DrawingAttributesChanged += new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); _stroke.StylusPointsReplaced += new StylusPointsReplacedEventHandler(OnStylusPointsReplaced); _stroke.StylusPoints.Changed += new EventHandler(OnStylusPointsChanged); _stroke.DrawingAttributesReplaced += new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced); } ///The stroke object associated with this helper structure internal Stroke Stroke { get { return _stroke; } } ///Pre-calculated bounds of the stroke internal Rect StrokeBounds { get { return _bounds; } } ///Tells whether the stroke or its drawing attributes have been modified /// since the last use (hit-testing) internal bool IsDirty { get { return _isDirty; } set { _isDirty = value; } } ///Tells whether the stroke was found (and reported) as hit internal bool IsHit { get { return _isHit; } set { _isHit = value; } } ////// Cache teh stroke points /// internal StylusPointCollection StylusPoints { get { if (_stylusPoints == null) { if (_stroke.DrawingAttributes.FitToCurve) { _stylusPoints = _stroke.GetBezierStylusPoints(); } else { _stylusPoints = _stroke.StylusPoints; } } return _stylusPoints; } } ////// Holds the current hit-testing result for the stroke. Represents the length of /// the stroke "inside" and "hit" by the lasso /// internal double HitWeight { get { return _hitWeight; } set { // it is ok to clamp this off, rounding error sends it over or under by a minimal amount. if (DoubleUtil.GreaterThan(value, TotalWeight)) { _hitWeight = TotalWeight; } else if (DoubleUtil.LessThan(value, 0f)) { _hitWeight = 0f; } else { _hitWeight = value; } } } ////// Get the total weight of the stroke. For this implementation, it is the total length of the stroke. /// ///internal double TotalWeight { get { if (!_totalWeightCached) { _totalWeight= 0; for (int i = 0; i < StylusPoints.Count; i++) { _totalWeight += this.GetPointWeight(i); } _totalWeightCached = true; } return _totalWeight; } } /// /// Calculate the weight of a point. /// internal double GetPointWeight(int index) { StylusPointCollection stylusPoints = this.StylusPoints; DrawingAttributes da = this.Stroke.DrawingAttributes; System.Diagnostics.Debug.Assert(stylusPoints != null && index >= 0 && index < stylusPoints.Count); double weight = 0f; if (index == 0) { weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; } else { Vector spine = (Point)stylusPoints[index] - (Point)stylusPoints[index - 1]; weight += Math.Sqrt(spine.LengthSquared) / 2.0f; } if (index == stylusPoints.Count - 1) { weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; } else { Vector spine = (Point)stylusPoints[index + 1] - (Point)stylusPoints[index]; weight += Math.Sqrt(spine.LengthSquared) / 2.0f; } return weight; } ////// A kind of disposing method /// internal void Detach() { if (_stroke != null) { // Detach the event handlers _stroke.DrawingAttributesChanged -= new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); _stroke.StylusPointsReplaced -= new StylusPointsReplacedEventHandler(OnStylusPointsReplaced); _stroke.StylusPoints.Changed -= new EventHandler(OnStylusPointsChanged); _stroke.DrawingAttributesReplaced -= new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced); _stroke = null; } } #endregion #region Stroke event handlers (Private) ///Event handler for stroke data changed events private void OnStylusPointsChanged(object sender, EventArgs args) { Invalidate(); } ///Event handler for stroke data changed events private void OnStylusPointsReplaced(object sender, StylusPointsReplacedEventArgs args) { Invalidate(); } ////// Event handler for stroke's drawing attributes changes. /// /// /// private void OnStrokeDrawingAttributesChanged(object sender, PropertyDataChangedEventArgs args) { // Only enforce rehittesting of the whole stroke when the DrawingAttribute change may affect hittesting if(DrawingAttributes.IsGeometricalDaGuid(args.PropertyGuid)) { Invalidate(); } } private void OnDrawingAttributesReplaced(Object sender, DrawingAttributesReplacedEventArgs args) { // If the drawing attributes change involves Width, Height, StylusTipTransform, IgnorePressure, or FitToCurve, // we need to invalidate if (false == DrawingAttributes.GeometricallyEqual(args.NewDrawingAttributes, args.PreviousDrawingAttributes)) { Invalidate(); } } ///Implementation for the event handlers above private void Invalidate() { _totalWeightCached = false; _stylusPoints = null; _hitWeight = 0; // Let the hit-tester know that it should not use incremental hit-testing _isDirty = true; // The Stroke.GetBounds may be overriden in the 3rd party code. // The out-side code could throw exception. If an exception is thrown, _bounds will keep the original value. // Re-compute the stroke bounds _bounds = _stroke.GetBounds(); } #endregion #region Fields private Stroke _stroke; private Rect _bounds; private double _hitWeight = 0f; private bool _isHit = false; private bool _isDirty = true; private StylusPointCollection _stylusPoints; // Cache the stroke rendering points private double _totalWeight = 0f; private bool _totalWeightCached = false; #endregion } #endregion // StrokeInfo } // The following code is for Stroke-Erasing scenario. Currently the IncrementalStrokeHitTester // can be used for Stroke-erasing but the following class is faster. If in the future there's a // perf issue with Stroke-Erasing, consider adding the following code. //#region Commented Code for IncrementalStrokeHitTester //#region IncrementalStrokeHitTester ////////// IncrementalHitTester implementation for hit-testing with a shape, StrokeErasing . ///// //public class IncrementalStrokeHitTester : IncrementalHitTester //{ // ///// /// event // /// // public event StrokesHitEventHandler StrokesHit; // #region C-tor and the overrides // ///// /// C-tor // /// // /// strokes to hit-test for erasing // /// erasing shape // internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) // : base(strokes) // { // System.Diagnostics.Debug.Assert(eraserShape != null); // // Create an ErasingStroke objects that implements the actual hit-testing // _erasingStroke = new ErasingStroke(eraserShape); // } // ///// /// // /// // /// // internal protected void OnStrokesHit(StrokesHitEventArgs eventArgs) // { // if (StrokesHit != null) // { // StrokesHit(this, eventArgs); // } // } // ///// /// The implementation behind the public methods AddPoint/AddPoints // /// // /// a set of points representing the last increment // /// in the moving of the erasing shape // internal protected override void AddPointsCore(Point[] points) // { // System.Diagnostics.Debug.Assert((points != null) && (points.Length != 0)); // System.Diagnostics.Debug.Assert(_erasingStroke != null); // // Move the shape through the new points and build the contour of the move. // _erasingStroke.MoveTo(points); // Rect erasingBounds = _erasingStroke.Bounds; // if (erasingBounds.IsEmpty) // { // return; // } // StrokeCollection strokesHit = null; // if (StrokesHit != null) // { // // Test stroke by stroke and collect hits. // foreach (StrokeInfo strokeInfo in StrokeInfos) // { // // Skip strokes that have already been reported hit or which bounds // // don't intersect with the bounds of the erasing stroke. // if ((strokeInfo.IsHit == false) && erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) // && _erasingStroke.HitTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Overrides))) // { // if (strokesHit == null) // { // strokesHit = new StrokeCollection(); // } // strokesHit.Add(strokeInfo.Stroke); // strokeInfo.IsHit = true; // } // } // } // // Raise StrokesHitChanged event if any strokes have been hit and there're listeners to the event. // if (strokesHit != null) // { // System.Diagnostics.Debug.Assert(strokesHit.Count != 0); // OnStrokesHit(new StrokesHitEventArgs(strokesHit)); // } // } // #endregion // #region Fields // private ErasingStroke _erasingStroke; // #endregion //} //#endregion ////////// Declaration for StrokesHit event handler. Used in stroke-erasing ///// //public delegate void StrokesHitEventHandler(object sender, StrokesHitEventArgs e); ////////// Event arguments for StrokesHit event ///// //public class StrokesHitEventArgs : EventArgs //{ // internal StrokesHitEventArgs(StrokeCollection hitStrokes) // { // System.Diagnostics.Debug.Assert(hitStrokes != null && hitStrokes.Count > 0); // _hitStrokes = hitStrokes; // } // ///// /// // /// // public StrokeCollection HitStrokes // { // get { return _hitStrokes; } // } // private StrokeCollection _hitStrokes; //} //#endregion // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- PropertyInformation.cs
- DataGridViewToolTip.cs
- Preprocessor.cs
- AuditLevel.cs
- complextypematerializer.cs
- IChannel.cs
- TemplateKey.cs
- ObjectConverter.cs
- Timeline.cs
- EdmType.cs
- ExtractorMetadata.cs
- FeatureSupport.cs
- ButtonFieldBase.cs
- DataControlField.cs
- GPPOINT.cs
- InvokeSchedule.cs
- DesignTimeValidationFeature.cs
- Size.cs
- XsdBuildProvider.cs
- ControlOperationBehavior.cs
- recordstatefactory.cs
- HostProtectionException.cs
- FileChangesMonitor.cs
- MimeAnyImporter.cs
- ObjectDataSourceDisposingEventArgs.cs
- Wildcard.cs
- SoapWriter.cs
- TransformValueSerializer.cs
- Validator.cs
- EntityDataSourceDesigner.cs
- RequestCachingSection.cs
- TextContainerHelper.cs
- HandleExceptionArgs.cs
- FontDriver.cs
- StylusEventArgs.cs
- CurrentChangingEventManager.cs
- JpegBitmapEncoder.cs
- TimerTable.cs
- DataErrorValidationRule.cs
- DecoratedNameAttribute.cs
- StringValidator.cs
- XmlImplementation.cs
- DbDataRecord.cs
- BuildProviderAppliesToAttribute.cs
- SymbolUsageManager.cs
- WebScriptClientGenerator.cs
- BindToObject.cs
- NegatedCellConstant.cs
- CommonXSendMessage.cs
- DesignTableCollection.cs
- Model3D.cs
- HwndHost.cs
- TableStyle.cs
- IdentityVerifier.cs
- GeneralTransform.cs
- WindowsGrip.cs
- ListDesigner.cs
- DataGridViewRowHeightInfoPushedEventArgs.cs
- AutomationElementIdentifiers.cs
- IPGlobalProperties.cs
- SiteMapProvider.cs
- QueryResults.cs
- HWStack.cs
- CapacityStreamGeometryContext.cs
- Splitter.cs
- MediaContext.cs
- ShortcutKeysEditor.cs
- DataGridView.cs
- FileDialog_Vista_Interop.cs
- shaperfactoryquerycacheentry.cs
- Sql8ExpressionRewriter.cs
- mansign.cs
- SecurityManager.cs
- ReachPrintTicketSerializerAsync.cs
- OnOperation.cs
- BinaryNegotiation.cs
- PerformanceCounter.cs
- SamlAuthenticationClaimResource.cs
- ButtonChrome.cs
- PrintingPermissionAttribute.cs
- ErrorTableItemStyle.cs
- XmlAutoDetectWriter.cs
- PeerChannelFactory.cs
- HtmlInputButton.cs
- VisualStyleElement.cs
- TagPrefixInfo.cs
- ProgressBarAutomationPeer.cs
- CacheMemory.cs
- PathHelper.cs
- OutputCacheSettingsSection.cs
- PartDesigner.cs
- TransactionContextValidator.cs
- StylusShape.cs
- RecipientInfo.cs
- SmtpClient.cs
- FrugalMap.cs
- Transactions.cs
- XmlQueryCardinality.cs
- InvalidOperationException.cs
- SessionPageStateSection.cs