Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Media / Animation / Storyboard.cs / 1305600 / 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; // Listusing 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 { static Storyboard() { PropertyMetadata targetPropertyMetadata = new PropertyMetadata(); targetPropertyMetadata.FreezeValueCallback = TargetFreezeValueCallback; TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(DependencyObject), typeof(Storyboard), targetPropertyMetadata); } ////// 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 Target property is designed to be attached to animation /// timelines to indicate the object they should target. /// public static readonly DependencyProperty TargetProperty; ////// Sets value of the Target property on the specified object. /// public static void SetTarget(DependencyObject element, DependencyObject value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TargetProperty, value); } ////// Gets the value of the Target property from the specified object. /// ////// The target property is not serializable, since it can be set to /// any DependencyObject, and it is not guaranteed that this object /// can be correctly referenced from XAML. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public static DependencyObject GetTarget(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (DependencyObject)element.GetValue(TargetProperty); } private static bool TargetFreezeValueCallback( DependencyObject d, DependencyProperty dp, EntryIndex entryIndex, PropertyMetadata metadata, bool isChecking) { // We allow the object to which the Target property is attached to be // frozen, even though the value of the Target property is not usable // from other threads. Clocks clone & freeze copies of their original // timelines because the clocks will not respond to changes to those // timelines. return true; } ////// 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 */ DependencyObject parentObject, 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; DependencyObject targetObject = parentObject; 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; } // The TargetProperty trumps the TargetName property. DependencyObject localTargetObject = (DependencyObject) currentTimeline.GetValue(TargetProperty); if( localTargetObject != null ) { targetObject = localTargetObject; currentObjectName = null; } 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 ) { DependencyProperty targetProperty = null; AnimationClock animationClock = (AnimationClock)currentClock; if( targetObject == null ) { // Resolve the target object name. If no name specified, use the // containing object. if( currentObjectName != null ) { DependencyObject mentor = Helper.FindMentor(containingObject); targetObject = ResolveTargetName(currentObjectName, nameScope, mentor); } else { // The containing object must be either an FE or FCE. // (Not a Storyboard, as used for "shared clocks" mode.) targetObject = containingObject as FrameworkElement; if(targetObject == null) { targetObject = containingObject as FrameworkContentElement; } if( targetObject == null ) { // The containing object is not an FE or FCE. throw new InvalidOperationException(SR.Get(SRID.Storyboard_NoTarget, currentTimeline.GetType().ToString() )); } } } // 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, targetObject, 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, targetObject, 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, DependencyObject currentObject, string currentObjectName, MediaClock mediaClock ) { MediaElement targetMediaElement = null; if( currentObjectName != null ) { // Find the object named as the current target name. DependencyObject mentor = Helper.FindMentor(containingObject); targetMediaElement = ResolveTargetName(currentObjectName, nameScope, mentor ) as MediaElement; if( targetMediaElement == null ) { throw new InvalidOperationException(SR.Get(SRID.Storyboard_MediaElementNotFound, currentObjectName )); } } else if( currentObject != null ) { targetMediaElement = currentObject as MediaElement; } else { 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; ListclockList; 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. (jeffbog: 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; } // 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 in "shared clocks" mode. /// public void Begin() { DependencyObject containingObject = this; // Helper.FindMentor(this); INameScope nameScope = null; HandoffBehavior handoffBehavior = HandoffBehavior.SnapshotAndReplace; bool isControllable = true; Int64 layer = Storyboard.Layers.Code; BeginCommon(containingObject, nameScope, handoffBehavior, isControllable, layer); } ////// 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.CurrentMediaContext.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 */ 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 NullableGetCurrentGlobalSpeed( FrameworkElement containingObject ) { return GetCurrentGlobalSpeedImpl(containingObject); } /// /// 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 NullableGetCurrentGlobalSpeed( FrameworkContentElement containingObject ) { return GetCurrentGlobalSpeedImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current global speed. /// public Double GetCurrentGlobalSpeed() { NullablecurrentGlobalSpeed = GetCurrentGlobalSpeedImpl(this); if(currentGlobalSpeed.HasValue) { return currentGlobalSpeed.Value; } else { return default(Double); } } private Nullable GetCurrentGlobalSpeedImpl( DependencyObject 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 NullableGetCurrentIteration( FrameworkElement containingObject ) { return GetCurrentIterationImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current iteration. /// public NullableGetCurrentIteration( FrameworkContentElement containingObject ) { return GetCurrentIterationImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current iteration. /// public Int32 GetCurrentIteration() { NullablecurrentIteration = GetCurrentIterationImpl(this); if(currentIteration.HasValue) { return currentIteration.Value; } else { return default(Int32); } } private Nullable GetCurrentIterationImpl( DependencyObject 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 NullableGetCurrentProgress( FrameworkElement containingObject ) { return GetCurrentProgressImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current progress. /// public NullableGetCurrentProgress( FrameworkContentElement containingObject ) { return GetCurrentProgressImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current progress. /// public Double GetCurrentProgress() { NullablecurrentProgress = GetCurrentProgressImpl(this); if(currentProgress.HasValue) { return currentProgress.Value; } else { return default(Double); } } private Nullable GetCurrentProgressImpl( DependencyObject 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 ) { return GetCurrentStateImpl(containingObject); } ////// 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 ) { return GetCurrentStateImpl(containingObject); } ////// Return the current state of this storyboard. This storyboard /// must have been created in "shared clocks" mode. /// public ClockState GetCurrentState() { return GetCurrentStateImpl(this); } private ClockState GetCurrentStateImpl( DependencyObject containingObject ) { Clock clock = GetStoryboardClock(containingObject); if (clock != null) { return clock.CurrentState; } return ClockState.Stopped; // Not default(ClockState)... } ////// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current time. /// public NullableGetCurrentTime( FrameworkElement containingObject ) { return GetCurrentTimeImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current time. /// public NullableGetCurrentTime( FrameworkContentElement containingObject ) { return GetCurrentTimeImpl(containingObject); } /// /// Return the current time of this storyboard. This storyboard /// must have been created in "shared clocks" mode. /// public TimeSpan GetCurrentTime() { NullablecurrentTime = GetCurrentTimeImpl(this); if(currentTime.HasValue) { return currentTime.Value; } else { return default(TimeSpan); } } private Nullable GetCurrentTimeImpl( DependencyObject 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 ) { return GetIsPausedImpl(containingObject); } ////// 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 ) { return GetIsPausedImpl(containingObject); } ////// 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() { return GetIsPausedImpl(this); } private bool GetIsPausedImpl( DependencyObject 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 ) { PauseImpl(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 ) { PauseImpl(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() { PauseImpl(this); } private void PauseImpl(DependencyObject containingObject) { Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Pause); if (clock != null) { clock.Controller.Pause(); } if( TraceAnimation.IsEnabled ) { TraceAnimation.TraceActivityItem( TraceAnimation.StoryboardPause, this, Name, this ); } } ////// If a clock was generated from 'this' storyboard for the given object, remove it. /// public void Remove(FrameworkElement containingObject) { RemoveImpl(containingObject); } ////// If a clock was generated from 'this' storyboard for the given object, remove it. /// public void Remove(FrameworkContentElement containingObject) { RemoveImpl(containingObject); } ////// If a clock was generated from this storyboard, remove it. /// public void Remove() { RemoveImpl(this); } private void RemoveImpl(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 ) { ResumeImpl(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 ) { ResumeImpl(containingObject); } ////// Look on the clock store for a clock that was generated from this /// storyboard. If found, call resume on the clock. /// public void Resume() { ResumeImpl(this); } private void ResumeImpl( DependencyObject 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 ) { SeekImpl(containingObject, 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 ) { SeekImpl(containingObject, offset, origin); } ////// 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( TimeSpan offset, TimeSeekOrigin origin ) { SeekImpl(this, offset, origin); } ////// 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( TimeSpan offset ) { SeekImpl(this, offset, TimeSeekOrigin.BeginTime); } private void SeekImpl( DependencyObject 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 ) { SeekAlignedToLastTickImpl(containingObject, 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 ) { SeekAlignedToLastTickImpl(containingObject, offset, origin); } ////// 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( TimeSpan offset, TimeSeekOrigin origin ) { SeekAlignedToLastTickImpl(this, offset, origin); } ////// 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( TimeSpan offset ) { SeekAlignedToLastTickImpl(this, offset, TimeSeekOrigin.BeginTime); } private void SeekAlignedToLastTickImpl( DependencyObject 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 ) { SetSpeedRatioImpl(containingObject, 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 ) { SetSpeedRatioImpl(containingObject, speedRatio); } ////// 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( double speedRatio ) { SetSpeedRatioImpl(this, speedRatio); } private void SetSpeedRatioImpl( DependencyObject 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 ) { SkipToFillImpl(containingObject); } ////// 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 ) { SkipToFillImpl(containingObject); } ////// 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() { SkipToFillImpl(this); } private void SkipToFillImpl( DependencyObject 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 ) { StopImpl(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 ) { StopImpl(containingObject); } ////// Look on the clock store for a clock that was generated from this /// storyboard. If found, call stop on the clock. /// public void Stop() { StopImpl(this); } private void StopImpl( DependencyObject 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 UncommonFieldStoryboardClockTreesField = 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 UncommonFieldComplexPathCloneField = 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. /****************************************************************************\ * * 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 { static Storyboard() { PropertyMetadata targetPropertyMetadata = new PropertyMetadata(); targetPropertyMetadata.FreezeValueCallback = TargetFreezeValueCallback; TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(DependencyObject), typeof(Storyboard), targetPropertyMetadata); } ////// 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 Target property is designed to be attached to animation /// timelines to indicate the object they should target. /// public static readonly DependencyProperty TargetProperty; ////// Sets value of the Target property on the specified object. /// public static void SetTarget(DependencyObject element, DependencyObject value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TargetProperty, value); } ////// Gets the value of the Target property from the specified object. /// ////// The target property is not serializable, since it can be set to /// any DependencyObject, and it is not guaranteed that this object /// can be correctly referenced from XAML. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public static DependencyObject GetTarget(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (DependencyObject)element.GetValue(TargetProperty); } private static bool TargetFreezeValueCallback( DependencyObject d, DependencyProperty dp, EntryIndex entryIndex, PropertyMetadata metadata, bool isChecking) { // We allow the object to which the Target property is attached to be // frozen, even though the value of the Target property is not usable // from other threads. Clocks clone & freeze copies of their original // timelines because the clocks will not respond to changes to those // timelines. return true; } ////// 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 */ DependencyObject parentObject, 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; DependencyObject targetObject = parentObject; 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; } // The TargetProperty trumps the TargetName property. DependencyObject localTargetObject = (DependencyObject) currentTimeline.GetValue(TargetProperty); if( localTargetObject != null ) { targetObject = localTargetObject; currentObjectName = null; } 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 ) { DependencyProperty targetProperty = null; AnimationClock animationClock = (AnimationClock)currentClock; if( targetObject == null ) { // Resolve the target object name. If no name specified, use the // containing object. if( currentObjectName != null ) { DependencyObject mentor = Helper.FindMentor(containingObject); targetObject = ResolveTargetName(currentObjectName, nameScope, mentor); } else { // The containing object must be either an FE or FCE. // (Not a Storyboard, as used for "shared clocks" mode.) targetObject = containingObject as FrameworkElement; if(targetObject == null) { targetObject = containingObject as FrameworkContentElement; } if( targetObject == null ) { // The containing object is not an FE or FCE. throw new InvalidOperationException(SR.Get(SRID.Storyboard_NoTarget, currentTimeline.GetType().ToString() )); } } } // 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, targetObject, 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, targetObject, 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, DependencyObject currentObject, string currentObjectName, MediaClock mediaClock ) { MediaElement targetMediaElement = null; if( currentObjectName != null ) { // Find the object named as the current target name. DependencyObject mentor = Helper.FindMentor(containingObject); targetMediaElement = ResolveTargetName(currentObjectName, nameScope, mentor ) as MediaElement; if( targetMediaElement == null ) { throw new InvalidOperationException(SR.Get(SRID.Storyboard_MediaElementNotFound, currentObjectName )); } } else if( currentObject != null ) { targetMediaElement = currentObject as MediaElement; } else { 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; ListclockList; 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. (jeffbog: 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; } // 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 in "shared clocks" mode. /// public void Begin() { DependencyObject containingObject = this; // Helper.FindMentor(this); INameScope nameScope = null; HandoffBehavior handoffBehavior = HandoffBehavior.SnapshotAndReplace; bool isControllable = true; Int64 layer = Storyboard.Layers.Code; BeginCommon(containingObject, nameScope, handoffBehavior, isControllable, layer); } ////// 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.CurrentMediaContext.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 */ 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 NullableGetCurrentGlobalSpeed( FrameworkElement containingObject ) { return GetCurrentGlobalSpeedImpl(containingObject); } /// /// 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 NullableGetCurrentGlobalSpeed( FrameworkContentElement containingObject ) { return GetCurrentGlobalSpeedImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current global speed. /// public Double GetCurrentGlobalSpeed() { NullablecurrentGlobalSpeed = GetCurrentGlobalSpeedImpl(this); if(currentGlobalSpeed.HasValue) { return currentGlobalSpeed.Value; } else { return default(Double); } } private Nullable GetCurrentGlobalSpeedImpl( DependencyObject 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 NullableGetCurrentIteration( FrameworkElement containingObject ) { return GetCurrentIterationImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current iteration. /// public NullableGetCurrentIteration( FrameworkContentElement containingObject ) { return GetCurrentIterationImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current iteration. /// public Int32 GetCurrentIteration() { NullablecurrentIteration = GetCurrentIterationImpl(this); if(currentIteration.HasValue) { return currentIteration.Value; } else { return default(Int32); } } private Nullable GetCurrentIterationImpl( DependencyObject 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 NullableGetCurrentProgress( FrameworkElement containingObject ) { return GetCurrentProgressImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current progress. /// public NullableGetCurrentProgress( FrameworkContentElement containingObject ) { return GetCurrentProgressImpl(containingObject); } /// /// Look on the clock store for a clock that was generated from this /// storyboard. If found, return the current progress. /// public Double GetCurrentProgress() { NullablecurrentProgress = GetCurrentProgressImpl(this); if(currentProgress.HasValue) { return currentProgress.Value; } else { return default(Double); } } private Nullable GetCurrentProgressImpl( DependencyObject 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 ) { return GetCurrentStateImpl(containingObject); } ////// 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 ) { return GetCurrentStateImpl(containingObject); } ////// Return the current state of this storyboard. This storyboard /// must have been created in "shared clocks" mode. /// public ClockState GetCurrentState() { return GetCurrentStateImpl(this); } private ClockState GetCurrentStateImpl( DependencyObject containingObject ) { Clock clock = GetStoryboardClock(containingObject); if (clock != null) { return clock.CurrentState; } return ClockState.Stopped; // Not default(ClockState)... } ////// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current time. /// public NullableGetCurrentTime( FrameworkElement containingObject ) { return GetCurrentTimeImpl(containingObject); } /// /// Given an object, look on the clock store for a clock that was /// generated from 'this' storyboard. If found, return the current time. /// public NullableGetCurrentTime( FrameworkContentElement containingObject ) { return GetCurrentTimeImpl(containingObject); } /// /// Return the current time of this storyboard. This storyboard /// must have been created in "shared clocks" mode. /// public TimeSpan GetCurrentTime() { NullablecurrentTime = GetCurrentTimeImpl(this); if(currentTime.HasValue) { return currentTime.Value; } else { return default(TimeSpan); } } private Nullable GetCurrentTimeImpl( DependencyObject 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 ) { return GetIsPausedImpl(containingObject); } ////// 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 ) { return GetIsPausedImpl(containingObject); } ////// 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() { return GetIsPausedImpl(this); } private bool GetIsPausedImpl( DependencyObject 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 ) { PauseImpl(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 ) { PauseImpl(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() { PauseImpl(this); } private void PauseImpl(DependencyObject containingObject) { Clock clock = GetStoryboardClock(containingObject, false, InteractiveOperation.Pause); if (clock != null) { clock.Controller.Pause(); } if( TraceAnimation.IsEnabled ) { TraceAnimation.TraceActivityItem( TraceAnimation.StoryboardPause, this, Name, this ); } } ////// If a clock was generated from 'this' storyboard for the given object, remove it. /// public void Remove(FrameworkElement containingObject) { RemoveImpl(containingObject); } ////// If a clock was generated from 'this' storyboard for the given object, remove it. /// public void Remove(FrameworkContentElement containingObject) { RemoveImpl(containingObject); } ////// If a clock was generated from this storyboard, remove it. /// public void Remove() { RemoveImpl(this); } private void RemoveImpl(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 ) { ResumeImpl(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 ) { ResumeImpl(containingObject); } ////// Look on the clock store for a clock that was generated from this /// storyboard. If found, call resume on the clock. /// public void Resume() { ResumeImpl(this); } private void ResumeImpl( DependencyObject 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 ) { SeekImpl(containingObject, 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 ) { SeekImpl(containingObject, offset, origin); } ////// 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( TimeSpan offset, TimeSeekOrigin origin ) { SeekImpl(this, offset, origin); } ////// 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( TimeSpan offset ) { SeekImpl(this, offset, TimeSeekOrigin.BeginTime); } private void SeekImpl( DependencyObject 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 ) { SeekAlignedToLastTickImpl(containingObject, 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 ) { SeekAlignedToLastTickImpl(containingObject, offset, origin); } ////// 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( TimeSpan offset, TimeSeekOrigin origin ) { SeekAlignedToLastTickImpl(this, offset, origin); } ////// 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( TimeSpan offset ) { SeekAlignedToLastTickImpl(this, offset, TimeSeekOrigin.BeginTime); } private void SeekAlignedToLastTickImpl( DependencyObject 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 ) { SetSpeedRatioImpl(containingObject, 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 ) { SetSpeedRatioImpl(containingObject, speedRatio); } ////// 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( double speedRatio ) { SetSpeedRatioImpl(this, speedRatio); } private void SetSpeedRatioImpl( DependencyObject 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 ) { SkipToFillImpl(containingObject); } ////// 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 ) { SkipToFillImpl(containingObject); } ////// 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() { SkipToFillImpl(this); } private void SkipToFillImpl( DependencyObject 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 ) { StopImpl(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 ) { StopImpl(containingObject); } ////// Look on the clock store for a clock that was generated from this /// storyboard. If found, call stop on the clock. /// public void Stop() { StopImpl(this); } private void StopImpl( DependencyObject 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 UncommonFieldStoryboardClockTreesField = 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 UncommonFieldComplexPathCloneField = 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
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- XPathNode.cs
- GregorianCalendarHelper.cs
- LogicalTreeHelper.cs
- ComboBox.cs
- StickyNoteAnnotations.cs
- EdmProperty.cs
- HMACMD5.cs
- FormsAuthenticationEventArgs.cs
- HttpCachePolicyElement.cs
- SafeCloseHandleCritical.cs
- TypeDescriptor.cs
- ProjectionPlanCompiler.cs
- AnonymousIdentificationModule.cs
- TimeSpanSecondsOrInfiniteConverter.cs
- WSTrust.cs
- XmlSchemaGroupRef.cs
- DiffuseMaterial.cs
- SafeLocalMemHandle.cs
- StorageMappingItemLoader.cs
- ExpanderAutomationPeer.cs
- CompModSwitches.cs
- ConnectionConsumerAttribute.cs
- DataGridPageChangedEventArgs.cs
- MemberCollection.cs
- SyndicationFeed.cs
- BuildProviderCollection.cs
- DoubleLinkListEnumerator.cs
- BitmapFrameEncode.cs
- MethodRental.cs
- QueueProcessor.cs
- Effect.cs
- GenericsInstances.cs
- DataSvcMapFileSerializer.cs
- CacheChildrenQuery.cs
- XslCompiledTransform.cs
- DbProviderServices.cs
- SetState.cs
- Stackframe.cs
- PictureBox.cs
- Oid.cs
- ProgressiveCrcCalculatingStream.cs
- FixedSOMSemanticBox.cs
- ValidationError.cs
- ProtocolsConfigurationHandler.cs
- ProtocolViolationException.cs
- PeerContact.cs
- Pkcs7Recipient.cs
- ToolStripItemCollection.cs
- BitmapMetadataBlob.cs
- ContentValidator.cs
- SafeCryptContextHandle.cs
- COM2TypeInfoProcessor.cs
- CodeParameterDeclarationExpression.cs
- ConnectionManager.cs
- TextTreeNode.cs
- FixedSOMElement.cs
- dsa.cs
- ResourcePool.cs
- StoreContentChangedEventArgs.cs
- XmlCharacterData.cs
- EdmItemCollection.cs
- TrackingProvider.cs
- OperationCanceledException.cs
- DrawingContextDrawingContextWalker.cs
- ReadOnlyDataSource.cs
- ExpressionVisitor.cs
- xmlsaver.cs
- RegexCapture.cs
- GlyphElement.cs
- DataKeyCollection.cs
- StrongNameKeyPair.cs
- DataGridViewDataErrorEventArgs.cs
- UserControl.cs
- StreamWithDictionary.cs
- MULTI_QI.cs
- ImpersonateTokenRef.cs
- AdapterUtil.cs
- MultipleViewProviderWrapper.cs
- Label.cs
- MulticastOption.cs
- TypeBuilder.cs
- SendMailErrorEventArgs.cs
- UnhandledExceptionEventArgs.cs
- HierarchicalDataBoundControl.cs
- ToolBarOverflowPanel.cs
- SharedStream.cs
- Graphics.cs
- Sentence.cs
- PropertyEmitterBase.cs
- MissingManifestResourceException.cs
- ProtocolsConfigurationHandler.cs
- _AcceptOverlappedAsyncResult.cs
- EntityRecordInfo.cs
- SafeNativeMemoryHandle.cs
- QueryStringParameter.cs
- SchemaImporterExtensionElement.cs
- Underline.cs
- ByteStreamGeometryContext.cs
- SqlDependencyListener.cs
- sqlmetadatafactory.cs