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

  1. Count Push/Pop pairs and ensure every Pop has a matching Push on the same code path
  2. Wrap the Push/Pop pair in try/finally so Pop only runs when Push succeeded
  3. Verify custom DisposeCore/cleanup logic does not call Pop unconditionally
  4. 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

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


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)