dotnet/wpf · error · InvalidOperationException

Storyboard_PropertyPathIncludesNonAnimatableProperty

Storyboard_PropertyPathIncludesNonAnimatableProperty

Error message

SR.Format(SR.Storyboard_PropertyPathIncludesNonAnimatableProperty, path.Path, intermediateDP.Name)

What it means

Every property along the animation property path must be animatable — AnimationStorage.IsPropertyAnimatable must accept it. VerifyPathIsAnimatable throws this InvalidOperationException when an intermediate or final DependencyProperty (named in the message) cannot host an AnimationClock (e.g. read-only or wrong value type).

Solutions

  1. Change the path property to an animatable one (e.g. Width instead of ActualWidth).
  2. Match the AnimationTimeline type to the DP's PropertyType (DoubleAnimation for double DPs, ColorAnimation for Color, etc.).
  3. For read-only DPs, animate the property that drives them instead.
  4. For custom DPs, ensure the type has a corresponding AnimationTimeline and the property is animatable.
  5. Wrap the value in an animatable DP and animate that.

Example fix

// before
new PropertyPath("(FrameworkElement.ActualWidth)"); // read-only, not animatable
// after
new PropertyPath("(FrameworkElement.Width)");
Defensive patterns

Strategy: validation

Validate before calling

bool CheckAnimatable(DependencyObject d, DependencyProperty p) =>
    AnimationStorage.IsPropertyAnimatable(d, p); // or reflect: !p.ReadOnly && p.GetMetadata(d.GetType()).TypeKey != null

Type guard

bool IsAnimatableProperty(DependencyObject d, DependencyProperty p) =>
    !p.ReadOnly && AnimationStorage.IsPropertyAnimatable(d, p);

Try / catch

try { storyboard.Begin(target); }
catch (InvalidOperationException ex) when (ex.Message.Contains("not animatable"))
{
    // substitute an animatable property for the one named in the message
}

Prevention

When it happens

Trigger: Storybard.TargetProperty references a DependencyProperty that is not animatable — read-only DPs, DPs whose type has no matching AnimationTimeline, or non-animatable metadata (CannotBeAnimated).

Common situations: Animating read-only properties like ActualWidth; animating a DP whose PropertyType has no corresponding animation type (e.g. animating a complex object DP with a DoubleAnimation); animating properties on custom DPs registered without animatable types.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/a5abfedb5dd76183. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/Storyboard.cs:760

                if( intermediateDO == null )
                {
                    Debug.Assert( i > 0, "The caller should not have set the PropertyPath context to a non DependencyObject." );
                    throw new InvalidOperationException(SR.Format(SR.Storyboard_PropertyPathMustPointToDependencyObject, AccessorName(path, i-1), path.Path));
                }

                if( intermediateDP == null )
                {
                    throw new InvalidOperationException(SR.Format(SR.Storyboard_PropertyPathMustPointToDependencyProperty, path.Path ));
                }

                if( checkingFrozenState && intermediateDO.IsSealed )
                {
                    throw new InvalidOperationException(SR.Format(SR.Storyboard_PropertyPathSealedCheckFailed, intermediateDP.Name, path.Path, intermediateDO));
                }

                if(!AnimationStorage.IsPropertyAnimatable(intermediateDO, intermediateDP) )
                {
                    throw new InvalidOperationException(SR.Format(SR.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 )

View on GitHub (pinned to 81131a70a4)