dotnet/wpf · error · InvalidOperationException

SR.Format(SR.Storyboard_ImmutableTargetNotSupported…

Error message

SR.Format(SR.Storyboard_ImmutableTargetNotSupported, path.Path)

What it means

Thrown by ProcessComplexPath when a complex path resolved to a frozen (immutable) Freezable target and the library's clone-in-place strategy failed. WPF animates frozen Freezables by substituting a mutable clone into the tree; if the target property's value is not the clone after processing, the app is halted rather than corrupting the frozen object.

Solutions

  1. Do not freeze the Freezable being animated: remove PresentationOptions:Freeze or call GetAsFrozen less aggressively so the animation can target it.
  2. Create an unfrozen copy of the resource specifically for animation and target that instance.
  3. Simplify the property path so it targets a mutable DependencyObject property rather than traversing into frozen sub-objects.
  4. If the freeze is mandatory, animate a different mechanism (e.g. bind to a computed value or use a CompositionTarget.Rendering loop).

Example fix

// before
<SolidColorBrush x:Key="animBrush" Color="Red" PresentationOptions:Freeze="True" />
// after
<SolidColorBrush x:Key="animBrush" Color="Red" /> <!-- leave unfrozen so it can be cloned/animated -->
Defensive patterns

Strategy: validation

Validate before calling

var val = targetObject.GetValue(targetProperty) as Freezable;
if (val != null && val.IsFrozen)
{
    var clone = val.CloneCurrentValue(); // animatable copy
}

Type guard

bool CanAnimate(object o) => o is Freezable f ? !f.IsFrozen : o is DependencyObject;

Try / catch

try { storyboard.Begin(target, true); }
catch (InvalidOperationException ex) when (ex.Message.Contains("immutable")) { /* use an unfrozen clone as the animation source */ }

Prevention

When it happens

Trigger: Storyboard with a complex PropertyPath that walks into a Freezable that is frozen (e.g. a Brush inside a frozen resource) and GetComplexPathValue cannot install a cloned, animatable instance at the expected property.

Common situations: Animating properties of shared resources defined with PresentationOptions:Freeze='True' or retrieved from a frozen cache; cross-thread shared brushes; deep property paths like 'Fill.(SolidColorBrush.Opacity)' on a frozen brush.

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/1bdf24b1b5179702. Report an issue: GitHub.

Appendix: source

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

            // 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.Format(SR.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 );

View on GitHub (pinned to 81131a70a4)