Supporting Access Keys
The final piece of keyboard navigation to support is jumping to a control via an access key (sometimes called a "mnemonic"). For example, the text boxes in Figure 4 would likely have corresponding labels with an access key (indicated by an underlined letter). When hosted in a WPF application, we still want focus to jump to the corresponding controls when the user presses Alt and the access key.
To support this, you can override HwndHost's OnMnemonic method. Like TranslateAccelerator, it is given a raw Windows message and a ModifierKeys enumeration. So you could implement it as follows, if you want to support two access keys--a and b:
virtual bool OnMnemonic(MSG% msg, ModifierKeys modifiers) override { // Ensure that we got the expected message if (msg.message == WM_SYSCHAR && (modifiers | ModifierKeys.Alt)) { // Convert the IntPtr to a char char key = (char)msg.wParam.ToPointer(); // Only handle the 'a' and 'b' characters if (key == 'a') return (SetFocus(someHwnd) != NULL); else if (key == 'b') return (SetFocus(someOtherHwnd) != NULL); } return false; }
Embedding WPF Controls in Win32 Applications
Lots of compelling WPF features can be integrated into a Win32 application: 3D, rich document support, animation, easy restyling, and so on. Even if you don't need this extra "flashiness," you can still take advantage of important features, such as flexible layout and resolution independence.
Since WPF's HWND interoperability is bidirectional, WPF controls can be embedded in Win32 applications much like how Win32 controls are embedded in WPF applications. In this section, I take a built-in WPF control--DocumentViewer, the viewer for XPS documents--and embed it in a simple Win32 window using a class called HwndSource.
HwndSource does the opposite of HwndHost--it exposes any WPF Visual as an HWND. Listing Six demonstrates the use of HwndSource with the relevant C++ source file from a Win32 project included with this book's source code. It is compiled with /clr, so it is managed code that uses both managed and unmanaged data types.
In this project, a simple dialog is defined via a Win32 resource script (not shown here). The application's entry point (_tWinMain) simply shows this dialog via the Win32 DialogBox function, specifying DialogFunction as the window procedure that receives the Win32 messages.
Inside DialogFunction, only two messages are processed--WM_INITDIALOG, which creates and embeds the WPF control on initialization, and WM_CLOSE, which terminates the dialog appropriately. Inside the processing of WM_INITDIALOG, an HwndSourceParameters structure is created, and some of its fields are initialized to give the HwndSource an initial size, position, and style. Most important, it is given a parent HWND (which, in this case, is the dialog itself). For Win32 programmers, this type of initialization should look familiar. It's mostly the same kind of information that you would pass to the Win32 CreateWindow function.
After HwndSourceParameters is populated, the code only needs to do two simple steps to put the WPF content in place. It instantiates an HwndSource object with the HwndSourceParameters data, then it sets HwndSource's RootVisual property (of type System.Windows.Media.Visual) to an appropriate instance. Here, a DocumentViewer is instantiated. Figure 5 is the result.

Although this example uses a built-in WPF control, you can follow the same approach with your own arbitrarily complex WPF content. Just take the top-level element (like a Grid or Page) and use HwndSource to expose it to the rest of Win32 as one big HWND.
Getting the Right Layout
Because you're in the world of Win32 when doing this type of integration, there's no special layout support for the top-level WPF control. In Listing Six, the DocumentViewer is given an initial placement of (10,10) and a size of (500,350).
#include "stdafx.h" #include "HostingWPF.h" #include "commctrl.h" #using <PresentationFramework.dll> #using <PresentationCore.dll> #using <WindowsBase.dll> LRESULT CALLBACK DialogFunction(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { // Describe the HwndSource System::Windows::Interop::HwndSourceParameters p; p.WindowStyle = WS_VISIBLE | WS_CHILD; p.PositionX = 10; p.PositionY = 10; p.Width = 500; p.Height = 350; p.ParentWindow = System::IntPtr(hDlg); System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(p); // Attach a new DocumentViewer to the HwndSource source->RootVisual = gcnew System::Windows::Controls::DocumentViewer(); return TRUE; } case WM_CLOSE: EndDialog(hDlg, LOWORD(wParam)); return TRUE; } return FALSE; } [System::STAThread] int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { DialogBox(hInstance, (LPCTSTR)IDD_MYDIALOG, NULL, (DLGPROC)DialogFunction); return 0; }
But that placement and size is never going to change without some explicit code to change them. For example, Listing Seven makes the DocumentViewer occupy the entire space of the window, even as the window is resized.
#include "stdafx.h" #include "HostingWPF.h" #include "commctrl.h" #using <PresentationFramework.dll> #using <PresentationCore.dll> #using <WindowsBase.dll> ref class Globals { public: static System::Windows::Interop::HwndSource^ source; } ; LRESULT CALLBACK DialogFunction(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { System::Windows::Interop::HwndSourceParameters p; p.WindowStyle = WS_VISIBLE | WS_CHILD; // Initial size and position don't matter due to WM_SIZE handling: p.PositionX = 0; p.PositionY = 0; p.Width = 100; p.Height = 100; p.ParentWindow = System::IntPtr(hDlg); Globals::source = gcnew System::Windows::Interop::HwndSource(p); Globals::source->RootVisual = gcnew System::Windows::Controls::DocumentViewer(); return TRUE; } case WM_SIZE: RECT r; GetClientRect(hDlg, &r); SetWindowPos((HWND)Globals::source->Handle.ToPointer(), NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, 0); return TRUE; case WM_CLOSE: EndDialog(hDlg, LOWORD(wParam)); return TRUE; } return FALSE; } [System::STAThreadAttribute] int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { DialogBox(hInstance, (LPCTSTR)IDD_MYDIALOG, NULL, (DLGPROC)DialogFunction); return 0; }
The result is shown in Figure 6.

The most important code is the handling of the WM_SIZE message. It uses the Win32 GetClientRect API to get the current window size, and then it applies it to the HwndSource using the Win32 SetWindowPos API. There are two interesting points about this new implementation:
- The HwndSource variable is now "global," so it can be shared by multiple places in the code. But C++/CLI does not allow a managed variable to be truly global, so the listing uses a common
- To operate on the HwndSource with Win32 APIs such as SetWindowPos, you need its HWND. This is exposed via a Handle property of type IntPtr. In C++/CLI, you can call its ToPointer method (which returns a void*), then cast the result to an HWND.