dotnet/wpf · error · InvalidOperationException

SR.TextRangeEdit_InvalidStructuralPropertyApply (property…

Error message

SR.TextRangeEdit_InvalidStructuralPropertyApply (property, nonMergeableAncestor)

What it means

TextRangeEdit throws InvalidOperationException when applying a structural (mergeable) formatting property would require splitting through a non-mergeable ancestor element. During ApplyPropertyToTextVirtual the edit engine walks up to find a common ancestor; if property != null and the parent chain diverges through an element that cannot be merged (nonMergeableAncestor), the structural apply is invalid. This is an internal invariant violation surfaced to public ApplyPropertyValue paths.

Solutions

  1. Apply the property per logical block (iterate Blocks and set on each Paragraph) instead of over the whole range
  2. Constrain the selection so it does not cross non-mergeable structural boundaries (table cells, sections)
  3. Use direct property assignment on the TextElement rather than range ApplyPropertyValue for structural properties

Example fix

// before
selection.ApplyPropertyValue(Block.TextAlignmentProperty, TextAlignment.Center);
// after
foreach (Paragraph p in GetSelectedParagraphs(selection))
    p.TextAlignment = TextAlignment.Center;
Defensive patterns

Strategy: try-catch

Validate before calling

bool crossesCells = /* determine via selection start/end ancestor checks */ false;
if (crossesCells) ApplyPerBlockInstead();

Try / catch

try { selection.ApplyPropertyValue(prop, value); } catch (InvalidOperationException ex) { /* structural apply across non-mergeable ancestors — apply per block */ }

Prevention

When it happens

Trigger: Applying a paragraph-level structural property (e.g. FlowDirection, TextAlignment) to a selection spanning multiple block elements whose ancestors cannot be merged — e.g. selection crossing TableCell boundaries or nested lists with differing structure.

Common situations: Selections spanning table cells, list items, or floated figures; programmatic formatting of whole-document content with mixed structure; applying paragraph properties via TextSelection.ApplyPropertyValue over heterogeneous content.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/02d33215402bfe88. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextRangeEdit.cs:2286

                    nonMergeableAncestor = parent;
                    commonAncestor = parent;
                    break;
                }
            }

            // Try to reach the start non-mergeable or original commonAncestor from end.
            for (parent = (Inline)end.Parent; parent != commonAncestor; parent = (Inline)parent.Parent)
            {
                if (!TextSchema.IsMergeableInline(parent.GetType()))
                {
                    nonMergeableAncestor = parent;
                    break;
                }
            }

            if (property != null && parent != commonAncestor)
            {
                throw new InvalidOperationException(SR.Format(SR.TextRangeEdit_InvalidStructuralPropertyApply, property, nonMergeableAncestor));
            }

            return (parent == commonAncestor);
        }

        #endregion Private Methods

        #region Private Types
        /// <summary>
        /// This class imposes value ranges, considered valid by editing code, for Dependency properties of type double.
        /// In other words this class defines value range policies for DPs of type double, in editing context.
        /// </summary>
        internal static class DoublePropertyBounds
        {
            /// <summary>
            /// Validates the value and if it's in permitable range then the <paramref name="value"/> is returned.
            /// Oterwise closest bound(lower/upper) of the range is returned.
            /// </summary>

View on GitHub (pinned to 81131a70a4)