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
- Only call DetachIncrementalRendering for visuals previously attached to the same Renderer instance
- Track attached visuals yourself and skip detach when not present
- Ensure attach/detach calls are paired and ordered correctly in lifecycle code
- 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
- Make teardown idempotent by tracking attach state
- Never call detach without a prior successful attach on the same renderer
- Keep attach/detach in symmetric lifecycle methods
- Log skipped detaches during cleanup instead of throwing
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
- Cannot attach a Visual that is already attached.
- ArgumentOutOfRangeException(nameof(oldProperty))
- ArgumentOutOfRangeException(nameof(percentageWithinBounds))
- ArgumentOutOfRangeException(nameof(percentageWithinLasso))
- ArgumentOutOfRangeException(percentageWithinBounds)
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)