dotnet/wpf · error · COMException

0x80040209

0x80040209

Error message

SR.TextStore_TS_E_READONLY

What it means

TextStore.SetText (ITextStoreACP::SetText implementation) throws this COMException with HRESULT TS_E_READONLY (0x80040209) when the underlying text container is read-only. TSF cannot modify text in a read-only store, so the modification request is rejected with the standard TSF read-only error code.

Solutions

  1. Make the target control editable (set IsReadOnly=false) before text services attempt composition.
  2. In the text service, check the TS_LF_READ-only status / document flags before calling SetText and fall back to not modifying text.
  3. Catch COMException with HRESULT TS_E_READONLY and treat the edit as rejected rather than crashing.

Example fix

// before
store.SetText(flags, start, end, chars, chars.Length, out change);
// after
if (!store.IsReadOnly)
    store.SetText(flags, start, end, chars, chars.Length, out change);
Defensive patterns

Strategy: validation

Validate before calling

if (!control.IsReadOnly) { store.SetText(flags, start, end, text, text.Length, out change); }

Try / catch

try { store.SetText(...); } catch (COMException ex) when (ex.ErrorCode == unchecked((int)0x80040209)) { /* treat edit as rejected */ }

Prevention

When it happens

Trigger: An IME/text service calls SetText while the WPF control (e.g. read-only TextBox/RichTextBox) reports IsReadOnly == true.

Common situations: User focuses an IME composition into a read-only text box; automation or test harness writing text into a read-only document; app sets TextBox.IsReadOnly while composition is active.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextStore.cs:474

            }

            nextIndex = navigator.CharOffset;

            if (IsTracing)
            {
                IMECompositionTracer.Trace(this, IMECompositionTraceOp.GetText,
                                            "text:", startIndex, endIndex, cchReq, charsCopied, new String(text, 0, charsCopied),
                                            "runs:", cRunInfoReq, cRunInfoRcv,
                                            "next:", nextIndex);
            }
        }

        // See msdn's ITextStoreACP documentation for a full description.
        public void SetText(UnsafeNativeMethods.SetTextFlags flags, int startIndex, int endIndex, char[] text, int cch, out UnsafeNativeMethods.TS_TEXTCHANGE change)
        {
            if (this.IsReadOnly)
            {
                throw new COMException(SR.TextStore_TS_E_READONLY, UnsafeNativeMethods.TS_E_READONLY);
            }

            if (IsTracing)
            {
                IMECompositionTracer.Trace(this, IMECompositionTraceOp.BSetText,
                                            "text:", startIndex, endIndex, cch, new String(text, 0, cch));
            }

            ITextPointer start;
            ITextPointer end;

            GetNormalizedRange(startIndex, endIndex, out start, out end);

            while (start != null && TextPointerBase.IsBeforeFirstTable(start))
            {
                start = start.GetNextInsertionPosition(LogicalDirection.Forward);
            }

View on GitHub (pinned to 81131a70a4)