dotnet/wpf · error · InvalidOperationException
SR.UndoNoOpenParentUnit
Error message
SR.UndoNoOpenParentUnit
What it means
UndoManager.Add throws this when the unit being added has no open parent unit to attach to (DeepestOpenUnit is null and the unit itself is not a top-level unit that can be pushed directly). Undo units must be added inside an open parent unit context; otherwise the manager cannot place them on a stack and fails fast.
Solutions
- Ensure a parent unit is open before Add: call Open(parentUnit) (or use the Create*UndoUnit flow that manages opening) and Close it in a finally block.
- Audit Open/Close pairing — make Close run even on exceptions so the open-unit stack stays balanced.
- For top-level units, use the API path designed for them instead of Add on a closed context.
- Do not cache an UndoManager open-unit context across user operations; open a fresh unit per edit.
Example fix
// before
var unit = undoManager.CreateInsertUndoUnit(...);
undoManager.Add(unit); // throws: no open parent
// after
using (new UndoScope(undoManager)) // helper that calls Open/Close in Dispose
{
var unit = undoManager.CreateInsertUndoUnit(...);
undoManager.Add(unit);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (undoManager.IsEnabled) { /* ensure unit open scope */ } Try / catch
try { undoManager.Add(unit); }
catch (InvalidOperationException) { /* no open parent: open a scope and retry or log */ } Prevention
- Wrap unit recording in Open/Close scopes with finally
- Never cache open-unit contexts across operations
- Audit exception paths so Close is never skipped
When it happens
Trigger: Calling Add with an IUndoUnit when no parent unit is open: e.g. calling Create*UndoUnit/Add helpers outside an Open(unit) ... Close() scope, or after the parent unit was already closed, so DeepestOpenUnit returns null.
Common situations: Custom IUndoUnit implementations recorded after their parent unit was closed; unbalanced Open/Close (exception skipped Close, or Close called twice); adding a unit from a different thread or re-entrant call after the open unit completed.
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
- SR.UndoUnitOpen
- SR.Format(SR.BindingExpressionStatusChanged, _status…
- SR.Format(SR.CannotCallRunMultipleTimes…
- SR.ShutdownModeWhenAppShutdown
- SR.Storyboard_NeverApplied
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/d53adc0045c79457.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/UndoManager.cs:461
{
_bottomUndoIndex = 0;
}
}
UndoStack[_topUndoIndex] = unit;
}
}
else if (State == UndoState.Undo)
{
RedoStack.Push(unit);
}
else if (State == UndoState.Rollback)
{
// do nothing, throwing out the unit
}
}
else
{
throw new InvalidOperationException(SR.UndoNoOpenParentUnit);
}
}
/// <summary>
/// Clear the undo and redo stacks, as well as LastUnit.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if UndoManager is disabled
/// </exception>
internal void Clear()
{
if (!IsEnabled)
{
throw new InvalidOperationException(SR.UndoServiceDisabled);
}
// In practice, we only clear when the public IsUndoEnabled property is set false.
// We'll check that property again when _imeSupportModeEnabled transitions to false.View on GitHub (pinned to 81131a70a4)