dotnet/wpf · error · ArgumentException

Duplicate Stroke in StrokeCollectionChangedEventArgs.Added.

Error message

Duplicate Stroke in StrokeCollectionChangedEventArgs.Added.

What it means

Renderer.OnStrokesChanged throws ArgumentException (SR.DuplicateStrokeAdded) when a StrokeCollectionChangedEventArgs reports a stroke in its Added collection that the Renderer already tracks in its stroke-to-visual map. Each stroke must map to exactly one StrokeVisual.

Solutions

  1. Never add the same Stroke instance twice to a StrokeCollection
  2. Deep-copy or clone strokes when moving between collections
  3. Remove the stroke from the old collection before adding to a new one
  4. Check StrokeCollection.Contains before Add in custom code

Example fix

// before
anotherCollection.Add(stroke); // stroke still in firstCollection
// after
firstCollection.Remove(stroke);
anotherCollection.Add(new Stroke(stroke.StylusPoints.Clone()));
Defensive patterns

Strategy: validation

Validate before calling

// Avoid duplicate adds
if (!collection.Contains(stroke)) collection.Add(stroke);

Type guard

bool CanAddSafely(StrokeCollection c, Stroke s) => s != null && !c.Contains(s);

Try / catch

try { collection.Add(stroke); }
catch (ArgumentException) { /* duplicate - ignore or log */ }

Prevention

When it happens

Trigger: Adding the same Stroke instance twice to a StrokeCollection, or sharing one Stroke instance across multiple StrokeCollections wired to the same Renderer/InkCanvas.

Common situations: Cloning strokes by reference instead of deep copy, moving strokes between collections by Add without Remove, event handler re-entrancy replaying an add notification.

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

Appendix: source

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

        /// <summary>
        /// StrokeCollectionChanged event handler
        /// </summary>
        private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs eventArgs)
        {
            System.Diagnostics.Debug.Assert(sender == _strokes);

            // Read the args
            StrokeCollection added = eventArgs.Added;
            StrokeCollection removed = eventArgs.Removed;

            // Add new strokes
            foreach (Stroke stroke in added)
            {
                // Verify that it's not a dupe
                if (_visuals.ContainsKey(stroke))
                {
                    throw new System.ArgumentException(SR.DuplicateStrokeAdded);
                }

                // Create a visual for the new stroke and add it to the dictionary
                StrokeVisual visual = new StrokeVisual(stroke, this);
                _visuals.Add(stroke, visual);

                // Start listening on the stroke events
                StartListeningOnStrokeEvents(visual.Stroke);

                // Attach it to the visual tree
                AttachVisual(visual, buildingStrokeCollection: false);
            }

            // Deal with removed strokes first
            foreach (Stroke stroke in removed)
            {
                // Verify that the event is in sync with the view
                StrokeVisual visual = null;

View on GitHub (pinned to 81131a70a4)