Storyboard.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Framework / System / Windows / Media / Animation / Storyboard.cs / 2 / Storyboard.cs

                            /****************************************************************************\ 
*
* File: Storyboard.cs
*
* A Storyboard coordinates a set of actions in a time-dependent manner.  An 
*  example usage is to coordinate animation events such as start/stop/pause.
* 
* Copyright (C) by Microsoft Corporation.  All rights reserved. 
*
\***************************************************************************/ 

using System.Collections;               // DictionaryEntry
using System.Collections.Generic;       // List
using System.Collections.Specialized;   // HybridDictionary 
using System.ComponentModel;            // PropertyDescriptor
using System.Diagnostics;               // Debug.Assert 
using System.Reflection;                // PropertyInfo 

using System.Windows.Controls;          // MediaElement 
using System.Windows.Documents;         // TableTemplate
using System.Windows.Markup;            // INameScope
using MS.Internal;                      // Helper
using MS.Utility;                       // FrugalMap 

namespace System.Windows.Media.Animation 
{ 
/// 
/// A Storyboard coordinates a set of actions in a time-dependent manner. 
/// 
public class Storyboard : ParallelTimeline
{
    ///  
    ///     Creates an instance of the Storyboard object.
    ///  
    public Storyboard() 
        : base()
    { 
    }

#region Freezable Requirements
 
    /// 
    ///     Override method required of Freezable-derived types 
    ///  
    protected override Freezable CreateInstanceCore()
    { 
        return new Storyboard();
    }

    // We don't need to override CopyCore since it doesn't do anything, the 
    // base class will handle what is necessary.
 
    ///  
    ///     Override method required of Freezable-derived types
    ///  
    public new Storyboard Clone()
    {
        return (Storyboard)base.Clone();
    } 

#endregion 
 

#region Attached Properties 

    /// 
    /// The TargetName property is designed to be attached to animation objects,
    ///  giving a string that will be matched against an element with the given name. 
    /// 
    public static readonly DependencyProperty TargetNameProperty = 
        DependencyProperty.RegisterAttached("TargetName", typeof(string), typeof(Storyboard)); 

    // The static setter/getter methods for the TargetName property is required 
    //  for parser support.

    /// 
    ///     Attaches the TargetName value on the given object. 
    /// 
    public static void SetTargetName(DependencyObject element, String name) 
    { 
        if (element == null) { throw new ArgumentNullException("element"); }
        if (name == null) { throw new ArgumentNullException("name"); } 
        element.SetValue(TargetNameProperty, name);
    }

    ///  
    ///     Retrieves the attached TargetName value of the given object.
    ///  
    public static string GetTargetName(DependencyObject element) 
    {
        if (element == null) { throw new ArgumentNullException("element"); } 
        return (string)element.GetValue(TargetNameProperty);
    }

    ///  
    /// The TargetProperty property is designed to be attached to animation objects,
    ///  giving the string representation of the DependencyProperty that the 
    ///  animation object will be manipulating. 
    /// 
    public static readonly DependencyProperty TargetPropertyProperty = 
        DependencyProperty.RegisterAttached("TargetProperty", typeof(PropertyPath), typeof(Storyboard));

    // The static setter/getter methods for the TargetProperty property is required
    //  for parser support. 

    ///  
    ///     Attaches the TargetProperty value on the given object. 
    /// 
    public static void SetTargetProperty(DependencyObject element, PropertyPath path) 
    {
        if (element == null) { throw new ArgumentNullException("element"); }
        if (path == null) { throw new ArgumentNullException("path"); }
        element.SetValue(TargetPropertyProperty, path); 
    }
 
    ///  
    ///     Retrieves the attached TargetProperty value of the given object.
    ///  
    public static PropertyPath GetTargetProperty(DependencyObject element)
    {
        if (element == null) { throw new ArgumentNullException("element"); }
        return (PropertyPath)element.GetValue(TargetPropertyProperty); 
    }
 
#endregion 

    ///  
    ///     An object that represents a DependencyObject+DependencyProperty
    /// pairing, designed to be used as a key into a Hashtable or similar data
    /// structure.
    ///  
    private class ObjectPropertyPair
    { 
        public ObjectPropertyPair(DependencyObject o, DependencyProperty p) 
        {
            _object = o; 
            _property = p;
        }

        public override int GetHashCode() 
        {
            return _object.GetHashCode() ^ _property.GetHashCode(); 
        } 

        public override bool Equals(object o) 
        {
            if ((o != null) && (o is ObjectPropertyPair))
            {
                return Equals((ObjectPropertyPair)o); 
            }
            else 
            { 
                return false;
            } 
        }

        public bool Equals(ObjectPropertyPair key)
        { 
            return (_object.Equals(key._object) && (_property == key._property));
        } 
 
        public DependencyObject DependencyObject { get { return _object; } }
        public DependencyProperty DependencyProperty { get { return _property; } } 

        private DependencyObject _object;
        private DependencyProperty _property;
    } 

    ///  
    ///     Finds the target element of a Storyboard.TargetName property. 
    /// 
    ///  
    ///     This is using a different set of FindName rules than that used
    /// by ResolveBeginStoryboardName for finding a BeginStoryboard object due
    /// to the different FindName behavior in templated objects.
    /// 
    ///     The templated object name is the name attached to the
    /// FrameworkElementFactory that created the object.  There are many of them 
    /// created, one per templated object.  So we need to use Template.FindName() 
    /// to find the templated child using the context of the templated parent.
    /// 
    ///     Note that this FindName() function on the template class is
    /// completely different from the INameScope.FindName() function on the
    /// same class
    ///  
    internal static DependencyObject ResolveTargetName(
        string targetName, 
        INameScope nameScope, 
        DependencyObject element )
    { 
        object           nameScopeUsed = null;
        object           namedObject = null;
        DependencyObject targetObject = null;
        FrameworkElement fe = element as FrameworkElement; 
        FrameworkContentElement fce = element as FrameworkContentElement;
 
        if( fe != null ) 
        {
            if( nameScope != null ) 
            {
                namedObject = ((FrameworkTemplate)nameScope).FindName(targetName, fe);
                nameScopeUsed = nameScope;
            } 
            else
            { 
                namedObject = fe.FindName(targetName); 
                nameScopeUsed = fe;
            } 
        }
        else if( fce != null )
        {
            Debug.Assert( nameScope == null ); 
            namedObject = fce.FindName(targetName);
            nameScopeUsed = fce; 
        } 
        else
        { 
            throw new InvalidOperationException(
                SR.Get(SRID.Storyboard_NoNameScope, targetName));
        }
 
        if( namedObject == null )
        { 
            throw new InvalidOperationException( 
                SR.Get(SRID.Storyboard_NameNotFound, targetName, nameScopeUsed.GetType().ToString()));
        } 

        targetObject = namedObject as DependencyObject;
        if( targetObject == null )
        { 
            throw new InvalidOperationException(SR.Get(SRID.Storyboard_TargetNameNotDependencyObject, targetName ));
        } 
 
        return targetObject;
    } 

    /// 
    ///     Finds a BeginStoryboard with the given name, following the rules
    /// governing Storyboard.  Returns null if not found. 
    /// 
    ///  
    ///     If a name scope is given, look there and nowhere else.  In the 
    /// absense of name scope, use Framework(Content)Element.FindName which
    /// has its own complex set of rules for looking up name scopes. 
    ///
    ///     This is a different set of rules than from that used to look up
    /// the TargetName.  BeginStoryboard name is registered with the template
    /// INameScope on a per-template basis.  So we look it up using 
    /// INameScope.FindName().  This is a function completely different from
    /// Template.FindName(). 
    ///  
    internal static BeginStoryboard ResolveBeginStoryboardName(
        string targetName, 
        INameScope nameScope,
        FrameworkElement fe,
        FrameworkContentElement fce)
    { 
        object          namedObject = null;
        BeginStoryboard beginStoryboard = null; 
 
        if( nameScope != null )
        { 
            namedObject = nameScope.FindName(targetName);
            if( namedObject == null )
            {
                throw new InvalidOperationException( 
                    SR.Get(SRID.Storyboard_NameNotFound, targetName, nameScope.GetType().ToString()));
            } 
        } 
        else if( fe != null )
        { 
            namedObject = fe.FindName(targetName);
            if( namedObject == null )
            {
                throw new InvalidOperationException( 
                    SR.Get(SRID.Storyboard_NameNotFound, targetName, fe.GetType().ToString()));
            } 
        } 
        else if( fce != null )
        { 
            namedObject = fce.FindName(targetName);
            if( namedObject == null )
            {
                throw new InvalidOperationException( 
                    SR.Get(SRID.Storyboard_NameNotFound, targetName, fce.GetType().ToString()));
            } 
        } 
        else
        { 
            throw new InvalidOperationException(
                SR.Get(SRID.Storyboard_NoNameScope, targetName));
        }
 
        beginStoryboard = namedObject as BeginStoryboard;
 
        if( beginStoryboard == null ) 
        {
            throw new InvalidOperationException(SR.Get(SRID.Storyboard_BeginStoryboardNameNotFound, targetName)); 
        }

        return beginStoryboard;
    } 

    ///  
    ///     Recursively walks the clock tree and determine the target object 
    /// and property for each clock in the tree.
    ///  
    /// 
    ///     The currently active object and property path are passed in as parameters,
    /// they will be used unless a target/property specification exists on
    /// the Timeline object corresponding to the current clock.  (So that the 
    /// leaf-most reference wins.)
    /// 
    ///     The active object and property parameters may be null if they have 
    /// never been specified.  If we reach a leaf node clock and a needed attribute
    /// is still null, it is an error condition.  Otherwise we keep hoping they'll be found. 
    /// 
    private void ClockTreeWalkRecursive(
        Clock currentClock,                /* No two calls will have the same currentClock     */
        DependencyObject containingObject, /* Remains the same through all the recursive calls */ 
        INameScope nameScope,              /* Remains the same through all the recursive calls */
        string parentObjectName, 
        PropertyPath parentPropertyPath, 
        HandoffBehavior handoffBehavior,   /* Remains the same through all the recursive calls */
        HybridDictionary clockMappings, 
        Int64 layer                        /* Remains the same through all the recursive calls */)
    {
        Timeline currentTimeline = currentClock.Timeline;
 
        string currentObjectName = parentObjectName;
        PropertyPath currentPropertyPath = parentPropertyPath; 
 
        // If we have target object/property information, use it instead of the
        //  parent's information. 
        string nameString = (string)currentTimeline.GetValue(TargetNameProperty);
        if( nameString != null )
        {
            if( nameScope is Style ) 
            {
                // We are inside a Style - we don't let people target anything. 
                //  They're only allowed to modify the Styled object, which is 
                //  already the implicit target.
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_TargetNameNotAllowedInStyle, nameString)); 
            }
            currentObjectName = nameString;
        }
 
        PropertyPath propertyPath = (PropertyPath)currentTimeline.GetValue(TargetPropertyProperty);
        if( propertyPath != null ) 
        { 
            currentPropertyPath = propertyPath;
        } 

        // Now see if the current clock is an animation clock
        if( currentClock is AnimationClock )
        { 
            DependencyObject targetObject = null;
            DependencyProperty targetProperty = null; 
            AnimationClock animationClock = (AnimationClock)currentClock; 

            // Resolve the target object name.  If no name specified, use the 
            //  containing object.
            if( currentObjectName != null )
            {
                targetObject = ResolveTargetName(currentObjectName, nameScope, containingObject ); 
            }
            else 
            { 
                targetObject = containingObject;
 
                Debug.Assert( targetObject != null, "We should always have an object to work off of.  Null contaningObject is not supported, check the logic of the calling code." );
            }

            // See if we have a property name to use. 
            if( currentPropertyPath == null )
            { 
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_TargetPropertyRequired, currentTimeline.GetType().ToString() )); 
            }
 
            // A property name can be a straightforward property name (like "Angle")
            //  but may be a more complex multi-step property path.  The two cases
            //  are handled differently.
 
            using(currentPropertyPath.SetContext(targetObject))
            { 
                if( currentPropertyPath.Length < 1 ) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathEmpty)); 
                }

                VerifyPathIsAnimatable(currentPropertyPath);
 
                if( currentPropertyPath.Length == 1 )
                { 
                    // We have a simple single-step property. 
                    targetProperty = currentPropertyPath.GetAccessor(0) as DependencyProperty;
 
                    if( targetProperty == null )
                    {
                        // Unfortunately it's not a DependencyProperty.
                        throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathMustPointToDependencyProperty, currentPropertyPath.Path )); 
                    }
 
                    VerifyAnimationIsValid(targetProperty, animationClock); 

                    ObjectPropertyPair animatedTarget = new ObjectPropertyPair( targetObject, targetProperty ); 
                    UpdateMappings( clockMappings, animatedTarget, animationClock );
                }
                else // path.Length > 1
                { 
                    // This is a multi-step property path that requires more extensive
                    //  setup. 
 
                    ProcessComplexPath( clockMappings, targetObject, currentPropertyPath, animationClock, handoffBehavior, layer );
                } 
            }
        }
        else if ( currentClock is MediaClock ) // Not an animation clock - maybe a media clock?
        { 
            // Yes it's a media clock.  Try to find the corresponding object and
            //  apply the clock to that object. 
            ApplyMediaClock( nameScope, containingObject, currentObjectName, (MediaClock) currentClock ); 
        }
        else 
        {
            // None of the types we recognize as leaf node clock types -
            //  recursively process child clocks.
            ClockGroup currentClockGroup = currentClock as ClockGroup; 

            if (currentClockGroup != null) 
            { 
                ClockCollection childrenClocks = currentClockGroup.Children;
 
                for( int i = 0; i < childrenClocks.Count; i++ )
                {
                    ClockTreeWalkRecursive(
                        childrenClocks[i], 
                        containingObject,
                        nameScope, 
                        currentObjectName, 
                        currentPropertyPath,
                        handoffBehavior, 
                        clockMappings,
                        layer);
                }
            } 
        }
    } 
 
    /// 
    ///     When we've found a media clock, try to find a corresponding media 
    /// element and attach the media clock to that element.
    /// 
    private static void ApplyMediaClock( INameScope nameScope, DependencyObject containingObject,
        string currentObjectName, MediaClock mediaClock ) 
    {
        MediaElement targetMediaElement = null; 
 
        if( currentObjectName != null )
        { 
            // Find the object named as the current target name.
            targetMediaElement = ResolveTargetName(currentObjectName, nameScope, containingObject ) as MediaElement;

            if( targetMediaElement == null ) 
            {
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_MediaElementNotFound, currentObjectName )); 
            } 
        }
        else 
        {
            // Target name not specified - if the containing object is a
            //  MediaElement we'll assume we're attached to that.
            targetMediaElement = containingObject as MediaElement; 

            if( targetMediaElement == null ) 
            { 
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_MediaElementRequired));
            } 
        }

        targetMediaElement.Clock = mediaClock;
    } 

    ///  
    ///     Given an animation clock, add it to the data structure which tracks 
    /// all the clocks along with their associated target object and property.
    ///  
    private static void UpdateMappings(
        HybridDictionary clockMappings,
        ObjectPropertyPair mappingKey,
        AnimationClock animationClock) 
    {
        object mappedObject = clockMappings[mappingKey]; 
 
        Debug.Assert( mappedObject == null || mappedObject is AnimationClock || mappedObject is List,
            "Internal error - clockMappings table contains an unexpected object " + ((mappedObject == null ) ? "" : mappedObject.GetType().ToString()) ); 

        if( mappedObject == null )
        {
            // No clock currently in storage, put this clock in that slot. 
            clockMappings[mappingKey] = animationClock;
        } 
        else if( mappedObject is AnimationClock ) 
        {
            // One clock currently in storage, up-convert to list and replace in slot. 
            List clockList = new List();

            clockList.Add((AnimationClock)mappedObject);
            clockList.Add(animationClock); 

            clockMappings[mappingKey] = clockList; 
        } 
        else // mappedObject is List
        { 
            // Add to existing list in storage.
            List clockList = (List)mappedObject;

            clockList.Add(animationClock); 
        }
 
        return; 
    }
 
    /// 
    ///     Takes the built up mapping table for animation clocks and applies
    /// them to the specified property on the specified object.
    ///  
    private static void ApplyAnimationClocks( HybridDictionary clockMappings, HandoffBehavior handoffBehavior, Int64 layer )
    { 
        foreach( DictionaryEntry entry in clockMappings ) 
        {
            ObjectPropertyPair key = (ObjectPropertyPair)entry.Key; 
            object value = entry.Value;
            List clockList;

            Debug.Assert( value is AnimationClock || value is List , 
                "Internal error - clockMappings table contains unexpected object of type" + value.GetType() );
 
            if( value is AnimationClock ) 
            {
                clockList = new List(1); 
                clockList.Add((AnimationClock)value);
            }
            else // if( value is List )
            { 
                clockList = (List)value;
            } 
 
            AnimationStorage.ApplyAnimationClocksToLayer(
                key.DependencyObject, 
                key.DependencyProperty,
                clockList,
                handoffBehavior,
                layer); 
        }
    } 
 
    /// 
    ///     Function that checks to see if a given PropertyPath (already given 
    /// the context object) can be used.
    /// 

    // The rules currently in effect: 
    // * The last object in the path must be a DependencyObject
    // * The last property (on that last object) must be a DependencyProperty 
    // * Any of these objects may be Freezable objects.  There are two cases for 
    //   this.
    //   1) The value of the first property is Frozen.  We might be able to 
    //      handle this via the cloning mechanism, so we don't check Frozen-ness
    //      if we see the first property is Frozen.  Whether the cloning
    //      mechanism can be used is verified elsewhere.
    //   2) The value of the first property is not Frozen, or is not a Freezable 
    //      at all.  In this case, the cloning code path does not apply, and
    //      thus we must not have any immutable Freezable objects any further 
    //      down the line. 

    // Another rule not enforced here: 
    // * If cloning is required, the first property value must be a Freezable,
    //   which knows how to clone itself.  However, this is only needed in
    //   cases of complex property paths and is checked elsewhere.
 
    // Things we don't care about:
    // * Whether or not any of the intermediate objects are DependencyObject or 
    //   not - this is supposed to work no matter the object type. 
    // * Whether or not any of the intermediate properties are DP or not - this
    //   is supposed to work whether it's a CLR property or DependencyProperty. 
    // * Whether or not any of the intermediate properties are animatable or not.
    //   Even though they are changing, we're not attaching an animation to clock
    //   to those properties specifically.
    // * By the same token, we don't care if any of them are marked Read-Only. 

    // Note that this means: If the intention is to make something fixed, it is 
    //  not sufficient to mark an intermediate property read-only and 
    //  not-animatable.  In fact, in the current design, it is impossible to
    //  be 100% sure that something will stay put. 
    internal static void VerifyPathIsAnimatable(PropertyPath path)
    {
        object    intermediateObject = null;
        object    intermediateProperty = null; // Might be DependencyProperty, PropertyInfo, or PropertyDescriptor 
        bool      checkingFrozenState = true;
        Freezable intermediateFreezable = null; 
 
        for( int i=0; i < path.Length; i++ )
        { 
            intermediateObject = path.GetItem(i);
            intermediateProperty = path.GetAccessor(i);

            if( intermediateObject == null ) 
            {
                Debug.Assert( i > 0, "The caller should not have set the PropertyPath context to a null object." ); 
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathObjectNotFound, AccessorName(path, i-1), path.Path )); 
            }
 
            if( intermediateProperty == null )
            {
                // Would love to throw error with the name of the property we couldn't find,
                //  but that information is not exposed from the PropertyPath class. 
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathPropertyNotFound, path.Path ));
            } 
 
            // If the first property value is an immutable Freezable, then turn
            //  off the Frozen state checking - let's hope we can use the cloning 
            //  mechanism for that case.
            // Index of zero is the path context object itself, one (that we're
            //  checking here) is the value of the first property.
            // Example: Property path "Background.Opacity" as applied to Button. 
            //  Object 0 is the Button, object 1 is the brush.
            if( i == 1 ) 
            { 
                intermediateFreezable = intermediateObject as Freezable;
                if( intermediateFreezable != null && intermediateFreezable.IsFrozen ) 
                {
                    checkingFrozenState = false;
                }
            } 
            // Freezable objects (other than the one returned as the value of
            //  the first property) must not be frozen if the first one isn't. 
            else if( checkingFrozenState ) 
            {
                intermediateFreezable = intermediateObject as Freezable; 
                if( intermediateFreezable != null && intermediateFreezable.IsFrozen )
                {
                    if( i > 0 )
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathFrozenCheckFailed, AccessorName(path, i-1), path.Path, intermediateFreezable.GetType().ToString() ));
                    } 
                    else 
                    {
                        // i == 0 means the targeted object itself is a frozen Freezable. 
                        //  This need a different error message.
                        throw new InvalidOperationException(SR.Get(SRID.Storyboard_ImmutableTargetNotSupported, path.Path));
                    }
                } 
            }
 
            // The last object + property pairing (the one we're actually going 
            //  to stick the clock on) has further requirements.
            if( i == path.Length-1 ) 
            {
                DependencyObject intermediateDO = intermediateObject as DependencyObject;
                DependencyProperty intermediateDP = intermediateProperty as DependencyProperty;
 
                if( intermediateDO == null )
                { 
                    Debug.Assert( i > 0, "The caller should not have set the PropertyPath context to a non DependencyObject." ); 
                    throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathMustPointToDependencyObject, AccessorName(path, i-1), path.Path));
                } 

                if( intermediateDP == null )
                {
                    throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathMustPointToDependencyProperty, path.Path )); 
                }
 
                if( checkingFrozenState && intermediateDO.IsSealed ) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathSealedCheckFailed, intermediateDP.Name, path.Path, intermediateDO)); 
                }

                if(!AnimationStorage.IsPropertyAnimatable(intermediateDO, intermediateDP) )
                { 
                    throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathIncludesNonAnimatableProperty, path.Path, intermediateDP.Name));
                } 
            } 
        }
    } 

    private static string AccessorName( PropertyPath path, int index )
    {
        object propertyAccessor = path.GetAccessor(index); 

        if( propertyAccessor is DependencyProperty ) 
        { 
            return ((DependencyProperty)propertyAccessor).Name;
        } 
        else if( propertyAccessor is PropertyInfo )
        {
            return ((PropertyInfo)propertyAccessor).Name;
        } 
        else if( propertyAccessor is PropertyDescriptor )
        { 
            return ((PropertyDescriptor)propertyAccessor).Name; 
        }
        else 
        {
            return "[Unknown]";
        }
    } 

    ///  
    ///     Makes sure that the given clock can animate the given property - 
    /// throw an exception otherwise.
    ///  
    private static void VerifyAnimationIsValid( DependencyProperty targetProperty, AnimationClock animationClock )
    {
        if( !AnimationStorage.IsAnimationClockValid(targetProperty, animationClock) )
        { 
            throw new InvalidOperationException(SR.Get(SRID.Storyboard_AnimationMismatch, animationClock.Timeline.GetType(), targetProperty.Name, targetProperty.PropertyType));
        } 
    } 

    ///  
    ///     For complex property paths, we need to dig our way down to the
    /// property and attach the animation clock there.  We will not be able to
    /// actually attach the clocks if the targetProperty points to a frozen
    /// Freezable.  More extensive handling will be required for that case. 
    /// 
    private void ProcessComplexPath( HybridDictionary clockMappings, DependencyObject targetObject, 
        PropertyPath path, AnimationClock animationClock, HandoffBehavior handoffBehavior, Int64 layer ) 
    {
        Debug.Assert(path.Length > 1, "This method shouldn't even be called for a simple property path."); 

        // For complex paths, the target object/property differs from the actual
        //  animated object/property.
        // 
        // Example:
        //  TargetName="Rect1" TargetProperty="(Rectangle.LayoutTransform).(RotateTransform.Angle)" 
        // 
        // The target object is a Rectangle.
        // The target property is LayoutTransform. 
        // The animated object is a RotateTransform
        // The animated property is Angle.

        // Currently unsolved problem: If the LayoutTransform is not a RotateTransform, 
        //  we have no way of knowing.  We'll merrily set up to animate the Angle
        //  property as an attached property, not knowing that the value will be 
        //  completely ignored. 

        DependencyProperty targetProperty   = path.GetAccessor(0) as DependencyProperty; 

        // Two different ways to deal with property paths.  If the target is
        //  on a frozen Freezable, we'll have to make a clone of the value and
        //  attach the animation on the clone instead. 
        // For all other objects, we attach the animation clock directly on the
        //  specified animating object and property. 
        object targetPropertyValue = targetObject.GetValue(targetProperty); 

        DependencyObject   animatedObject   = path.LastItem as DependencyObject; 
        DependencyProperty animatedProperty = path.LastAccessor as DependencyProperty;

        if( animatedObject == null ||
            animatedProperty == null || 
            targetProperty == null )
        { 
            throw new InvalidOperationException(SR.Get(SRID.Storyboard_PropertyPathUnresolved, path.Path)); 
        }
 
        VerifyAnimationIsValid(animatedProperty, animationClock);

        if( PropertyCloningRequired( targetPropertyValue ) )
        { 
            // Verify that property paths are supported for the specified
            //  object and property.  If the property value query (usually in 
            //  GetValueCore) doesn't call into Storyboard code, then none of this 
            //  will have any effect.  (Silently do nothing.)
            // Throwing here is for user's sake to alert that nothing will happen. 
            VerifyComplexPathSupport( targetObject );

            // We need to clone the value of the target, and from here onwards
            //  try to pretend that it is the actual value. 
            Debug.Assert(targetPropertyValue is Freezable, "We shouldn't be trying to clone a type we don't understand.  PropertyCloningRequired() has improperly flagged the current value as 'need to clone'.");
 
            // To enable animations on frozen Freezable objects, complex 
            //  path processing is done on a clone of the value.
            Freezable clone = ((Freezable)targetPropertyValue).Clone(); 
            SetComplexPathClone( targetObject, targetProperty, targetPropertyValue, clone );

            // Promote the clone to the EffectiveValues cache
            targetObject.InvalidateProperty(targetProperty); 

            // We're supposed to have the animatable clone in place by now.  But if 
            //  things went sour for whatever reason, halt the app instead of corrupting 
            //  the frozen object.
            if( targetObject.GetValue(targetProperty) != clone ) 
            {
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_ImmutableTargetNotSupported, path.Path));
            }
 
            // Now that we have a clone, update the animatedObject and animatedProperty
            //  with references to those on the clone. 
            using(path.SetContext(targetObject)) 
            {
                animatedObject = path.LastItem as DependencyObject; 
                animatedProperty = path.LastAccessor as DependencyProperty;
            }

            // And set up to listen to changes on this clone. 
            ChangeListener.ListenToChangesOnFreezable(
                targetObject, clone, targetProperty, (Freezable)targetPropertyValue ); 
        } 

        // Apply animation clock on the animated object/animated property. 
        ObjectPropertyPair directApplyTarget = new ObjectPropertyPair( animatedObject, animatedProperty );
        UpdateMappings( clockMappings, directApplyTarget, animationClock );
    }
 
    private bool PropertyCloningRequired( object targetPropertyValue )
    { 
        bool cloningRequired; 

        if( targetPropertyValue is Freezable ) 
        {
            if( ((Freezable)targetPropertyValue).IsFrozen )
            {
                // The target property value is a Freezable, and is frozen. 
                //  we will need to clone in order to use a complex property path.
                cloningRequired = true; 
            } 
            else
            { 
                // The target property value is a Freezable, and is not frozen.
                //  We can apply the animation clocks directly.
                cloningRequired = false;
            } 
        }
        else 
        { 
            // We have no idea what this might be and can't tell if we need to
            //  clone it.  But even if we do, we don't know how, so we won't. 
            cloningRequired = false;
        }

        return cloningRequired; 
    }
 
    ///  
    ///     Check to see if the given object and property combination will be
    /// able to resolve complex paths. 
    /// 
    private void VerifyComplexPathSupport( DependencyObject targetObject )
    {
        if( FrameworkElement.DType.IsInstanceOfType(targetObject) ) 
        {
            // FrameworkElement and derived types are supported. 
            return; 
        }
 
        if( FrameworkContentElement.DType.IsInstanceOfType(targetObject) )
        {
            // FrameworkContentElement and derived types are supported.
            return; 
        }
 
        // ... and anything else that knows to call into Storyboard.GetComplexPathValue. 

        // Otherwise - throw. 
        throw new InvalidOperationException(SR.Get(SRID.Storyboard_ComplexPathNotSupported, targetObject.GetType().ToString()));
    }

    ///  
    ///     Check to see if there is a complex path that started with the
    /// given target object and property.  If so, process the complex path 
    /// information and return the results. 
    /// 
    internal static void GetComplexPathValue( 
            DependencyObject targetObject,
            DependencyProperty targetProperty,
        ref EffectiveValueEntry entry,
            PropertyMetadata metadata) 
    {
        CloneCacheEntry cacheEntry = GetComplexPathClone(targetObject, targetProperty); 
 
        if (cacheEntry != null)
        { 
            object baseValue = entry.Value;
            if (baseValue == DependencyProperty.UnsetValue)
            {
                // If the incoming baseValue is DependencyProperty.UnsetValue, that 
                // means the current property value is the default value.  Either
                // the cacheEntry.Clone was a clone of a default value (and should be 
                // returned to the caller) or someone called ClearValue() (and 
                // cacheEntry.Clone should be cleared accordingly).
                // To distinguish these cases we must check the cached source 
                // against the default value.
                //
                // We don't have to handle the ClearValue case in this clause;
                // the comparison with the cached source to the base value 
                // will fail in that case (since the cached source won't be UnsetValue)
                // and we'll clear out the cache. 
 
                Debug.Assert(cacheEntry.Source != DependencyProperty.UnsetValue,
                    "Storyboard complex path�s clone cache should never contain DependencyProperty.UnsetValue.  Either something went wrong in Storyboard.ProcessComplexPath() or somebody else is messing with the Storyboard clone cache."); 

                if (cacheEntry.Source == metadata.GetDefaultValue(targetObject, targetProperty))
                {
                    //  The cacheEntry.Clone is the clone of the default value.  In normal 
                    //  non-Storyboard code paths, BaseValueSourceInternal is Unknown for default
                    //  values at this time, so we need to switch it over explicitly. 
                    // 
                    //  And to prevent DependencyObject.UpdateEffectiveValue from misconstruing this
                    //  as an unaltered default value (which would result in UEV thinking no change 
                    //  in value occurred and discarding this new value), we will go ahead and set the
                    //  animated value modifier on this value entry. ([....]:  B#1616678  5/19/2006)
                    //
                    //  In all other cases, valueSource should have the correct 
                    //  valueSource corresponding to the object we cloned from,
                    //  so we don't need to do anything. 
 
                    entry.BaseValueSourceInternal = BaseValueSourceInternal.Default;
                    entry.SetAnimatedValue(cacheEntry.Clone, DependencyProperty.UnsetValue); 
                    return;
                }
            }
 
            // If the incoming baseValue is a deferred object, we need to get the
            //  real value to make a valid comparison against the cache entry source. 
            DeferredReference deferredBaseValue = baseValue as DeferredReference; 
            if (deferredBaseValue != null)
            { 
                baseValue = deferredBaseValue.GetValue(entry.BaseValueSourceInternal);
                entry.Value = baseValue;
                entry.IsDeferredReference = false;
            } 

            // If the incoming baseValue is different from the original source object that 
            // we cloned and cached then we need to invalidate this cache. Otherwise we use 
            // the value in the cache as is.
            if (cacheEntry.Source == baseValue) 
            {
                CloneEffectiveValue(ref entry, cacheEntry);
                return;
            } 
            else
            { 
                // Setting to DependencyProperty.UnsetValue is how FrugalMap does delete. 
                SetComplexPathClone(
                        targetObject, 
                        targetProperty,
                        DependencyProperty.UnsetValue,
                        DependencyProperty.UnsetValue);
            } 
        }
    } 
 
    private static void CloneEffectiveValue(ref EffectiveValueEntry entry, CloneCacheEntry cacheEntry)
    { 
        object clonedValue = cacheEntry.Clone;
/*
        if (!entry.IsExpression)
        { 
            if (entry.LocalValue != clonedValue)
            { 
                entry.Value = clonedValue; 
            }
        } 
        else
        {
            ModifiedValue modifiedValue = entry.ModifiedValue;
            if (modifiedValue.ExpressionValue != clonedValue) 
            {
                modifiedValue.ExpressionValue = clonedValue; 
            } 
        }
*/ 
        if (!entry.IsExpression)
        {
            entry.Value = clonedValue;
        } 
        else
        { 
            entry.ModifiedValue.ExpressionValue = clonedValue; 
        }
    } 

    /// 
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object.
    ///  
    public void Begin( FrameworkElement containingObject )
    { 
        Begin( containingObject, HandoffBehavior.SnapshotAndReplace, false ); 
    }
 
    /// 
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object.
    /// 
    public void Begin( FrameworkElement containingObject, HandoffBehavior handoffBehavior ) 
    {
        Begin( containingObject, handoffBehavior, false ); 
    } 

    ///  
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object.
    /// 
    public void Begin( FrameworkElement containingObject, bool isControllable )
    { 
        Begin(containingObject, HandoffBehavior.SnapshotAndReplace, isControllable );
    } 
 
    /// 
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object. 
    /// 
    public void Begin( FrameworkElement containingObject, HandoffBehavior handoffBehavior, bool isControllable )
    {
        BeginCommon(containingObject, null, handoffBehavior, isControllable, Storyboard.Layers.Code ); 
    }
 
    ///  
    ///     Begins all ControlTemplate animations underneath this storyboard, clock tree starts at the given Control.
    ///  
    public void Begin( FrameworkElement containingObject, FrameworkTemplate frameworkTemplate )
    {
        Begin( containingObject, frameworkTemplate, HandoffBehavior.SnapshotAndReplace, false );
    } 

    ///  
    ///     Begins all ControlTemplate animations underneath this storyboard, clock tree starts at the given Control. 
    /// 
    public void Begin( FrameworkElement containingObject, FrameworkTemplate frameworkTemplate, HandoffBehavior handoffBehavior ) 
    {
        Begin( containingObject, frameworkTemplate, handoffBehavior, false );
    }
 
    /// 
    ///     Begins all ControlTemplate animations underneath this storyboard, clock tree starts at the given Control. 
    ///  
    public void Begin( FrameworkElement containingObject, FrameworkTemplate frameworkTemplate, bool isControllable )
    { 
        Begin(containingObject, frameworkTemplate, HandoffBehavior.SnapshotAndReplace, isControllable );
    }

    ///  
    ///     Begins all ControlTemplate animations underneath this storyboard, clock tree starts at the given Control.
    ///  
    public void Begin( FrameworkElement containingObject, FrameworkTemplate frameworkTemplate, HandoffBehavior handoffBehavior, bool isControllable ) 
    {
        BeginCommon(containingObject, frameworkTemplate, handoffBehavior, isControllable, Storyboard.Layers.Code ); 
    }

/*  This is the ContentControl+DataTemplate counterpert to Control+ControlTemplate above, need test signoff before enabling.
    ///  
    ///     Begins all DataTemplate animations underneath this storyboard, clock tree starts at the given ContentControl.
    ///  
    public void Begin( ContentControl contentControl, DataTemplate dataTemplate ) 
    {
        Begin( contentControl, dataTemplate, HandoffBehavior.SnapshotAndReplace, false ); 
    }

    /// 
    ///     Begins all DataTemplate animations underneath this storyboard, clock tree starts at the given ContentControl. 
    /// 
    public void Begin( ContentControl contentControl, DataTemplate dataTemplate, HandoffBehavior handoffBehavior ) 
    { 
        Begin( contentControl, dataTemplate, handoffBehavior, false );
    } 

    /// 
    ///     Begins all DataTemplate animations underneath this storyboard, clock tree starts at the given ContentControl.
    ///  
    public void Begin( ContentControl contentControl, DataTemplate dataTemplate, bool isControllable )
    { 
        Begin( contentControl, dataTemplate, HandoffBehavior.SnapshotAndReplace, isControllable ); 
    }
 
    /// 
    ///     Begins all DataTemplate animations underneath this storyboard, clock tree starts at the given ContentControl.
    /// 
    public void Begin( ContentControl contentControl, DataTemplate dataTemplate, HandoffBehavior handoffBehavior, bool isControllable ) 
    {
        BeginCommon( contentControl, dataTemplate, handoffBehavior, isControllable, Storyboard.Layers.Code ); 
    } 
*/
    ///  
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object.
    /// 
    public void Begin( FrameworkContentElement containingObject )
    { 
        Begin( containingObject, HandoffBehavior.SnapshotAndReplace, false );
    } 
 
    /// 
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object. 
    /// 
    public void Begin( FrameworkContentElement containingObject, HandoffBehavior handoffBehavior )
    {
        Begin( containingObject, handoffBehavior, false ); 
    }
 
    ///  
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object.
    ///  
    public void Begin( FrameworkContentElement containingObject, bool isControllable )
    {
        Begin(containingObject, HandoffBehavior.SnapshotAndReplace, isControllable );
    } 

    ///  
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object. 
    /// 
    public void Begin( FrameworkContentElement containingObject, HandoffBehavior handoffBehavior, bool isControllable ) 
    {
        BeginCommon(containingObject, null, handoffBehavior, isControllable, Storyboard.Layers.Code );
    }
 
    /// 
    ///     Begins all animations underneath this storyboard, clock tree starts at the given containing object. 
    ///  
    internal void BeginCommon( DependencyObject containingObject, INameScope nameScope,
        HandoffBehavior handoffBehavior, bool isControllable, Int64 layer ) 
    {

        if (containingObject == null)
        { 
            throw new ArgumentNullException("containingObject");
        } 
 
        if (!HandoffBehaviorEnum.IsDefined(handoffBehavior))
        { 
            throw new ArgumentException(SR.Get(SRID.Storyboard_UnrecognizedHandoffBehavior));
        }

        if (BeginTime == null) 
        {
            // a null BeginTime means to not allocate or start the clock 
            return; 
        }
 
        // It's not possible to begin when there is no TimeManager.  This condition
        //  is known to occur during app shutdown.  Since an app being shut down
        //  won't care about its Storyboards, we silently exit.
        // If we don't exit here, we'll need to catch and handle the "no time 
        //  manager" exception implemented for bug #1247862
        if( MediaContext.From(containingObject.Dispatcher).TimeManager == null ) 
        { 
            return;
        } 


        if( TraceAnimation.IsEnabled )
        { 
            TraceAnimation.TraceActivityItem(
                TraceAnimation.StoryboardBegin, 
                this, 
                Name,
                containingObject, 
                nameScope );
        }

 
        // This table maps an [object,property] key pair to one or more clocks.
        // If we have knowledge of whether this Storyboard was changed between multiple 
        //  Begin(), we can cache this.  But we don't know, so we don't cache. 
        HybridDictionary simplePathClockMappings = new HybridDictionary();
 
        // Create (and Begin) a clock tree corresponding to this Storyboard timeline tree
        Clock storyboardClockTree = CreateClock(isControllable);

        // We now have one or more clocks that are created from this storyboard. 
        //  However, the individual clocks are not necessarily intended for
        //  the containing object so we need to do a tree walk and sort out 
        //  which clocks go on which objects and their properties. 
        ClockTreeWalkRecursive(
            storyboardClockTree, 
            containingObject,
            nameScope,
            null, /* target object name */
            null, /* target property path */ 
            handoffBehavior,
            simplePathClockMappings, 
            layer); 

        // Apply the storyboard's animation clocks we found in the tree walk 
        ApplyAnimationClocks( simplePathClockMappings, handoffBehavior, layer );

        if (isControllable)
        { 
            // Save a reference to this clock tree on the containingObject.  We
            //  need it there in order to control this clock tree later. 
            SetStoryboardClock(containingObject, storyboardClockTree); 
        }
 
        return;
    }

 
    /// 
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return the current global speed. 
    /// 
    public Nullable GetCurrentGlobalSpeed( FrameworkElement containingObject ) 
    {
        Clock clock =  GetStoryboardClock(containingObject);
        if (clock != null)
        { 
            return clock.CurrentGlobalSpeed;
        } 
 
        return null;
    } 


    /// 
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return the current global speed.
    ///  
    public Nullable GetCurrentGlobalSpeed( FrameworkContentElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject); 

        if (clock != null)
        {
            return clock.CurrentGlobalSpeed; 
        }
 
        return null; 
    }
 
    /// 
    ///  Given an object, look on the clock store for a clock that was
    ///  generated from 'this' storyboard.  If found, return the current iteration.
    ///  
    public Nullable GetCurrentIteration( FrameworkElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject); 

        if (clock != null) 
        {
            return clock.CurrentIteration;
        }
 
        return null;
    } 
 

    ///  
    ///  Given an object, look on the clock store for a clock that was
    ///  generated from 'this' storyboard.  If found, return the current iteration.
    /// 
    public Nullable GetCurrentIteration( FrameworkContentElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject); 
 
        if (clock != null)
        { 
            return clock.CurrentIteration;
        }

        return null; 
    }
 
    ///  
    ///  Given an object, look on the clock store for a clock that was
    ///  generated from 'this' storyboard.  If found, return the current progress. 
    /// 
    public Nullable GetCurrentProgress( FrameworkElement containingObject )
    {
        Clock clock = GetStoryboardClock(containingObject); 

        if (clock != null) 
        { 
            return clock.CurrentProgress;
        } 

        return null;
    }
 
    /// 
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return the current progress. 
    /// 
    public Nullable GetCurrentProgress( FrameworkContentElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject);

        if (clock != null) 
        {
            return clock.CurrentProgress; 
        } 

        return null; 
    }

    /// 
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return the current state.
    ///  
    public ClockState GetCurrentState( FrameworkElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject); 

        if (clock != null)
        {
            return clock.CurrentState; 
        }
 
        return ClockState.Stopped; 
    }
 

    /// 
    ///  Given an object, look on the clock store for a clock that was
    ///  generated from 'this' storyboard.  If found, return the current state. 
    /// 
    public ClockState GetCurrentState( FrameworkContentElement containingObject ) 
    { 
        Clock clock = GetStoryboardClock(containingObject);
 
        if (clock != null)
        {
            return clock.CurrentState;
        } 

        return ClockState.Stopped; 
    } 

    ///  
    ///  Given an object, look on the clock store for a clock that was
    ///  generated from 'this' storyboard.  If found, return the current time.
    /// 
    public Nullable GetCurrentTime( FrameworkElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject); 
 
        if (clock != null)
        { 
            return clock.CurrentTime;
        }

        return null; 
    }
 
 
    /// 
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return the current time.
    /// 
    public Nullable GetCurrentTime( FrameworkContentElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject);
 
        if (clock != null) 
        {
            return clock.CurrentTime; 
        }

        return null;
    } 

    ///  
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return whether the clock is paused.
    ///  
    public bool GetIsPaused( FrameworkElement containingObject )
    {
        Clock clock = GetStoryboardClock(containingObject);
 
        if (clock != null)
        { 
            return clock.IsPaused; 
        }
 
        // A clock that has been disposed is not in a paused state.
        return false;
    }
 

    ///  
    ///  Given an object, look on the clock store for a clock that was 
    ///  generated from 'this' storyboard.  If found, return whether the clock is paused.
    ///  
    public bool GetIsPaused( FrameworkContentElement containingObject )
    {
        Clock clock = GetStoryboardClock(containingObject);
 
        if (clock != null)
        { 
            return clock.IsPaused; 
        }
 
        // A clock that has been disposed is not in a paused state.
        return false;
    }
 

    ///  
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call pause on the clock.
    ///  
    public void Pause( FrameworkElement containingObject )
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Pause);
 
        if (clock != null)
        { 
            clock.Controller.Pause(); 
        }
 
        if( TraceAnimation.IsEnabled )
        {
            TraceAnimation.TraceActivityItem(
                TraceAnimation.StoryboardPause, 
                this,
                Name, 
                containingObject ); 
        }
 
    }

    /// 
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call pause on the clock.
    ///  
    public void Pause( FrameworkContentElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Pause); 

        if (clock != null)
        {
            clock.Controller.Pause(); 
        }
 
        if( TraceAnimation.IsEnabled ) 
        {
            TraceAnimation.TraceActivityItem( 
                TraceAnimation.StoryboardPause,
                this,
                Name,
                containingObject ); 
        }
    } 
 
    /// 
    /// If a clock was generated from 'this' storyboard for the given object, remove it. 
    /// 
    public void Remove(FrameworkElement containingObject)
    {
        RemoveInternal(containingObject); 
    }
 
    ///  
    /// If a clock was generated from 'this' storyboard for the given object, remove it.
    ///  
    public void Remove(FrameworkContentElement containingObject)
    {
        RemoveInternal(containingObject);
    } 

    ///  
    /// If a clock was generated from 'this' storyboard for the given object, remove it. 
    /// This method works for any DO, but the Remove(FE) and Remove(FCE) methods are still needed
    /// for api compatibility. 
    /// 
    internal void RemoveInternal(DependencyObject containingObject)
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Remove); 

        if (clock != null) 
        { 
            clock.Controller.Remove();
            HybridDictionary clocks = StoryboardClockTreesField.GetValue(containingObject); 
            if (clocks != null)
            {
                clocks.Remove(this);
            } 
        }
 
        if( TraceAnimation.IsEnabled ) 
        {
            TraceAnimation.TraceActivityItem( 
                TraceAnimation.StoryboardRemove,
                this,
                Name,
                containingObject ); 
        }
    } 
 
    /// 
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call resume on the clock.
    /// 
    public void Resume( FrameworkElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Resume);
 
        if (clock != null) 
        {
            clock.Controller.Resume(); 
        }

        if( TraceAnimation.IsEnabled )
        { 
            TraceAnimation.TraceActivityItem(
                TraceAnimation.StoryboardResume, 
                this, 
                Name,
                containingObject ); 
        }
    }

    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call resume on the clock. 
    ///  
    public void Resume( FrameworkContentElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Resume);

        if (clock != null)
        { 
            clock.Controller.Resume();
        } 
 
        if( TraceAnimation.IsEnabled )
        { 
            TraceAnimation.TraceActivityItem(
                TraceAnimation.StoryboardResume,
                this,
                Name, 
                containingObject );
        } 
    } 

    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call seek on the clock
    /// with the given parameters.
    ///  
    public void Seek( FrameworkElement containingObject, TimeSpan offset, TimeSeekOrigin origin )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Seek); 

        if (clock != null) 
        {
            // Seek is a public API as well, so its parameters should get validated there.
            clock.Controller.Seek(offset, origin);
        } 
    }
 
    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call seek on the clock 
    /// with the given parameters.
    /// 
    public void Seek( FrameworkContentElement containingObject, TimeSpan offset, TimeSeekOrigin origin )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Seek);
 
        if (clock != null) 
        {
            // Seek is a public API as well, so its parameters should get validated there. 
            clock.Controller.Seek(offset, origin);
        }
    }
 
    /// 
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call SeekAlignedToLastTick 
    /// on the clock with the given parameters.
    ///  
    public void SeekAlignedToLastTick( FrameworkElement containingObject, TimeSpan offset, TimeSeekOrigin origin )
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SeekAlignedToLastTick);
 
        if (clock != null)
        { 
            // SeekAlignedToLastTick is a public API as well, so its parameters should get validated there. 
            clock.Controller.SeekAlignedToLastTick(offset, origin);
        } 
    }

    /// 
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call SeekAlignedToLastTick
    /// on the clock with the given parameters. 
    ///  
    public void SeekAlignedToLastTick( FrameworkContentElement containingObject, TimeSpan offset, TimeSeekOrigin origin )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SeekAlignedToLastTick);

        if (clock != null)
        { 
            // SeekAlignedToLastTick is a public API as well, so its parameters should get validated there.
            clock.Controller.SeekAlignedToLastTick(offset, origin); 
        } 
    }
 
    /// 
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, set the speed ratio on
    /// the clock to the given ratio. 
    /// 
    public void SetSpeedRatio( FrameworkElement containingObject, double speedRatio ) 
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SetSpeedRatio);
 
        if (clock != null)
        {
            clock.Controller.SpeedRatio = speedRatio;
        } 
    }
 
    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, set the speed ratio on the clock 
    /// with the given parameters.
    /// 
    public void SetSpeedRatio( FrameworkContentElement containingObject, double speedRatio )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SetSpeedRatio);
 
        if (clock != null) 
        {
            clock.Controller.SpeedRatio = speedRatio; 
        }
    }

    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call skip-to-fill on the clock. 
    ///  
    public void SkipToFill( FrameworkElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SkipToFill);

        if (clock != null)
        { 
            clock.Controller.SkipToFill();
        } 
    } 

    ///  
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call skip-to-fill on the clock.
    /// 
    public void SkipToFill( FrameworkContentElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.SkipToFill); 
 
        if (clock != null)
        { 
            clock.Controller.SkipToFill();
        }
    }
 
    /// 
    ///     Given an object, look on the clock store for a clock that was 
    /// generated from 'this' storyboard.  If found, call stop on the clock. 
    /// 
    public void Stop( FrameworkElement containingObject ) 
    {
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Stop);

        if (clock != null) 
        {
            clock.Controller.Stop(); 
        } 

        if( TraceAnimation.IsEnabled ) 
        {
            TraceAnimation.TraceActivityItem(
                TraceAnimation.StoryboardStop,
                this, 
                Name,
                containingObject ); 
        } 
    }
 
    /// 
    ///     Given an object, look on the clock store for a clock that was
    /// generated from 'this' storyboard.  If found, call stop on the clock.
    ///  
    public void Stop( FrameworkContentElement containingObject )
    { 
        Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Stop); 

        if (clock != null) 
        {
            clock.Controller.Stop();
        }
 
        if( TraceAnimation.IsEnabled )
        { 
            TraceAnimation.TraceActivityItem( 
                TraceAnimation.StoryboardStop,
                this, 
                Name,
                containingObject );
        }
    } 

    ///  
    ///   HybridDictionary for storing the root clock tree for each storyboard. 
    ///     Key: An instance of the Storyboard object.
    ///     Value: The root of the clock tree that was created from the key object. 
    /// 
    /// 
    ///     Another way to describe the key-value relation is that the value
    /// clock object's Timeline property points to the Storyboard. 
    /// 
    private static readonly UncommonField StoryboardClockTreesField = new UncommonField(); 
 
    /// 
    ///     Given an object, look in the attached storage for storyboard clocks 
    /// and retrieve the one that is associated with 'this' Storyboard instance.
    /// throw if not found.
    /// 
    private Clock GetStoryboardClock(DependencyObject o) 
    {
        return GetStoryboardClock(o, true, InteractiveOperation.Unknown); 
    } 

    ///  
    ///     Given an object, look in the attached storage for storyboard clocks
    /// and retrieve the one that is associated with 'this' Storyboard instance.
    /// If the clock is null we'll either throw an exception or emit a trace, depending
    /// on the value of the throwIfNull parameter.  The InteractiveOperation 
    /// parameter is used to give more detailed info in the trace.
    /// 
    ///  
    private Clock GetStoryboardClock(DependencyObject o, bool throwIfNull, InteractiveOperation operation)
    { 
        Clock clock = null;
        WeakReference clockReference = null;

        HybridDictionary clocks = StoryboardClockTreesField.GetValue(o); 

        if (clocks != null) 
        { 
            clockReference = clocks[this] as WeakReference;
        } 

        if (clockReference == null)
        {
            if (throwIfNull) 
            {
                // This exception indicates that the storyboard has never been applied. 
                // We check the weak reference because the only way it can be null 
                // is if it had never been put in the dictionary.
                throw new InvalidOperationException(SR.Get(SRID.Storyboard_NeverApplied)); 
            }
            else  if (TraceAnimation.IsEnabledOverride )
            {
                TraceAnimation.Trace( 
                    TraceEventType.Warning,
                    TraceAnimation.StoryboardNotApplied, 
                    operation, 
                    this,
                    o); 
            }
        }

 

        if (clockReference != null) 
        { 
            clock = clockReference.Target as Clock;
 
            // At this point the clock may have been garbage collected.
            // We may have a null clock even though this Storyboard had
            // been applied to the given DependencyObject. One way this
            // can happen is if another Storyboard Begins an animation 
            // on that same DO / DP pair with SnapshotAndReplace semantics.
            // In that case AnimationStorage will toss out the old clock. 
        } 

        return clock; 
    }


    ///  
    ///     Given an object, and a clock to associate with 'this' Storyboard
    /// instance, save a reference to the clock on the object's attached storage 
    /// for storyboard clocks.  We are storing a weak reference so that the 
    /// clock is not kept alive.  Currently we don't have a way of removing
    /// clocks from the list when it is no longer required. 
    /// 
    /// 
    ///     We don't care if there's already a clock there - if there is one,
    /// the reference is overridden in the HybridDictionary, and the old clock 
    /// is abandoned.
    ///  
    private void SetStoryboardClock(DependencyObject o, Clock clock) 
    {
        HybridDictionary clocks = StoryboardClockTreesField.GetValue(o); 

        if (clocks == null)
        {
            clocks = new HybridDictionary(); 
            StoryboardClockTreesField.SetValue(o, clocks);
        } 
 
        clocks[this] = new WeakReference(clock);
 
        return;
    }

    ///  
    ///     The complex path clone storage field stores the clone that we're using
    /// in place of the original value. 
    ///  
    /// 
    ///     This field is attached to the target object from which the path 
    /// starts.  The field is a map indexed by the property affected.  For the
    /// example
    ///
    ///  TargetName="Rect1" TargetProperty="(Rectangle.LayoutTransform).(RotateTransform.Angle)" 
    ///
    ///     The FrugalMap will be attached to whatever "Rect1" is.  The data 
    /// will then be stored in the FrugalMap at the index for the property 
    /// (in this case the LayoutTransformProperty.GlobalIndex)
    ///  
    private static readonly UncommonField ComplexPathCloneField = new UncommonField();

    private static CloneCacheEntry GetComplexPathClone(DependencyObject o, DependencyProperty dp)
    { 
        FrugalMap clonesMap = ComplexPathCloneField.GetValue(o);
 
        // FrugalMap is a struct, so no need to check against null. 
        // when there is no clones field on this object we will get a FrugalMap with no elements.
        object value = clonesMap[dp.GlobalIndex]; 
        if (value != DependencyProperty.UnsetValue)
        {
            return (CloneCacheEntry)clonesMap[dp.GlobalIndex];
        } 
        else
        { 
            return null; 
        }
    } 

    private static void SetComplexPathClone(
        DependencyObject    o,
        DependencyProperty  dp, 
        object              source,
        object              clone) 
    { 
        FrugalMap clonesMap = ComplexPathCloneField.GetValue(o);
 
        if (clone != DependencyProperty.UnsetValue)
        {
            clonesMap[dp.GlobalIndex] = new CloneCacheEntry(source, clone);
        } 
        else
        { 
            clonesMap[dp.GlobalIndex] = DependencyProperty.UnsetValue; 
        }
 
        // FrugalMap is a struct - after a change it needs to be set back on the object.
        ComplexPathCloneField.SetValue(o, clonesMap);
    }
 
    // This is the entry in the ComplexPathClone cache
    private class CloneCacheEntry 
    { 
        internal CloneCacheEntry(object source, object clone)
        { 
            Source = source;
            Clone = clone;
        }
 
        internal object Source;
        internal object Clone; 
    } 

    // Small object used to send a property invalidation when the InvalidatePropertyOnChange 
    //  delegate is called in response to an event.
    // The ChangeListener class supports Storyboard animation scenarios with
    //  multi-step property paths.  In these cases, a clone of the original
    //  value is made and the storyboard animation is attached to the clone. 
    // This class listens to the changes on both the original object and the
    //  clone. 
    // If the original object has changed, this class signals the need to 
    //  re-clone in order to pick up the state of the original object.
    // If the cloned object has changed, this class signals an animation- 
    //  driven sub-property invalidation.
    internal class ChangeListener
    {
        // Constructor of the object, the parameters include the property to 
        //  invalidate and the object to invalidate it on.  As well as the
        //  two Freezable objects (original and clone) that are associated 
        //  with the property on the target object. 
        internal ChangeListener( DependencyObject target, Freezable clone, DependencyProperty property, Freezable original )
        { 
            Debug.Assert( target != null && clone != null && property != null && original != null,
                "Internal utility class requires non-null arguments.  Check the caller of this method for an error.");
            _target = target;
            _property = property; 
            _clone = clone;
            _original = original; 
        } 

        // Called when the clone has changed.  We check the clone cache on 
        //  the target object to see if we were the most recent clone.  If so,
        //  signal a sub-property invalidation.  If not, we are no longer
        //  relevant and we should clean up.
        internal void InvalidatePropertyOnCloneChange( object source, EventArgs e ) 
        {
            CloneCacheEntry cacheEntry = GetComplexPathClone( _target, _property ); 
 
            // If the changed freezable is the currently outstanding instance
            //  then we need to trigger a sub-property invalidation. 
            if( cacheEntry != null && cacheEntry.Clone == _clone )
            {
                _target.InvalidateSubProperty(_property);
            } 
            // Otherwise, we are no longer relevant and need to clean up.
            else 
            { 
                Cleanup();
            } 
        }

        // This is the event handler on the original.  When the original
        //  changes, the clone is no longer valid.  This method triggers a 
        //  re-clone by calling InvalidateProperty, then clean up.  Now that
        //  the associated clone is no longer valid, there's nothing useful 
        //  for us to listen on. 
        internal void InvalidatePropertyOnOriginalChange( object source, EventArgs e )
        { 
            // recompute animated value
            _target.InvalidateProperty(_property);
            Cleanup();
        } 

        // This is the internal method called to set up the listeners on both 
        //  the original and the clone. 
        internal static void ListenToChangesOnFreezable(
            DependencyObject target, 
            Freezable clone,
            DependencyProperty dp,
            Freezable original)
        { 
            ChangeListener listener = new ChangeListener( target, clone, dp, original );
 
            listener.Setup(); 
        }
 
        private void Setup()
        {
            EventHandler changeEventHandler = new EventHandler(InvalidatePropertyOnCloneChange);
 
            // Listen to changes on clone.
            _clone.Changed += changeEventHandler; 
 
            if( _original.IsFrozen )
            { 
                // We skip setting up for the original object when it is Frozen,
                //  because it won't change so we don't need to worry about listening.
                _original = null;
            } 
            else
            { 
                // If the original is not Frozen, we do need to listen and 
                //  signal a re-clone if the original changes.
                changeEventHandler = new EventHandler(InvalidatePropertyOnOriginalChange); 

                _original.Changed += changeEventHandler;
            }
        } 

        // Stop listening to the Changed event on the given Freezable objects 
        //  and clean up. 
        private void Cleanup()
        { 
            // Remove ourself from the clone
            EventHandler changeEventHandler = new EventHandler(InvalidatePropertyOnCloneChange);

            _clone.Changed -= changeEventHandler; 

            // If we're listening on the original, remove ourselves from there too. 
            //  (In Setup() _original was nulled out if we aren't listening.) 
            if( _original != null )
            { 
                changeEventHandler = new EventHandler(InvalidatePropertyOnOriginalChange);

                _original.Changed -= changeEventHandler;
            } 

            // Clear all object references. 
            _target = null; 
            _property = null;
            _clone = null; 
            _original = null;
        }

        DependencyObject _target;     // The object to invalidate 
        DependencyProperty _property; // The property to invalidate on the above object.
        Freezable _clone;             // The cloned Freezable whose Changed event we were listening to. 
        Freezable _original;          // The original Freezable whose Changed event we're also listening to. 
    }
 
    internal static class Layers
    {
        internal static Int64 ElementEventTrigger = 1;
        internal static Int64 StyleOrTemplateEventTrigger = 1; 
        internal static Int64 Code = 1;
        internal static Int64 PropertyTriggerStartLayer = 2; // First PropertyTrigger takes this layer number. 
    } 

    // Describes the various interactive operations we can do to a controllable 
    // storyboard.  Used by GetStoryboardClock for debug tracing.
    private enum InteractiveOperation : ushort
    {
        Unknown = 0, 
        Pause,
        Remove, 
        Resume, 
        Seek,
        SeekAlignedToLastTick, 
        SetSpeedRatio,
        SkipToFill,
        Stop
    } 
}
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK