dotnet/wpf · error · InvalidOperationException

Specified Visual cannot be detached.

Error message

Specified Visual cannot be detached.

What it means

Renderer.DetachIncrementalRendering throws InvalidOperationException (SR.VisualCannotBeDetached) when the given Visual was never attached via AttachIncrementalRendering (or the attach list is null). Detaching an unattached visual is treated as a programming error.

Solutions

  1. Only call DetachIncrementalRendering for visuals previously attached to the same Renderer instance
  2. Track attached visuals yourself and skip detach when not present
  3. Ensure attach/detach calls are paired and ordered correctly in lifecycle code
  4. Catch InvalidOperationException during cleanup teardown to make it idempotent

Example fix

// before
renderer.DetachIncrementalRendering(visual); // throws if never attached
// after
if (attached.Remove(visual))
{
    renderer.DetachIncrementalRendering(visual);
}
Defensive patterns

Strategy: validation

Validate before calling

// Only detach visuals you attached
if (attachedVisuals.Contains(visual)) renderer.DetachIncrementalRendering(visual);

Type guard

bool CanDetach(Visual v) => v != null && attachedVisuals.Contains(v);

Try / catch

try { renderer.DetachIncrementalRendering(visual); }
catch (InvalidOperationException) { attachedVisuals.Remove(visual); /* idempotent cleanup */ }

Prevention

When it happens

Trigger: Calling DetachIncrementalRendering with a visual not in the Renderer's _attachedVisuals list — e.g. detach called twice, or a visual attached to a different Renderer instance.

Common situations: Double-detach in cleanup code, swapping renderers without re-attaching visuals, teardown ordering where attach never ran but detach did.

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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Ink/Renderer.cs:325

                parent.Children.Add(visual);

                // Put the visual into the list of visuals attached via this method
                _attachedVisuals.Add(visual);
            }
        }

        /// <summary>
        /// Detaches a visual previously attached via AttachIncrementalRendering
        /// </summary>
        /// <param name="visual">the visual to detach</param>
        internal void DetachIncrementalRendering(Visual visual)
        {
            ArgumentNullException.ThrowIfNull(visual);

            // Remove the visual in the list of attached via AttachIncrementalRendering
            if ((_attachedVisuals == null) || (!_attachedVisuals.Remove(visual)))
            {
                throw new System.InvalidOperationException(SR.VisualCannotBeDetached);
            }

            // Detach it from the tree
            DetachVisual(visual);
        }

        /// <summary>
        /// Internal helper used to indicate if a visual was previously attached 
        /// via a call to AttachIncrementalRendering
        /// </summary>
        internal bool ContainsAttachedIncrementalRenderingVisual(Visual visual)
        {
            if (visual == null || _attachedVisuals == null)
            {
                return false;
            }

            return _attachedVisuals.Contains(visual);

View on GitHub (pinned to 81131a70a4)