dotnet/wpf · error · InvalidOperationException
SR.DrawingContext_TooManyPops
Error message
SR.DrawingContext_TooManyPops
What it means
DrawingContext.Pop() was called more times than Push(). Pop restores the drawing group saved by the last Push; when _previousDrawingGroupStack is null or empty there is nothing to restore, so DrawingDrawingContext throws InvalidOperationException with SR.DrawingContext_TooManyPops. Every Push must be balanced by exactly one Pop.
Solutions
- Count Push/Pop pairs and ensure every Pop has a matching Push on the same code path
- Wrap the Push/Pop pair in try/finally so Pop only runs when Push succeeded
- Verify custom DisposeCore/cleanup logic does not call Pop unconditionally
- Prefer Push-guid APIs (e.g. PushOpacity, PushTransform) whose Disposer pattern auto-balances the pop
Example fix
// before
if (needClip)
ctx.PushClip(clipGeometry);
code(ctx);
ctx.Pop(); // throws when needClip == false
// after
bool pushed = false;
if (needClip)
{
ctx.PushClip(clipGeometry);
pushed = true;
}
try { code(ctx); }
finally { if (pushed) ctx.Pop(); } Defensive patterns
Strategy: validation
Validate before calling
int pushDepth = 0;
void SafePush(DrawingContext ctx, Action<DrawingContext> draw)
{
ctx.PushTransform(Transform.Identity);
pushDepth++;
try { draw(ctx); }
finally { ctx.Pop(); pushDepth--; }
}
// assert pushDepth == 0 before calling Pop manually Prevention
- Use try/finally around every Push/Pop pair
- Track push depth in a counter and assert balance before manual Pop
- Prefer built-in PushXxx APIs with the disposable balancer pattern
- Never call Pop in DisposeCore unless your own Push succeeded
When it happens
Trigger: Calling ctx.Pop() when no matching ctx.Push(...) was issued, or calling Pop twice for one Push, typically inside DrawingVisual/DrawingGroup render code that builds a DrawingContext manually.
Common situations: Conditional Push followed by unconditional Pop (Push skipped inside an if but Pop always runs); exception thrown between Push and Pop skipping one branch; copying sample code where Push count was miscounted; DisposeCore calling Pop on behalf of an unbalanced context.
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
- Animation_Invalid_DefaultValue
- Cannot remove signature from read-only file.
- Image_EncoderNoColorContext
- Image_EncoderNoGlobalMetadata
- Image_EncoderNoGlobalThumbnail
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/07d08bc719dec41a.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/DrawingDrawingContext.cs:1052
_currentDrawingGroup.BitmapEffectInput = effectInput ?? new BitmapEffectInput();
}
/// <summary>
/// Pop
/// </summary>
public override void Pop()
{
#if DEBUG
MediaTrace.DrawingContextOp.Trace("Pop");
#endif
VerifyApiNonstructuralChange();
// Verify that Pop hasn't been called too many times
if ( (_previousDrawingGroupStack == null) ||
(_previousDrawingGroupStack.Count == 0))
{
throw new InvalidOperationException(SR.DrawingContext_TooManyPops);
}
// Restore the previous value of the current drawing group
_currentDrawingGroup = _previousDrawingGroupStack.Pop();
}
/// <summary>
/// Draw a GlyphRun.
/// </summary>
/// <param name="foregroundBrush">Foreground brush to draw GlyphRun with. </param>
/// <param name="glyphRun"> The GlyphRun to draw. </param>
/// <exception cref="ObjectDisposedException">
/// This call is illegal if this object has already been closed or disposed.
/// </exception>
public override void DrawGlyphRun(Brush foregroundBrush, GlyphRun glyphRun)
{
#if DEBUG
MediaTrace.DrawingContextOp.Trace("DrawGlyphRun(constant)");View on GitHub (pinned to 81131a70a4)