dotnet/wpf · error · InvalidOperationException

SR.IAnimatable_CantAnimateSealedDO

Error message

SR.IAnimatable_CantAnimateSealedDO

What it means

Once a DependencyObject is sealed (immutable, e.g. a frozen Freezable or a sealed template/resource instance), animations can no longer be applied to it. Visual3D.ApplyAnimationClock checks IsSealed and throws InvalidOperationException with IAnimatable_CantAnimateSealedDO. Sealing is a WPF mechanism to make objects thread-safe and immutable; mutation via animation is disallowed.

Solutions

  1. Call Clone() (or CloneCurrentValue()) on the frozen/sealed object and apply the animation to the clone.
  2. Declare the target as a mutable instance (x:Shared resources or code-created objects) instead of a frozen shared resource.
  3. Delay any Freeze() call until animations no longer need to be applied to the object.

Example fix

// before
var transform = (RotateTransform3D)FindResource("spinTransform");
transform.ApplyAnimationClock(...); // sealed/frozen
// after
var transform = ((RotateTransform3D)FindResource("spinTransform")).Clone();
transform.ApplyAnimationClock(...);
Defensive patterns

Strategy: validation

Validate before calling

if (target.IsSealed)
{
    target = (Visual3D)((Freezable)target).GetAsFrozen is null ? target : CloneForAnimation(target);
}
target.ApplyAnimationClock(dp, clock, behavior);

Type guard

static bool CanAnimate(Visual3D v) => !v.IsSealed;

Try / catch

try { visual.ApplyAnimationClock(dp, clock, behavior); }
catch (InvalidOperationException ex) when (ex.Message.Contains("sealed")) { var clone = CloneForAnimation(visual); clone.ApplyAnimationClock(dp, clock, behavior); }

Prevention

When it happens

Trigger: Calling ApplyAnimationClock on a Visual3D (or its transforms/materials) that has been sealed — commonly after Freeze() was called on an associated Freezable, or the object came from a sealed resource/template instance.

Common situations: Animating a frozen Transform3D shared via a StaticResource; objects declared in a ResourceDictionary that WPF sealed; performance-driven Freeze() calls made before animations are attached.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media3D/Generated/Visual3D.cs:86

            if (!AnimationStorage.IsPropertyAnimatable(this, dp))
            {
                throw new ArgumentException(SR.Format(SR.Animation_DependencyPropertyIsNotAnimatable, dp.Name, this.GetType()), nameof(dp));
            }

            if (clock != null
                && !AnimationStorage.IsAnimationValid(dp, clock.Timeline))
            {
                throw new ArgumentException(SR.Format(SR.Animation_AnimationTimelineTypeMismatch, clock.Timeline.GetType(), dp.Name, dp.PropertyType), nameof(clock));
            }

            if (!HandoffBehaviorEnum.IsDefined(handoffBehavior))
            {
                throw new ArgumentException(SR.Animation_UnrecognizedHandoffBehavior);
            }

            if (IsSealed)
            {
                throw new InvalidOperationException(SR.Format(SR.IAnimatable_CantAnimateSealedDO, dp, this.GetType()));
            }

            AnimationStorage.ApplyAnimationClock(this, dp, clock, handoffBehavior);
        }

        /// <summary>
        /// Starts an animation for a DependencyProperty. The animation will
        /// begin when the next frame is rendered.
        /// </summary>
        /// <param name="dp">
        /// The DependencyProperty to animate.
        /// </param>
        /// <param name="animation">
        /// <para>The AnimationTimeline to used to animate the property.</para>
        /// <para>If the AnimationTimeline's BeginTime is null, any current animations
        /// will be removed and the current value of the property will be held.</para>
        /// <para>If this value is null, all animations will be removed from the property
        /// and the property value will revert back to its base value.</para>

View on GitHub (pinned to 81131a70a4)