| File: MS\Internal\Ink\EraserBehavior.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: // ErasingBehavior for Ink // Features: // using System.Windows.Input; using System.Windows; using System.Windows.Controls; using System.Windows.Ink; using System.Windows.Media; namespace MS.Internal.Ink { /// <summary> /// Eraser Behavior /// </summary> internal sealed class EraserBehavior : StylusEditingBehavior { //------------------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------------------- #region Constructors internal EraserBehavior(EditingCoordinator editingCoordinator, InkCanvas inkCanvas) : base(editingCoordinator, inkCanvas) { } #endregion Constructors //------------------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------------------- #region Protected Methods /// <summary> /// Overrides SwitchToMode as the following expectations /// 19. From EraseByPoint To InkAndGesture /// After mode change ink is being collected. On StylusUp gesture event fires. If it’s not a gesture, StrokeCollected event fires. /// 20. From EraseByPoint To GestureOnly /// After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if it’s not a gesture. /// 21. From EraseByPoint To Ink /// Ink collection starts when changing the mode. /// 22. From EraseByPoint To EraseByStroke /// After mode change StrokeErasing is performed. /// 23. From EraseByPoint To Select /// Lasso is drawn for all packets between StylusDown and StylusUp. Strokes/elements within the lasso will be selected. /// 24. From EraseByPoint To None /// No erasing is performed after mode change. /// 25. From EraseByStroke To InkAndGesture /// After mode change ink is being collected. On StylusUp gesture event fires. If it’s not a gesture, StrokeCollected event fires. /// 26. From EraseByStroke To GestureOnly /// After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if it’s not a gesture. /// 27. From EraseByStroke To EraseByPoint /// After mode change PointErasing is performed. /// 28. From EraseByStroke To Ink /// Ink collection starts when changing the mode. /// 29. From EraseByStroke To Select /// Lasso is drawn for all packets between StylusDown and StylusUp. Strokes/elements within the lasso will be selected /// 30. From EraseByStroke To None /// No erasing is performed after mode change. /// </summary> /// <param name="mode"></param> 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: { // Commit the current behavior Commit(true); // Change the mode. The dynamic renderer will be reset automatically. EditingCoordinator.ChangeStylusEditingMode(this, mode); break; } case InkCanvasEditingMode.EraseByPoint: case InkCanvasEditingMode.EraseByStroke: { Debug.Assert(_cachedEraseMode != mode); // Commit the current behavior Commit(true); // Change the mode EditingCoordinator.ChangeStylusEditingMode(this, mode); break; } case InkCanvasEditingMode.Select: { // Make a copy of the current cached points. StylusPointCollection cachedPoints = _stylusPoints?.Clone(); // Commit the current behavior. Commit(true); // Change the Select mode IStylusEditing newBehavior = EditingCoordinator.ChangeStylusEditingMode(this, mode); if ( cachedPoints != null // NOTICE-2006/04/27-WAYNEZEN, // EditingCoordinator.ChangeStylusEditingMode raises external event. // The user code could take any arbitrary action for instance calling InkCanvas.ReleaseMouseCapture() // So there is no guarantee that we could receive the newBehavior. && newBehavior != null ) { // Now add the previous points to the lasso behavior newBehavior.AddStylusPoints(cachedPoints, false/*userInitiated*/); } break; } case InkCanvasEditingMode.None: { // Discard the collected ink. Commit(true); // Change to the None mode EditingCoordinator.ChangeStylusEditingMode(this, mode); break; } default: Debug.Fail("Unknown InkCanvasEditingMode!"); break; } } protected override void OnActivate() { base.OnActivate(); InkCanvasEditingMode newEraseMode = EditingCoordinator.ActiveEditingMode; Debug.Assert(newEraseMode == InkCanvasEditingMode.EraseByPoint || newEraseMode == InkCanvasEditingMode.EraseByStroke); // Check whether we have to update cursor. if ( _cachedEraseMode != newEraseMode ) { // EraseMode is changed _cachedEraseMode = newEraseMode; EditingCoordinator.InvalidateBehaviorCursor(this); } else if ( newEraseMode == InkCanvasEditingMode.EraseByPoint ) { // Invalidate the PointEraser if we don't have the cache yet. bool isPointEraserCursorValid = _cachedStylusShape != null; // // If the cached EraserShape is different from the current EraserShape, we shoud just reset the cache. // The new cursor will be generated when it's needed later. if ( isPointEraserCursorValid && ( _cachedStylusShape.Width != InkCanvas.EraserShape.Width || _cachedStylusShape.Height != InkCanvas.EraserShape.Height || _cachedStylusShape.Rotation != InkCanvas.EraserShape.Rotation || _cachedStylusShape.GetType() != InkCanvas.EraserShape.GetType()) ) { Debug.Assert(_cachedPointEraserCursor != null, "_cachedPointEraserCursor shouldn't be null."); ResetCachedPointEraserCursor(); isPointEraserCursorValid = false; } if ( !isPointEraserCursorValid ) { // EraserShape is changed when the new mode is EraseByPoint EditingCoordinator.InvalidateBehaviorCursor(this); } } } /// <summary> /// StylusInputBegin /// </summary> /// <param name="stylusPoints">stylusPoints</param> /// <param name="userInitiated">true if the source eventArgs.UserInitiated flag was set to true</param> protected override void StylusInputBegin(StylusPointCollection stylusPoints, bool userInitiated) { // // get a disposable dynamic hit-tester and add event handler // _incrementalStrokeHitTester = this.InkCanvas.Strokes.GetIncrementalStrokeHitTester(this.InkCanvas.EraserShape); if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode ) { _incrementalStrokeHitTester.StrokeHit += new StrokeHitEventHandler(OnPointEraseResultChanged); } else { //we're in stroke hit test mode _incrementalStrokeHitTester.StrokeHit += new StrokeHitEventHandler(OnStrokeEraseResultChanged); } _stylusPoints = new StylusPointCollection(stylusPoints.Description, 100); _stylusPoints.Add(stylusPoints); // // start erasing // _incrementalStrokeHitTester.AddPoints(stylusPoints); // // Since InkCanvas will ignore the animated tranforms when it receives the property changes. // So we should update our cursor when the stylus is down if there are animated transforms applied to InkCanvas. if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode ) { // Call InvalidateBehaviorCursor at the end of the routine. The method will cause an external event fired. // So it should be invoked after we set up our states. EditingCoordinator.InvalidateBehaviorCursor(this); } } /// <summary> /// StylusInputContinue /// </summary> /// <param name="stylusPoints">stylusPoints</param> /// <param name="userInitiated">true if the source eventArgs.UserInitiated flag was set to true</param> protected override void StylusInputContinue(StylusPointCollection stylusPoints, bool userInitiated) { _stylusPoints.Add(stylusPoints); _incrementalStrokeHitTester.AddPoints(stylusPoints); } /// <summary> /// StylusInputEnd /// </summary> /// <param name="commit">commit</param> protected override void StylusInputEnd(bool commit) { if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode ) { _incrementalStrokeHitTester.StrokeHit-= new StrokeHitEventHandler(OnPointEraseResultChanged); } else { //we're in stroke hit test mode _incrementalStrokeHitTester.StrokeHit -= new StrokeHitEventHandler(OnStrokeEraseResultChanged); } _stylusPoints = null; // // Don't forget ending the current incremental hit testing. _incrementalStrokeHitTester.EndHitTesting(); _incrementalStrokeHitTester = null; } /// <summary> /// Get eraser cursor. /// </summary> /// <returns></returns> protected override Cursor GetCurrentCursor() { if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode ) { if ( _cachedPointEraserCursor == null ) { _cachedStylusShape = InkCanvas.EraserShape; // // The eraser cursor should respect the InkCanvas' Transform properties as the pen tip. Matrix xf = GetElementTransformMatrix(); if ( !xf.IsIdentity ) { // Zero the offsets if the element's transform in invertable. // Otherwise fallback the matrix to the identity. if ( xf.HasInverse ) { xf.OffsetX = 0; xf.OffsetY = 0; } else { xf = Matrix.Identity; } } DpiScale dpi = this.InkCanvas.GetDpi(); _cachedPointEraserCursor = PenCursorManager.GetPointEraserCursor(_cachedStylusShape, xf, dpi.DpiScaleX, dpi.DpiScaleY); } return _cachedPointEraserCursor; } else { return PenCursorManager.GetStrokeEraserCursor(); } } /// <summary> /// ApplyTransformToCursor /// </summary> protected override void OnTransformChanged() { // Reset the cached point eraser cursor. ResetCachedPointEraserCursor(); } #endregion Protected Methods //------------------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------------------- #region Private Methods /// <summary> /// Reset the cached point eraser cursor. /// </summary> private void ResetCachedPointEraserCursor() { _cachedPointEraserCursor = null; _cachedStylusShape = null; } /// <summary> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnStrokeEraseResultChanged(object sender, StrokeHitEventArgs e) { Debug.Assert(null != e.HitStroke); bool fSucceeded = false; // The below code calls out StrokeErasing or StrokeErased event. // The out-side code could throw exception. // We use try/finally block to protect our status. try { InkCanvasStrokeErasingEventArgs args = new InkCanvasStrokeErasingEventArgs(e.HitStroke); this.InkCanvas.RaiseStrokeErasing(args); if ( !args.Cancel ) { // Erase only if the event wasn't cancelled InkCanvas.Strokes.Remove(e.HitStroke); this.InkCanvas.RaiseInkErased(); } fSucceeded = true; } finally { if ( !fSucceeded ) { // Abort the editing. Commit(false); } } } /// <summary> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnPointEraseResultChanged(object sender, StrokeHitEventArgs e) { Debug.Assert(null != e.HitStroke, "e.HitStroke cannot be null"); bool fSucceeded = false; // The below code might call out StrokeErasing or StrokeErased event. // The out-side code could throw exception. // We use try/finally block to protect our status. try { InkCanvasStrokeErasingEventArgs args = new InkCanvasStrokeErasingEventArgs(e.HitStroke); this.InkCanvas.RaiseStrokeErasing(args); if ( !args.Cancel ) { // Erase only if the event wasn't cancelled StrokeCollection eraseResult = e.GetPointEraseResults(); Debug.Assert(eraseResult != null, "eraseResult cannot be null"); StrokeCollection strokesToReplace = new StrokeCollection(); strokesToReplace.Add(e.HitStroke); try { // replace or remove the stroke if (eraseResult.Count > 0) { this.InkCanvas.Strokes.Replace(strokesToReplace, eraseResult); } else { this.InkCanvas.Strokes.Remove(strokesToReplace); } } catch (ArgumentException ex) { //this can happen if someone sits in an event handler //for StrokeErasing and removes the stroke. //this to harden against failure here. if (!ex.Data.Contains("System.Windows.Ink.StrokeCollection")) { //System.Windows.Ink.StrokeCollection didn't throw this, //we need to just throw the original exception throw; } } //raise ink erased this.InkCanvas.RaiseInkErased(); } fSucceeded = true; } finally { if ( !fSucceeded ) { // Abort the editing. Commit(false); } } } #endregion Private Methods //------------------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------------------- #region Private Fields private InkCanvasEditingMode _cachedEraseMode; private IncrementalStrokeHitTester _incrementalStrokeHitTester; private Cursor _cachedPointEraserCursor; private StylusShape _cachedStylusShape; private StylusPointCollection _stylusPoints = null; #endregion Private Fields } }