dotnet/wpf · error · InvalidOperationException
SR.UndoUnitLocked
Error message
SR.UndoUnitLocked
What it means
ParentUndoUnit has a Locked state (set during undo/redo playback) that forbids structural changes. Calling Add while Locked throws InvalidOperationException(SR.UndoUnitLocked), because adding units during undo/redo would corrupt the stack being replayed.
Solutions
- Defer Add (or the edit causing it) until after the undo/redo operation completes, e.g. via Dispatcher.BeginInvoke.
- Check UndoManager.State (Undo/Redo) before performing edits and suppress undo-unit creation during playback.
- Use the undo manager's locks/pause API (if available) around batch programmatic edits instead of adding during playback.
- Avoid raising text changes synchronously from UndoUnit.Undo/Redo implementations.
Example fix
// before
void OnDocumentChanged(...) => parentUnit.Add(newUnit); // may run during undo playback
// after
void OnDocumentChanged(...)
{
if (undoManager.State != UndoState.Undo && undoManager.State != UndoState.Redo)
parentUnit.Add(newUnit);
} Defensive patterns
Strategy: validation
Validate before calling
bool safeToAdd = !parentUnit.Locked
&& undoManager.State != UndoState.Undo
&& undoManager.State != UndoState.Redo; Type guard
bool IsUndoPlaybackActive(UndoManager m) => m.State is UndoState.Undo or UndoState.Redo;
Try / catch
try { parentUnit.Add(unit); }
catch (InvalidOperationException ex) when (ex.Message.Contains("locked"))
{
// during undo/redo playback: defer via Dispatcher.BeginInvoke
} Prevention
- Check UndoManager.State before generating undo units
- Defer programmatic edits made during Undo/Redo with Dispatcher.BeginInvoke
- Do not mutate documents synchronously inside UndoUnit.Undo/Redo
When it happens
Trigger: Calling Add on a ParentUndoUnit (or triggering edits that open/close units) while an Undo or Redo operation is in progress and the unit is locked.
Common situations: Programmatic edits applied inside Undo/Redo handlers that generate new undo units; event handlers reacting to undo playback that mutate the document; re-entrant user input during replay.
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
- Processing is disabled while the Dispatcher is in this…
- SR.ArrangeReentrancyInvalid
- SR.ArrangeReentrancyInvalid
- SR.Automation_RecursivePublicCall
- SR.Automation_RecursivePublicCall
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/ff81a5180174dc71.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/ParentUndoUnit.cs:230
ArgumentNullException.ThrowIfNull(unit);
parentUndoUnit = DeepestOpenUnit;
// If we have an open unit, call Add on it
if (parentUndoUnit != null)
{
parentUndoUnit.Add(unit);
return;
}
if (IsInParentUnitChain(unit))
{
throw new InvalidOperationException(SR.UndoUnitCantBeAddedTwice);
}
if (Locked)
{
throw new InvalidOperationException(SR.UndoUnitLocked);
}
if (!Merge(unit))
{
_units.Push(unit);
if (LastUnit is IParentUndoUnit)
{
((IParentUndoUnit)LastUnit).OnNextAdd();
}
SetLastUnit(unit);
}
}
/// <summary>
/// Clear all undo units.
/// </summary>
/// <exception cref="InvalidOperationException">View on GitHub (pinned to 81131a70a4)