dotnet/wpf · error · InvalidOperationException
CannotChangeAfterSealed
CannotChangeAfterSealed
Error message
SR.Format(SR.CannotChangeAfterSealed, "SeekStoryboard")
What it means
SeekStoryboard.Offset's setter throws InvalidOperationException once the SeekStoryboard object is sealed (read-only). ControllableStoryboardAction objects become sealed when they are in use inside a Storyboard/trigger tree, freezing their configuration.
Solutions
- Create a new SeekStoryboard instance for each seek and set Offset before use.
- Set Offset immediately after construction, before adding the action to any trigger or storyboard.
- If sharing is needed, keep a prototype and clone it per use.
Example fix
// before
sharedSeek.Offset = TimeSpan.FromSeconds(3); // throws after first use
// after
var seek = new SeekStoryboard { Storyboard = targetStoryboard, Offset = TimeSpan.FromSeconds(3), Origin = TimeSeekOrigin.BeginTime }; Defensive patterns
Strategy: type-guard
Validate before calling
if (seek.IsSealed) seek = new SeekStoryboard { Storyboard = seek.Storyboard, Offset = newOffset, Origin = seek.Origin }; Type guard
static SeekStoryboard EnsureMutable(SeekStoryboard s, TimeSpan newOffset) => s.IsSealed ? new SeekStoryboard { Storyboard = s.Storyboard, Offset = newOffset, Origin = s.Origin } : s; Try / catch
try { seek.Offset = newOffset; } catch (InvalidOperationException) { seek = new SeekStoryboard { /* reconfigure */ }; } Prevention
- Treat storyboard action objects as immutable once used
- Create a fresh SeekStoryboard per seek operation
- Set all properties immediately after construction
When it happens
Trigger: Setting the Offset property on a SeekStoryboard instance that has already been sealed — typically after it has been applied to a running storyboard or shared/attached in a template.
Common situations: Reusing a single SeekStoryboard instance across multiple seek operations from code; mutating a template-defined SeekStoryboard at runtime.
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
- CannotChangeAfterSealed
- CannotChangeAfterSealed
- CannotChangeAfterSealed
- SR.Format(SR.IAnimatable_CantAnimateSealedDO, dp…
- SR.Format(SR.Storyboard_AnimationMismatch…
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/b533577ae9ed4b86.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/SeekStoryboard.cs:37
/// </summary>
public sealed class SeekStoryboard : ControllableStoryboardAction
{
/// <summary>
/// A time offset to use for this action. If it is never explicitly
/// specified, it will be zero.
/// </summary>
// [DefaultValue(TimeSpan.Zero)] - not usable because TimeSpan.Zero is not a constant expression.
public TimeSpan Offset
{
get
{
return _offset;
}
set
{
if (IsSealed)
{
throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "SeekStoryboard"));
}
// TimeSpan is a struct and can't be null - hence no ArgumentNullException check.
_offset = value;
}
}
/// <summary>
/// This method is used by TypeDescriptor to determine if this property should
/// be serialized.
/// </summary>
// Because we can't use [DefaultValue(TimeSpan.Zero)] - TimeSpan.Zero is not a constant expression.
[EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeOffset()
{
return !(TimeSpan.Zero.Equals(_offset));
}
View on GitHub (pinned to 81131a70a4)