dotnet/wpf · error · InvalidOperationException

SR.TextPositionIsFrozen

Error message

SR.TextPositionIsFrozen

What it means

TextPointer.VerifyNotFrozen is called before mutating operations on a TextPointer. TextPointers can be frozen (e.g. those returned as static positions like Document.ContentStart in some contexts, or via FrozenTextPointer implementations) and frozen pointers are immutable. Any mutation attempt on a frozen pointer throws InvalidOperationException(SR.TextPositionIsFrozen).

Solutions

  1. Clone the pointer into a mutable one before mutating (e.g. richTextBox.CaretPosition or use the pointer's CreatePointer/GetInsertionPosition semantics).
  2. Check TextPointer.IsFrozen before mutating and obtain an unfrozen copy if needed.
  3. Only use live pointers from Selection/CaretPosition for edits; treat frozen/snapshot pointers as read-only.

Example fix

// before
frozenPointer.MoveToPosition(target, LogicalDirection.Forward);
// after
var mutable = richTextBox.CaretPosition;
mutable.MoveToPosition(target, LogicalDirection.Forward);
Defensive patterns

Strategy: type-guard

Validate before calling

if (pointer.IsFrozen) pointer = richTextBox.CaretPosition; // obtain a mutable pointer

Type guard

static bool IsMutable(TextPointer p) => !p.IsFrozen;

Try / catch

try { pointer.MoveToPosition(target, dir); }
catch (InvalidOperationException) { /* pointer frozen: obtain a live pointer and retry */ }

Prevention

When it happens

Trigger: Calling mutation methods (InsertTextInRun, MoveToPosition, MoveToNextContextPosition, InsertUIElement, etc.) on a frozen TextPointer instance — commonly the caret/selection pointers returned from frozen positions or pointer instances marked frozen by the editor.

Common situations: Storing a frozen pointer from an editor snapshot and later trying to move it; freezing via TextEditor snapshot APIs then mutating; confusing frozen positions with live selection positions (Selection.Start/CaretPosition).

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/11fa68c8620f9b69. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextPointer.cs:3951

            SetNodeAndEdge(node.IncrementReferenceCount(edge), edge);
            _generation = generation;

            this.CaretUnitBoundaryCache = caretUnitBoundaryCache;
            this.IsCaretUnitBoundaryCacheValid = isCaretUnitBoundaryCacheValid;
            _layoutGeneration = layoutGeneration;

            VerifyFlags();
            tree.AssertTree();
            AssertState();
        }

        // Throws an exception if this TextPointer is frozen.
        private void VerifyNotFrozen()
        {
            if (this.IsFrozen)
            {
                throw new InvalidOperationException(SR.TextPositionIsFrozen);
            }
        }

        // Inc/decs the position ref counts on TextTreeTextNodes as the navigator
        // is repositioned.
        // If the new ref is to a TextTreeTextNode, the node may be split.
        // Returns the actual node referenced, which will always be newNode,
        // unless newNode is a TextTreeTextNode that gets split.  The caller
        // should use the returned node to position navigators.
        private TextTreeNode AdjustRefCounts(TextTreeNode newNode, ElementEdge newNodeEdge, TextTreeNode oldNode, ElementEdge oldNodeEdge)
        {
            TextTreeNode node;

            // This test should walk the tree upwards to catch all errors...probably not worth the slowdown though.
            Invariant.Assert(oldNode.ParentNode == null || oldNode.IsChildOfNode(oldNode.ParentNode), "Trying to add ref a dead node!");
            Invariant.Assert(newNode.ParentNode == null || newNode.IsChildOfNode(newNode.ParentNode), "Trying to add ref a dead node!");

            node = newNode;

View on GitHub (pinned to 81131a70a4)