dotnet/wpf · error · InvalidOperationException

SR.UndoManagerAlreadyAttached

Error message

SR.UndoManagerAlreadyAttached

What it means

UndoManager.AttachUndoManager throws InvalidOperationException(SR.UndoManagerAlreadyAttached) when the supplied UndoManager instance is already bound to another scope (its _scope is non-null). Each UndoManager can serve exactly one PropertyEditor/IScope at a time; attaching the same instance to a second scope would corrupt undo-unit bookkeeping, so WPF rejects it. Detach first or create a new UndoManager.

Solutions

  1. Call UndoManager.DetachUndoManager(oldScope) (or Detach on the manager) before attaching it to a new scope.
  2. Create a new UndoManager instance for each scope instead of sharing one.
  3. Check ((UndoManager)undoManager)._scope != null (or expose/track attachment state) before attaching and route accordingly.
  4. Ensure error paths that abort attach logic still detach the manager to avoid leaked scope bindings.

Example fix

// before
UndoManager.AttachUndoManager(newScope, sharedUndoManager); // throws if already attached
// after
var existingScope = ((UndoManager)sharedUndoManager).Scope;
if (existingScope != null)
{
    UndoManager.DetachUndoManager(existingScope);
}
UndoManager.AttachUndoManager(newScope, sharedUndoManager);
Defensive patterns

Strategy: validation

Validate before calling

static bool CanAttach(object undoManager) => undoManager is UndoManager um && um._scope == null; // or track attachment externally

Type guard

static bool IsAttachable(UndoManager um) => um != null && um.GetType().GetProperty("Scope", BindingFlags.NonPublic | BindingFlags.Instance) == null; // prefer wrapping with your own attached-state flag

Try / catch

try { UndoManager.AttachUndoManager(scope, um); }
catch (InvalidOperationException) { UndoManager.DetachUndoManager(previousScope); UndoManager.AttachUndoManager(scope, um); }

Prevention

When it happens

Trigger: Calling UndoManager.AttachUndoManager(scope, undoManager) where undoManager was previously attached to a different (or the same) scope and never detached — e.g. re-using one UndoManager across two TextBox scopes or re-attaching after a failed teardown.

Common situations: Re-using a cached UndoManager instance when recreating controls; attaching undo managers in a loop to multiple property scopes; framework code paths (e.g. editing services) that assume a fresh UndoManager but receive a recycled one.

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

Appendix: source

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

        #region Internal Methods

        /// <summary>
        /// Defines a given FrameworkElement as a scope for undo service.
        /// New instance of UndoManager created and attached to this element.
        /// </summary>
        /// <param name="scope">
        /// FrameworkElement to which new instance of UndoManager is attached.
        /// </param>
        /// <param name="undoManager">
        /// </param>
        internal static void AttachUndoManager(DependencyObject scope, UndoManager undoManager)
        {
            ArgumentNullException.ThrowIfNull(scope);
            ArgumentNullException.ThrowIfNull(undoManager);

            if (undoManager is not null && ((UndoManager)undoManager)._scope != null)
            {
                throw new InvalidOperationException(SR.UndoManagerAlreadyAttached);
            }

            // Detach existing instance of undo manager if any
            DetachUndoManager(scope);

            // Attach the service to the scope via private dependency property
            scope.SetValue(UndoManager.UndoManagerInstanceProperty, undoManager);
            if (undoManager is not null)
            {
                Debug.Assert(((UndoManager)undoManager)._scope == null);
                ((UndoManager)undoManager)._scope = scope;
            }

            undoManager.IsEnabled = true;
        }

        /// <summary>
        /// Detaches an undo service from the given FrameworkElement.

View on GitHub (pinned to 81131a70a4)