/// <summary> /// Function that checks to see if a given PropertyPath (already given /// the context object) can be used. /// </summary> // 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)); } } } }