Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Controls / InkCanvas.cs / 1 / InkCanvas.cs
//#define DEBUG_LASSO_FEEDBACK // DO NOT LEAVE ENABLED IN CHECKED IN CODE //---------------------------------------------------------------------------- // // File: InkCanvas.cs // // Description: // Defines an inkable canvas that represents the primary api for // editing ink // // Features: // // History: // 1/29/2002 samgeo: Created // 9/12/2003 samgeo: Started porting to WCP // // Copyright (C) 2001 by Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using MS.Utility; using MS.Internal; using MS.Internal.Commands; using MS.Internal.Controls; using MS.Internal.Ink; using MS.Internal.KnownBoxes; using MS.Internal.PresentationFramework; using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.IO; using System.Windows; using System.Collections.Generic; using System.Security; using System.Security.Permissions; using System.Runtime.InteropServices; using System.Windows.Media; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Input.StylusPlugIns; using System.Windows.Controls; using System.Windows.Markup; // IAddChild, ContentPropertyAttribute using System.Windows.Threading; using System.Windows.Automation.Peers; namespace System.Windows.Controls { ////// InkCanvas is used to allow inking on a canvas /// [ContentProperty("Children")] public class InkCanvas : FrameworkElement, IAddChild { #region Constructors / Initialization ////// The static constructor /// static InkCanvas() { Type ownerType = typeof(InkCanvas); // NTRAID-WINDOWS#1423922-2005/12/15-WAYNEZEN, // We should add the following listener as the class handler which will be guarantied to receive the // notification before the handler on the instances. So we won't be trapped in the bad state due to the // event routing. // Listen to stylus events which will be redirected to the current StylusEditingBehavior //Down EventManager.RegisterClassHandler(ownerType, Stylus.StylusDownEvent, new StylusDownEventHandler(_OnDeviceDown)); EventManager.RegisterClassHandler(ownerType, Mouse.MouseDownEvent, new MouseButtonEventHandler(_OnDeviceDown )); //Up EventManager.RegisterClassHandler(ownerType, Stylus.StylusUpEvent, new StylusEventHandler(_OnDeviceUp )); EventManager.RegisterClassHandler(ownerType, Mouse.MouseUpEvent, new MouseButtonEventHandler(_OnDeviceUp )); EventManager.RegisterClassHandler(ownerType, Mouse.QueryCursorEvent, new QueryCursorEventHandler(_OnQueryCursor), true); // Set up the commanding handlers _RegisterClipboardHandlers(); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Delete, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled)); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.SelectAll, Key.A, ModifierKeys.Control, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled)); CommandHelpers.RegisterCommandHandler(ownerType, InkCanvas.DeselectCommand, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.InkCanvasDeselectKey, SRID.InkCanvasDeselectKeyDisplayString); // //set our clipping // ClipToBoundsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(BooleanBoxes.TrueBox)); // //enable input focus // FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(BooleanBoxes.TrueBox)); // The default InkCanvas style Style defaultStyle = new Style(ownerType); // The background - Window Color defaultStyle.Setters.Add(new Setter(InkCanvas.BackgroundProperty, new DynamicResourceExtension(SystemColors.WindowBrushKey))); // Default InkCanvas to having flicks disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsFlicksEnabledProperty, false)); // Default InkCanvas to having tap feedback disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsTapFeedbackEnabledProperty, false)); // Default InkCanvas to having touch feedback disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsTouchFeedbackEnabledProperty, false)); // Set MinWidth to 350d if Width is set to Auto Trigger trigger = new Trigger(); trigger.Property = WidthProperty; trigger.Value = double.NaN; Setter setter = new Setter(); setter.Property = MinWidthProperty; setter.Value = 350d; trigger.Setters.Add(setter); defaultStyle.Triggers.Add(trigger); // Set MinHeight to 250d if Height is set to Auto trigger = new Trigger(); trigger.Property = HeightProperty; trigger.Value = double.NaN; setter = new Setter(); setter.Property = MinHeightProperty; setter.Value = 250d; trigger.Setters.Add(setter); defaultStyle.Triggers.Add(trigger); // Seal the default style defaultStyle.Seal(); StyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(defaultStyle)); DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(InkCanvas))); FocusVisualStyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata((object)null /* default value */)); } /// /// Public constructor. /// public InkCanvas() : base() { Initialize(); } ////// Private initialization method used by the constructors /// private void Initialize() { // // instance the DynamicRenderer and add it to the StylusPlugIns // _dynamicRenderer = new DynamicRenderer(); _dynamicRenderer.Enabled = false; this.StylusPlugIns.Add(_dynamicRenderer); // // create and initialize an editing coordinator // _editingCoordinator = new EditingCoordinator(this); _editingCoordinator.UpdateActiveEditingState(); // connect the attributes event handler after setting the stylus shape to avoid unnecessary // calls into the RTI service DefaultDrawingAttributes.AttributeChanged += new PropertyDataChangedEventHandler(DefaultDrawingAttributes_Changed); // // // We must initialize this here (after adding DynamicRenderer to Sytlus). // this.InitializeInkObject(); _rtiHighContrastCallback = new RTIHighContrastCallback(this); // Register rti high contrast callback. Then check whether we are under the high contrast already. HighContrastHelper.RegisterHighContrastCallback(_rtiHighContrastCallback); if ( SystemParameters.HighContrast ) { _rtiHighContrastCallback.TurnHighContrastOn(SystemColors.WindowTextColor); } } ////// Private helper used to change the Ink objects. Used in the constructor /// and the Ink property. /// /// NOTE -- Caller is responsible for clearing any selection! (We can't clear it /// here because the Constructor calls this method and it would end up calling /// looking like it could call a virtual method and FxCop doesn't like that!) /// /// private void InitializeInkObject() { // Update the RealTimeInking PlugIn for the Renderer changes. UpdateDynamicRenderer(); // Initialize DefaultPacketDescription _defaultStylusPointDescription = new StylusPointDescription(); } #endregion Constructors / Initialization #region Protected Overrides ////// MeasureOverride /// /// ///protected override Size MeasureOverride(Size availableSize) { // No need to invoke VerifyAccess since _localAdornerDecorator.Measure should check it. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } _localAdornerDecorator.Measure(availableSize); return _localAdornerDecorator.DesiredSize; } /// /// ArrangeOverride /// /// ///protected override Size ArrangeOverride(Size arrangeSize) { // No need to invoke VerifyAccess since _localAdornerDecorator.Arrange should check it. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } _localAdornerDecorator.Arrange(new Rect(arrangeSize)); return arrangeSize; } /// /// HitTestCore implements precise hit testing against render contents /// protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParams) { VerifyAccess(); Rect r = new Rect(new Point(), RenderSize); if (r.Contains(hitTestParams.HitPoint)) { return new PointHitTestResult(this, hitTestParams.HitPoint); } return null; } ////// OnPropertyChanged /// protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.IsAValueChange || e.IsASubPropertyChange) { if (e.Property == UIElement.RenderTransformProperty || e.Property == FrameworkElement.LayoutTransformProperty) { EditingCoordinator.InvalidateTransform(); Transform transform = e.NewValue as Transform; if (transform != null && !transform.HasAnimatedProperties) { TransformGroup transformGroup = transform as TransformGroup; if ( transformGroup != null ) { //walk down the tree looking for animated transforms Stacktransforms = new Stack (); transforms.Push(transform); while ( transforms.Count > 0 ) { transform = transforms.Pop(); if ( transform.HasAnimatedProperties ) { return; } transformGroup = transform as TransformGroup; if ( transformGroup != null ) { for ( int i = 0; i < transformGroup.Children.Count; i++ ) { transforms.Push(transformGroup.Children[i]); } } } } // // only invalidate when there is not an animation on the xf, // or we could wind up creating thousands of new cursors. That's bad. // _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); EditingCoordinator.UpdatePointEraserCursor(); } } if (e.Property == FrameworkElement.FlowDirectionProperty) { //flow direction only affects the inking cursor. _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } } } /// /// Called when the Template's tree is about to be generated /// internal override void OnPreApplyTemplate() { // No need for calling VerifyAccess since we call the method on the base here. base.OnPreApplyTemplate(); // Build our visual tree here. //// if ( _localAdornerDecorator == null ) { // _localAdornerDecorator = new AdornerDecorator(); InkPresenter inkPresenter = InkPresenter; // Build the visual tree top-down AddVisualChild(_localAdornerDecorator); _localAdornerDecorator.Child = inkPresenter; inkPresenter.Child = InnerCanvas; // Add the SelectionAdorner after Canvas is added. _localAdornerDecorator.AdornerLayer.Add(SelectionAdorner); } } ///// //// //// // // // //// // /// Returns the Visual children count. /// protected override int VisualChildrenCount { get { return (_localAdornerDecorator == null) ? 0 : 1; } } ////// Returns the child at the specified index. /// protected override Visual GetVisualChild(int index) { if ( (_localAdornerDecorator == null) || (index != 0)) { throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)); } return _localAdornerDecorator; } ////// UIAutomation support /// protected override AutomationPeer OnCreateAutomationPeer() { return new InkCanvasAutomationPeer(this); } #endregion Protected Overrides #region Public Properties ////// The DependencyProperty for the Background property. /// public static readonly DependencyProperty BackgroundProperty = Panel.BackgroundProperty.AddOwner( typeof(InkCanvas), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender)); ////// An object that describes the background. /// [Bindable(true), Category("Appearance")] public Brush Background { get { return (Brush) GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } } ////// Top DependencyProperty /// public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Top from the given element. /// /// The element from which to read the Top attached property. ///The property's value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetTop(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(TopProperty); } /// /// Writes the attached property Top to the given element. /// /// The element to which to write the Top attached property. /// The length to set ///public static void SetTop(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TopProperty, length); } /// /// The Bottom DependencyProperty /// public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached("Bottom", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Bottom from the given element. /// /// The element from which to read the Bottom attached property. ///The property's Length value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetBottom(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(BottomProperty); } /// /// Writes the attached property Bottom to the given element. /// /// The element to which to write the Bottom attached property. /// The Length to set ///public static void SetBottom(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(BottomProperty, length); } /// /// The Left DependencyProperty /// public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Left from the given element. /// /// The element from which to read the Left attached property. ///The property's value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetLeft(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(LeftProperty); } /// /// Writes the attached property Left to the given element. /// /// The element to which to write the Left attached property. /// The length to set ///public static void SetLeft(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(LeftProperty, length); } /// /// The Right DependencyProperty /// public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached("Right", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Right from the given element. /// /// The element from which to read the Right attached property. ///The property's Length value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetRight(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(RightProperty); } /// /// Writes the attached property Right to the given element. /// /// The element to which to write the Right attached property. /// The Length to set ///public static void SetRight(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(RightProperty, length); } /// /// OnPositioningChanged /// /// /// private static void OnPositioningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UIElement uie = d as UIElement; if ( uie != null ) { // Make sure the UIElement is a child of InkCanvasInnerCanvas. InkCanvasInnerCanvas p = VisualTreeHelper.GetParent(uie) as InkCanvasInnerCanvas; if ( p != null ) { if ( e.Property == InkCanvas.LeftProperty || e.Property == InkCanvas.TopProperty ) { // Invalidate measure for Left and/or Top. p.InvalidateMeasure(); } else { Debug.Assert(e.Property == InkCanvas.RightProperty || e.Property == InkCanvas.BottomProperty, string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unknown dependency property detected - {0}.", e.Property)); // Invalidate arrange for Right and/or Bottom. p.InvalidateArrange(); } } } } ////// The DependencyProperty for the Strokes property. /// public static readonly DependencyProperty StrokesProperty = InkPresenter.StrokesProperty.AddOwner( typeof(InkCanvas), new FrameworkPropertyMetadata( new StrokeCollectionDefaultValueFactory(), new PropertyChangedCallback(OnStrokesChanged))); ////// Gets/Sets the Strokes property. /// public StrokeCollection Strokes { get { return (StrokeCollection)GetValue(StrokesProperty); } set { SetValue(StrokesProperty, value); } } private static void OnStrokesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { InkCanvas inkCanvas = (InkCanvas)d; StrokeCollection oldValue = (StrokeCollection)e.OldValue; StrokeCollection newValue = (StrokeCollection)e.NewValue; // // only change the prop if it's a different object. We don't // want to be doing this for no reason // if ( !object.ReferenceEquals(oldValue, newValue) ) { // Clear the selected strokes without raising event. inkCanvas.CoreChangeSelection(new StrokeCollection(), inkCanvas.InkCanvasSelection.SelectedElements, false); inkCanvas.InitializeInkObject(); InkCanvasStrokesReplacedEventArgs args = new InkCanvasStrokesReplacedEventArgs(newValue, oldValue); //new, previous //raise the StrokesChanged event through our protected virtual inkCanvas.OnStrokesReplaced(args); } } ////// Returns the SelectionAdorner /// internal InkCanvasSelectionAdorner SelectionAdorner { get { // We have to create our visual at this point. if ( _selectionAdorner == null ) { // Create the selection Adorner. _selectionAdorner = new InkCanvasSelectionAdorner(InnerCanvas); // Bind the InkCanvas.ActiveEditingModeProperty // to SelectionAdorner.VisibilityProperty. Binding activeEditingModeBinding = new Binding(); activeEditingModeBinding.Path = new PropertyPath(InkCanvas.ActiveEditingModeProperty); activeEditingModeBinding.Mode = BindingMode.OneWay; activeEditingModeBinding.Source = this; activeEditingModeBinding.Converter = new ActiveEditingMode2VisibilityConverter(); _selectionAdorner.SetBinding(UIElement.VisibilityProperty, activeEditingModeBinding); } return _selectionAdorner; } } ////// Returns the FeedbackAdorner /// internal InkCanvasFeedbackAdorner FeedbackAdorner { get { VerifyAccess(); if ( _feedbackAdorner == null ) { _feedbackAdorner = new InkCanvasFeedbackAdorner(this); } return _feedbackAdorner; } } ////// Read/Write access to the EraserShape property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsGestureRecognizerAvailable { get { //this property will verify access return this.GestureRecognizer.IsRecognizerAvailable; } } ////// Emulate Panel's Children property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public UIElementCollection Children { get { // No need to invoke VerifyAccess since the call is forwarded. return InnerCanvas.Children; } } ////// The DependencyProperty for the DefaultDrawingAttributes property. /// public static readonly DependencyProperty DefaultDrawingAttributesProperty = DependencyProperty.Register( "DefaultDrawingAttributes", typeof(DrawingAttributes), typeof(InkCanvas), new FrameworkPropertyMetadata( new DrawingAttributesDefaultValueFactory(), new PropertyChangedCallback(OnDefaultDrawingAttributesChanged)), (ValidateValueCallback)delegate(object value) { return value != null; }); ////// Gets/Sets the DefaultDrawingAttributes property. /// public DrawingAttributes DefaultDrawingAttributes { get { return (DrawingAttributes)GetValue(DefaultDrawingAttributesProperty); } set { SetValue(DefaultDrawingAttributesProperty, value); } } private static void OnDefaultDrawingAttributesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { InkCanvas inkCanvas = (InkCanvas)d; DrawingAttributes oldValue = (DrawingAttributes)e.OldValue; DrawingAttributes newValue = (DrawingAttributes)e.NewValue; // This can throw, so call it first inkCanvas.UpdateDynamicRenderer(newValue); // We only fire Changed event when there is an instance change. if ( !object.ReferenceEquals(oldValue, newValue) ) { //we didn't throw, change our backing value oldValue.AttributeChanged -= new PropertyDataChangedEventHandler(inkCanvas.DefaultDrawingAttributes_Changed); DrawingAttributesReplacedEventArgs args = new DrawingAttributesReplacedEventArgs(newValue, oldValue); newValue.AttributeChanged += new PropertyDataChangedEventHandler(inkCanvas.DefaultDrawingAttributes_Changed); inkCanvas.RaiseDefaultDrawingAttributeReplaced(args); } } ////// Read/Write access to the EraserShape property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public StylusShape EraserShape { get { VerifyAccess(); if (_eraserShape == null) { _eraserShape = new RectangleStylusShape(8f, 8f); } return _eraserShape; } set { VerifyAccess(); if (value == null) { throw new ArgumentNullException("value"); } else { // Invoke getter since this property is lazily created. StylusShape oldShape = EraserShape; _eraserShape = value; if ( oldShape.Width != _eraserShape.Width || oldShape.Height != _eraserShape.Height || oldShape.Rotation != _eraserShape.Rotation || oldShape.GetType() != _eraserShape.GetType()) { EditingCoordinator.UpdatePointEraserCursor(); } } } } ////// ActiveEditingMode /// internal static readonly DependencyPropertyKey ActiveEditingModePropertyKey = DependencyProperty.RegisterReadOnly( "ActiveEditingMode", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata(InkCanvasEditingMode.Ink)); ////// ActiveEditingModeProperty Dependency Property /// public static readonly DependencyProperty ActiveEditingModeProperty = ActiveEditingModePropertyKey.DependencyProperty; ////// Gets the ActiveEditingMode /// public InkCanvasEditingMode ActiveEditingMode { get { return (InkCanvasEditingMode)GetValue(ActiveEditingModeProperty); } } ////// The DependencyProperty for the EditingMode property. /// public static readonly DependencyProperty EditingModeProperty = DependencyProperty.Register( "EditingMode", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata( InkCanvasEditingMode.Ink, new PropertyChangedCallback(OnEditingModeChanged)), new ValidateValueCallback(ValidateEditingMode)); ////// Gets/Sets EditingMode /// public InkCanvasEditingMode EditingMode { get { return (InkCanvasEditingMode)GetValue(EditingModeProperty); } set { SetValue(EditingModeProperty, value); } } private static void OnEditingModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ( (InkCanvas)d ).RaiseEditingModeChanged( new RoutedEventArgs(InkCanvas.EditingModeChangedEvent, d)); } ////// The DependencyProperty for the EditingModeInverted property. /// public static readonly DependencyProperty EditingModeInvertedProperty = DependencyProperty.Register( "EditingModeInverted", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata( InkCanvasEditingMode.EraseByStroke, new PropertyChangedCallback(OnEditingModeInvertedChanged)), new ValidateValueCallback(ValidateEditingMode)); ////// Gets/Sets EditingMode /// public InkCanvasEditingMode EditingModeInverted { get { return (InkCanvasEditingMode)GetValue(EditingModeInvertedProperty); } set { SetValue(EditingModeInvertedProperty, value); } } private static void OnEditingModeInvertedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ( (InkCanvas)d ).RaiseEditingModeInvertedChanged( new RoutedEventArgs(InkCanvas.EditingModeInvertedChangedEvent, d)); } private static bool ValidateEditingMode(object value) { return EditingModeHelper.IsDefined((InkCanvasEditingMode)value); } ////// This flag indicates whether the developer is using a custom mouse cursor. /// /// If this flag is true, we will never change the current cursor on them. Not /// on edit mode change. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool UseCustomCursor { get { VerifyAccess(); return _useCustomCursor; } set { VerifyAccess(); if ( _useCustomCursor != value ) { _useCustomCursor = value; UpdateCursor(); } } } ////// Gets or set if moving of selection is enabled /// ///bool public bool MoveEnabled { get { VerifyAccess(); return _editingCoordinator.MoveEnabled; } set { VerifyAccess(); bool oldValue = _editingCoordinator.MoveEnabled; if (oldValue != value) { _editingCoordinator.MoveEnabled = value; } } } ////// Gets or set if resizing selection is enabled /// ///bool public bool ResizeEnabled { get { VerifyAccess(); return _editingCoordinator.ResizeEnabled; } set { VerifyAccess(); bool oldValue = _editingCoordinator.ResizeEnabled; if (oldValue != value) { _editingCoordinator.ResizeEnabled = value; } } } ////// Read/Write access to the DefaultPacketDescription property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public StylusPointDescription DefaultStylusPointDescription { get { VerifyAccess(); return _defaultStylusPointDescription; } set { VerifyAccess(); // // no nulls allowed // if ( value == null ) { throw new ArgumentNullException("value"); } _defaultStylusPointDescription = value; } } ////// Read/Write the enabled ClipboardFormats /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IEnumerablePreferredPasteFormats { get { VerifyAccess(); return ClipboardProcessor.PreferredFormats; } set { VerifyAccess(); // Cannot be null if ( value == null ) { // Null is not allowed as the argument value throw new ArgumentNullException("value"); } ClipboardProcessor.PreferredFormats = value; } } #endregion Public Properties #region Public Events /// /// The StrokeErased Routed Event /// public static readonly RoutedEvent StrokeCollectedEvent = EventManager.RegisterRoutedEvent("StrokeCollected", RoutingStrategy.Bubble, typeof(InkCanvasStrokeCollectedEventHandler), typeof(InkCanvas)); ////// Add / Remove StrokeCollected handler /// [Category("Behavior")] public event InkCanvasStrokeCollectedEventHandler StrokeCollected { add { AddHandler(InkCanvas.StrokeCollectedEvent, value); } remove { RemoveHandler(InkCanvas.StrokeCollectedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokeCollectedEventArgs to raise the event with protected virtual void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Allows the InkCollectionBehavior to raise the StrokeCollected event via the protected virtual /// /// InkCanvasStrokeCollectedEventArgs to raise the event with /// true only if 100% of the stylusPoints that makes up the stroke /// came from eventargs with the UserInitiated flag set to true ////// Critical: Calls critical method GestureRecognizer.CriticalRecognize. It is important /// that this is only called if userInitiated is true. /// [SecurityCritical] internal void RaiseGestureOrStrokeCollected(InkCanvasStrokeCollectedEventArgs e, bool userInitiated) { Debug.Assert(e != null, "EventArg can not be null"); bool addStrokeToInkCanvas = true; // Initialize our flag. // The follow code raises Gesture event // The out-side code could throw exception in the their handlers. We use try/finally block to protect our status. try { // // perform gesture reco before raising this event // if we're in the right mode // //IMPORTANT: only call gesture recognition if userInitiated. See SecurityNote. if (userInitiated) { if ((this.ActiveEditingMode == InkCanvasEditingMode.InkAndGesture || this.ActiveEditingMode == InkCanvasEditingMode.GestureOnly) && this.GestureRecognizer.IsRecognizerAvailable) { StrokeCollection strokes = new StrokeCollection(); strokes.Add(e.Stroke); // // GestureRecognizer.Recognize demands unmanaged code, we assert it here // as this codepath is only called in response to user input // ReadOnlyCollectionresults = this.GestureRecognizer.CriticalRecognize(strokes); if (results.Count > 0) { InkCanvasGestureEventArgs args = new InkCanvasGestureEventArgs(strokes, results); if (results[0].ApplicationGesture == ApplicationGesture.NoGesture) { // // we set Cancel=true if we didn't detect a gesture // args.Cancel = true; } else { args.Cancel = false; } this.OnGesture(args); // // now that we've raised the Gesture event and the developer // has had a chance to change args.Cancel, see what their intent is. // if (args.Cancel == false) { //bail out and don't add //the stroke to InkCanvas.Strokes addStrokeToInkCanvas = false; // Reset the flag. return; } } } } // Reset the flag. addStrokeToInkCanvas = false; // // only raise StrokeCollected if we're in InkCanvasEditingMode.Ink or InkCanvasEditingMode.InkAndGesture // if ( this.ActiveEditingMode == InkCanvasEditingMode.Ink || this.ActiveEditingMode == InkCanvasEditingMode.InkAndGesture ) { //add the stroke to the StrokeCollection and raise this event this.Strokes.Add(e.Stroke); this.OnStrokeCollected(e); } } finally { // If the gesture events are failed, we should still add Stroke to the InkCanvas so that the data won't be lost. if ( addStrokeToInkCanvas ) { this.Strokes.Add(e.Stroke); } } } /// /// The Gesture Routed Event /// public static readonly RoutedEvent GestureEvent = EventManager.RegisterRoutedEvent("Gesture", RoutingStrategy.Bubble, typeof(InkCanvasGestureEventHandler), typeof(InkCanvas)); ////// Add / Remove Gesture handler /// [Category("Behavior")] public event InkCanvasGestureEventHandler Gesture { add { AddHandler(InkCanvas.GestureEvent, value); } remove { RemoveHandler(InkCanvas.GestureEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasGestureEventArgs to raise the event with protected virtual void OnGesture(InkCanvasGestureEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Raised when the InkCanvas.Strokes StrokeCollection has been replaced with another one /// public event InkCanvasStrokesReplacedEventHandler StrokesReplaced; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokesChangedEventArgs to raise the event with protected virtual void OnStrokesReplaced(InkCanvasStrokesReplacedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != this.StrokesReplaced) { StrokesReplaced(this, e); } } ////// Raised when the InkCanvas.DefaultDrawingAttributes has been replaced with another one /// public event DrawingAttributesReplacedEventHandler DefaultDrawingAttributesReplaced; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// DrawingAttributesReplacedEventArgs to raise the event with protected virtual void OnDefaultDrawingAttributesReplaced(DrawingAttributesReplacedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if (e == null) { throw new ArgumentNullException("e"); } if (null != this.DefaultDrawingAttributesReplaced) { DefaultDrawingAttributesReplaced(this, e); } } ////// Private helper for raising DDAReplaced. Invalidates the inking cursor /// /// private void RaiseDefaultDrawingAttributeReplaced(DrawingAttributesReplacedEventArgs e) { this.OnDefaultDrawingAttributesReplaced(e); // Invalidate the inking cursor _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } ////// Event corresponds to ActiveEditingModeChanged /// public static readonly RoutedEvent ActiveEditingModeChangedEvent = EventManager.RegisterRoutedEvent("ActiveEditingModeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove ActiveEditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler ActiveEditingModeChanged { add { AddHandler(InkCanvas.ActiveEditingModeChangedEvent, value); } remove { RemoveHandler(InkCanvas.ActiveEditingModeChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnActiveEditingModeChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if (e == null) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises ActiveEditingModeChanged /// /// EventArgs to raise the event with internal void RaiseActiveEditingModeChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); InkCanvasEditingMode mode = this.ActiveEditingMode; if (mode != _editingCoordinator.ActiveEditingMode) { //change our DP, then raise the event via our protected override SetValue(ActiveEditingModePropertyKey, _editingCoordinator.ActiveEditingMode); this.OnActiveEditingModeChanged(e); } } ////// Event corresponds to EditingModeChanged /// public static readonly RoutedEvent EditingModeChangedEvent = EventManager.RegisterRoutedEvent("EditingModeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler EditingModeChanged { add { AddHandler(InkCanvas.EditingModeChangedEvent, value); } remove { RemoveHandler(InkCanvas.EditingModeChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnEditingModeChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises EditingModeChanged but first /// talks to the InkEditor about it /// /// EventArgs to raise the event with private void RaiseEditingModeChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); _editingCoordinator.UpdateEditingState(false /* EditingMode */); this.OnEditingModeChanged(e); } //note: there is no need for an internal RaiseEditingModeInvertedChanging //since this isn't a dynamic property and therefore can not be set //outside of this class ////// Event corresponds to EditingModeInvertedChanged /// public static readonly RoutedEvent EditingModeInvertedChangedEvent = EventManager.RegisterRoutedEvent("EditingModeInvertedChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler EditingModeInvertedChanged { add { AddHandler(InkCanvas.EditingModeInvertedChangedEvent, value); } remove { RemoveHandler(InkCanvas.EditingModeInvertedChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnEditingModeInvertedChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises EditingModeInvertedChanged but first /// talks to the InkEditor about it /// /// EventArgs to raise the event with private void RaiseEditingModeInvertedChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); _editingCoordinator.UpdateEditingState(true /* EditingModeInverted */); this.OnEditingModeInvertedChanged(e); } ////// Occurs when the user has moved the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the move. /// public event InkCanvasSelectionEditingEventHandler SelectionMoving; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionEditingEventArgs to raise the event with protected virtual void OnSelectionMoving( InkCanvasSelectionEditingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionMoving) { SelectionMoving(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionMoving event via the protected virtual /// /// InkCanvasSelectionEditingEventArgs to raise the event with internal void RaiseSelectionMoving( InkCanvasSelectionEditingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionMoving(e); } ////// Occurs when the user has moved the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the move. /// public event EventHandler SelectionMoved; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionMoved(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionMoved) { SelectionMoved(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionMoved event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionMoved(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionMoved(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// Occurs when the user has erased Strokes using the erase behavior /// /// This event allows the developer to cancel the erase -- therefore, the Stroke should not disappear until /// this event has finished. /// public event InkCanvasStrokeErasingEventHandler StrokeErasing; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokeErasingEventArgs to raise the event with protected virtual void OnStrokeErasing(InkCanvasStrokeErasingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != StrokeErasing) { StrokeErasing(this, e); } } ////// Allows the EditingBehaviors to raise the InkErasing event via the protected virtual /// /// InkCanvasStrokeErasingEventArgs to raise the event with internal void RaiseStrokeErasing(InkCanvasStrokeErasingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnStrokeErasing(e); } ////// The StrokeErased Routed Event /// public static readonly RoutedEvent StrokeErasedEvent = EventManager.RegisterRoutedEvent("StrokeErased", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler StrokeErased { add { AddHandler(InkCanvas.StrokeErasedEvent, value); } remove { RemoveHandler(InkCanvas.StrokeErasedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnStrokeErased(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Allows the EditingBehaviors to raise the InkErasing event via the protected virtual /// internal void RaiseInkErased() { this.OnStrokeErased( new RoutedEventArgs(InkCanvas.StrokeErasedEvent, this)); } ////// Occurs when the user has resized the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the resize. /// public event InkCanvasSelectionEditingEventHandler SelectionResizing; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionEditingEventArgs to raise the event with protected virtual void OnSelectionResizing( InkCanvasSelectionEditingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionResizing) { SelectionResizing(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionResizing event via the protected virtual /// /// InkCanvasSelectionEditingEventArgs to raise the event with internal void RaiseSelectionResizing( InkCanvasSelectionEditingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionResizing(e); } ////// Occurs when the selection has been resized via UI interaction. /// public event EventHandler SelectionResized; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionResized(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionResized) { SelectionResized(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionResized event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionResized(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionResized(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// Occurs when the selection has been changed, either using the lasso or programmatically. /// This event allows the developer to cancel the change. /// public event InkCanvasSelectionChangingEventHandler SelectionChanging; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionChangingEventArgs to raise the event with protected virtual void OnSelectionChanging(InkCanvasSelectionChangingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionChanging) { SelectionChanging(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionChanging event via the protected virtual /// /// InkCanvasSelectionChangingEventArgs to raise the event with private void RaiseSelectionChanging(InkCanvasSelectionChangingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionChanging(e); } ////// Occurs when the selection has been changed /// public event EventHandler SelectionChanged; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionChanged(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionChanged) { SelectionChanged(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionChanged event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionChanged(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionChanged(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// The InkCanvas uses an inner Canvas to host children. When the inner Canvas's children /// are changed, we need to call the protected virtual OnVisualChildrenChanged on the InkCanvas /// so that subclasses can be notified /// internal void RaiseOnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { this.OnVisualChildrenChanged(visualAdded, visualRemoved); } #endregion Public Events #region Public Methods ////// Returns the enabled gestures. This method throws an exception if GestureRecognizerAvailable /// is false /// ///public ReadOnlyCollection GetEnabledGestures() { // No need to invoke VerifyAccess since it's checked in GestureRecognizer.GetEnabledGestures. //gestureRecognizer throws appropriately if there is no gesture recognizer available return new ReadOnlyCollection (this.GestureRecognizer.GetEnabledGestures()); } /// /// Sets the enabled gestures. This method throws an exception if GestureRecognizerAvailable /// is false /// ///public void SetEnabledGestures(IEnumerable applicationGestures) { // No need to invoke VerifyAccess since it's checked in GestureRecognizer.GetEnabledGestures. //gestureRecognizer throws appropriately if there is no gesture recognizer available this.GestureRecognizer.SetEnabledGestures(applicationGestures); } /// /// Get the selection bounds. /// ///public Rect GetSelectionBounds() { VerifyAccess(); return InkCanvasSelection.SelectionBounds; } /// /// provides access to the currently selected elements which are children of this InkCanvas /// public ReadOnlyCollectionGetSelectedElements() { VerifyAccess(); return InkCanvasSelection.SelectedElements; } /// /// provides read access to the currently selected strokes /// public StrokeCollection GetSelectedStrokes() { VerifyAccess(); StrokeCollection sc = new StrokeCollection(); sc.Add(InkCanvasSelection.SelectedStrokes); return sc; } ////// Overload which calls the more complex version, passing null for selectedElements /// /// The strokes to select public void Select(StrokeCollection selectedStrokes) { // No need to invoke VerifyAccess since this call is forwarded. Select(selectedStrokes, null); } ////// Overload which calls the more complex version, passing null for selectedStrokes /// /// The elements to select public void Select(IEnumerableselectedElements) { // No need to invoke VerifyAccess since this call is forwarded. Select(null, selectedElements); } /// /// Overload which calls the more complex version, passing null for selectedStrokes /// /// The strokes to select /// The elements to select public void Select(StrokeCollection selectedStrokes, IEnumerableselectedElements) { VerifyAccess(); // NTRAID-WINDOWS#1134932-2005/12/01-WAYNEZEN // Try to switch to Select mode first. If we fail to change the mode, then just simply no-op. if ( EnsureActiveEditingMode(InkCanvasEditingMode.Select) ) { // // validate // UIElement[] validElements = ValidateSelectedElements(selectedElements); StrokeCollection validStrokes = ValidateSelectedStrokes(selectedStrokes); // // this will raise the 'SelectionChanging' event ONLY if the selection // is actually different // ChangeInkCanvasSelection(validStrokes, validElements); } } /// /// Hit test on the selection /// /// ///public InkCanvasSelectionHitResult HitTestSelection(Point point) { VerifyAccess(); // Ensure the visual tree. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } return InkCanvasSelection.HitTestSelection(point); } /// /// Copy the current selection in the InkCanvas to the clipboard /// public void CopySelection() { VerifyAccess(); PrivateCopySelection(); } ////// Copy the current selection in the InkCanvas to the clipboard and then delete it /// public void CutSelection() { VerifyAccess(); // Copy first InkCanvasClipboardDataFormats copiedDataFormats = PrivateCopySelection(); // Don't even bother if we don't have a selection. if ( copiedDataFormats != InkCanvasClipboardDataFormats.None ) { // Then delete the current selection. Note the XAML format won't be avaliable under Partial // Trust. So, the selected element shouldn't be copied or removed. DeleteCurrentSelection( /* We want to delete the selected Strokes if there is ISF and/or XAML data being copied */ (copiedDataFormats & (InkCanvasClipboardDataFormats.ISF | InkCanvasClipboardDataFormats.XAML)) != 0, /* We only want to delete the selected elements if there is XAML data being copied */ (copiedDataFormats & InkCanvasClipboardDataFormats.XAML) != 0); } } ////// Paste the contents of the clipboard into the InkCanvas /// public void Paste() { // No need to call VerifyAccess since this call is forwarded. // We always paste the data to the default location which is (0,0). Paste(new Point(c_pasteDefaultLocation, c_pasteDefaultLocation)); } ////// Paste the contents of the clipboard to the specified location in the InkCanvas /// public void Paste(Point point) { VerifyAccess(); if (DoubleUtil.IsNaN(point.X) || DoubleUtil.IsNaN(point.Y) || Double.IsInfinity(point.X)|| Double.IsInfinity(point.Y) ) { throw new ArgumentException(SR.Get(SRID.InvalidPoint), "point"); } // // only do this if the user is not editing (input active) // or we will violate a dispatcher lock // if (!_editingCoordinator.UserIsEditing) { IDataObject dataObj = null; try { dataObj = Clipboard.GetDataObject(); } catch (ExternalException) { //harden against ExternalException return; } if (dataObj != null) { PasteFromDataObject(dataObj, point); } } } ////// Return true if clipboard contents can be pasted into the InkCanvas. /// public bool CanPaste() { VerifyAccess(); bool ret = false; // // can't paste if the user is editing (input active) // or we will violate a dispatcher lock // if (_editingCoordinator.UserIsEditing) { return false; } // Check whether the caller has the clipboard permission. if ( !SecurityHelper.CallerHasAllClipboardPermission() ) { return false; } ret = PrivateCanPaste(); return ret; } #endregion Public Methods //----------------------------------------------------- // // IAddChild Interface // //----------------------------------------------------- #region IAddChild Interface ////// Called to Add the object as a Child. /// /// /// Object to add as a child /// void IAddChild.AddChild(Object value) { // VerifyAccess(); if ( value == null ) { throw new ArgumentNullException("value"); } ( (IAddChild)InnerCanvas ).AddChild(value); } ////// Called when text appears under the tag in markup. /// /// /// Text to Add to the Canvas /// void IAddChild.AddText(string textData) { // VerifyAccess(); ( (IAddChild)InnerCanvas ).AddText(textData); } #endregion IAddChild Interface //------------------------------------------------------ // // Protected Properties // //----------------------------------------------------- #region Protected Properties ////// Returns enumerator to logical children. /// protected internal override IEnumerator LogicalChildren { get { // VerifyAccess( ); // Return the private logical children of the InnerCanvas return ( (InkCanvasInnerCanvas)InnerCanvas).PrivateLogicalChildren; } } ////// Protected DynamicRenderer property. /// protected DynamicRenderer DynamicRenderer { get { VerifyAccess(); return InternalDynamicRenderer; } set { VerifyAccess(); if (!object.ReferenceEquals(value, _dynamicRenderer)) { int previousIndex = -1; //remove the existing plugin if (_dynamicRenderer != null) { //remove the plugin from the collection previousIndex = this.StylusPlugIns.IndexOf(_dynamicRenderer); if (-1 != previousIndex) { this.StylusPlugIns.RemoveAt(previousIndex); } //remove the plugin's visual from the InkPresenter if (this.InkPresenter.ContainsAttachedVisual(_dynamicRenderer.RootVisual)) { this.InkPresenter.DetachVisuals(_dynamicRenderer.RootVisual); } } _dynamicRenderer = value; if (_dynamicRenderer != null) //null is acceptable { //remove the plugin from the collection if (!this.StylusPlugIns.Contains(_dynamicRenderer)) { if (-1 != previousIndex) { //insert the new DR in the same location as the old one this.StylusPlugIns.Insert(previousIndex, _dynamicRenderer); } else { this.StylusPlugIns.Add(_dynamicRenderer); } } //refer to the same DrawingAttributes as the InkCanvas _dynamicRenderer.DrawingAttributes = this.DefaultDrawingAttributes; //attach the DynamicRenderer if it is not already if (!(this.InkPresenter.ContainsAttachedVisual(_dynamicRenderer.RootVisual)) && _dynamicRenderer.Enabled && _dynamicRenderer.RootVisual != null) { this.InkPresenter.AttachVisuals(_dynamicRenderer.RootVisual, this.DefaultDrawingAttributes); } } } } } ////// Protected read only access to the InkPresenter this InkCanvas uses /// protected InkPresenter InkPresenter { get { VerifyAccess(); if ( _inkPresenter == null ) { _inkPresenter = new InkPresenter(); // Bind the InkPresenter.Strokes to InkCanvas.Strokes Binding strokes = new Binding(); strokes.Path = new PropertyPath(InkCanvas.StrokesProperty); strokes.Mode = BindingMode.OneWay; strokes.Source = this; _inkPresenter.SetBinding(InkPresenter.StrokesProperty, strokes); } return _inkPresenter; } } #endregion Protected Properties #region Internal Properties / Methods ////// Deselect the current selection /// internal static readonly RoutedCommand DeselectCommand = new RoutedCommand("Deselect", typeof(InkCanvas)); ////// UserInitiatedCanPaste /// ////// /// Critical - Elevates the AllClipboard permission for checking the supported data in InkCanvas. /// [SecurityCritical] private bool UserInitiatedCanPaste() { ( new UIPermission(UIPermissionClipboard.AllClipboard) ).Assert();//BlessedAssert try { return PrivateCanPaste(); } finally { UIPermission.RevertAssert(); } } ////// PrivateCanPaste /// ///private bool PrivateCanPaste() { bool canPaste = false; IDataObject dataObj = null; try { dataObj = Clipboard.GetDataObject(); } catch (ExternalException) { //harden against ExternalException return false; } if ( dataObj != null ) { canPaste = ClipboardProcessor.CheckDataFormats(dataObj); } return canPaste; } /// /// This method pastes data from an IDataObject object /// internal void PasteFromDataObject(IDataObject dataObj, Point point) { // Reset the current selection ClearSelection(false); // Assume that there is nothing to be selected. StrokeCollection newStrokes = new StrokeCollection(); ListnewElements = new List (); // Paste the data from the data object. if ( !ClipboardProcessor.PasteData(dataObj, ref newStrokes, ref newElements) ) { // Paste was failed. return; } else if ( newStrokes.Count == 0 && newElements.Count == 0 ) { // Nothing has been received from the clipboard. return; } // We add elements here. Then we have to wait for the layout update. UIElementCollection children = Children; foreach ( UIElement element in newElements ) { children.Add(element); } if ( newStrokes != null ) { Strokes.Add(newStrokes); } try { // We should fire SelectionChanged event if the current editing mode is Select. CoreChangeSelection(newStrokes, newElements.ToArray(), EditingMode == InkCanvasEditingMode.Select); } finally { // Now move the selection to the desired location. Rect bounds = GetSelectionBounds( ); InkCanvasSelection.CommitChanges(Rect.Offset(bounds, -bounds.Left + point.X, -bounds.Top + point.Y), false); if (EditingMode != InkCanvasEditingMode.Select) { // Clear the selection without the event if the editing mode is not Select. ClearSelection(false); } } } /// /// Copies the InkCanvas contents to a DataObject and returns it to the caller. /// Can return NULL for DataObject. /// ////// Critical: Clipboard.SetDataObject will invoke DataObject.DataStore.GetFormats. /// The methods demands SerializationPermission. We perform the elevation before /// calling SetDataObject. /// TreatAsSafe: There is no input here. The ISF data are safe to being put in the clipboard. /// [SecurityCritical, SecurityTreatAsSafe] private InkCanvasClipboardDataFormats CopyToDataObject() { DataObject dataObj; (new UIPermission(UIPermissionClipboard.AllClipboard)).Assert();//BlessedAssert try { dataObj = new DataObject(); } finally { UIPermission.RevertAssert(); } InkCanvasClipboardDataFormats copiedDataFormats = InkCanvasClipboardDataFormats.None; // Try to copy the data from the InkCanvas to the clipboard. copiedDataFormats = ClipboardProcessor.CopySelectedData(dataObj); if ( copiedDataFormats != InkCanvasClipboardDataFormats.None ) { PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter)); ps.AddPermission(new UIPermission(UIPermissionClipboard.AllClipboard)); ps.Assert(); // BlessedAssert try { // Put our data object into the clipboard. Clipboard.SetDataObject(dataObj, true); } finally { SecurityPermission.RevertAssert(); } } return copiedDataFormats; } ////// Read-only property of the associated EditingCoordinator. /// internal EditingCoordinator EditingCoordinator { get { return _editingCoordinator; } } ////// Internal access to the protected DynamicRenderer. Can be null. /// internal DynamicRenderer InternalDynamicRenderer { get { return _dynamicRenderer; } } ////// Return the inner Canvas. /// internal InkCanvasInnerCanvas InnerCanvas { get { // We have to create our visual at this point. if (_innerCanvas == null) { // Create our InnerCanvas to change the logical parent of Canvas' children. _innerCanvas = new InkCanvasInnerCanvas(this); // Bind the inner Canvas' Background to InkCanvas' Background Binding background = new Binding(); background.Path = new PropertyPath(InkCanvas.BackgroundProperty); background.Mode = BindingMode.OneWay; background.Source = this; _innerCanvas.SetBinding(Panel.BackgroundProperty, background); } return _innerCanvas; } } ////// Internal access to the current selection /// internal InkCanvasSelection InkCanvasSelection { get { if ( _selection == null ) { _selection = new InkCanvasSelection(this); } return _selection; } } ////// Internal helper called by the LassoSelectionBehavior /// internal void BeginDynamicSelection(Visual visual) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); _dynamicallySelectedStrokes = new StrokeCollection(); InkPresenter.AttachVisuals(visual, new DrawingAttributes()); } ////// Internal helper called by LassoSelectionBehavior to update the display /// of dynamically added strokes /// internal void UpdateDynamicSelection( StrokeCollection strokesToDynamicallySelect, StrokeCollection strokesToDynamicallyUnselect) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); // // update our internal stroke collections used by dynamic selection // if (strokesToDynamicallySelect != null) { foreach (Stroke s in strokesToDynamicallySelect) { _dynamicallySelectedStrokes.Add(s); s.IsSelected = true; } } if (strokesToDynamicallyUnselect != null) { foreach (Stroke s in strokesToDynamicallyUnselect) { System.Diagnostics.Debug.Assert(_dynamicallySelectedStrokes.Contains(s)); _dynamicallySelectedStrokes.Remove(s); s.IsSelected = false; } } } ////// Internal helper used by LassoSelectionBehavior /// internal StrokeCollection EndDynamicSelection(Visual visual) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); InkPresenter.DetachVisuals(visual); StrokeCollection selectedStrokes = _dynamicallySelectedStrokes; _dynamicallySelectedStrokes = null; return selectedStrokes; } ////// Clears the selection in the ink canvas /// and returns a bool indicating if the selection was actually cleared /// (developers can cancel selectionchanging) /// /// If the InkCanvas has no selection, selectionchanging is not raised /// and this method returns true /// /// used by InkEditor during editing operations /// ///true if selection was cleared even after raising selectionchanging internal bool ClearSelectionRaiseSelectionChanging() { if ( !InkCanvasSelection.HasSelection ) { return true; } // // attempt to clear selection // ChangeInkCanvasSelection(new StrokeCollection(), new UIElement[]{}); return !InkCanvasSelection.HasSelection; } ////// ClearSelection /// Called by: /// PasteFromDataObject /// EditingCoordinator.UpdateEditingState /// internal void ClearSelection(bool raiseSelectionChangedEvent) { if ( InkCanvasSelection.HasSelection ) { // Reset the current selection CoreChangeSelection(new StrokeCollection(), new UIElement[] { }, raiseSelectionChangedEvent); } } ////// Helper that creates selection for an InkCanvas. Used by the SelectedStrokes and /// SelectedElements properties /// internal void ChangeInkCanvasSelection(StrokeCollection strokes, UIElement[] elements) { //validate in debug only for this internal static Debug.Assert(strokes != null && elements != null, "Invalid arguments in ChangeInkCanvasSelection"); bool strokesAreDifferent; bool elementsAreDifferent; InkCanvasSelection.SelectionIsDifferentThanCurrent(strokes, out strokesAreDifferent, elements, out elementsAreDifferent); if ( strokesAreDifferent || elementsAreDifferent ) { InkCanvasSelectionChangingEventArgs args = new InkCanvasSelectionChangingEventArgs(strokes, elements); StrokeCollection validStrokes = strokes; UIElement[] validElements = elements; this.RaiseSelectionChanging(args); //now that the event has been raised and all of the delegates //have had their way with it, process the result if ( !args.Cancel ) { // // rock and roll, time to validate our arguments // note: these event args are visible outside the apis, // so we need to validate them again // // PERF-2006/05/02-WAYNEZEN, // Check our internal flag. If the SelectedStrokes has been changed, we shouldn't do any extra work here. if ( args.StrokesChanged ) { validStrokes = ValidateSelectedStrokes(args.GetSelectedStrokes()); int countOldSelectedStrokes = strokes.Count; for ( int i = 0; i < countOldSelectedStrokes; i++ ) { // PERF-2006/05/02-WAYNEZEN, // We only have to reset IsSelected for the elements no longer exists in the new collection. if ( !validStrokes.Contains(strokes[i]) ) { // NTRAID#WINDOWS-1045099-2006/05/02-waynezen, // Make sure we reset the IsSelected property which could have been // set to true in the dynamic selection. strokes[i].IsSelected = false; } } } // PERF-2006/05/02-WAYNEZEN, // Check our internal flag. If the SelectedElements has been changed, we shouldn't do any extra work here. if ( args.ElementsChanged ) { validElements = ValidateSelectedElements(args.GetSelectedElements()); } CoreChangeSelection(validStrokes, validElements, true); } else { StrokeCollection currentSelectedStrokes = InkCanvasSelection.SelectedStrokes; int countOldSelectedStrokes = strokes.Count; for ( int i = 0; i < countOldSelectedStrokes; i++ ) { // Make sure we reset the IsSelected property which could have been // set to true in the dynamic selection but not being selected previously. if ( !currentSelectedStrokes.Contains(strokes[i]) ) { strokes[i].IsSelected = false; } } } } } ////// Helper method used by ChangeInkCanvasSelection and directly by ClearSelectionWithoutSelectionChanging /// /// validStrokes /// validElements /// raiseSelectionChanged private void CoreChangeSelection(StrokeCollection validStrokes, IListvalidElements, bool raiseSelectionChanged) { InkCanvasSelection.Select(validStrokes, validElements, raiseSelectionChanged); } #if DEBUG_LASSO_FEEDBACK internal ContainerVisual RendererRootContainer { get {return _inkContainerVisual;} } #endif /// /// Private helper method used to retrieve a StrokeCollection which does AND operation on two input collections. /// /// The possible subset /// The container set ///True if subset is a subset of superset, false otherwise internal static StrokeCollection GetValidStrokes(StrokeCollection subset, StrokeCollection superset) { StrokeCollection validStrokes = new StrokeCollection(); int subsetCount = subset.Count; // special case an empty subset as a guaranteed subset if ( subsetCount == 0 ) { return validStrokes; } for ( int i = 0; i < subsetCount; i++ ) { Stroke stroke = subset[i]; if ( superset.Contains(stroke) ) { validStrokes.Add(stroke); } } return validStrokes; } ////// Register the commanding handlers for the clipboard operations /// ////// Critical: Elevates to associate a protected command (paste) with keyboard /// TreatAsSafe: We don't take user input here. Shift+Insert is the correct key binding, /// and therefore is expected by the user. /// [SecurityCritical, SecurityTreatAsSafe] private static void _RegisterClipboardHandlers() { Type ownerType = typeof(InkCanvas); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Cut, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.KeyShiftDelete, SRID.KeyShiftDeleteDisplayString); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Copy, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.KeyCtrlInsert, SRID.KeyCtrlInsertDisplayString); // Use temp variables to reduce code under elevation ExecutedRoutedEventHandler pasteExecuteEventHandler = new ExecutedRoutedEventHandler(_OnCommandExecuted); CanExecuteRoutedEventHandler pasteQueryEnabledEventHandler = new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled); InputGesture pasteInputGesture = KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyShiftInsert), SR.Get(SRID.KeyShiftInsertDisplayString)); new UIPermission(UIPermissionClipboard.AllClipboard).Assert(); // BlessedAssert: try { CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Paste, pasteExecuteEventHandler, pasteQueryEnabledEventHandler, pasteInputGesture); } finally { CodeAccessPermission.RevertAssert(); } } ////// Private helper used to ensure that any stroke collection /// passed to the InkCanvas is valid. Throws exceptions if the argument is invalid /// private StrokeCollection ValidateSelectedStrokes(StrokeCollection strokes) { // // null is a valid input // if (strokes == null) { return new StrokeCollection(); } else { return GetValidStrokes(strokes, this.Strokes); } } ////// Private helper used to ensure that a UIElement argument passed in /// is valid. /// private UIElement[] ValidateSelectedElements(IEnumerableselectedElements) { if (selectedElements == null) { return new UIElement[]{}; } List elements = new List (); foreach (UIElement element in selectedElements) { // NTRAID:WINDOWSOS#1621480-2006/04/26-WAYNEZEN, // Don't add the duplicated element. if ( !elements.Contains(element) ) { // // check the common case first, e // if ( InkCanvasIsAncestorOf(element) ) { elements.Add(element); } } } return elements.ToArray(); } /// /// Helper method used by DesignActivation to see if an element /// has this InkCanvas as an Ancestor /// private bool InkCanvasIsAncestorOf(UIElement element) { if (this != element && this.IsAncestorOf(element)) { return true; } return false; } ////// Handler called whenever changes are made to properties of the DefaultDrawingAttributes /// /// /// ///For example, when a developer changes the Color property on the DefaultDrawingAttributes, /// this event will fire - allowing the InkCanvas a chance to notify the BasicRTI Service. /// Also - we do not currently call through the DefaultDrawingAttributes setter since /// parameter validation in the setter may detect if the reference isn't changing, and ignore /// the call. Also - there is no need for extra parameter validation. private void DefaultDrawingAttributes_Changed(object sender, PropertyDataChangedEventArgs args) { // note that sender should be the same as _defaultDrawingAttributes // If a developer writes code to change the DefaultDrawingAttributes inside of the event // handler before the InkCanvas receives the notification (multi-cast delegate scenario) - // The DefaultDrawingAttributes should still be updated, and in that case we would // update the RTI DAC twice. Typically, however, this will just refresh the // attributes in the RTI thread. System.Diagnostics.Debug.Assert(object.ReferenceEquals(sender, DefaultDrawingAttributes)); InvalidateSubProperty(DefaultDrawingAttributesProperty); // Be sure to update the RealTimeInking PlugIn with the drawing attribute changes. UpdateDynamicRenderer(); // Invalidate the inking cursor _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } ////// Helper method used to set up the DynamicRenderer. /// internal void UpdateDynamicRenderer() { UpdateDynamicRenderer(DefaultDrawingAttributes); } ////// Helper method used to set up the DynamicRenderer. /// private void UpdateDynamicRenderer(DrawingAttributes newDrawingAttributes) { ApplyTemplate(); if (this.DynamicRenderer != null) { this.DynamicRenderer.DrawingAttributes = newDrawingAttributes; if (!this.InkPresenter.AttachedVisualIsPositionedCorrectly(this.DynamicRenderer.RootVisual, newDrawingAttributes)) { if (this.InkPresenter.ContainsAttachedVisual(this.DynamicRenderer.RootVisual)) { this.InkPresenter.DetachVisuals(this.DynamicRenderer.RootVisual); } // Only hook up if we are enabled. As we change editing modes this routine will be called // to clean up things. if (this.DynamicRenderer.Enabled && this.DynamicRenderer.RootVisual != null) { this.InkPresenter.AttachVisuals(this.DynamicRenderer.RootVisual, newDrawingAttributes); } } } } private bool EnsureActiveEditingMode(InkCanvasEditingMode newEditingMode) { bool ret = true; if ( ActiveEditingMode != newEditingMode ) { if ( EditingCoordinator.IsStylusInverted ) { EditingModeInverted = newEditingMode; } else { EditingMode = newEditingMode; } // Verify whether user has cancelled the change in EditingModeChanging event. ret = ( ActiveEditingMode == newEditingMode ); } return ret; } // The ClipboardProcessor instance which deals with the operations relevant to the clipboard. private ClipboardProcessor ClipboardProcessor { get { if ( _clipboardProcessor == null ) { _clipboardProcessor = new ClipboardProcessor(this); } return _clipboardProcessor; } } //lazy instance the gesture recognizer private GestureRecognizer GestureRecognizer { get { if (_gestureRecognizer == null) { _gestureRecognizer = new GestureRecognizer(); } return _gestureRecognizer; } } ////// Delete the current selection /// private void DeleteCurrentSelection(bool removeSelectedStrokes, bool removeSelectedElements) { Debug.Assert(removeSelectedStrokes || removeSelectedElements, "At least either Strokes or Elements should be removed!"); // Now delete the current selection. StrokeCollection strokes = GetSelectedStrokes(); IListelements = GetSelectedElements(); // Clear the selection first. CoreChangeSelection( removeSelectedStrokes ? new StrokeCollection() : strokes, removeSelectedElements ? new List () : elements, true); // Remove the ink. if ( removeSelectedStrokes && strokes != null && strokes.Count != 0 ) { Strokes.Remove(strokes); } // Remove the elements. if ( removeSelectedElements ) { UIElementCollection children = Children; foreach ( UIElement element in elements ) { children.Remove(element); } } } /// /// A class handler of the Commands /// /// /// private static void _OnCommandExecuted(object sender, ExecutedRoutedEventArgs args) { ICommand command = args.Command; InkCanvas inkCanvas = sender as InkCanvas; Debug.Assert(inkCanvas != null); if ( inkCanvas.IsEnabled && !inkCanvas.EditingCoordinator.UserIsEditing ) { if ( command == ApplicationCommands.Delete ) { inkCanvas.DeleteCurrentSelection(true, true); } else if ( command == ApplicationCommands.Cut ) { inkCanvas.CutSelection(); } else if ( command == ApplicationCommands.Copy ) { inkCanvas.CopySelection(); } else if ( command == ApplicationCommands.SelectAll ) { if ( inkCanvas.ActiveEditingMode == InkCanvasEditingMode.Select ) { IEnumerablechildren = null; UIElementCollection uiElementCollection = inkCanvas.Children; if ( uiElementCollection.Count > 0 ) { //UIElementCollection doesn't implement IEnumerable //for some reason UIElement[] uiElementArray = new UIElement[uiElementCollection.Count]; for ( int i = 0; i < uiElementCollection.Count; i++ ) { uiElementArray[i] = uiElementCollection[i]; } children = uiElementArray; } inkCanvas.Select(inkCanvas.Strokes, children); } } else if ( command == ApplicationCommands.Paste ) { try { inkCanvas.Paste(); } // Eat it and do nothing if one of the following exceptions is caught. catch ( System.Runtime.InteropServices.COMException ) { // The window may be destroyed which could cause the opening failed.. } catch ( XamlParseException ) { // The Xaml parser fails } catch ( ArgumentException ) { // The ISF decoder fails } } else if ( command == InkCanvas.DeselectCommand ) { inkCanvas.ClearSelectionRaiseSelectionChanging(); } } } /// /// A class handler for querying the enabled status of the commands. /// /// /// ////// Critical - Call into UserInitiatedCanPaste which is SecurityCritical. /// TreatAsSafe - We check whether QueryCanPaste is initiated by user or not /// before invoking the critical method. /// [SecurityCritical, SecurityTreatAsSafe] private static void _OnQueryCommandEnabled(object sender, CanExecuteRoutedEventArgs args) { RoutedCommand command = (RoutedCommand)(args.Command); InkCanvas inkCanvas = sender as InkCanvas; Debug.Assert(inkCanvas != null); if ( inkCanvas.IsEnabled // NTRAID-WINDOWSOS#1578484-2006/04/14-WAYNEZEN, // If user is editing, we should disable all commands. && !inkCanvas.EditingCoordinator.UserIsEditing ) { if ( command == ApplicationCommands.Delete || command == ApplicationCommands.Cut || command == ApplicationCommands.Copy || command == InkCanvas.DeselectCommand ) { args.CanExecute = inkCanvas.InkCanvasSelection.HasSelection; } else if ( command == ApplicationCommands.Paste ) { try { args.CanExecute = args.UserInitiated ? inkCanvas.UserInitiatedCanPaste() /* Call UserInitiatedCanPaste when the query is initiated by user */ : inkCanvas.CanPaste() /* Call the public CanPaste if not */; } catch ( System.Runtime.InteropServices.COMException ) { // The window may be destroyed which could cause the opening failed.. // Eat the exception and do nothing. args.CanExecute = false; } } else if ( command == ApplicationCommands.SelectAll ) { //anything to select? args.CanExecute = ( inkCanvas.ActiveEditingMode == InkCanvasEditingMode.Select && (inkCanvas.Strokes.Count > 0 || inkCanvas.Children.Count > 0)); } } else { // NTRAID:WINDOWSOS#1564508-2006/03/20-WAYNEZEN, // Return false for CanExecute if InkCanvas is disabled. args.CanExecute = false; } // NTRAID#WINDOWS-1371659-2005/11/08-waynezen, // Mark Handled as true so that the clipboard commands stops routing to InkCanvas' ancestors. if ( command == ApplicationCommands.Cut || command == ApplicationCommands.Copy || command == ApplicationCommands.Paste ) { args.Handled = true; } } private InkCanvasClipboardDataFormats PrivateCopySelection() { InkCanvasClipboardDataFormats copiedDataFormats = InkCanvasClipboardDataFormats.None; // Don't even bother if we don't have a selection or UserIsEditing has been set. if ( InkCanvasSelection.HasSelection && !_editingCoordinator.UserIsEditing) { copiedDataFormats = CopyToDataObject(); } return copiedDataFormats; } ////// _OnDeviceDown /// ////// /// private static void _OnDeviceDown (object sender, TEventArgs e) where TEventArgs : InputEventArgs { ( (InkCanvas)sender ).EditingCoordinator.OnInkCanvasDeviceDown(sender, e); } /// /// _OnDeviceUp /// ////// /// private static void _OnDeviceUp (object sender, TEventArgs e) where TEventArgs : InputEventArgs { ((InkCanvas)sender).EditingCoordinator.OnInkCanvasDeviceUp(sender, e); } /// /// _OnQueryCursor /// /// /// private static void _OnQueryCursor(object sender, QueryCursorEventArgs e) { InkCanvas inkCanvas = (InkCanvas)sender; if ( inkCanvas.UseCustomCursor ) { // If UseCustomCursor is set, we bail out. Let the base class (FrameworkElement) to do the rest. return; } // We should behave like our base - honor ForceCursor property. if ( !e.Handled || inkCanvas.ForceCursor ) { Cursor cursor = inkCanvas.EditingCoordinator.GetActiveBehaviorCursor(); // If cursor is null, we don't handle the event and leave it as whatever the default is. if ( cursor != null ) { e.Cursor = cursor; e.Handled = true; } } } ////// Update the current cursor if mouse is over InkCanvas. Called by /// EditingCoordinator.InvalidateBehaviorCursor /// EditingCoordinator.UpdateEditingState /// InkCanvas.set_UseCustomCursor /// internal void UpdateCursor() { if ( IsMouseOver ) { Mouse.UpdateCursor(); } } #endregion Private Properties / Methods //------------------------------------------------------ // // Private Classes // //------------------------------------------------------ #region Private Classes ////// A helper class for RTI high contrast support /// private class RTIHighContrastCallback : HighContrastCallback { //----------------------------------------------------- // // Cnostructors // //------------------------------------------------------ #region Constructors internal RTIHighContrastCallback(InkCanvas inkCanvas) { _thisInkCanvas = inkCanvas; } private RTIHighContrastCallback() { } #endregion Constructors //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- #region Internal Methods ////// TurnHighContrastOn /// /// internal override void TurnHighContrastOn(Color highContrastColor) { // The static strokes already have been taken care of by InkPresenter. // We only update the RTI renderer here. DrawingAttributes highContrastDa = _thisInkCanvas.DefaultDrawingAttributes.Clone(); highContrastDa.Color = highContrastColor; _thisInkCanvas.UpdateDynamicRenderer(highContrastDa); } ////// TurnHighContrastOff /// internal override void TurnHighContrastOff() { // The static strokes already have been taken care of by InkPresenter. // We only update the RTI renderer here. _thisInkCanvas.UpdateDynamicRenderer(_thisInkCanvas.DefaultDrawingAttributes); } #endregion Internal Methods //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties ////// Returns the dispatcher if the object is associated to a UIContext. /// internal override Dispatcher Dispatcher { get { return _thisInkCanvas.Dispatcher; } } #endregion Internal Properties //----------------------------------------------------- // // Private Fields // //------------------------------------------------------ #region Private Fields private InkCanvas _thisInkCanvas; #endregion Private Fields } ////// This is a binding converter which translates the InkCanvas.ActiveEditingMode to UIElement.Visibility. /// private class ActiveEditingMode2VisibilityConverter : IValueConverter { public object Convert(object o, Type type, object parameter, System.Globalization.CultureInfo culture) { InkCanvasEditingMode activeMode = (InkCanvasEditingMode)o; // If the current EditingMode is the mode which menuitem is expecting, return true for IsChecked. if ( activeMode != InkCanvasEditingMode.None ) { return Visibility.Visible; } else { return Visibility.Collapsed; } } public object ConvertBack(object o, Type type, object parameter, System.Globalization.CultureInfo culture) { // Non-reversed convertion return null; } } #endregion Private Classes #region Private Members ////// The element that represents the selected ink strokes, if any exist. Will frequently be null. /// private InkCanvasSelection _selection = null; private InkCanvasSelectionAdorner _selectionAdorner = null; private InkCanvasFeedbackAdorner _feedbackAdorner = null; ////// The internal Canvas used to hold elements /// private InkCanvasInnerCanvas _innerCanvas = null; ////// The internal private AdornerDecorator /// private AdornerDecorator _localAdornerDecorator = null; ////// Runtime Selection StrokeCollection /// private StrokeCollection _dynamicallySelectedStrokes; ////// Our editing logic /// private EditingCoordinator _editingCoordinator; ////// Defines the default StylusPointDescription /// private StylusPointDescription _defaultStylusPointDescription; ////// Defines the shape of the eraser tip /// private StylusShape _eraserShape; ////// Determines if EditingBehaviors should use their own cursor or a custom one specified. /// private bool _useCustomCursor = false; // // Rendering support. // private InkPresenter _inkPresenter; // // The RealTimeInking PlugIn that handles our off UIContext rendering. // private DynamicRenderer _dynamicRenderer; // // Clipboard Helper // private ClipboardProcessor _clipboardProcessor; // // Gesture support // private GestureRecognizer _gestureRecognizer; // // HighContrast support // private RTIHighContrastCallback _rtiHighContrastCallback; private const double c_pasteDefaultLocation = 0.0; #endregion Private Members } } // 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: InkCanvas.cs // // Description: // Defines an inkable canvas that represents the primary api for // editing ink // // Features: // // History: // 1/29/2002 samgeo: Created // 9/12/2003 samgeo: Started porting to WCP // // Copyright (C) 2001 by Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using MS.Utility; using MS.Internal; using MS.Internal.Commands; using MS.Internal.Controls; using MS.Internal.Ink; using MS.Internal.KnownBoxes; using MS.Internal.PresentationFramework; using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.IO; using System.Windows; using System.Collections.Generic; using System.Security; using System.Security.Permissions; using System.Runtime.InteropServices; using System.Windows.Media; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Input.StylusPlugIns; using System.Windows.Controls; using System.Windows.Markup; // IAddChild, ContentPropertyAttribute using System.Windows.Threading; using System.Windows.Automation.Peers; namespace System.Windows.Controls { ////// InkCanvas is used to allow inking on a canvas /// [ContentProperty("Children")] public class InkCanvas : FrameworkElement, IAddChild { #region Constructors / Initialization ////// The static constructor /// static InkCanvas() { Type ownerType = typeof(InkCanvas); // NTRAID-WINDOWS#1423922-2005/12/15-WAYNEZEN, // We should add the following listener as the class handler which will be guarantied to receive the // notification before the handler on the instances. So we won't be trapped in the bad state due to the // event routing. // Listen to stylus events which will be redirected to the current StylusEditingBehavior //Down EventManager.RegisterClassHandler(ownerType, Stylus.StylusDownEvent, new StylusDownEventHandler(_OnDeviceDown)); EventManager.RegisterClassHandler(ownerType, Mouse.MouseDownEvent, new MouseButtonEventHandler(_OnDeviceDown )); //Up EventManager.RegisterClassHandler(ownerType, Stylus.StylusUpEvent, new StylusEventHandler(_OnDeviceUp )); EventManager.RegisterClassHandler(ownerType, Mouse.MouseUpEvent, new MouseButtonEventHandler(_OnDeviceUp )); EventManager.RegisterClassHandler(ownerType, Mouse.QueryCursorEvent, new QueryCursorEventHandler(_OnQueryCursor), true); // Set up the commanding handlers _RegisterClipboardHandlers(); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Delete, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled)); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.SelectAll, Key.A, ModifierKeys.Control, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled)); CommandHelpers.RegisterCommandHandler(ownerType, InkCanvas.DeselectCommand, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.InkCanvasDeselectKey, SRID.InkCanvasDeselectKeyDisplayString); // //set our clipping // ClipToBoundsProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(BooleanBoxes.TrueBox)); // //enable input focus // FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(BooleanBoxes.TrueBox)); // The default InkCanvas style Style defaultStyle = new Style(ownerType); // The background - Window Color defaultStyle.Setters.Add(new Setter(InkCanvas.BackgroundProperty, new DynamicResourceExtension(SystemColors.WindowBrushKey))); // Default InkCanvas to having flicks disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsFlicksEnabledProperty, false)); // Default InkCanvas to having tap feedback disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsTapFeedbackEnabledProperty, false)); // Default InkCanvas to having touch feedback disabled by default. defaultStyle.Setters.Add(new Setter(Stylus.IsTouchFeedbackEnabledProperty, false)); // Set MinWidth to 350d if Width is set to Auto Trigger trigger = new Trigger(); trigger.Property = WidthProperty; trigger.Value = double.NaN; Setter setter = new Setter(); setter.Property = MinWidthProperty; setter.Value = 350d; trigger.Setters.Add(setter); defaultStyle.Triggers.Add(trigger); // Set MinHeight to 250d if Height is set to Auto trigger = new Trigger(); trigger.Property = HeightProperty; trigger.Value = double.NaN; setter = new Setter(); setter.Property = MinHeightProperty; setter.Value = 250d; trigger.Setters.Add(setter); defaultStyle.Triggers.Add(trigger); // Seal the default style defaultStyle.Seal(); StyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(defaultStyle)); DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(InkCanvas))); FocusVisualStyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata((object)null /* default value */)); } /// /// Public constructor. /// public InkCanvas() : base() { Initialize(); } ////// Private initialization method used by the constructors /// private void Initialize() { // // instance the DynamicRenderer and add it to the StylusPlugIns // _dynamicRenderer = new DynamicRenderer(); _dynamicRenderer.Enabled = false; this.StylusPlugIns.Add(_dynamicRenderer); // // create and initialize an editing coordinator // _editingCoordinator = new EditingCoordinator(this); _editingCoordinator.UpdateActiveEditingState(); // connect the attributes event handler after setting the stylus shape to avoid unnecessary // calls into the RTI service DefaultDrawingAttributes.AttributeChanged += new PropertyDataChangedEventHandler(DefaultDrawingAttributes_Changed); // // // We must initialize this here (after adding DynamicRenderer to Sytlus). // this.InitializeInkObject(); _rtiHighContrastCallback = new RTIHighContrastCallback(this); // Register rti high contrast callback. Then check whether we are under the high contrast already. HighContrastHelper.RegisterHighContrastCallback(_rtiHighContrastCallback); if ( SystemParameters.HighContrast ) { _rtiHighContrastCallback.TurnHighContrastOn(SystemColors.WindowTextColor); } } ////// Private helper used to change the Ink objects. Used in the constructor /// and the Ink property. /// /// NOTE -- Caller is responsible for clearing any selection! (We can't clear it /// here because the Constructor calls this method and it would end up calling /// looking like it could call a virtual method and FxCop doesn't like that!) /// /// private void InitializeInkObject() { // Update the RealTimeInking PlugIn for the Renderer changes. UpdateDynamicRenderer(); // Initialize DefaultPacketDescription _defaultStylusPointDescription = new StylusPointDescription(); } #endregion Constructors / Initialization #region Protected Overrides ////// MeasureOverride /// /// ///protected override Size MeasureOverride(Size availableSize) { // No need to invoke VerifyAccess since _localAdornerDecorator.Measure should check it. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } _localAdornerDecorator.Measure(availableSize); return _localAdornerDecorator.DesiredSize; } /// /// ArrangeOverride /// /// ///protected override Size ArrangeOverride(Size arrangeSize) { // No need to invoke VerifyAccess since _localAdornerDecorator.Arrange should check it. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } _localAdornerDecorator.Arrange(new Rect(arrangeSize)); return arrangeSize; } /// /// HitTestCore implements precise hit testing against render contents /// protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParams) { VerifyAccess(); Rect r = new Rect(new Point(), RenderSize); if (r.Contains(hitTestParams.HitPoint)) { return new PointHitTestResult(this, hitTestParams.HitPoint); } return null; } ////// OnPropertyChanged /// protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.IsAValueChange || e.IsASubPropertyChange) { if (e.Property == UIElement.RenderTransformProperty || e.Property == FrameworkElement.LayoutTransformProperty) { EditingCoordinator.InvalidateTransform(); Transform transform = e.NewValue as Transform; if (transform != null && !transform.HasAnimatedProperties) { TransformGroup transformGroup = transform as TransformGroup; if ( transformGroup != null ) { //walk down the tree looking for animated transforms Stacktransforms = new Stack (); transforms.Push(transform); while ( transforms.Count > 0 ) { transform = transforms.Pop(); if ( transform.HasAnimatedProperties ) { return; } transformGroup = transform as TransformGroup; if ( transformGroup != null ) { for ( int i = 0; i < transformGroup.Children.Count; i++ ) { transforms.Push(transformGroup.Children[i]); } } } } // // only invalidate when there is not an animation on the xf, // or we could wind up creating thousands of new cursors. That's bad. // _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); EditingCoordinator.UpdatePointEraserCursor(); } } if (e.Property == FrameworkElement.FlowDirectionProperty) { //flow direction only affects the inking cursor. _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } } } /// /// Called when the Template's tree is about to be generated /// internal override void OnPreApplyTemplate() { // No need for calling VerifyAccess since we call the method on the base here. base.OnPreApplyTemplate(); // Build our visual tree here. //// if ( _localAdornerDecorator == null ) { // _localAdornerDecorator = new AdornerDecorator(); InkPresenter inkPresenter = InkPresenter; // Build the visual tree top-down AddVisualChild(_localAdornerDecorator); _localAdornerDecorator.Child = inkPresenter; inkPresenter.Child = InnerCanvas; // Add the SelectionAdorner after Canvas is added. _localAdornerDecorator.AdornerLayer.Add(SelectionAdorner); } } ///// //// //// // // // //// // /// Returns the Visual children count. /// protected override int VisualChildrenCount { get { return (_localAdornerDecorator == null) ? 0 : 1; } } ////// Returns the child at the specified index. /// protected override Visual GetVisualChild(int index) { if ( (_localAdornerDecorator == null) || (index != 0)) { throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)); } return _localAdornerDecorator; } ////// UIAutomation support /// protected override AutomationPeer OnCreateAutomationPeer() { return new InkCanvasAutomationPeer(this); } #endregion Protected Overrides #region Public Properties ////// The DependencyProperty for the Background property. /// public static readonly DependencyProperty BackgroundProperty = Panel.BackgroundProperty.AddOwner( typeof(InkCanvas), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender)); ////// An object that describes the background. /// [Bindable(true), Category("Appearance")] public Brush Background { get { return (Brush) GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } } ////// Top DependencyProperty /// public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached("Top", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Top from the given element. /// /// The element from which to read the Top attached property. ///The property's value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetTop(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(TopProperty); } /// /// Writes the attached property Top to the given element. /// /// The element to which to write the Top attached property. /// The length to set ///public static void SetTop(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TopProperty, length); } /// /// The Bottom DependencyProperty /// public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached("Bottom", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Bottom from the given element. /// /// The element from which to read the Bottom attached property. ///The property's Length value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetBottom(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(BottomProperty); } /// /// Writes the attached property Bottom to the given element. /// /// The element to which to write the Bottom attached property. /// The Length to set ///public static void SetBottom(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(BottomProperty, length); } /// /// The Left DependencyProperty /// public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Left from the given element. /// /// The element from which to read the Left attached property. ///The property's value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetLeft(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(LeftProperty); } /// /// Writes the attached property Left to the given element. /// /// The element to which to write the Left attached property. /// The length to set ///public static void SetLeft(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(LeftProperty, length); } /// /// The Right DependencyProperty /// public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached("Right", typeof(double), typeof(InkCanvas), new FrameworkPropertyMetadata(Double.NaN, new PropertyChangedCallback(OnPositioningChanged)), new ValidateValueCallback(System.Windows.Shapes.Shape.IsDoubleFiniteOrNaN)); ////// Reads the attached property Right from the given element. /// /// The element from which to read the Right attached property. ///The property's Length value. ///[TypeConverter("System.Windows.LengthConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [AttachedPropertyBrowsableForChildren()] public static double GetRight(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(RightProperty); } /// /// Writes the attached property Right to the given element. /// /// The element to which to write the Right attached property. /// The Length to set ///public static void SetRight(UIElement element, double length) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(RightProperty, length); } /// /// OnPositioningChanged /// /// /// private static void OnPositioningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UIElement uie = d as UIElement; if ( uie != null ) { // Make sure the UIElement is a child of InkCanvasInnerCanvas. InkCanvasInnerCanvas p = VisualTreeHelper.GetParent(uie) as InkCanvasInnerCanvas; if ( p != null ) { if ( e.Property == InkCanvas.LeftProperty || e.Property == InkCanvas.TopProperty ) { // Invalidate measure for Left and/or Top. p.InvalidateMeasure(); } else { Debug.Assert(e.Property == InkCanvas.RightProperty || e.Property == InkCanvas.BottomProperty, string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unknown dependency property detected - {0}.", e.Property)); // Invalidate arrange for Right and/or Bottom. p.InvalidateArrange(); } } } } ////// The DependencyProperty for the Strokes property. /// public static readonly DependencyProperty StrokesProperty = InkPresenter.StrokesProperty.AddOwner( typeof(InkCanvas), new FrameworkPropertyMetadata( new StrokeCollectionDefaultValueFactory(), new PropertyChangedCallback(OnStrokesChanged))); ////// Gets/Sets the Strokes property. /// public StrokeCollection Strokes { get { return (StrokeCollection)GetValue(StrokesProperty); } set { SetValue(StrokesProperty, value); } } private static void OnStrokesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { InkCanvas inkCanvas = (InkCanvas)d; StrokeCollection oldValue = (StrokeCollection)e.OldValue; StrokeCollection newValue = (StrokeCollection)e.NewValue; // // only change the prop if it's a different object. We don't // want to be doing this for no reason // if ( !object.ReferenceEquals(oldValue, newValue) ) { // Clear the selected strokes without raising event. inkCanvas.CoreChangeSelection(new StrokeCollection(), inkCanvas.InkCanvasSelection.SelectedElements, false); inkCanvas.InitializeInkObject(); InkCanvasStrokesReplacedEventArgs args = new InkCanvasStrokesReplacedEventArgs(newValue, oldValue); //new, previous //raise the StrokesChanged event through our protected virtual inkCanvas.OnStrokesReplaced(args); } } ////// Returns the SelectionAdorner /// internal InkCanvasSelectionAdorner SelectionAdorner { get { // We have to create our visual at this point. if ( _selectionAdorner == null ) { // Create the selection Adorner. _selectionAdorner = new InkCanvasSelectionAdorner(InnerCanvas); // Bind the InkCanvas.ActiveEditingModeProperty // to SelectionAdorner.VisibilityProperty. Binding activeEditingModeBinding = new Binding(); activeEditingModeBinding.Path = new PropertyPath(InkCanvas.ActiveEditingModeProperty); activeEditingModeBinding.Mode = BindingMode.OneWay; activeEditingModeBinding.Source = this; activeEditingModeBinding.Converter = new ActiveEditingMode2VisibilityConverter(); _selectionAdorner.SetBinding(UIElement.VisibilityProperty, activeEditingModeBinding); } return _selectionAdorner; } } ////// Returns the FeedbackAdorner /// internal InkCanvasFeedbackAdorner FeedbackAdorner { get { VerifyAccess(); if ( _feedbackAdorner == null ) { _feedbackAdorner = new InkCanvasFeedbackAdorner(this); } return _feedbackAdorner; } } ////// Read/Write access to the EraserShape property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsGestureRecognizerAvailable { get { //this property will verify access return this.GestureRecognizer.IsRecognizerAvailable; } } ////// Emulate Panel's Children property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public UIElementCollection Children { get { // No need to invoke VerifyAccess since the call is forwarded. return InnerCanvas.Children; } } ////// The DependencyProperty for the DefaultDrawingAttributes property. /// public static readonly DependencyProperty DefaultDrawingAttributesProperty = DependencyProperty.Register( "DefaultDrawingAttributes", typeof(DrawingAttributes), typeof(InkCanvas), new FrameworkPropertyMetadata( new DrawingAttributesDefaultValueFactory(), new PropertyChangedCallback(OnDefaultDrawingAttributesChanged)), (ValidateValueCallback)delegate(object value) { return value != null; }); ////// Gets/Sets the DefaultDrawingAttributes property. /// public DrawingAttributes DefaultDrawingAttributes { get { return (DrawingAttributes)GetValue(DefaultDrawingAttributesProperty); } set { SetValue(DefaultDrawingAttributesProperty, value); } } private static void OnDefaultDrawingAttributesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { InkCanvas inkCanvas = (InkCanvas)d; DrawingAttributes oldValue = (DrawingAttributes)e.OldValue; DrawingAttributes newValue = (DrawingAttributes)e.NewValue; // This can throw, so call it first inkCanvas.UpdateDynamicRenderer(newValue); // We only fire Changed event when there is an instance change. if ( !object.ReferenceEquals(oldValue, newValue) ) { //we didn't throw, change our backing value oldValue.AttributeChanged -= new PropertyDataChangedEventHandler(inkCanvas.DefaultDrawingAttributes_Changed); DrawingAttributesReplacedEventArgs args = new DrawingAttributesReplacedEventArgs(newValue, oldValue); newValue.AttributeChanged += new PropertyDataChangedEventHandler(inkCanvas.DefaultDrawingAttributes_Changed); inkCanvas.RaiseDefaultDrawingAttributeReplaced(args); } } ////// Read/Write access to the EraserShape property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public StylusShape EraserShape { get { VerifyAccess(); if (_eraserShape == null) { _eraserShape = new RectangleStylusShape(8f, 8f); } return _eraserShape; } set { VerifyAccess(); if (value == null) { throw new ArgumentNullException("value"); } else { // Invoke getter since this property is lazily created. StylusShape oldShape = EraserShape; _eraserShape = value; if ( oldShape.Width != _eraserShape.Width || oldShape.Height != _eraserShape.Height || oldShape.Rotation != _eraserShape.Rotation || oldShape.GetType() != _eraserShape.GetType()) { EditingCoordinator.UpdatePointEraserCursor(); } } } } ////// ActiveEditingMode /// internal static readonly DependencyPropertyKey ActiveEditingModePropertyKey = DependencyProperty.RegisterReadOnly( "ActiveEditingMode", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata(InkCanvasEditingMode.Ink)); ////// ActiveEditingModeProperty Dependency Property /// public static readonly DependencyProperty ActiveEditingModeProperty = ActiveEditingModePropertyKey.DependencyProperty; ////// Gets the ActiveEditingMode /// public InkCanvasEditingMode ActiveEditingMode { get { return (InkCanvasEditingMode)GetValue(ActiveEditingModeProperty); } } ////// The DependencyProperty for the EditingMode property. /// public static readonly DependencyProperty EditingModeProperty = DependencyProperty.Register( "EditingMode", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata( InkCanvasEditingMode.Ink, new PropertyChangedCallback(OnEditingModeChanged)), new ValidateValueCallback(ValidateEditingMode)); ////// Gets/Sets EditingMode /// public InkCanvasEditingMode EditingMode { get { return (InkCanvasEditingMode)GetValue(EditingModeProperty); } set { SetValue(EditingModeProperty, value); } } private static void OnEditingModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ( (InkCanvas)d ).RaiseEditingModeChanged( new RoutedEventArgs(InkCanvas.EditingModeChangedEvent, d)); } ////// The DependencyProperty for the EditingModeInverted property. /// public static readonly DependencyProperty EditingModeInvertedProperty = DependencyProperty.Register( "EditingModeInverted", typeof(InkCanvasEditingMode), typeof(InkCanvas), new FrameworkPropertyMetadata( InkCanvasEditingMode.EraseByStroke, new PropertyChangedCallback(OnEditingModeInvertedChanged)), new ValidateValueCallback(ValidateEditingMode)); ////// Gets/Sets EditingMode /// public InkCanvasEditingMode EditingModeInverted { get { return (InkCanvasEditingMode)GetValue(EditingModeInvertedProperty); } set { SetValue(EditingModeInvertedProperty, value); } } private static void OnEditingModeInvertedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ( (InkCanvas)d ).RaiseEditingModeInvertedChanged( new RoutedEventArgs(InkCanvas.EditingModeInvertedChangedEvent, d)); } private static bool ValidateEditingMode(object value) { return EditingModeHelper.IsDefined((InkCanvasEditingMode)value); } ////// This flag indicates whether the developer is using a custom mouse cursor. /// /// If this flag is true, we will never change the current cursor on them. Not /// on edit mode change. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool UseCustomCursor { get { VerifyAccess(); return _useCustomCursor; } set { VerifyAccess(); if ( _useCustomCursor != value ) { _useCustomCursor = value; UpdateCursor(); } } } ////// Gets or set if moving of selection is enabled /// ///bool public bool MoveEnabled { get { VerifyAccess(); return _editingCoordinator.MoveEnabled; } set { VerifyAccess(); bool oldValue = _editingCoordinator.MoveEnabled; if (oldValue != value) { _editingCoordinator.MoveEnabled = value; } } } ////// Gets or set if resizing selection is enabled /// ///bool public bool ResizeEnabled { get { VerifyAccess(); return _editingCoordinator.ResizeEnabled; } set { VerifyAccess(); bool oldValue = _editingCoordinator.ResizeEnabled; if (oldValue != value) { _editingCoordinator.ResizeEnabled = value; } } } ////// Read/Write access to the DefaultPacketDescription property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public StylusPointDescription DefaultStylusPointDescription { get { VerifyAccess(); return _defaultStylusPointDescription; } set { VerifyAccess(); // // no nulls allowed // if ( value == null ) { throw new ArgumentNullException("value"); } _defaultStylusPointDescription = value; } } ////// Read/Write the enabled ClipboardFormats /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IEnumerablePreferredPasteFormats { get { VerifyAccess(); return ClipboardProcessor.PreferredFormats; } set { VerifyAccess(); // Cannot be null if ( value == null ) { // Null is not allowed as the argument value throw new ArgumentNullException("value"); } ClipboardProcessor.PreferredFormats = value; } } #endregion Public Properties #region Public Events /// /// The StrokeErased Routed Event /// public static readonly RoutedEvent StrokeCollectedEvent = EventManager.RegisterRoutedEvent("StrokeCollected", RoutingStrategy.Bubble, typeof(InkCanvasStrokeCollectedEventHandler), typeof(InkCanvas)); ////// Add / Remove StrokeCollected handler /// [Category("Behavior")] public event InkCanvasStrokeCollectedEventHandler StrokeCollected { add { AddHandler(InkCanvas.StrokeCollectedEvent, value); } remove { RemoveHandler(InkCanvas.StrokeCollectedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokeCollectedEventArgs to raise the event with protected virtual void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Allows the InkCollectionBehavior to raise the StrokeCollected event via the protected virtual /// /// InkCanvasStrokeCollectedEventArgs to raise the event with /// true only if 100% of the stylusPoints that makes up the stroke /// came from eventargs with the UserInitiated flag set to true ////// Critical: Calls critical method GestureRecognizer.CriticalRecognize. It is important /// that this is only called if userInitiated is true. /// [SecurityCritical] internal void RaiseGestureOrStrokeCollected(InkCanvasStrokeCollectedEventArgs e, bool userInitiated) { Debug.Assert(e != null, "EventArg can not be null"); bool addStrokeToInkCanvas = true; // Initialize our flag. // The follow code raises Gesture event // The out-side code could throw exception in the their handlers. We use try/finally block to protect our status. try { // // perform gesture reco before raising this event // if we're in the right mode // //IMPORTANT: only call gesture recognition if userInitiated. See SecurityNote. if (userInitiated) { if ((this.ActiveEditingMode == InkCanvasEditingMode.InkAndGesture || this.ActiveEditingMode == InkCanvasEditingMode.GestureOnly) && this.GestureRecognizer.IsRecognizerAvailable) { StrokeCollection strokes = new StrokeCollection(); strokes.Add(e.Stroke); // // GestureRecognizer.Recognize demands unmanaged code, we assert it here // as this codepath is only called in response to user input // ReadOnlyCollectionresults = this.GestureRecognizer.CriticalRecognize(strokes); if (results.Count > 0) { InkCanvasGestureEventArgs args = new InkCanvasGestureEventArgs(strokes, results); if (results[0].ApplicationGesture == ApplicationGesture.NoGesture) { // // we set Cancel=true if we didn't detect a gesture // args.Cancel = true; } else { args.Cancel = false; } this.OnGesture(args); // // now that we've raised the Gesture event and the developer // has had a chance to change args.Cancel, see what their intent is. // if (args.Cancel == false) { //bail out and don't add //the stroke to InkCanvas.Strokes addStrokeToInkCanvas = false; // Reset the flag. return; } } } } // Reset the flag. addStrokeToInkCanvas = false; // // only raise StrokeCollected if we're in InkCanvasEditingMode.Ink or InkCanvasEditingMode.InkAndGesture // if ( this.ActiveEditingMode == InkCanvasEditingMode.Ink || this.ActiveEditingMode == InkCanvasEditingMode.InkAndGesture ) { //add the stroke to the StrokeCollection and raise this event this.Strokes.Add(e.Stroke); this.OnStrokeCollected(e); } } finally { // If the gesture events are failed, we should still add Stroke to the InkCanvas so that the data won't be lost. if ( addStrokeToInkCanvas ) { this.Strokes.Add(e.Stroke); } } } /// /// The Gesture Routed Event /// public static readonly RoutedEvent GestureEvent = EventManager.RegisterRoutedEvent("Gesture", RoutingStrategy.Bubble, typeof(InkCanvasGestureEventHandler), typeof(InkCanvas)); ////// Add / Remove Gesture handler /// [Category("Behavior")] public event InkCanvasGestureEventHandler Gesture { add { AddHandler(InkCanvas.GestureEvent, value); } remove { RemoveHandler(InkCanvas.GestureEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasGestureEventArgs to raise the event with protected virtual void OnGesture(InkCanvasGestureEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Raised when the InkCanvas.Strokes StrokeCollection has been replaced with another one /// public event InkCanvasStrokesReplacedEventHandler StrokesReplaced; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokesChangedEventArgs to raise the event with protected virtual void OnStrokesReplaced(InkCanvasStrokesReplacedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != this.StrokesReplaced) { StrokesReplaced(this, e); } } ////// Raised when the InkCanvas.DefaultDrawingAttributes has been replaced with another one /// public event DrawingAttributesReplacedEventHandler DefaultDrawingAttributesReplaced; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// DrawingAttributesReplacedEventArgs to raise the event with protected virtual void OnDefaultDrawingAttributesReplaced(DrawingAttributesReplacedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if (e == null) { throw new ArgumentNullException("e"); } if (null != this.DefaultDrawingAttributesReplaced) { DefaultDrawingAttributesReplaced(this, e); } } ////// Private helper for raising DDAReplaced. Invalidates the inking cursor /// /// private void RaiseDefaultDrawingAttributeReplaced(DrawingAttributesReplacedEventArgs e) { this.OnDefaultDrawingAttributesReplaced(e); // Invalidate the inking cursor _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } ////// Event corresponds to ActiveEditingModeChanged /// public static readonly RoutedEvent ActiveEditingModeChangedEvent = EventManager.RegisterRoutedEvent("ActiveEditingModeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove ActiveEditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler ActiveEditingModeChanged { add { AddHandler(InkCanvas.ActiveEditingModeChangedEvent, value); } remove { RemoveHandler(InkCanvas.ActiveEditingModeChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnActiveEditingModeChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if (e == null) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises ActiveEditingModeChanged /// /// EventArgs to raise the event with internal void RaiseActiveEditingModeChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); InkCanvasEditingMode mode = this.ActiveEditingMode; if (mode != _editingCoordinator.ActiveEditingMode) { //change our DP, then raise the event via our protected override SetValue(ActiveEditingModePropertyKey, _editingCoordinator.ActiveEditingMode); this.OnActiveEditingModeChanged(e); } } ////// Event corresponds to EditingModeChanged /// public static readonly RoutedEvent EditingModeChangedEvent = EventManager.RegisterRoutedEvent("EditingModeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler EditingModeChanged { add { AddHandler(InkCanvas.EditingModeChangedEvent, value); } remove { RemoveHandler(InkCanvas.EditingModeChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnEditingModeChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises EditingModeChanged but first /// talks to the InkEditor about it /// /// EventArgs to raise the event with private void RaiseEditingModeChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); _editingCoordinator.UpdateEditingState(false /* EditingMode */); this.OnEditingModeChanged(e); } //note: there is no need for an internal RaiseEditingModeInvertedChanging //since this isn't a dynamic property and therefore can not be set //outside of this class ////// Event corresponds to EditingModeInvertedChanged /// public static readonly RoutedEvent EditingModeInvertedChangedEvent = EventManager.RegisterRoutedEvent("EditingModeInvertedChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler EditingModeInvertedChanged { add { AddHandler(InkCanvas.EditingModeInvertedChangedEvent, value); } remove { RemoveHandler(InkCanvas.EditingModeInvertedChangedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnEditingModeInvertedChanged(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Private helper that raises EditingModeInvertedChanged but first /// talks to the InkEditor about it /// /// EventArgs to raise the event with private void RaiseEditingModeInvertedChanged(RoutedEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); _editingCoordinator.UpdateEditingState(true /* EditingModeInverted */); this.OnEditingModeInvertedChanged(e); } ////// Occurs when the user has moved the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the move. /// public event InkCanvasSelectionEditingEventHandler SelectionMoving; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionEditingEventArgs to raise the event with protected virtual void OnSelectionMoving( InkCanvasSelectionEditingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionMoving) { SelectionMoving(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionMoving event via the protected virtual /// /// InkCanvasSelectionEditingEventArgs to raise the event with internal void RaiseSelectionMoving( InkCanvasSelectionEditingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionMoving(e); } ////// Occurs when the user has moved the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the move. /// public event EventHandler SelectionMoved; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionMoved(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionMoved) { SelectionMoved(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionMoved event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionMoved(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionMoved(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// Occurs when the user has erased Strokes using the erase behavior /// /// This event allows the developer to cancel the erase -- therefore, the Stroke should not disappear until /// this event has finished. /// public event InkCanvasStrokeErasingEventHandler StrokeErasing; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasStrokeErasingEventArgs to raise the event with protected virtual void OnStrokeErasing(InkCanvasStrokeErasingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != StrokeErasing) { StrokeErasing(this, e); } } ////// Allows the EditingBehaviors to raise the InkErasing event via the protected virtual /// /// InkCanvasStrokeErasingEventArgs to raise the event with internal void RaiseStrokeErasing(InkCanvasStrokeErasingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnStrokeErasing(e); } ////// The StrokeErased Routed Event /// public static readonly RoutedEvent StrokeErasedEvent = EventManager.RegisterRoutedEvent("StrokeErased", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(InkCanvas)); ////// Add / Remove EditingModeChanged handler /// [Category("Behavior")] public event RoutedEventHandler StrokeErased { add { AddHandler(InkCanvas.StrokeErasedEvent, value); } remove { RemoveHandler(InkCanvas.StrokeErasedEvent, value); } } ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnStrokeErased(RoutedEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } RaiseEvent(e); } ////// Allows the EditingBehaviors to raise the InkErasing event via the protected virtual /// internal void RaiseInkErased() { this.OnStrokeErased( new RoutedEventArgs(InkCanvas.StrokeErasedEvent, this)); } ////// Occurs when the user has resized the selection, after they lift their stylus to commit the change. /// This event allows the developer to cancel the resize. /// public event InkCanvasSelectionEditingEventHandler SelectionResizing; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionEditingEventArgs to raise the event with protected virtual void OnSelectionResizing( InkCanvasSelectionEditingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionResizing) { SelectionResizing(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionResizing event via the protected virtual /// /// InkCanvasSelectionEditingEventArgs to raise the event with internal void RaiseSelectionResizing( InkCanvasSelectionEditingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionResizing(e); } ////// Occurs when the selection has been resized via UI interaction. /// public event EventHandler SelectionResized; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionResized(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionResized) { SelectionResized(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionResized event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionResized(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionResized(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// Occurs when the selection has been changed, either using the lasso or programmatically. /// This event allows the developer to cancel the change. /// public event InkCanvasSelectionChangingEventHandler SelectionChanging; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// InkCanvasSelectionChangingEventArgs to raise the event with protected virtual void OnSelectionChanging(InkCanvasSelectionChangingEventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionChanging) { SelectionChanging(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionChanging event via the protected virtual /// /// InkCanvasSelectionChangingEventArgs to raise the event with private void RaiseSelectionChanging(InkCanvasSelectionChangingEventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionChanging(e); } ////// Occurs when the selection has been changed /// public event EventHandler SelectionChanged; ////// Protected virtual version for developers deriving from InkCanvas. /// This method is what actually throws the event. /// /// EventArgs to raise the event with protected virtual void OnSelectionChanged(EventArgs e) { // No need to invoke VerifyAccess since this method is thread free. if ( e == null ) { throw new ArgumentNullException("e"); } if (null != SelectionChanged) { SelectionChanged(this, e); } } ////// Allows the EditingBehaviors to raise the SelectionChanged event via the protected virtual /// /// EventArgs to raise the event with internal void RaiseSelectionChanged(EventArgs e) { Debug.Assert(e != null, "EventArg can not be null"); this.OnSelectionChanged(e); // Update the cursor of SelectionEditor behavior. EditingCoordinator.SelectionEditor.OnInkCanvasSelectionChanged(); } ////// The InkCanvas uses an inner Canvas to host children. When the inner Canvas's children /// are changed, we need to call the protected virtual OnVisualChildrenChanged on the InkCanvas /// so that subclasses can be notified /// internal void RaiseOnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { this.OnVisualChildrenChanged(visualAdded, visualRemoved); } #endregion Public Events #region Public Methods ////// Returns the enabled gestures. This method throws an exception if GestureRecognizerAvailable /// is false /// ///public ReadOnlyCollection GetEnabledGestures() { // No need to invoke VerifyAccess since it's checked in GestureRecognizer.GetEnabledGestures. //gestureRecognizer throws appropriately if there is no gesture recognizer available return new ReadOnlyCollection (this.GestureRecognizer.GetEnabledGestures()); } /// /// Sets the enabled gestures. This method throws an exception if GestureRecognizerAvailable /// is false /// ///public void SetEnabledGestures(IEnumerable applicationGestures) { // No need to invoke VerifyAccess since it's checked in GestureRecognizer.GetEnabledGestures. //gestureRecognizer throws appropriately if there is no gesture recognizer available this.GestureRecognizer.SetEnabledGestures(applicationGestures); } /// /// Get the selection bounds. /// ///public Rect GetSelectionBounds() { VerifyAccess(); return InkCanvasSelection.SelectionBounds; } /// /// provides access to the currently selected elements which are children of this InkCanvas /// public ReadOnlyCollectionGetSelectedElements() { VerifyAccess(); return InkCanvasSelection.SelectedElements; } /// /// provides read access to the currently selected strokes /// public StrokeCollection GetSelectedStrokes() { VerifyAccess(); StrokeCollection sc = new StrokeCollection(); sc.Add(InkCanvasSelection.SelectedStrokes); return sc; } ////// Overload which calls the more complex version, passing null for selectedElements /// /// The strokes to select public void Select(StrokeCollection selectedStrokes) { // No need to invoke VerifyAccess since this call is forwarded. Select(selectedStrokes, null); } ////// Overload which calls the more complex version, passing null for selectedStrokes /// /// The elements to select public void Select(IEnumerableselectedElements) { // No need to invoke VerifyAccess since this call is forwarded. Select(null, selectedElements); } /// /// Overload which calls the more complex version, passing null for selectedStrokes /// /// The strokes to select /// The elements to select public void Select(StrokeCollection selectedStrokes, IEnumerableselectedElements) { VerifyAccess(); // NTRAID-WINDOWS#1134932-2005/12/01-WAYNEZEN // Try to switch to Select mode first. If we fail to change the mode, then just simply no-op. if ( EnsureActiveEditingMode(InkCanvasEditingMode.Select) ) { // // validate // UIElement[] validElements = ValidateSelectedElements(selectedElements); StrokeCollection validStrokes = ValidateSelectedStrokes(selectedStrokes); // // this will raise the 'SelectionChanging' event ONLY if the selection // is actually different // ChangeInkCanvasSelection(validStrokes, validElements); } } /// /// Hit test on the selection /// /// ///public InkCanvasSelectionHitResult HitTestSelection(Point point) { VerifyAccess(); // Ensure the visual tree. if ( _localAdornerDecorator == null ) { ApplyTemplate(); } return InkCanvasSelection.HitTestSelection(point); } /// /// Copy the current selection in the InkCanvas to the clipboard /// public void CopySelection() { VerifyAccess(); PrivateCopySelection(); } ////// Copy the current selection in the InkCanvas to the clipboard and then delete it /// public void CutSelection() { VerifyAccess(); // Copy first InkCanvasClipboardDataFormats copiedDataFormats = PrivateCopySelection(); // Don't even bother if we don't have a selection. if ( copiedDataFormats != InkCanvasClipboardDataFormats.None ) { // Then delete the current selection. Note the XAML format won't be avaliable under Partial // Trust. So, the selected element shouldn't be copied or removed. DeleteCurrentSelection( /* We want to delete the selected Strokes if there is ISF and/or XAML data being copied */ (copiedDataFormats & (InkCanvasClipboardDataFormats.ISF | InkCanvasClipboardDataFormats.XAML)) != 0, /* We only want to delete the selected elements if there is XAML data being copied */ (copiedDataFormats & InkCanvasClipboardDataFormats.XAML) != 0); } } ////// Paste the contents of the clipboard into the InkCanvas /// public void Paste() { // No need to call VerifyAccess since this call is forwarded. // We always paste the data to the default location which is (0,0). Paste(new Point(c_pasteDefaultLocation, c_pasteDefaultLocation)); } ////// Paste the contents of the clipboard to the specified location in the InkCanvas /// public void Paste(Point point) { VerifyAccess(); if (DoubleUtil.IsNaN(point.X) || DoubleUtil.IsNaN(point.Y) || Double.IsInfinity(point.X)|| Double.IsInfinity(point.Y) ) { throw new ArgumentException(SR.Get(SRID.InvalidPoint), "point"); } // // only do this if the user is not editing (input active) // or we will violate a dispatcher lock // if (!_editingCoordinator.UserIsEditing) { IDataObject dataObj = null; try { dataObj = Clipboard.GetDataObject(); } catch (ExternalException) { //harden against ExternalException return; } if (dataObj != null) { PasteFromDataObject(dataObj, point); } } } ////// Return true if clipboard contents can be pasted into the InkCanvas. /// public bool CanPaste() { VerifyAccess(); bool ret = false; // // can't paste if the user is editing (input active) // or we will violate a dispatcher lock // if (_editingCoordinator.UserIsEditing) { return false; } // Check whether the caller has the clipboard permission. if ( !SecurityHelper.CallerHasAllClipboardPermission() ) { return false; } ret = PrivateCanPaste(); return ret; } #endregion Public Methods //----------------------------------------------------- // // IAddChild Interface // //----------------------------------------------------- #region IAddChild Interface ////// Called to Add the object as a Child. /// /// /// Object to add as a child /// void IAddChild.AddChild(Object value) { // VerifyAccess(); if ( value == null ) { throw new ArgumentNullException("value"); } ( (IAddChild)InnerCanvas ).AddChild(value); } ////// Called when text appears under the tag in markup. /// /// /// Text to Add to the Canvas /// void IAddChild.AddText(string textData) { // VerifyAccess(); ( (IAddChild)InnerCanvas ).AddText(textData); } #endregion IAddChild Interface //------------------------------------------------------ // // Protected Properties // //----------------------------------------------------- #region Protected Properties ////// Returns enumerator to logical children. /// protected internal override IEnumerator LogicalChildren { get { // VerifyAccess( ); // Return the private logical children of the InnerCanvas return ( (InkCanvasInnerCanvas)InnerCanvas).PrivateLogicalChildren; } } ////// Protected DynamicRenderer property. /// protected DynamicRenderer DynamicRenderer { get { VerifyAccess(); return InternalDynamicRenderer; } set { VerifyAccess(); if (!object.ReferenceEquals(value, _dynamicRenderer)) { int previousIndex = -1; //remove the existing plugin if (_dynamicRenderer != null) { //remove the plugin from the collection previousIndex = this.StylusPlugIns.IndexOf(_dynamicRenderer); if (-1 != previousIndex) { this.StylusPlugIns.RemoveAt(previousIndex); } //remove the plugin's visual from the InkPresenter if (this.InkPresenter.ContainsAttachedVisual(_dynamicRenderer.RootVisual)) { this.InkPresenter.DetachVisuals(_dynamicRenderer.RootVisual); } } _dynamicRenderer = value; if (_dynamicRenderer != null) //null is acceptable { //remove the plugin from the collection if (!this.StylusPlugIns.Contains(_dynamicRenderer)) { if (-1 != previousIndex) { //insert the new DR in the same location as the old one this.StylusPlugIns.Insert(previousIndex, _dynamicRenderer); } else { this.StylusPlugIns.Add(_dynamicRenderer); } } //refer to the same DrawingAttributes as the InkCanvas _dynamicRenderer.DrawingAttributes = this.DefaultDrawingAttributes; //attach the DynamicRenderer if it is not already if (!(this.InkPresenter.ContainsAttachedVisual(_dynamicRenderer.RootVisual)) && _dynamicRenderer.Enabled && _dynamicRenderer.RootVisual != null) { this.InkPresenter.AttachVisuals(_dynamicRenderer.RootVisual, this.DefaultDrawingAttributes); } } } } } ////// Protected read only access to the InkPresenter this InkCanvas uses /// protected InkPresenter InkPresenter { get { VerifyAccess(); if ( _inkPresenter == null ) { _inkPresenter = new InkPresenter(); // Bind the InkPresenter.Strokes to InkCanvas.Strokes Binding strokes = new Binding(); strokes.Path = new PropertyPath(InkCanvas.StrokesProperty); strokes.Mode = BindingMode.OneWay; strokes.Source = this; _inkPresenter.SetBinding(InkPresenter.StrokesProperty, strokes); } return _inkPresenter; } } #endregion Protected Properties #region Internal Properties / Methods ////// Deselect the current selection /// internal static readonly RoutedCommand DeselectCommand = new RoutedCommand("Deselect", typeof(InkCanvas)); ////// UserInitiatedCanPaste /// ////// /// Critical - Elevates the AllClipboard permission for checking the supported data in InkCanvas. /// [SecurityCritical] private bool UserInitiatedCanPaste() { ( new UIPermission(UIPermissionClipboard.AllClipboard) ).Assert();//BlessedAssert try { return PrivateCanPaste(); } finally { UIPermission.RevertAssert(); } } ////// PrivateCanPaste /// ///private bool PrivateCanPaste() { bool canPaste = false; IDataObject dataObj = null; try { dataObj = Clipboard.GetDataObject(); } catch (ExternalException) { //harden against ExternalException return false; } if ( dataObj != null ) { canPaste = ClipboardProcessor.CheckDataFormats(dataObj); } return canPaste; } /// /// This method pastes data from an IDataObject object /// internal void PasteFromDataObject(IDataObject dataObj, Point point) { // Reset the current selection ClearSelection(false); // Assume that there is nothing to be selected. StrokeCollection newStrokes = new StrokeCollection(); ListnewElements = new List (); // Paste the data from the data object. if ( !ClipboardProcessor.PasteData(dataObj, ref newStrokes, ref newElements) ) { // Paste was failed. return; } else if ( newStrokes.Count == 0 && newElements.Count == 0 ) { // Nothing has been received from the clipboard. return; } // We add elements here. Then we have to wait for the layout update. UIElementCollection children = Children; foreach ( UIElement element in newElements ) { children.Add(element); } if ( newStrokes != null ) { Strokes.Add(newStrokes); } try { // We should fire SelectionChanged event if the current editing mode is Select. CoreChangeSelection(newStrokes, newElements.ToArray(), EditingMode == InkCanvasEditingMode.Select); } finally { // Now move the selection to the desired location. Rect bounds = GetSelectionBounds( ); InkCanvasSelection.CommitChanges(Rect.Offset(bounds, -bounds.Left + point.X, -bounds.Top + point.Y), false); if (EditingMode != InkCanvasEditingMode.Select) { // Clear the selection without the event if the editing mode is not Select. ClearSelection(false); } } } /// /// Copies the InkCanvas contents to a DataObject and returns it to the caller. /// Can return NULL for DataObject. /// ////// Critical: Clipboard.SetDataObject will invoke DataObject.DataStore.GetFormats. /// The methods demands SerializationPermission. We perform the elevation before /// calling SetDataObject. /// TreatAsSafe: There is no input here. The ISF data are safe to being put in the clipboard. /// [SecurityCritical, SecurityTreatAsSafe] private InkCanvasClipboardDataFormats CopyToDataObject() { DataObject dataObj; (new UIPermission(UIPermissionClipboard.AllClipboard)).Assert();//BlessedAssert try { dataObj = new DataObject(); } finally { UIPermission.RevertAssert(); } InkCanvasClipboardDataFormats copiedDataFormats = InkCanvasClipboardDataFormats.None; // Try to copy the data from the InkCanvas to the clipboard. copiedDataFormats = ClipboardProcessor.CopySelectedData(dataObj); if ( copiedDataFormats != InkCanvasClipboardDataFormats.None ) { PermissionSet ps = new PermissionSet(PermissionState.None); ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter)); ps.AddPermission(new UIPermission(UIPermissionClipboard.AllClipboard)); ps.Assert(); // BlessedAssert try { // Put our data object into the clipboard. Clipboard.SetDataObject(dataObj, true); } finally { SecurityPermission.RevertAssert(); } } return copiedDataFormats; } ////// Read-only property of the associated EditingCoordinator. /// internal EditingCoordinator EditingCoordinator { get { return _editingCoordinator; } } ////// Internal access to the protected DynamicRenderer. Can be null. /// internal DynamicRenderer InternalDynamicRenderer { get { return _dynamicRenderer; } } ////// Return the inner Canvas. /// internal InkCanvasInnerCanvas InnerCanvas { get { // We have to create our visual at this point. if (_innerCanvas == null) { // Create our InnerCanvas to change the logical parent of Canvas' children. _innerCanvas = new InkCanvasInnerCanvas(this); // Bind the inner Canvas' Background to InkCanvas' Background Binding background = new Binding(); background.Path = new PropertyPath(InkCanvas.BackgroundProperty); background.Mode = BindingMode.OneWay; background.Source = this; _innerCanvas.SetBinding(Panel.BackgroundProperty, background); } return _innerCanvas; } } ////// Internal access to the current selection /// internal InkCanvasSelection InkCanvasSelection { get { if ( _selection == null ) { _selection = new InkCanvasSelection(this); } return _selection; } } ////// Internal helper called by the LassoSelectionBehavior /// internal void BeginDynamicSelection(Visual visual) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); _dynamicallySelectedStrokes = new StrokeCollection(); InkPresenter.AttachVisuals(visual, new DrawingAttributes()); } ////// Internal helper called by LassoSelectionBehavior to update the display /// of dynamically added strokes /// internal void UpdateDynamicSelection( StrokeCollection strokesToDynamicallySelect, StrokeCollection strokesToDynamicallyUnselect) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); // // update our internal stroke collections used by dynamic selection // if (strokesToDynamicallySelect != null) { foreach (Stroke s in strokesToDynamicallySelect) { _dynamicallySelectedStrokes.Add(s); s.IsSelected = true; } } if (strokesToDynamicallyUnselect != null) { foreach (Stroke s in strokesToDynamicallyUnselect) { System.Diagnostics.Debug.Assert(_dynamicallySelectedStrokes.Contains(s)); _dynamicallySelectedStrokes.Remove(s); s.IsSelected = false; } } } ////// Internal helper used by LassoSelectionBehavior /// internal StrokeCollection EndDynamicSelection(Visual visual) { EditingCoordinator.DebugCheckActiveBehavior(EditingCoordinator.LassoSelectionBehavior); InkPresenter.DetachVisuals(visual); StrokeCollection selectedStrokes = _dynamicallySelectedStrokes; _dynamicallySelectedStrokes = null; return selectedStrokes; } ////// Clears the selection in the ink canvas /// and returns a bool indicating if the selection was actually cleared /// (developers can cancel selectionchanging) /// /// If the InkCanvas has no selection, selectionchanging is not raised /// and this method returns true /// /// used by InkEditor during editing operations /// ///true if selection was cleared even after raising selectionchanging internal bool ClearSelectionRaiseSelectionChanging() { if ( !InkCanvasSelection.HasSelection ) { return true; } // // attempt to clear selection // ChangeInkCanvasSelection(new StrokeCollection(), new UIElement[]{}); return !InkCanvasSelection.HasSelection; } ////// ClearSelection /// Called by: /// PasteFromDataObject /// EditingCoordinator.UpdateEditingState /// internal void ClearSelection(bool raiseSelectionChangedEvent) { if ( InkCanvasSelection.HasSelection ) { // Reset the current selection CoreChangeSelection(new StrokeCollection(), new UIElement[] { }, raiseSelectionChangedEvent); } } ////// Helper that creates selection for an InkCanvas. Used by the SelectedStrokes and /// SelectedElements properties /// internal void ChangeInkCanvasSelection(StrokeCollection strokes, UIElement[] elements) { //validate in debug only for this internal static Debug.Assert(strokes != null && elements != null, "Invalid arguments in ChangeInkCanvasSelection"); bool strokesAreDifferent; bool elementsAreDifferent; InkCanvasSelection.SelectionIsDifferentThanCurrent(strokes, out strokesAreDifferent, elements, out elementsAreDifferent); if ( strokesAreDifferent || elementsAreDifferent ) { InkCanvasSelectionChangingEventArgs args = new InkCanvasSelectionChangingEventArgs(strokes, elements); StrokeCollection validStrokes = strokes; UIElement[] validElements = elements; this.RaiseSelectionChanging(args); //now that the event has been raised and all of the delegates //have had their way with it, process the result if ( !args.Cancel ) { // // rock and roll, time to validate our arguments // note: these event args are visible outside the apis, // so we need to validate them again // // PERF-2006/05/02-WAYNEZEN, // Check our internal flag. If the SelectedStrokes has been changed, we shouldn't do any extra work here. if ( args.StrokesChanged ) { validStrokes = ValidateSelectedStrokes(args.GetSelectedStrokes()); int countOldSelectedStrokes = strokes.Count; for ( int i = 0; i < countOldSelectedStrokes; i++ ) { // PERF-2006/05/02-WAYNEZEN, // We only have to reset IsSelected for the elements no longer exists in the new collection. if ( !validStrokes.Contains(strokes[i]) ) { // NTRAID#WINDOWS-1045099-2006/05/02-waynezen, // Make sure we reset the IsSelected property which could have been // set to true in the dynamic selection. strokes[i].IsSelected = false; } } } // PERF-2006/05/02-WAYNEZEN, // Check our internal flag. If the SelectedElements has been changed, we shouldn't do any extra work here. if ( args.ElementsChanged ) { validElements = ValidateSelectedElements(args.GetSelectedElements()); } CoreChangeSelection(validStrokes, validElements, true); } else { StrokeCollection currentSelectedStrokes = InkCanvasSelection.SelectedStrokes; int countOldSelectedStrokes = strokes.Count; for ( int i = 0; i < countOldSelectedStrokes; i++ ) { // Make sure we reset the IsSelected property which could have been // set to true in the dynamic selection but not being selected previously. if ( !currentSelectedStrokes.Contains(strokes[i]) ) { strokes[i].IsSelected = false; } } } } } ////// Helper method used by ChangeInkCanvasSelection and directly by ClearSelectionWithoutSelectionChanging /// /// validStrokes /// validElements /// raiseSelectionChanged private void CoreChangeSelection(StrokeCollection validStrokes, IListvalidElements, bool raiseSelectionChanged) { InkCanvasSelection.Select(validStrokes, validElements, raiseSelectionChanged); } #if DEBUG_LASSO_FEEDBACK internal ContainerVisual RendererRootContainer { get {return _inkContainerVisual;} } #endif /// /// Private helper method used to retrieve a StrokeCollection which does AND operation on two input collections. /// /// The possible subset /// The container set ///True if subset is a subset of superset, false otherwise internal static StrokeCollection GetValidStrokes(StrokeCollection subset, StrokeCollection superset) { StrokeCollection validStrokes = new StrokeCollection(); int subsetCount = subset.Count; // special case an empty subset as a guaranteed subset if ( subsetCount == 0 ) { return validStrokes; } for ( int i = 0; i < subsetCount; i++ ) { Stroke stroke = subset[i]; if ( superset.Contains(stroke) ) { validStrokes.Add(stroke); } } return validStrokes; } ////// Register the commanding handlers for the clipboard operations /// ////// Critical: Elevates to associate a protected command (paste) with keyboard /// TreatAsSafe: We don't take user input here. Shift+Insert is the correct key binding, /// and therefore is expected by the user. /// [SecurityCritical, SecurityTreatAsSafe] private static void _RegisterClipboardHandlers() { Type ownerType = typeof(InkCanvas); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Cut, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.KeyShiftDelete, SRID.KeyShiftDeleteDisplayString); CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Copy, new ExecutedRoutedEventHandler(_OnCommandExecuted), new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled), SRID.KeyCtrlInsert, SRID.KeyCtrlInsertDisplayString); // Use temp variables to reduce code under elevation ExecutedRoutedEventHandler pasteExecuteEventHandler = new ExecutedRoutedEventHandler(_OnCommandExecuted); CanExecuteRoutedEventHandler pasteQueryEnabledEventHandler = new CanExecuteRoutedEventHandler(_OnQueryCommandEnabled); InputGesture pasteInputGesture = KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyShiftInsert), SR.Get(SRID.KeyShiftInsertDisplayString)); new UIPermission(UIPermissionClipboard.AllClipboard).Assert(); // BlessedAssert: try { CommandHelpers.RegisterCommandHandler(ownerType, ApplicationCommands.Paste, pasteExecuteEventHandler, pasteQueryEnabledEventHandler, pasteInputGesture); } finally { CodeAccessPermission.RevertAssert(); } } ////// Private helper used to ensure that any stroke collection /// passed to the InkCanvas is valid. Throws exceptions if the argument is invalid /// private StrokeCollection ValidateSelectedStrokes(StrokeCollection strokes) { // // null is a valid input // if (strokes == null) { return new StrokeCollection(); } else { return GetValidStrokes(strokes, this.Strokes); } } ////// Private helper used to ensure that a UIElement argument passed in /// is valid. /// private UIElement[] ValidateSelectedElements(IEnumerableselectedElements) { if (selectedElements == null) { return new UIElement[]{}; } List elements = new List (); foreach (UIElement element in selectedElements) { // NTRAID:WINDOWSOS#1621480-2006/04/26-WAYNEZEN, // Don't add the duplicated element. if ( !elements.Contains(element) ) { // // check the common case first, e // if ( InkCanvasIsAncestorOf(element) ) { elements.Add(element); } } } return elements.ToArray(); } /// /// Helper method used by DesignActivation to see if an element /// has this InkCanvas as an Ancestor /// private bool InkCanvasIsAncestorOf(UIElement element) { if (this != element && this.IsAncestorOf(element)) { return true; } return false; } ////// Handler called whenever changes are made to properties of the DefaultDrawingAttributes /// /// /// ///For example, when a developer changes the Color property on the DefaultDrawingAttributes, /// this event will fire - allowing the InkCanvas a chance to notify the BasicRTI Service. /// Also - we do not currently call through the DefaultDrawingAttributes setter since /// parameter validation in the setter may detect if the reference isn't changing, and ignore /// the call. Also - there is no need for extra parameter validation. private void DefaultDrawingAttributes_Changed(object sender, PropertyDataChangedEventArgs args) { // note that sender should be the same as _defaultDrawingAttributes // If a developer writes code to change the DefaultDrawingAttributes inside of the event // handler before the InkCanvas receives the notification (multi-cast delegate scenario) - // The DefaultDrawingAttributes should still be updated, and in that case we would // update the RTI DAC twice. Typically, however, this will just refresh the // attributes in the RTI thread. System.Diagnostics.Debug.Assert(object.ReferenceEquals(sender, DefaultDrawingAttributes)); InvalidateSubProperty(DefaultDrawingAttributesProperty); // Be sure to update the RealTimeInking PlugIn with the drawing attribute changes. UpdateDynamicRenderer(); // Invalidate the inking cursor _editingCoordinator.InvalidateBehaviorCursor(_editingCoordinator.InkCollectionBehavior); } ////// Helper method used to set up the DynamicRenderer. /// internal void UpdateDynamicRenderer() { UpdateDynamicRenderer(DefaultDrawingAttributes); } ////// Helper method used to set up the DynamicRenderer. /// private void UpdateDynamicRenderer(DrawingAttributes newDrawingAttributes) { ApplyTemplate(); if (this.DynamicRenderer != null) { this.DynamicRenderer.DrawingAttributes = newDrawingAttributes; if (!this.InkPresenter.AttachedVisualIsPositionedCorrectly(this.DynamicRenderer.RootVisual, newDrawingAttributes)) { if (this.InkPresenter.ContainsAttachedVisual(this.DynamicRenderer.RootVisual)) { this.InkPresenter.DetachVisuals(this.DynamicRenderer.RootVisual); } // Only hook up if we are enabled. As we change editing modes this routine will be called // to clean up things. if (this.DynamicRenderer.Enabled && this.DynamicRenderer.RootVisual != null) { this.InkPresenter.AttachVisuals(this.DynamicRenderer.RootVisual, newDrawingAttributes); } } } } private bool EnsureActiveEditingMode(InkCanvasEditingMode newEditingMode) { bool ret = true; if ( ActiveEditingMode != newEditingMode ) { if ( EditingCoordinator.IsStylusInverted ) { EditingModeInverted = newEditingMode; } else { EditingMode = newEditingMode; } // Verify whether user has cancelled the change in EditingModeChanging event. ret = ( ActiveEditingMode == newEditingMode ); } return ret; } // The ClipboardProcessor instance which deals with the operations relevant to the clipboard. private ClipboardProcessor ClipboardProcessor { get { if ( _clipboardProcessor == null ) { _clipboardProcessor = new ClipboardProcessor(this); } return _clipboardProcessor; } } //lazy instance the gesture recognizer private GestureRecognizer GestureRecognizer { get { if (_gestureRecognizer == null) { _gestureRecognizer = new GestureRecognizer(); } return _gestureRecognizer; } } ////// Delete the current selection /// private void DeleteCurrentSelection(bool removeSelectedStrokes, bool removeSelectedElements) { Debug.Assert(removeSelectedStrokes || removeSelectedElements, "At least either Strokes or Elements should be removed!"); // Now delete the current selection. StrokeCollection strokes = GetSelectedStrokes(); IListelements = GetSelectedElements(); // Clear the selection first. CoreChangeSelection( removeSelectedStrokes ? new StrokeCollection() : strokes, removeSelectedElements ? new List () : elements, true); // Remove the ink. if ( removeSelectedStrokes && strokes != null && strokes.Count != 0 ) { Strokes.Remove(strokes); } // Remove the elements. if ( removeSelectedElements ) { UIElementCollection children = Children; foreach ( UIElement element in elements ) { children.Remove(element); } } } /// /// A class handler of the Commands /// /// /// private static void _OnCommandExecuted(object sender, ExecutedRoutedEventArgs args) { ICommand command = args.Command; InkCanvas inkCanvas = sender as InkCanvas; Debug.Assert(inkCanvas != null); if ( inkCanvas.IsEnabled && !inkCanvas.EditingCoordinator.UserIsEditing ) { if ( command == ApplicationCommands.Delete ) { inkCanvas.DeleteCurrentSelection(true, true); } else if ( command == ApplicationCommands.Cut ) { inkCanvas.CutSelection(); } else if ( command == ApplicationCommands.Copy ) { inkCanvas.CopySelection(); } else if ( command == ApplicationCommands.SelectAll ) { if ( inkCanvas.ActiveEditingMode == InkCanvasEditingMode.Select ) { IEnumerablechildren = null; UIElementCollection uiElementCollection = inkCanvas.Children; if ( uiElementCollection.Count > 0 ) { //UIElementCollection doesn't implement IEnumerable //for some reason UIElement[] uiElementArray = new UIElement[uiElementCollection.Count]; for ( int i = 0; i < uiElementCollection.Count; i++ ) { uiElementArray[i] = uiElementCollection[i]; } children = uiElementArray; } inkCanvas.Select(inkCanvas.Strokes, children); } } else if ( command == ApplicationCommands.Paste ) { try { inkCanvas.Paste(); } // Eat it and do nothing if one of the following exceptions is caught. catch ( System.Runtime.InteropServices.COMException ) { // The window may be destroyed which could cause the opening failed.. } catch ( XamlParseException ) { // The Xaml parser fails } catch ( ArgumentException ) { // The ISF decoder fails } } else if ( command == InkCanvas.DeselectCommand ) { inkCanvas.ClearSelectionRaiseSelectionChanging(); } } } /// /// A class handler for querying the enabled status of the commands. /// /// /// ////// Critical - Call into UserInitiatedCanPaste which is SecurityCritical. /// TreatAsSafe - We check whether QueryCanPaste is initiated by user or not /// before invoking the critical method. /// [SecurityCritical, SecurityTreatAsSafe] private static void _OnQueryCommandEnabled(object sender, CanExecuteRoutedEventArgs args) { RoutedCommand command = (RoutedCommand)(args.Command); InkCanvas inkCanvas = sender as InkCanvas; Debug.Assert(inkCanvas != null); if ( inkCanvas.IsEnabled // NTRAID-WINDOWSOS#1578484-2006/04/14-WAYNEZEN, // If user is editing, we should disable all commands. && !inkCanvas.EditingCoordinator.UserIsEditing ) { if ( command == ApplicationCommands.Delete || command == ApplicationCommands.Cut || command == ApplicationCommands.Copy || command == InkCanvas.DeselectCommand ) { args.CanExecute = inkCanvas.InkCanvasSelection.HasSelection; } else if ( command == ApplicationCommands.Paste ) { try { args.CanExecute = args.UserInitiated ? inkCanvas.UserInitiatedCanPaste() /* Call UserInitiatedCanPaste when the query is initiated by user */ : inkCanvas.CanPaste() /* Call the public CanPaste if not */; } catch ( System.Runtime.InteropServices.COMException ) { // The window may be destroyed which could cause the opening failed.. // Eat the exception and do nothing. args.CanExecute = false; } } else if ( command == ApplicationCommands.SelectAll ) { //anything to select? args.CanExecute = ( inkCanvas.ActiveEditingMode == InkCanvasEditingMode.Select && (inkCanvas.Strokes.Count > 0 || inkCanvas.Children.Count > 0)); } } else { // NTRAID:WINDOWSOS#1564508-2006/03/20-WAYNEZEN, // Return false for CanExecute if InkCanvas is disabled. args.CanExecute = false; } // NTRAID#WINDOWS-1371659-2005/11/08-waynezen, // Mark Handled as true so that the clipboard commands stops routing to InkCanvas' ancestors. if ( command == ApplicationCommands.Cut || command == ApplicationCommands.Copy || command == ApplicationCommands.Paste ) { args.Handled = true; } } private InkCanvasClipboardDataFormats PrivateCopySelection() { InkCanvasClipboardDataFormats copiedDataFormats = InkCanvasClipboardDataFormats.None; // Don't even bother if we don't have a selection or UserIsEditing has been set. if ( InkCanvasSelection.HasSelection && !_editingCoordinator.UserIsEditing) { copiedDataFormats = CopyToDataObject(); } return copiedDataFormats; } ////// _OnDeviceDown /// ////// /// private static void _OnDeviceDown (object sender, TEventArgs e) where TEventArgs : InputEventArgs { ( (InkCanvas)sender ).EditingCoordinator.OnInkCanvasDeviceDown(sender, e); } /// /// _OnDeviceUp /// ////// /// private static void _OnDeviceUp (object sender, TEventArgs e) where TEventArgs : InputEventArgs { ((InkCanvas)sender).EditingCoordinator.OnInkCanvasDeviceUp(sender, e); } /// /// _OnQueryCursor /// /// /// private static void _OnQueryCursor(object sender, QueryCursorEventArgs e) { InkCanvas inkCanvas = (InkCanvas)sender; if ( inkCanvas.UseCustomCursor ) { // If UseCustomCursor is set, we bail out. Let the base class (FrameworkElement) to do the rest. return; } // We should behave like our base - honor ForceCursor property. if ( !e.Handled || inkCanvas.ForceCursor ) { Cursor cursor = inkCanvas.EditingCoordinator.GetActiveBehaviorCursor(); // If cursor is null, we don't handle the event and leave it as whatever the default is. if ( cursor != null ) { e.Cursor = cursor; e.Handled = true; } } } ////// Update the current cursor if mouse is over InkCanvas. Called by /// EditingCoordinator.InvalidateBehaviorCursor /// EditingCoordinator.UpdateEditingState /// InkCanvas.set_UseCustomCursor /// internal void UpdateCursor() { if ( IsMouseOver ) { Mouse.UpdateCursor(); } } #endregion Private Properties / Methods //------------------------------------------------------ // // Private Classes // //------------------------------------------------------ #region Private Classes ////// A helper class for RTI high contrast support /// private class RTIHighContrastCallback : HighContrastCallback { //----------------------------------------------------- // // Cnostructors // //------------------------------------------------------ #region Constructors internal RTIHighContrastCallback(InkCanvas inkCanvas) { _thisInkCanvas = inkCanvas; } private RTIHighContrastCallback() { } #endregion Constructors //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- #region Internal Methods ////// TurnHighContrastOn /// /// internal override void TurnHighContrastOn(Color highContrastColor) { // The static strokes already have been taken care of by InkPresenter. // We only update the RTI renderer here. DrawingAttributes highContrastDa = _thisInkCanvas.DefaultDrawingAttributes.Clone(); highContrastDa.Color = highContrastColor; _thisInkCanvas.UpdateDynamicRenderer(highContrastDa); } ////// TurnHighContrastOff /// internal override void TurnHighContrastOff() { // The static strokes already have been taken care of by InkPresenter. // We only update the RTI renderer here. _thisInkCanvas.UpdateDynamicRenderer(_thisInkCanvas.DefaultDrawingAttributes); } #endregion Internal Methods //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties ////// Returns the dispatcher if the object is associated to a UIContext. /// internal override Dispatcher Dispatcher { get { return _thisInkCanvas.Dispatcher; } } #endregion Internal Properties //----------------------------------------------------- // // Private Fields // //------------------------------------------------------ #region Private Fields private InkCanvas _thisInkCanvas; #endregion Private Fields } ////// This is a binding converter which translates the InkCanvas.ActiveEditingMode to UIElement.Visibility. /// private class ActiveEditingMode2VisibilityConverter : IValueConverter { public object Convert(object o, Type type, object parameter, System.Globalization.CultureInfo culture) { InkCanvasEditingMode activeMode = (InkCanvasEditingMode)o; // If the current EditingMode is the mode which menuitem is expecting, return true for IsChecked. if ( activeMode != InkCanvasEditingMode.None ) { return Visibility.Visible; } else { return Visibility.Collapsed; } } public object ConvertBack(object o, Type type, object parameter, System.Globalization.CultureInfo culture) { // Non-reversed convertion return null; } } #endregion Private Classes #region Private Members ////// The element that represents the selected ink strokes, if any exist. Will frequently be null. /// private InkCanvasSelection _selection = null; private InkCanvasSelectionAdorner _selectionAdorner = null; private InkCanvasFeedbackAdorner _feedbackAdorner = null; ////// The internal Canvas used to hold elements /// private InkCanvasInnerCanvas _innerCanvas = null; ////// The internal private AdornerDecorator /// private AdornerDecorator _localAdornerDecorator = null; ////// Runtime Selection StrokeCollection /// private StrokeCollection _dynamicallySelectedStrokes; ////// Our editing logic /// private EditingCoordinator _editingCoordinator; ////// Defines the default StylusPointDescription /// private StylusPointDescription _defaultStylusPointDescription; ////// Defines the shape of the eraser tip /// private StylusShape _eraserShape; ////// Determines if EditingBehaviors should use their own cursor or a custom one specified. /// private bool _useCustomCursor = false; // // Rendering support. // private InkPresenter _inkPresenter; // // The RealTimeInking PlugIn that handles our off UIContext rendering. // private DynamicRenderer _dynamicRenderer; // // Clipboard Helper // private ClipboardProcessor _clipboardProcessor; // // Gesture support // private GestureRecognizer _gestureRecognizer; // // HighContrast support // private RTIHighContrastCallback _rtiHighContrastCallback; private const double c_pasteDefaultLocation = 0.0; #endregion Private Members } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- PeerContact.cs
- WindowClosedEventArgs.cs
- ListViewItem.cs
- EUCJPEncoding.cs
- CodeLinePragma.cs
- ChannelServices.cs
- MouseButton.cs
- ToolStripItemEventArgs.cs
- ThicknessKeyFrameCollection.cs
- DelayedRegex.cs
- ForceCopyBuildProvider.cs
- TextParaClient.cs
- ResourceDictionary.cs
- LongCountAggregationOperator.cs
- FileSystemEventArgs.cs
- uribuilder.cs
- PointLight.cs
- NegotiationTokenAuthenticator.cs
- ContentControl.cs
- CodeArrayIndexerExpression.cs
- InfiniteIntConverter.cs
- RegexRunner.cs
- SystemDropShadowChrome.cs
- StringUtil.cs
- VScrollProperties.cs
- AccessDataSourceView.cs
- TagMapInfo.cs
- AccessKeyManager.cs
- DragEventArgs.cs
- SelectingProviderEventArgs.cs
- SqlFormatter.cs
- FlowDocumentPage.cs
- XmlSchemaException.cs
- HttpFileCollection.cs
- XmlReaderSettings.cs
- HwndAppCommandInputProvider.cs
- PatternMatchRules.cs
- BitmapEffect.cs
- LeafCellTreeNode.cs
- Misc.cs
- TabPanel.cs
- ToolStripItemEventArgs.cs
- SecurityHeader.cs
- MemberRelationshipService.cs
- FileLoadException.cs
- ToolboxItemAttribute.cs
- ObjectViewEntityCollectionData.cs
- SurrogateChar.cs
- DataGridViewCellMouseEventArgs.cs
- BeginSelectCardRequest.cs
- SystemInformation.cs
- HandlerFactoryCache.cs
- ResolveNameEventArgs.cs
- CounterCreationDataCollection.cs
- AtomServiceDocumentSerializer.cs
- ComplexBindingPropertiesAttribute.cs
- FixedTextView.cs
- ByteRangeDownloader.cs
- InputBuffer.cs
- LinkLabelLinkClickedEvent.cs
- WarningException.cs
- NumericUpDownAcceleration.cs
- RenameRuleObjectDialog.cs
- MdiWindowListStrip.cs
- ResourceProviderFactory.cs
- SQLInt64.cs
- RegistrySecurity.cs
- RoutedCommand.cs
- EmbossBitmapEffect.cs
- Roles.cs
- PersistenceParticipant.cs
- GridViewSortEventArgs.cs
- SHA1CryptoServiceProvider.cs
- DataSourceGroupCollection.cs
- EncryptedHeaderXml.cs
- UnicodeEncoding.cs
- ArrayWithOffset.cs
- DropSource.cs
- LockCookie.cs
- ColumnWidthChangedEvent.cs
- ResourceReader.cs
- HMAC.cs
- WindowsFormsHelpers.cs
- Freezable.cs
- StorageRoot.cs
- SqlDependencyListener.cs
- MailDefinitionBodyFileNameEditor.cs
- Parsers.cs
- DiscardableAttribute.cs
- CheckPair.cs
- ByteAnimation.cs
- TreeNodeStyleCollection.cs
- remotingproxy.cs
- Maps.cs
- TextInfo.cs
- _KerberosClient.cs
- GradientSpreadMethodValidation.cs
- SingleTagSectionHandler.cs
- ImportCatalogPart.cs
- StreamAsIStream.cs