Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

WPF Interoperability


Supporting Keyboard Navigation

In addition to the two abstract methods that must be implemented, HwndHost has a few virtual methods that can optionally be overridden if you want to handle seamless keyboard navigation between WPF elements and hosted Win32 content. This doesn't apply to the hosted Webcam control as is, as it never needs to gain keyboard focus. But for controls that accept input, there are some common features that you'd undoubtedly want to support:

  • Tabbing into the hosted Win32 content.
  • Tabbing out of the hosted Win32 content.
  • Supporting access keys.

Figure 4 illustrates contents of a hypothetical WPF Window with two WPF controls surrounding a Win32 control (hosted in HwndHost) with four child Win32 controls. The numbers represent the expected order of navigation. For the three WPF controls (1, 6, and the HwndHost containing 2-5), the ordering could come implicitly from the way in which they were added to their parent or it could come from an explicit TabIndex being set for each control. For the four Win32 controls (2-5), the order is defined by application-specific logic.

[Click image to view at full size]
Figure 4: A scenario in which keyboard navigation is important with hosted Win32 content.

Tabbing Into the Hosted Win32 Content

"Tabbing into" the Win32 content means two things:

  • When the previous WPF element has focus, pressing Tab moves focus to the first item in the Win32 control. In Figure 4, this means focus moves from 1 to 2.
  • When the next WPF element has focus, pressing Shift+Tab moves focus back to the last item in the Win32 control. In Figure 4, this means focus moves from 6 to 5.

Both of these actions can be supported fairly easily by overriding HwndHost's TabInto method, which is called when HwndHost receives focus via Tab or Shift+Tab. In C++/CLI, a typical implementation would look likes this:

virtual bool TabInto(TraversalRequest^ request) override
{ 
 if (request->FocusNavigationDirection == 
              FocusNavigationDirection::Next)
  SetFocus(hwndForFirstWin32Control);
 else
  SetFocus(hwndForLastWin32Control);
 return true;
} 

TabInto's parameter reveals whether the user has just pressed Tab (giving FocusNavigationDirection.Next) or Shift+Tab (giving FocusNavigationDirection.Previous). Therefore, this code uses this information to decide whether to give focus to its first child or last child. It does this using the Win32 SetFocus API. After setting focus to the correct element, it returns true to indicate that it successfully handled the request.

Tabbing Out of the Hosted Win32 Content

Supporting tabbing into a Win32 control is not enough, of course. If you don't also support tabbing out of the control, keyboard navigation can get "stuck" inside of the Win32 control. For Figure 4, tabbing out of the control means being able to navigate from 5 to 6 with Tab, or from 2 to 1 with Shift+Tab.

Supporting this direction is a little more complicated than the other direction. That's because after focus enters Win32 content, WPF no longer has the same kind of control over what's going on. The application still receives Windows messages that are ultimately passed along to HwndHost, but WPF's keyboard navigation functionality can't "see" what's going on with focus.

Therefore, there is no TabOutOf method to override. Instead, there is a TranslateAccelerator method, which gets called whenever the application receives WM_KEYDOWN or WM_SYSKEYDOWN message from Windows (much like the Win32 API with the same name). Listing Five is a typical C++/CLI implementation of TranslateAccelerator for the purpose of supporting tabbing out of Win32 content (and tabbing within it).

TranslateAccelerator is passed a reference to a "raw" Windows message (represented as a managed System.Windows.Interop.MSG structure) and a ModifierKeys enumeration that reveals if the user is pressing Shift, Alt, Control, and/or the Windows key. (This information can also be retrieved using the Win32 GetKeyState API.)

In Listing Five, the code only takes action if the message is WM_KEYDOWN and if Tab is being pressed (which includes Shift+Tab). After determining whether the user pressed Tab or Shift+Tab using GetKeyState, the code must determine if it is time to tab out of the control or within the control. Tabbing out should occur if focus is already on the first child control and the user pressed Shift+Tab, or if focus is already on the last child control and the user pressed Tab. So in these cases, the implementation calls OnNoMoreTabStops on HwndHost's KeyboardInputSite property. This is the way to tell WPF that focus should return under its control. OnNoMoreTabStops needs to be passed a FocusNavigationDirection value so it knows which WPF element should get focus (1 or 6 in Figure 4). The implementation of TranslateAccelerator must return true if it handles the keyboard event. Otherwise, the event bubbles or tunnels to other elements. One point that Listing Five glosses over is that setting the values of hwndOfPreviousControl and hwndOfNextControl appropriately involves a small amount of application-specific code to determine what the previous/next Win32 control is based on the HWND that currently has focus.

virtual bool TranslateAccelerator(MSG% msg, ModifierKeys modifiers) override
{ 
 if (msg.message == WM_KEYDOWN && msg.wParam == IntPtr(VK_TAB))  { 
  // Handle Shift+Tab
  if (GetKeyState(VK_SHIFT))   { 
   if (GetFocus() == hwndOfFirstControl)
   { 
    // We're at the beginning, so send focus to the previous WPF element
    return this->KeyboardInputSite->OnNoMoreTabStops(
     gcnew TraversalRequest(FocusNavigationDirection::Previous));
   } 
   else 
    return (SetFocus(hwndOfPreviousControl) != NULL);
  } 
  // Handle Shift without Tab
  else   { 
   if (GetFocus() == hwndOfLastControl)
   { 
    // We're at the end, so send focus to the next WPF element
    return this->KeyboardInputSite->OnNoMoreTabStops(
     gcnew TraversalRequest(FocusNavigationDirection::Next));
   } 
   else 
    return (SetFocus(hwndOfNextControl) != NULL);
  } 
 } 
} 
Listing Five

With such an implementation of TranslateAccelerator and TabInto (from the previous section), a user of the application represented by Figure 4 would now be able to navigate all the way from 1 to 6 and back from 6 to 1 using Tab and Shift+Tab, respectively.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.