{"record":{"id":"6042c8a1dd969d05","repo":"dotnet/wpf","slug":"sr-textboxbase-unmatchedendchange","errorCode":null,"errorMessage":"SR.TextBoxBase_UnmatchedEndChange","messagePattern":"SR\\.TextBoxBase_UnmatchedEndChange","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/TextBoxBase.cs","lineNumber":396,"sourceCode":"        //\n        //.........................................................\n\n        /// <summary>\n        /// Begins a change block.\n        /// </summary>\n        public void BeginChange()\n        {\n            this.TextEditor.Selection.BeginChange();\n        }\n\n        /// <summary>\n        /// Ends a change block.\n        /// </summary>\n        public void EndChange()\n        {\n            if (this.TextEditor.Selection.ChangeBlockLevel == 0)\n            {\n                throw new InvalidOperationException(SR.TextBoxBase_UnmatchedEndChange);\n            }\n\n            this.TextEditor.Selection.EndChange();\n        }\n\n        /// <summary>\n        /// Creates and returns a change block\n        /// </summary>\n        /// <returns>IDisposable</returns>\n        public IDisposable DeclareChangeBlock()\n        {\n            return this.TextEditor.Selection.DeclareChangeBlock();\n        }\n\n        #endregion Public Methods\n\n        //------------------------------------------------------\n        //","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/TextBoxBase.cs#L378-L414","documentation":"TextBoxBase.EndChange() closes a change block opened with BeginChange(). The change-block level in the underlying TextEditor.Selection must be greater than zero; if it is zero, there is no matching BeginChange(), so an InvalidOperationException is thrown. Change blocks group a series of text edits into a single undo unit.","triggerScenarios":"Calling textBoxBase.EndChange() without a preceding, still-open BeginChange() on the same control; calling EndChange() twice for one BeginChange(); calling EndChange() after the change block was already closed by an earlier EndChange() or by undo logic resetting the level.","commonSituations":"Programmatic text manipulation code that brackets edits with BeginChange/EndChange but has unbalanced early-return or exception paths; helper methods that call EndChange defensively without tracking whether they opened the block.","solutions":["Ensure every EndChange() call is paired with exactly one prior BeginChange() on the same TextBoxBase instance.","Use a try/finally around BeginChange...EndChange so early exceptions cannot leave unbalanced state, or better, track the level yourself before calling EndChange.","Refactor to use TextEditor/TextSelection edit APIs (e.g. TextEditor.BeginChange scope) that manage block lifetime automatically."],"exampleFix":"// before\ntextBox.BeginChange();\nif (invalid) return; // EndChange never called -> later EndChange throws\ntextBox.EndChange();\n// after\ntextBox.BeginChange();\ntry\n{\n    if (invalid) throw new InvalidOperationException();\n}\nfinally\n{\n    textBox.EndChange();\n}","handlingStrategy":"try-catch","validationCode":"bool canEndChange = textBox.GetType().GetProperty(\"TextSelectionInternal\", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) == null; // track your own level instead:\nint changeBlockLevel = 0; // increment on BeginChange, decrement on EndChange; only call EndChange when changeBlockLevel > 0","typeGuard":"static bool CanEndChange(int localChangeBlockLevel) => localChangeBlockLevel > 0;","tryCatchPattern":"try\n{\n    textBox.EndChange();\n}\ncatch (InvalidOperationException)\n{\n    // no open change block; log and continue\n}","preventionTips":["Wrap BeginChange/EndChange in try/finally","Track change-block nesting in an instance counter","Never expose EndChange on public helper APIs without ownership of the BeginChange"],"tags":["wpf","textbox","undo","invalid-operation"],"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-22T01:17:13.364Z"}