10/30/2007

WPF-Win32 interop with HwndSource

I was given a task to design a new GUI using WPF, and then interface that with some legacy Win32 code. The challenge of course was the interop between the native side and the managed side. I looked up some sample code from MSDN and a couple of books, and thought I was well on my way. The idea was to host WPF controls in an MFC CDialog. Switching the MFC project into managed code was not an option, so I decided to create a mixed-mode MFC Extension DLL. This would allow me to wrap the WPF controls inside a CWnd, which I would then be able to use in a native CDialog. WPF interop provides a HwndSource class for hosting WPF content in a HWND window. I actually learned this technique from Nishant Sivakumar's book C++/CLI in Action.



Everything worked beautifully until I tried to write text into a WPF TextBox. Nothing happened. I was able to delete characters by pressing Del or Backspace. HwndSource has its own window procedure and is supposed to handle message dispatching so I was baffled. Little testing showed that the TextBox was firing KeyDown events but not TextInput events. KeyDown is equivalent to WM_KEYDOWN and TextInput is equivalent to WM_CHAR. Somewhere along the way the WM_CHAR messages were lost. Turns out the standard dialog message loop calls IsDialogMessage, which "eats up" WM_CHAR messages. Had I been using a modeless dialog or a non-dialog window, everything would have worked. MS dev support suspected this might be a flaw in the WPF interop and might be fixed later.

And now the workaround. HwndSource allows you to add hooks to the event handler. The following shows an event hook that will ensure WM_CHAR messages are dispatched to the WPF side.

IntPtr ChildHwndSourceHook(IntPtr hwnd, int msg,
IntPtr wParam, IntPtr lParam, bool% handled)
{
if(msg == WM_GETDLGCODE)
{
handled = true;

return IntPtr(DLGC_WANTCHARS);
}

return IntPtr(0);
}

...

HwndSource^ source = gcnew
HwndSource(sourceParams);

source->AddHook(gcnew
HwndSourceHook(ChildHwndSourceHook));


Thanks to Chango for the workaround.

No comments: