File: System\Windows\Controls\InkPresenter.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description:
//      A rendering element which binds to the strokes data
//
 
using System.Windows.Media;
using System.Windows.Ink;
using System.Windows.Threading;
using MS.Internal.Ink;
using System.Windows.Automation.Peers;
 
namespace System.Windows.Controls
{
    /// <summary>
    /// Renders the specified StrokeCollection data.
    /// </summary>
    public class InkPresenter : Decorator
    {                                        
        //-------------------------------------------------------------------------------
        //
        // Constructors
        //
        //-------------------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// The constructor of InkPresenter
        /// </summary>
        public InkPresenter()
        {        
            // Create the internal Renderer object.
            _renderer = new Ink.Renderer();
 
            SetStrokesChangedHandlers(this.Strokes, null);
 
            _contrastCallback = new InkPresenterHighContrastCallback(this);
 
            // Register rti high contrast callback. Then check whether we are under the high contrast already.
            HighContrastHelper.RegisterHighContrastCallback(_contrastCallback);
            if ( SystemParameters.HighContrast )
            {
                _contrastCallback.TurnHighContrastOn(SystemColors.WindowTextColor);
            }
 
            _constraintSize = Size.Empty;
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------------------
        //
        // Public Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// AttachVisual method
        /// </summary>
        /// <param name="visual">The stroke visual which needs to be attached</param>
        /// <param name="drawingAttributes">The DrawingAttributes of the stroke</param>
        public void AttachVisuals(Visual visual, DrawingAttributes drawingAttributes)
        {
            VerifyAccess();
 
            EnsureRootVisual();
            _renderer.AttachIncrementalRendering(visual, drawingAttributes);
        }
 
        /// <summary>
        /// DetachVisual method
        /// </summary>
        /// <param name="visual">The stroke visual which needs to be detached</param>
        public void DetachVisuals(Visual visual)
        {
            VerifyAccess();
 
            EnsureRootVisual();
            _renderer.DetachIncrementalRendering(visual);
        }
 
        #endregion Public Methods
 
        //-------------------------------------------------------------------------------
        //
        // Public Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// The DependencyProperty for the Strokes property.
        /// </summary>
        public static readonly DependencyProperty StrokesProperty =
                DependencyProperty.Register(
                        "Strokes",
                        typeof(StrokeCollection),
                        typeof(InkPresenter),
                        new FrameworkPropertyMetadata(
                                new StrokeCollectionDefaultValueFactory(),
                                new PropertyChangedCallback(OnStrokesChanged)),
                        (ValidateValueCallback)delegate(object value)
                            { return value != null; });
 
        /// <summary>
        /// Gets/Sets the Strokes property.
        /// </summary>
        public StrokeCollection Strokes
        {
            get { return (StrokeCollection)GetValue(StrokesProperty); }
            set { SetValue(StrokesProperty, value); }
        }
 
        private static void OnStrokesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InkPresenter inkPresenter = (InkPresenter)d;
 
            StrokeCollection oldValue = (StrokeCollection)e.OldValue;
            StrokeCollection newValue = (StrokeCollection)e.NewValue;
 
            inkPresenter.SetStrokesChangedHandlers(newValue, oldValue);
            inkPresenter.OnStrokeChanged(inkPresenter, EventArgs.Empty);
        }
 
 
        #endregion Public Properties
 
        //-------------------------------------------------------------------------------
        //
        // Protected Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// Override of <seealso cref="FrameworkElement.MeasureOverride" />
        /// </summary>
        /// <param name="constraint">Constraint size.</param>
        /// <returns>Computed desired size.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            // No need to call VerifyAccess since we call the method on the base here.
 
            StrokeCollection strokes = Strokes;
 
            // Measure the child first
            Size newSize = base.MeasureOverride(constraint);
 
            // If there are strokes in IP, we need to combine the size to final size.
            if ( strokes != null && strokes.Count != 0 )
            {
                // Get the bounds of the stroks
                Rect boundingRect = StrokesBounds;
 
                // If we have an empty bounding box or the Right/Bottom value is negative,
                // an empty size will be returned.
                if ( !boundingRect.IsEmpty && boundingRect.Right > 0.0 && boundingRect.Bottom > 0.0 )
                {
                    // The new size needs to contain the right boundary and bottom boundary.
                    Size sizeStrokes = new Size(boundingRect.Right, boundingRect.Bottom);
 
                    newSize.Width = Math.Max(newSize.Width, sizeStrokes.Width);
                    newSize.Height = Math.Max(newSize.Height, sizeStrokes.Height);
                }
            }
 
            if ( Child != null )
            {
                _constraintSize = constraint;
            }
            else
            {
                _constraintSize = Size.Empty;
            }
 
            return newSize;
        }
 
        /// <summary>
        /// Override of <seealso cref="FrameworkElement.ArrangeOverride" />.
        /// </summary>
        /// <param name="arrangeSize">Size that element should use to arrange itself and its children.</param>
        /// <returns>The InkPresenter's desired size.</returns>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            VerifyAccess();
 
            EnsureRootVisual();
 
            // 
            // When we arrange the child, we shouldn't count in the strokes' bounds. 
            // We only use the constraint size for the child.
            Size availableSize = arrangeSize;
            if ( !_constraintSize.IsEmpty )
            {
                availableSize = new Size(Math.Min(arrangeSize.Width, _constraintSize.Width), 
                                                Math.Min(arrangeSize.Height, _constraintSize.Height));
            }
 
            // We arrange our child as what Decorator does 
            // exceopt we are using the available size computed from our cached measure size.
            UIElement child = Child;
            if ( child != null )
            {
                child.Arrange(new Rect(availableSize));
            } 
            
            return arrangeSize;
        }
 
        /// <summary>
        /// The overridden GetLayoutClip method
        /// </summary>
        /// <returns>Geometry to use as additional clip if ClipToBounds=true</returns>
        protected override Geometry GetLayoutClip(Size layoutSlotSize)
        {
            // 
            // By default an FE will clip its content if the ink size exceeds the layout size (the final arrange size).
            // Since we are auto growing, the ink size is same as the desired size. So it ends up the strokes will be clipped
            // regardless ClipToBounds is set or not. 
            // We override the GetLayoutClip method so that we can bypass the default layout clip if ClipToBounds is set to false.
            // So we allow the ink to be drown anywhere when no clip is set.
            if ( ClipToBounds )
            {
                return base.GetLayoutClip(layoutSlotSize);
            }
            else
                return null;
        }
 
        /// <summary>
        /// Returns the child at the specified index.
        /// </summary>
        protected override Visual GetVisualChild(int index)
        {
            int count = VisualChildrenCount;
                
            if(count == 2)
            {
                switch (index)
                {
                    case 0: 
                        return  base.Child;
 
                    case 1:
                        return _renderer.RootVisual;
 
                    default:
                        throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
                }
            }
            else if (index == 0 && count == 1)
            {
                if ( _hasAddedRoot )
                {
                    return _renderer.RootVisual;
                }
                else if(base.Child != null)
                {
                    return base.Child;
                }
            }
            throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
        }
 
        
        /// <summary>
        ///  Derived classes override this property to enable the Visual code to enumerate 
        ///  the Visual children. Derived classes need to return the number of children
        ///  from this method.
        ///
        ///    By default a Visual does not have any children.
        ///
        ///  Remark: During this virtual method the Visual tree must not be modified.
        /// </summary>        
        protected override int VisualChildrenCount
        {           
            get 
            {
                // we can have 4 states:-
                // 1. no children
                // 2. only base.Child
                // 3. only _renderer.RootVisual as the child
                // 4. both base.Child and  _renderer.RootVisual
                
                if(base.Child != null)
                {
                    if ( _hasAddedRoot )
                    {
                       return 2;
                    }
                    else 
                    {
                        return 1;
                    }
                }
                else if ( _hasAddedRoot )
                {
                    return 1;
                }
                
                return 0;
            }            
        }
 
        /// <summary>
        /// UIAutomation support
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new InkPresenterAutomationPeer(this);
        }
 
 
        #endregion Protected Methods
 
        #region Internal Methods
 
        /// <summary>
        /// Internal helper used to indicate if a visual was previously attached 
        /// via a call to AttachIncrementalRendering
        /// </summary>
        internal bool ContainsAttachedVisual(Visual visual)
        {
            VerifyAccess();
            return _renderer.ContainsAttachedIncrementalRenderingVisual(visual);
        }
 
 
        /// <summary>
        /// Internal helper used to determine if a visual is in the right spot in the visual tree
        /// </summary>
        internal bool AttachedVisualIsPositionedCorrectly(Visual visual, DrawingAttributes drawingAttributes)
        {
            VerifyAccess();
            return _renderer.AttachedVisualIsPositionedCorrectly(visual, drawingAttributes);
        }
 
        #endregion Internal Methods
        //------------------------------------------------------
        //
        //  Private Classes
        //
        //------------------------------------------------------
 
        #region Private Classes
 
        /// <summary>
        /// A helper class for the high contrast support
        /// </summary>
        private class InkPresenterHighContrastCallback : HighContrastCallback
        {
            //------------------------------------------------------
            //
            //  Cnostructors
            //
            //------------------------------------------------------
 
            #region Constructors
 
            internal InkPresenterHighContrastCallback(InkPresenter inkPresenter)
            {
                _thisInkPresenter = inkPresenter;
            }
 
            private InkPresenterHighContrastCallback() { }
 
            #endregion Constructors
 
            //------------------------------------------------------
            //
            //  Internal Methods
            //
            //------------------------------------------------------
 
            #region Internal Methods
 
            /// <summary>
            /// TurnHighContrastOn
            /// </summary>
            /// <param name="highContrastColor"></param>
            internal override void TurnHighContrastOn(Color highContrastColor)
            {
                _thisInkPresenter._renderer.TurnHighContrastOn(highContrastColor);
                _thisInkPresenter.OnStrokeChanged();
            }
 
            /// <summary>
            /// TurnHighContrastOff
            /// </summary>
            internal override void TurnHighContrastOff()
            {
                _thisInkPresenter._renderer.TurnHighContrastOff();
                _thisInkPresenter.OnStrokeChanged();
            }
 
            #endregion Internal Methods
 
            //------------------------------------------------------
            //
            //  Internal Properties
            //
            //------------------------------------------------------
 
            #region Internal Properties
 
            /// <summary>
            /// Returns the dispatcher if the object is associated to a UIContext.
            /// </summary>
            internal override Dispatcher Dispatcher
            {
                get
                {
                    return _thisInkPresenter.Dispatcher;
                }
            }
 
            #endregion Internal Properties
 
            //------------------------------------------------------
            //
            //  Private Fields
            //
            //------------------------------------------------------
 
            #region Private Fields
 
            private InkPresenter _thisInkPresenter;
 
            #endregion Private Fields
        }
 
        #endregion Private Classes
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        private void SetStrokesChangedHandlers(StrokeCollection newStrokes, StrokeCollection oldStrokes)
        {
            Debug.Assert(newStrokes != null, "Cannot set a null to InkPresenter");
 
            // Remove the event handlers from the old stroke collection
            if ( null != oldStrokes )
            {
                // Stop listening on events from the stroke collection.
                oldStrokes.StrokesChanged -= new StrokeCollectionChangedEventHandler(OnStrokesChanged);
            }
 
            // Start listening on events from the stroke collection.
            newStrokes.StrokesChanged += new StrokeCollectionChangedEventHandler(OnStrokesChanged);
 
            // Replace the renderer stroke collection.
            _renderer.Strokes = newStrokes;
 
            SetStrokeChangedHandlers(newStrokes, oldStrokes);
        }
 
        /// <summary>
        /// StrokeCollectionChanged event handler
        /// </summary>
        private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs eventArgs)
        {
            System.Diagnostics.Debug.Assert(sender == this.Strokes);
 
            SetStrokeChangedHandlers(eventArgs.Added, eventArgs.Removed);
            OnStrokeChanged(this, EventArgs.Empty);
        }
 
        private void SetStrokeChangedHandlers(StrokeCollection addedStrokes, StrokeCollection removedStrokes)
        {
            Debug.Assert(addedStrokes != null, "The added StrokeCollection cannot be null.");
            int count, i;
 
            if ( removedStrokes != null )
            {
                // Deal with removed strokes first
                count = removedStrokes.Count;
                for ( i = 0; i < count; i++ )
                {
                    StopListeningOnStrokeEvents(removedStrokes[i]);
                }
            }
 
            // Add new strokes
            count = addedStrokes.Count;
            for ( i = 0; i < count; i++ )
            {
                StartListeningOnStrokeEvents(addedStrokes[i]);
            }
        }
 
        private void OnStrokeChanged(object sender, EventArgs e)
        {
            OnStrokeChanged();
        }
 
        /// <summary>
        /// The method is called from Stroke setter, OnStrokesChanged, DrawingAttributesChanged and OnPacketsChanged
        /// </summary>
        private void OnStrokeChanged()
        {
            // Recalculate the bound of the StrokeCollection
            _cachedBounds = null;
            // Invalidate the current measure when any change happens on the stroke or strokes.
            InvalidateMeasure();
        }
 
        /// <summary>
        /// Attaches event handlers to stroke events
        /// </summary>
        private void StartListeningOnStrokeEvents(Stroke stroke)
        {
            System.Diagnostics.Debug.Assert(stroke != null);
            stroke.Invalidated += new EventHandler(OnStrokeChanged);
        }
 
        /// <summary>
        /// Detaches event handlers from stroke
        /// </summary>
        private void StopListeningOnStrokeEvents(Stroke stroke)
        {
            System.Diagnostics.Debug.Assert(stroke != null);
            stroke.Invalidated -= new EventHandler(OnStrokeChanged);
        }
 
        /// <summary>
        /// Ensure the renderer root to be connected. The method is called from
        ///     AttachVisuals
        ///     DetachVisuals
        ///     ArrangeOverride
        /// </summary>
        private void EnsureRootVisual()
        {
            if ( !_hasAddedRoot )
            {
                // Ideally we should set _hasAddedRoot to true before calling AddVisualChild.
                // So that VisualDiagnostics.OnVisualChildChanged can get correct child index.
                // However it is not clear what can regress. For now we'll temporary set
                // _parentIndex property. We'll use _parentIndex.
                // Note that there is a comment in Visual.cs stating that _parentIndex should
                // be set to -1 in DEBUG builds when child is removed. We are not going to 
                // honor it. There is no _parentIndex == -1 validation is performed anywhere.
                _renderer.RootVisual._parentIndex = 0;
 
                AddVisualChild(_renderer.RootVisual);
                _hasAddedRoot = true;
            }
        }
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Private Properties
 
        private Rect StrokesBounds
        {
            get
            {
                if ( _cachedBounds == null )
                {
                    _cachedBounds = Strokes.GetBounds();
                }
 
                return _cachedBounds.Value;
            }
        }
 
        #endregion Private Properties
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Private Fields
        private Ink.Renderer    _renderer;
        private Nullable<Rect>  _cachedBounds = null;
        private bool            _hasAddedRoot;
        //
        // HighContrast support
        //
        private InkPresenterHighContrastCallback    _contrastCallback;
 
        private Size                                _constraintSize;
 
        #endregion Private Fields
    }
}