{"record":{"id":"a2fed18d279ca00c","repo":"dotnet/wpf","slug":"sr-strokecollectionisreadonly","errorCode":null,"errorMessage":"SR.StrokeCollectionIsReadOnly","messagePattern":"SR\\.StrokeCollectionIsReadOnly","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Ink/StrokeCollection.cs","lineNumber":858,"sourceCode":"        /// </summary>\n        internal class ReadOnlyStrokeCollection : StrokeCollection, ICollection<Stroke>, IList\n        {\n            internal ReadOnlyStrokeCollection(StrokeCollection strokeCollection)\n            {\n                if ( strokeCollection != null )\n                {\n                    ( (List<Stroke>)this.Items ).AddRange(strokeCollection);\n                }\n            }\n\n            /// <summary>\n            /// Change is not allowed.  We would override SetItem, InsertItem etc but \n            /// they need to be sealed on StrokeCollection to prevent dupes from being added\n            /// </summary>\n            /// <param name=\"e\"></param>\n            protected override void OnStrokesChanged(StrokeCollectionChangedEventArgs e)\n            {\n                throw new NotSupportedException(SR.StrokeCollectionIsReadOnly);\n            }\n\n            /// <summary>\n            /// IsReadOnly\n            /// </summary>\n            bool IList.IsReadOnly\n            {\n                get { return true; }\n            }\n\n            /// <summary>\n            /// IsReadOnly\n            /// </summary>\n            bool ICollection<Stroke>.IsReadOnly\n            {\n                get { return true; }\n            }\n        }","sourceCodeStart":840,"sourceCodeEnd":876,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Ink/StrokeCollection.cs#L840-L876","documentation":"ReadOnlyStrokeCollection overrides OnStrokesChanged to throw NotSupportedException, because the collection is immutable. Any API that would add, remove, clip, or erase strokes on a read-only collection is rejected. Read-only collections are typically used for presentation of ink that cannot be modified.","triggerScenarios":"Calling StrokeCollection mutating members (Add, Remove, Insert, Clip, Erase, Clear, indexer set) on a StrokeCollection obtained from a read-only source such as ReadOnlyStrokeCollection returned by certain InkCanvas/Ink APIs; the mutation raises OnStrokesChanged which throws.","commonSituations":"Attempting ink.Erase(...) or strokes.Add(...) on a collection fetched from an InkCanvas in read-only mode, or after wrapping strokes in AsReadOnly; often a surprise because the method exists but the collection is immutable.","solutions":["Check strokes.IsReadOnly before mutating and take a writable copy first: new StrokeCollection(strokes)","Use the original, writable InkCanvas.Strokes collection instead of the read-only snapshot","Avoid calling mutating APIs like Clip/Erase on read-only collections; perform hit-testing only"],"exampleFix":"// before\nreadOnlyStrokes.Add(newStroke); // NotSupportedException\n// after\nvar writable = new StrokeCollection(readOnlyStrokes);\nwritable.Add(newStroke);","handlingStrategy":"validation","validationCode":"if (strokes.IsReadOnly) strokes = new StrokeCollection(strokes);","typeGuard":"bool IsWritable(StrokeCollection sc) => sc != null && !sc.IsReadOnly;","tryCatchPattern":null,"preventionTips":["Check IsReadOnly before any Add/Remove/Clip/Erase call","Copy read-only collections into a new StrokeCollection when mutation is needed","Keep references to the original writable InkCanvas.Strokes instead of snapshots"],"tags":["wpf","ink","read-only","not-supported"],"backgroundTag":"unsupported-operation","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}