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 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.

[Click image to view at full size]
Figure 5: The WPF DocumentViewer control hosted in a simple Win32 dialog.

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;
} 
Listing Six

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;
} 
Listing Seven

The result is shown in Figure 6.

[Click image to view at full size]
Figure 6: The WPF DocumentViewer control hosted and resized in a simple Win32 dialog.

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.


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.