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

  1. Defer Add (or the edit causing it) until after the undo/redo operation completes, e.g. via Dispatcher.BeginInvoke.
  2. Check UndoManager.State (Undo/Redo) before performing edits and suppress undo-unit creation during playback.
  3. Use the undo manager's locks/pause API (if available) around batch programmatic edits instead of adding during playback.
  4. 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

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


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)