IKeyboardInputSink (IKIS)

I've been promising for awhile that I would write something about keyboard interop, and I finally got a free afternoon...

With hwnd interop, keyboard input normally goes to the hwnd with focus.  But tabbing, accelerators, and mnemonics are concepts that work across hwnds -- how can we make them work?  IKeyboardInputSink (abbreviated IKIS) provides a limited form of sharing the keyboard between hwnds to solve these scenarios.

IKeyboardInputSink is an interface that every HwndHost and HwndSource implements; the definition looks like:

    public interface IKeyboardInputSink
    {
        IKeyboardInputSite RegisterKeyboardInputSink(IKeyboardInputSink sink);
        IKeyboardInputSite KeyboardInputSite {get; set;}
        bool HasFocusWithin();
        bool TabInto(TraversalRequest request);
        bool TranslateAccelerator(ref MSG msg, ModifierKeys modifiers);
        bool TranslateChar(ref MSG msg, ModifierKeys modifiers);
        bool OnMnemonic(ref MSG msg, ModifierKeys modifiers);
    }

Every sink has an associated site (IKeyboardInputSite) -- you can think of this as the parent IKIS.  When the IKIS is created, it calls its parent's IKIS.RegisterKeyboardInputSink() to get a site which it will use for future communications with the parent. 

Accelerators
TranslateAccelerator is only for key down messages (WM_KEYDOWN and WM_SYSKEYDOWN) -- it brings tunneling and bubbling to the world of hwnds.  For keyboard messages, the message pump is expected to call the root IKIS's TranslateAccelerator method, and each IKIS.TranslateAccelerator is expected in turn to find out which child IKIS has focus (by calling IKIS.HasFocusWithin) and call that child IKIS.TranslateAccelerator.   If the parent IKIS wants to process the keystroke before the child, do it before calling the child's TranslateAccelerator, otherwise do it after -- no need to call the child if you've already handled the keystroke.

HwndSource's TranslateAccelerator fires the WPF PreviewKeyDown event, then calls the appropriate child IKIS, then fires the KeyDown event -- so IKIS's effectively become part of the tunneling and bubbling of a key down event.

Note that TranslateAccelerator must either handle the message or let another IKIS handle it -- there's no "I'll handle it but later, please dispatch it and I'll handle it then" option.  If you really need the message to be dispatched, you can call DispatchMessage from within your IKIS::TranslateAccelerator.

TranslateChar behaves similarly to TranslateAccelerator, and is for WM_CHAR, WM_SYSCHAR, WM_DEADCHAR and WM_SYSDEADCHAR messages.

Mnemonics
A mnemonic (“access key” in WPF speak) is typically displayed in the UI as an underlined letter -- e.g., Button.  Mnemonics are different from accelerators because for mnemonics, we call all IKIS's until we find a handler, while accelerators only IKIS's in the focus chain are called.

OnMnemonic is called in response to wm_char and wm_syschar (and not wm_keydown).  When one of those messages happened, first TranslateChar is called on the IKIS focus chain, and if there are no handlers, OnMnemonic is called.  Each IKIS is expected to call all of its child IKIS's OnMnemonics until one of them handles the mnemonic.

Tabbing
When your IKIS.TabInto method is called, you should set focus to your first or last control, depending on the TraversalRequest.  TraversalRequest is part of WPF's tabbing/keyboard navigation system, but IKIS really only needs a small part of that -- traversal request has a FocusNavigationDirection property, which in the case of IKIS will either be FocusNavigationDirection::First or FocusNavigationDirection::Last. Return true from TabInto if you are able to successfully set focus, otherwise return false (i.e., you have no tab stops).  Similarly, if you have a child IKIS, when it's time to tab into them, call their TabInto method.  (If that child IKIS is your first tab stop, then your TabInto calls their TabInto)

The child-most IKIS.TranslateAccelerator is expected to turn a VK_TAB into a SetFocus/TabInto call if appropriate; parent IKIS’s should usually leave this decision to a child IKIS.  Doing the "tab key means move focus" decision low in the tree gives flexibility for, say, a Win32 text box to choose to interpret the tab key as "insert tab" rather than "move focus".

As you move to the next tab stop, you may find you're at the end of the component -- you can call KeyboardInputSite->OnNoMoreTabStops() to let your parent know that you are done and your parent should start tabbing through itself.  OnNoMoreTabStops can in turn call its site's OnNoMoreTabStops, etc..  Note that OnNoMoreTabStops can return false, which means the parent IKIS doesn't have any tab stops and the child IKIS should cycle back to its first tab stop (or last, if shift-tab).

If you are a parent IKIS and wish to override OnNoMoreTabStops, you can do that by writing your own IKeyboardInputSite and overriding IKIS.RegisterKeyboardInputSink to return your custom site.

Comments