dotnet/wpf · error · InvalidOperationException

SR.KeyboardSinkNotAChild

Error message

SR.KeyboardSinkNotAChild

What it means

When an HwndSource child keyboard-input site is being detached, the code removes it from _keyboardInputSinkChildren; if the site is not in that list, Remove returns false and the code throws InvalidOperationException(SR.KeyboardSinkNotAChild). This means the caller tried to unregister a keyboard sink that this HwndSource never (or no longer) owns.

Solutions

  1. Unregister each child sink exactly once — guard removal with a flag so disposal paths don't run twice.
  2. Only remove a site from the same HwndSource instance that registered it; verify parent identity before removal.
  3. Check that the removal isn't triggered after the source was disposed, which may have already cleared the child list.

Example fix

// before
site.Dispose();
site.Dispose(); // second call throws KeyboardSinkNotAChild
// after
private bool _siteRemoved;
public void RemoveSite(HwndSourceKeyboardInputSite site)
{
    if (_siteRemoved) return;
    _siteRemoved = true;
    site.Dispose();
}
Defensive patterns

Strategy: validation

Validate before calling

if (site == null || _removedSites.Contains(site)) return;
_removedSites.Add(site);
site.Dispose();

Try / catch

try
{
    site.Dispose();
}
catch (InvalidOperationException)
{
    // site was not a child of this source (already removed or wrong parent)
}

Prevention

When it happens

Trigger: Calling the child-site removal path with a HwndSourceKeyboardInputSite created by a different HwndSource, or a site that was already removed/disposed; double-disposal that unregisters the same site twice.

Common situations: Hosted interop control torn down twice (handle destroyed event plus explicit dispose); mismatched parent/child windows during reparenting; finalization racing with explicit cleanup.

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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/HwndSource.cs:2238

            {
                return _acquireHwndFocusInMenuMode;
            }
        }

        /// <summary>
        ///   The method is not part of the interface (IKeyboardInputSink).
        /// </summary>
        /// <param name="site">The Site that containes the sink to unregister</param>
        internal void CriticalUnregisterKeyboardInputSink(HwndSourceKeyboardInputSite site)
        {
            if(_isDisposed)
                return;

            if (null != _keyboardInputSinkChildren)
            {
                if (!_keyboardInputSinkChildren.Remove(site))
                {
                    throw new InvalidOperationException(SR.KeyboardSinkNotAChild);
                }
            }
        }

        private IKeyboardInputSink ChildSinkWithFocus
        {
            get
            {
                IKeyboardInputSink ikis=null;

                if(null == _keyboardInputSinkChildren)
                    return null;

                foreach (HwndSourceKeyboardInputSite site in _keyboardInputSinkChildren)
                {
                    IKeyboardInputSite isite = (IKeyboardInputSite)site;

                    if (isite.Sink.HasFocusWithin())

View on GitHub (pinned to 81131a70a4)