{"record":{"id":"ff81a5180174dc71","repo":"dotnet/wpf","slug":"sr-undounitlocked","errorCode":null,"errorMessage":"SR.UndoUnitLocked","messagePattern":"SR\\.UndoUnitLocked","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/ParentUndoUnit.cs","lineNumber":230,"sourceCode":"            ArgumentNullException.ThrowIfNull(unit);\n\n            parentUndoUnit = DeepestOpenUnit;\n\n            // If we have an open unit, call Add on it\n            if (parentUndoUnit != null)\n            {\n                parentUndoUnit.Add(unit);\n                return;\n            }\n\n            if (IsInParentUnitChain(unit))\n            {\n                throw new InvalidOperationException(SR.UndoUnitCantBeAddedTwice);\n            }\n\n            if (Locked)\n            {\n                throw new InvalidOperationException(SR.UndoUnitLocked);\n            }\n\n            if (!Merge(unit))\n            {\n                _units.Push(unit);\n                if (LastUnit is IParentUndoUnit)\n                {\n                    ((IParentUndoUnit)LastUnit).OnNextAdd();\n                }\n\n                SetLastUnit(unit);\n            }\n        }\n\n        /// <summary>\n        /// Clear all undo units.\n        /// </summary>\n        /// <exception cref=\"InvalidOperationException\">","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/ParentUndoUnit.cs#L212-L248","documentation":"ParentUndoUnit has a Locked state (set during undo/redo playback) that forbids structural changes. Calling Add while Locked throws InvalidOperationException(SR.UndoUnitLocked), because adding units during undo/redo would corrupt the stack being replayed.","triggerScenarios":"Calling Add on a ParentUndoUnit (or triggering edits that open/close units) while an Undo or Redo operation is in progress and the unit is locked.","commonSituations":"Programmatic edits applied inside Undo/Redo handlers that generate new undo units; event handlers reacting to undo playback that mutate the document; re-entrant user input during replay.","solutions":["Defer Add (or the edit causing it) until after the undo/redo operation completes, e.g. via Dispatcher.BeginInvoke.","Check UndoManager.State (Undo/Redo) before performing edits and suppress undo-unit creation during playback.","Use the undo manager's locks/pause API (if available) around batch programmatic edits instead of adding during playback.","Avoid raising text changes synchronously from UndoUnit.Undo/Redo implementations."],"exampleFix":"// before\nvoid OnDocumentChanged(...) => parentUnit.Add(newUnit); // may run during undo playback\n// after\nvoid OnDocumentChanged(...)\n{\n    if (undoManager.State != UndoState.Undo && undoManager.State != UndoState.Redo)\n        parentUnit.Add(newUnit);\n}","handlingStrategy":"validation","validationCode":"bool safeToAdd = !parentUnit.Locked\n    && undoManager.State != UndoState.Undo\n    && undoManager.State != UndoState.Redo;","typeGuard":"bool IsUndoPlaybackActive(UndoManager m) => m.State is UndoState.Undo or UndoState.Redo;","tryCatchPattern":"try { parentUnit.Add(unit); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"locked\"))\n{\n    // during undo/redo playback: defer via Dispatcher.BeginInvoke\n}","preventionTips":["Check UndoManager.State before generating undo units","Defer programmatic edits made during Undo/Redo with Dispatcher.BeginInvoke","Do not mutate documents synchronously inside UndoUnit.Undo/Redo"],"tags":["wpf","undo-unit","locked","reentrancy"],"backgroundTag":"invalid-state-transition","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"}