File: MS\Internal\Annotations\Component\AnnotationComponentManager.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:
//      AnnotationComponentManager
//
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Annotations;
using System.Windows.Documents;
using System.Windows.Media;
 
namespace MS.Internal.Annotations.Component
{
    /// <summary>
    /// Instances of this class manage listen for events from the annotation service and create/remove/modify annotation components in response.
    /// Annotation components are created via indirection through an AnnotationComponentChooser which is set in a DP on the app tree.
    /// Annotation components are hosted, wrapped inside AnnotationAdorners, in an AdornerLayer respective to the annotated element.
    /// Every time an annotation service is instantiated an instance of this class is created.
    /// </summary>
    internal class AnnotationComponentManager : DependencyObject
    {
        #region Constructors
 
        /// <summary>
        /// Return an annotation component manager.  If an annotation service is passed in, manager will
        /// receive notification from the service.  Otherwise it will expect to be called directly.
        /// </summary>
        /// <param name="service">optional, annotation service the annotation component manager will register on</param>
        internal AnnotationComponentManager(AnnotationService service)
            : base()
        {
            // Only register if a service was passed in. If no service was passed in, we will not receive events.
            // This means a client will be calling us directly.
            if (service != null)
            {
                service.AttachedAnnotationChanged += new AttachedAnnotationChangedEventHandler(AttachedAnnotationUpdateEventHandler);
            }
        }
 
        #endregion Constructors
 
        #region Internal Methods
 
        /// <summary>
        /// The given attached annotation was added by the annotation service
        /// Find annotation components for the given attached annotation, maintain 
        /// mapping from attached annotation to annotation components, and add to the
        /// adorner layer if the annotation component is not already in a presentation context.
        /// </summary>
        /// <param name="attachedAnnotation">Attached annotation to add</param>
        /// <param name="reorder">If true - update components ZOrder after adding the component</param>
        internal void AddAttachedAnnotation(IAttachedAnnotation attachedAnnotation, bool reorder)
        {
            Debug.Assert(attachedAnnotation != null, "AttachedAnnotation should not be null");
 
            IAnnotationComponent component = FindComponent(attachedAnnotation);
            if (component == null) return;
            AddComponent(attachedAnnotation, component, reorder);
        }
 
        /// <summary>
        /// The given attached annotation was removed by the annotation service.  
        /// Find all annotation components and let them know. 
        /// If an annotation component does not have any other attached annotations, remove it.
        /// Update the attachedAnnotations map.
        /// </summary>
        /// <param name="attachedAnnotation">Attached annotation to remove</param>
        /// <param name="reorder">If true - update components ZOrder after removing the component</param>
        internal void RemoveAttachedAnnotation(IAttachedAnnotation attachedAnnotation, bool reorder)
        {
            Debug.Assert(attachedAnnotation != null, "AttachedAnnotation should not be null");
 
            if (!_attachedAnnotations.ContainsKey(attachedAnnotation))
            {
                // note, this is not always a bug, since an annotation might have resolved, but no annotation component found
                // the tree traversal bug we have currently results in this failing even if annotation components were found, because
                // in certain cases annotationService.AttachedAnnotations returns wrong list.
                return;
            }
 
            IList<IAnnotationComponent> currentList = _attachedAnnotations[attachedAnnotation];
 
            _attachedAnnotations.Remove(attachedAnnotation); // clean up the map
            foreach (IAnnotationComponent component in currentList)
            {
                component.RemoveAttachedAnnotation(attachedAnnotation);  // let the annotation component know
                if (component.AttachedAnnotations.Count == 0)
                { // if it has no more attached annotations, remove it
                    if (component.PresentationContext != null)
                        component.PresentationContext.RemoveFromHost(component, reorder);
                }
            }
        }
 
        #endregion Internal Methods
 
        #region Private Methods
 
        /// <summary>
        /// Handle the notification coming from the service.  Attached annotations can be added, removed, modified.
        /// Delegate to appropriate method
        /// </summary>
        /// <param name="sender">The sender of the event, an annotation service</param>
        /// <param name="e">The event arguments containing information on added/deleted/modified attached annotation.</param>
        private void AttachedAnnotationUpdateEventHandler(object sender, AttachedAnnotationChangedEventArgs e)
        {
            switch (e.Action)
            {
                case AttachedAnnotationAction.Added:
                    this.AddAttachedAnnotation(e.AttachedAnnotation, true);
                    break;
 
                case AttachedAnnotationAction.Deleted:
                    this.RemoveAttachedAnnotation(e.AttachedAnnotation, true);
                    break;
 
                case AttachedAnnotationAction.Loaded:
                    this.AddAttachedAnnotation(e.AttachedAnnotation, false);
                    break;
 
                case AttachedAnnotationAction.Unloaded:
                    this.RemoveAttachedAnnotation(e.AttachedAnnotation, false);
                    break;
 
                case AttachedAnnotationAction.AnchorModified:
                    this.ModifyAttachedAnnotation(e.AttachedAnnotation, e.PreviousAttachedAnchor, e.PreviousAttachmentLevel);
                    break;
            }
        }
 
        /// <summary>
        /// given an attachedAnnotation find the chooser attached at its parent
        /// and ask it to choose a component suitable to handle the attachedAnnotation 
        /// </summary>
        /// <param name="attachedAnnotation">the attachedAnnotation we are to find a component for</param>
        /// <returns>an IAnnotationComponent that can handle the attachedAnnotation (or null)</returns>
        private IAnnotationComponent FindComponent(IAttachedAnnotation attachedAnnotation)
        {
            Debug.Assert(attachedAnnotation != null, "AttachedAnnotation should not be null");
 
            UIElement annotatedElement = attachedAnnotation.Parent as UIElement; // casted from DependencyObject
            Debug.Assert(annotatedElement != null, "the annotatedElement should inherit from UIElement");
 
            AnnotationComponentChooser chooser = AnnotationService.GetChooser(annotatedElement);
 
            // should we return a list instead?
            IAnnotationComponent component = chooser.ChooseAnnotationComponent(attachedAnnotation);
 
            return component;
        }
 
        /// <summary>
        /// Add an attached annotation to a component and add the component to the adorner layer 
        /// if the annotation component is not already in a presentation context.        
        /// </summary>
        /// <param name="attachedAnnotation">the attachedAnnotation we are to add to the component</param>
        /// <param name="component">the component we are to add to the adorner layer</param>
        /// <param name="reorder">if true - the z-order must be reevaluated</param>
        private void AddComponent(IAttachedAnnotation attachedAnnotation, IAnnotationComponent component, bool reorder)
        {
            UIElement annotatedElement = attachedAnnotation.Parent as UIElement; // casted from DependencyObject
            Debug.Assert(annotatedElement != null, "the annotatedElement should inherit from UIElement");
 
            // if annotation component is already in presentation context, nothing else to do
            if (component.PresentationContext != null) return;
 
            // otherwise host in the appropriate adorner layer
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(annotatedElement); // note, GetAdornerLayer requires UIElement
            if (layer == null)
            {
                if (PresentationSource.FromVisual(annotatedElement) == null)
                {
                    // The annotated element is no longer part of the application tree.
                    // This probably means we are out of sync - trying to add an annotation
                    // for an element that has already gone away.  Bug # 1580288 tracks
                    // the need to figure this out.
                    return;
                }
 
                throw new InvalidOperationException(SR.Format(SR.NoPresentationContextForGivenElement, annotatedElement));
            }
 
            // add to the attachedAnnotations
            this.AddToAttachedAnnotations(attachedAnnotation, component);
 
            // let the annotation component know about the attached annotation
            // call add before adding to adorner layer so the component can be initialized
            component.AddAttachedAnnotation(attachedAnnotation); // this might cause recursion in modify if annotation component adds to annotation
 
            AdornerPresentationContext.HostComponent(layer, component, annotatedElement, reorder);
        }
 
        /// <summary>
        /// Service indicates attached annotation has changed.
        /// For now, take all annotation components maped from old attached annotation and map from new.
        /// Then iterate through annotation components to let them know.
        /// Note, this needs to change later.  If modify is radical, existing component might not want it anymore,
        /// and new one might need to be found... 
        /// </summary>
        /// <param name="attachedAnnotation">The modified attached annotation</param>
        /// <param name="previousAttachedAnchor">The previous attached anchor for the attached annotation</param>
        /// <param name="previousAttachmentLevel">The previous attachment level for the attached annotation</param>
        private void ModifyAttachedAnnotation(IAttachedAnnotation attachedAnnotation, object previousAttachedAnchor, AttachmentLevel previousAttachmentLevel)
        {
            Debug.Assert(attachedAnnotation != null, "attachedAnnotation should not be null");
            Debug.Assert(previousAttachedAnchor != null, "previousAttachedAnchor should not be null");
 
            // if there was no component found for this attached annotation
            // then we treat the modify case as an add 
            if (!_attachedAnnotations.ContainsKey(attachedAnnotation))
            {
                // this is not necessarily a bug, it can be that the old attached annotation does not have an
                // associated annotation component.
                this.AddAttachedAnnotation(attachedAnnotation, true);
                return;
            }
 
            // we have a previous component for this attached annotation
            // we find the chooser for the new attached annotation
            // and ask it to choose a component
            // 1- if it returned null then we just remove the attached annotation
            // 2- if it returned a different component, then we treat it as a remove/add
            // 3- if it returned the same component then we call ModifyAttachedAnnotation on the component
            IAnnotationComponent newComponent = FindComponent(attachedAnnotation);
            if (newComponent == null)
            {
                RemoveAttachedAnnotation(attachedAnnotation, true);
            }
            else
            {
                IList<IAnnotationComponent> currentList = _attachedAnnotations[attachedAnnotation]; //save the current list
 
                // if we found the new component in any of the list of components we already have for this
                // attached annotation
                if (currentList.Contains(newComponent))
                {
                    // ask the components to handle the anchor modification event
                    foreach (IAnnotationComponent component in currentList)
                    {
                        component.ModifyAttachedAnnotation(attachedAnnotation, previousAttachedAnchor, previousAttachmentLevel);  // let the annotation component know
                        if (component.AttachedAnnotations.Count == 0)
                        {
                            // if component decides it can not handle it, remove it
                            component.PresentationContext.RemoveFromHost(component, true);
                        }
                    }
                }
                else
                {
                    // remove all components
                    RemoveAttachedAnnotation(attachedAnnotation, true);
 
                    // add the new component
                    AddComponent(attachedAnnotation, newComponent, true);
                }
            }
        }
 
        /// <summary>
        /// Add to the map from attached annotations -> annotation components
        /// </summary>
        /// <param name="attachedAnnotation">The attached annotation to use as key</param>
        /// <param name="component">The component to add to list</param>
        private void AddToAttachedAnnotations(IAttachedAnnotation attachedAnnotation, IAnnotationComponent component)
        {
            Debug.Assert(attachedAnnotation != null, "attachedAnnotation should not be null");
            Debug.Assert(component != null, "component should not be null");
 
            IList<IAnnotationComponent> currentList;
 
            if (!_attachedAnnotations.TryGetValue(attachedAnnotation, out currentList))
            {
                currentList = new List<IAnnotationComponent>();
                _attachedAnnotations[attachedAnnotation] = currentList;
            }
 
            currentList.Add(component);
        }
 
        #endregion Private Methods
 
        #region Private Fields
 
        /// <summary>
        /// Map from attached annotation to list of annotation components.  
        /// This map contains all attached anntotations the component manager currently knows.
        /// </summary>
        private Dictionary<IAttachedAnnotation, IList<IAnnotationComponent>> _attachedAnnotations = new Dictionary<IAttachedAnnotation, IList<IAnnotationComponent>>();
 
        #endregion Private Fields
    }
}