dotnet/wpf · error · InvalidOperationException

SR.UndoNotInNormalState

Error message

SR.UndoNotInNormalState

What it means

UndoManager.Undo throws this when the manager's State is not UndoState.Normal — i.e. an Undo or Redo pass is already running (State == Undo/Redo). Undo operations cannot be nested or interleaved because the manager mutates a single state machine while replaying units.

Solutions

  1. Check State == UndoState.Normal before calling Undo; queue the request if an undo/redo is in progress.
  2. Avoid triggering undo/redo re-entrantly from inside IUndoUnit.Do() or from events raised during replay — defer via Dispatcher.BeginInvoke.
  3. Make unit implementations idempotent and event-safe so replay does not recursively invoke undo.
  4. If State is stuck after an exception, recreate or reset the undo manager instead of retrying.

Example fix

// before
undoManager.Undo(1); // throws if state != Normal
// after
if (undoManager.State == UndoState.Normal)
{
    undoManager.Undo(1);
}
else
{
    Dispatcher.BeginInvoke(() => undoManager.Undo(1)); // defer until replay finishes
}
Defensive patterns

Strategy: validation

Validate before calling

if (undoManager.State == UndoState.Normal) { undoManager.Undo(1); }

Type guard

static bool IsIdle(UndoManager m) => m.State == UndoState.Normal;

Try / catch

try { undoManager.Undo(1); }
catch (InvalidOperationException) { Dispatcher.BeginInvoke(() => undoManager.Undo(1)); }

Prevention

When it happens

Trigger: Calling Undo while a previous Undo/Redo call is still executing (State != UndoState.Normal), e.g. re-entrant calls triggered by unit.Do()/event handlers raised during undo replay, or invoking Undo from a message box/event handler fired inside an ongoing undo.

Common situations: Custom undo units whose Do() implementation raises property-change events that trigger another Undo; automation scripts issuing Undo commands faster than replay completes; a crash inside a unit leaving State stuck and subsequent calls failing.

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/6410e874f8bc40b1. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/UndoManager.cs:514

        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if count is out of range
        /// </exception>
        /// <exception cref="Exception">
        /// Thrown if there's an error performing the undo
        /// </exception>
        internal void Undo(int count)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, UndoCount);
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);

            if (State != UndoState.Normal)
            {
                throw new InvalidOperationException(SR.UndoNotInNormalState);
            }

            if (OpenedUnit != null)
            {
                throw new InvalidOperationException(SR.UndoUnitOpen);
            }

            Invariant.Assert(UndoCount > _minUndoStackCount);

            SetState(UndoState.Undo);

            bool exceptionThrown = true;

            try
            {
                while (count > 0)
                {
                    IUndoUnit unit;

View on GitHub (pinned to 81131a70a4)