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
- Check State == UndoState.Normal before calling Undo; queue the request if an undo/redo is in progress.
- Avoid triggering undo/redo re-entrantly from inside IUndoUnit.Do() or from events raised during replay — defer via Dispatcher.BeginInvoke.
- Make unit implementations idempotent and event-safe so replay does not recursively invoke undo.
- 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
- Never call undo re-entrantly from IUndoUnit.Do() or replay-raised events
- Defer queued undo requests via the dispatcher until state returns to Normal
- Reset the manager if state appears stuck after an exception
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
- SR.UndoNoOpenParentUnit
- SR.UndoUnitOpen
- Current DocumentSequence, FixedDocument, or FixedPage not…
- IAmbientProvider
- InvalidOperationException()
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)