Freigeben über


WPF-Win32 Interop Part 2: Hosting Win32 Controls (ListBox) in WPF Windows

Introduction

This article describes hosting of a Win32 user control in a WPF window. It is supplemented by the following two projects, which show working examples of the material presented in the document:

  • Win32ControlInWpfWindow_CompositeWin32Messages
  • Win32ControlInWpfWindow_SimpleWin32Messages

The only difference between the two projects is the Win32ListBoxHost.cs file. The differences between the two versions of the file are related to the material covered in the “Simple” Messages vs “Composite” Messages section below. You can view the differences between the two versions of the file by using a utility such as WinDiff.exe, which is shipped with the Windows SDK.

This article is based on the material available on the WPF/Win32 interoperability portal on MSDN.

 

Hosting of a Win32 Control in a WPF Window

In traditional Win32 programming, user controls (buttons, listboxes, etc.) are instantiated through a call to the CreateWindow or CreateWindowEx APIs. Every control has a unique (per session) identifier of type HWND[1] and is recognized as a separate window (albeit a child window) by the window manager of the OS. What this means is that the window manager of the OS has the ability to do hit-testing[2] of the user control without the assistance of the parent window and is thus capable of sending window messages (e.g. WM_LBUTTONDOWN, etc.) directly to the user control. These window messages are typically intercepted and processed by the message loop of the parent window, but can also be processed by the user control.

In WPF programming user controls are not separate windows with separate HWNDs. Hit-testing is accomplished by the Visual layer in the WPF stack. Thus the window manager of the OS sends messages only to the top-level WPF window.

Hosting of a Win32 user control in a WPF window is accomplished using the following steps:

  1. Create a class deriving from HwndHost;
  2. In the HwndHost-derived class implement 3 methods declared in HwndHost (BuildWindowCore, DestroyWindowCore and WndProc):

 

class MyHost : HwndHost

{

  // Public adapter interface goes here

 

  protected override HandleRef BuildWindowCore(HandleRef hwndParent)

  {

    // PInvoke to CreateWindow(Ex)...

    return new HandleRef(this, hwnd);

  }

 

  protected override void DestroyWindowCore(HandleRef hwnd)

  {

    // PInvoke to DestroyWindow

  }

 

  protected override IntPtr WndProc(

    IntPtr hwnd,

    int msg,

    IntPrt wparam,

    IntPtr lparam,

    ref bool handled)

  {

    // Handle incoming messages here.

    // The incoming messages are typically delegated

    // to the hosted control.

  }

}

 

 

You use the BuildWindowCore to instantiate the Win32 user control you want to host. This is typically done through a call to the CreateWindow / CreateWindowEx API. Similarly, in DestroyWindowCore you destroy your Win32 user control, typically by a call to the DestroyWindow API.

The WndProc method allows you to handle incoming window messages.

  1. In your HwndHost-derived class (MyHost in this particular example), declare an adapter interface[3] to allow communication with the hosted Win32 control. In order to ensure reusability of the host class in different WPF applications, you should strive to avoid any Win32 idiosyncrasies in the interface. Instead, the adapter interface should conform to the .NET coding and style guidelines and should hide the fact that there is an underlying Win32 control.

For example, if your HwndHost-derived class is wrapping a Win32 listbox, then it probably makes sense to expose basic listbox methods such as:

· AddItem

· DeleteItem

· SelectedItemIndex {get; set}

· SelectedItemText {get;}

· SelectionChanged event (see the “Communicating Win32 Events to the WPF Window” section later in this document)

 

Conversely, it is not a good idea to expose a method such as SendMessage, wrapping the Win32 SendMessage API.

  1. In the XAML file declaring the UI of your WPF window, add a declaration for your HwndHost class:

<Window

  x:Class="Win32ControlInWpfWindow.MainWindow"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:custom="clr-namespace:Win32ControlInWpfWindow;assembly="

  Title="Win32 Control in a WPF Window"

  Height="500" Width="600"

  >

  <StackPanel>

    <Button Click="ButtonAddItemOnClick">Add Item</Button>

    <custom:Win32ListBoxHost x:Name="listbox" Width="100" Height="100"/>

  </StackPanel>

</Window>

Note how the XAML file above defines the name space for your HwndHost-derived class (the “custom” namespace) and the assembly where the class is implemented. In this particular case the “assembly=” directive points to nothing, which indicates that the host class is implemented in the same assembly as the one of the MainWindow class.

 

“Simple” Messages vs “Composite” Messages

In a traditional Win32 application, you display a number of Win32 user controls in a top-level Win32 window. The top-level window is typically handling all window messages resulting from interaction of the user with the controls through a top-level WindowProc.

As the user interacts with the controls on the window, the top-level WindowProc receives both “simple” messages (e.g. WM_LBUTTONDOWN, etc.) that are generated by the OS window manager as well as “composite” messages (e.g. WM_COMMAND, etc.) that are generated by the user controls and sent as notifications to the parent window[4].

These “composite” notifications present a problem in the WPF hosting case, because there is no parent window WindowProc to process them[5]. As a result, in the code below would not really work as expected:

 

class MyHost : HwndHost

{

  protected override HandleRef BuildWindowCore(HandleRef hwndParent)

  {

    _hwndListBox = CreateWindowEx(

    0, "listbox", "",

        WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | WS_BORDER,

       0, 0,

        (int)Width, (int)Height,

        hwndParent.Handle,

        IntPtr.Zero,

        IntPtr.Zero,

        0);

 

    return new HandleRef(this, _hwndListBox);

  }

 

  protected override IntPtr WndProc(

    IntPtr hwnd,

    int msg,

    IntPrt wparam,

    IntPtr lparam,

    ref bool handled)

  {

    switch (msg)

    {

      case WM_COMMAND:

        // This code-path will never get hit as the listbox control will

        // never receive the WM_COMMAND message that it posts to its parent

        break;

 

      case WM_LBUTTONDOWN:

        // This code-path will get hit as the listbox control will

        // be receiving the message from the OS window manager

        break;

    }

  }

 

  ...

}

 

In most cases handling “simple” messages in the WndProc method is sufficient, although it may be somewhat cumbersome.

There are, however, cases in which you would want to receive “composite” messages. To achieve this, you need to create an artificial parent HWND, so that you can receive the “composite” notifications, generated by the hosted child windows. In other words you end up with the following general structure:

clip_image002[6]

This is achieved as follows:

class MyHost : HwndHost

{

  protected override HandleRef BuildWindowCore(HandleRef hwndParent)

  {

    _hwndListBoxParent = CreateWindowEx(

  0, "static", "",

      WS_CHILD,

      0, 0,

      (int)Width, (int)Height,

      hwndParent.Handle,

      IntPtr.Zero,

      IntPtr.Zero,

      0);

 

    _hwndListBox = CreateWindowEx(

      0, "listbox", "",

      WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | WS_BORDER,

      0, 0,

      (int)Width, (int)Height,

      _hwndListBoxParent,

      IntPtr.Zero,

      IntPtr.Zero,

      0);

 

      return new HandleRef(this, _hwndListBoxParent);

  }

  ...

 

  Private IntPtr _hwndListBoxParent = null;

  Private IntPtr _hwndListBox = null;

}

 

 

Communicating Win32 Events to the WPF Window

With the code above we can capture and process the window messages sent to the hosted Win32 user control. We do that in the WndProc method.

In addition to that we need to be able to inform interested WPF elements of the fact that we have received a certain event. For example, a hosted Win32 button may need to inform a WPF textbox that is has been clicked, a hosted Win32 listbox may need to inform a WPF textbox that the selection in the listbox has been changed, etc. The traditional way to do expose that capability in .NET is through events.

So we declare a public event and a protected event handler as follows:

 

class MyHost : HwndHost

{

  ...

 

  public event EventHandler SelectionChanged;

 

  protected void OnSelectionChanged(EventArgs args)

  {

    EventHandler handler = SelectionChanged;

    if (handler != null)

    {

    handler(this, args);

    }

  }

 

  ...

}

 

 

This allows the users of the class to write code such as:

 

class Test

{

  public static void Main()

  {

    ...

    myHost.SelectionChanged += MySelectionChangedHandler;

    ...

  }

 

  public void MySelectionChangedHandler(EventArgs args)

  {

    // This method gets called whenever someone changes the selection in the Win32

    // control (which is presumably a listbox) hosted in MyHost.

  }

 

  ...

}

 

 

Adding Tab and Accelerators Support for the Hosted Win32 Control

One current problem that you will see in the application is that keyboard navigation does not work well with the Win32 control. Keyboard navigation has several aspects:

a) Enabling tabbing into and out of the hosted Win32 control (listbox)

b) Enabling keyboard navigation within the hosted Win32 control (e.g. moving the selection in the listbox)

c) Supporting access keys

In order to achieve a) and b), your HwndHost-derived class needs to implement two methods of the IKeyboardInputSink interface: TabInto and TranslateAccelerator.

The TabInto method gives you (as the name suggests) tabbing into the Win32 listbox (by pressing either Tab or Shift+Tab). In this particular case, the implementation of TabInto is trivial because we only have a single Win32 control:

 

class Win32ListBoxHost : HwndHost, IKeyboardInputSink

{

  ...

 

  bool IKeyboardInputSink.TabInto(TraversalRequest request)

  {

  if (request.FocusNavigationDirection == FocusNavigationDirection.Next)

    {

  NativeMethods.SetFocus(_hwndListBox);

    }

    else

    {

      NativeMethods.SetFocus(_hwndListBox);

    }

 

    return true;

  }

 

  ...

}

 

 

The TranslateAccelerator method gets called whenever the OS sends keyboard messages (WM_KEYDOWN or WM_SYSKEYDOWN) to the application. We use the method to intercept pressing of the up and down arrow so that we can change the selected item in the hosted Win32 listbox:

 

class Win32ListBoxHost : HwndHost, IKeyboardInputSink

{

  ...

       

  bool IKeyboardInputSink.TranslateAccelerator(ref MSG msg, ModifierKeys modifiers)

  {

  bool isHandled = false;

 

    if (msg.message == NativeMethods.WM_KEYDOWN)

    {

    if (msg.wParam == (IntPtr)NativeMethods.VK_UP)

      {

      if (this.SelectedItemIndex > 0) { this.SelectedItemIndex--; }

        else { this.SelectedItemIndex = 0; }

 

        isHandled = true;

      }

 

      if (msg.wParam == (IntPtr)NativeMethods.VK_DOWN)

  {

  this.SelectedItemIndex++;

   isHandled = true;

      }

    }

    return isHandled;

  }

}

 

The only thing that remains is supporting access keys (also known as mnemonics). In order to do that, you need to provide an implementation for the IKeyboardInputSink.OnMnemonic method.

 

Conclusion

The attached projects demonstrate the final solution for hosting a Win32 listbox in a WPF window. The final interface of the HwndHost-derived Win32 listbox container class is:

class Win32ListBoxHost : HwndHost, IKeyboardInputSink

{

  public void AddItem(string item);

  public void DeleteItem(int itemIndex);

  public int SelectedItemIndex { get; set; }

  public string SelectedItemText { get; }

  public event EventHandler SelectionChanged;

 

  protected override HandlerRef BuildWindowCore(HandleRef hwndParent);

  protected override void DestroyWindowCore(HandleRef hwnd);

  protected virtual void OnSelectionChanged(EventArgs args);

  protected override IntPtr WndProc(IntPtr hwnd, int message,

                                    IntPtr wParam, IntPtr lParam, ref bool handled);

 

  bool IKeyboardInputSink.TabInto(TraversalRequest request);

  bool IKeyboardInputSink.TranslateAccelerator(ref MSG msg, ModifierKeys modifiers);

}

 

For demonstration purposes only, I have included both a project that only uses “simple” (aka “raw”) window messages only and one that uses composite window messages. The only differences between the two projects are in the Win32ListBoxHost.cs file. In most real-world situations, you will end up using “composite” window messages – otherwise you would have to re-implement parts of the business logic of the component you are hosting, which is obviously undesirable.

 


[1] A handle to a window

[2] Hit-testing is the act of determining whether the position of the mouse coincides with the control representation on the screen

[3] “Adapter” in this context refers to the Adapter design pattern

[4] The notion of “simple” and “composite” messages is not something that is defined by the OS API or documentation. It is introduced in this text only as a way to illustrate the need for a parent HWND in order to be able to handle certain window messages. Instead, I could have used “OS generated” and “control generated” messages, etc.

[5] Strictly speaking, there is a parent WindowProc, but you don’t have direct access to it from within your HwndHost-derived class.

Win32ControlInWpfWindow.zip

Comments

  • Anonymous
    October 07, 2007
    PingBack from http://www.artofbam.com/wordpress/?p=6007

  • Anonymous
    October 13, 2007
    Introduction This article describes hosting of a Win32 user control in a WPF window. It is supplemented

  • Anonymous
    February 10, 2008
    Good article. It is really useful because official documentation (MSDN 2008) is not 100% successful, a lot of topics about WPF are not enough detailed.

  • Anonymous
    May 24, 2009
    Is there any way to send a windows message notification(s) to a WPF application from win32 application? Note: The WPF app already exists and cannot be modified.

  • Anonymous
    October 25, 2009
    Good article.  Thank you for your information.  We have little doubts in this. We hosted a win32 control inside WPF window using 'System::Windows::Interop::HwndHost'.  We need to handle the WM_MOUSEWHEEL ( mouse wheel ) event in this control. How we can solve this problem?

  • Anonymous
    June 27, 2010
    But too bad the sample cannot compile... Downloaded the zip, extracted, loaded the project, build, throw up on multiple errors.... why do people do tthis??

  • Anonymous
    June 28, 2010
    @AdsCft: I just tried building the samples again and they both worked. Which version of VS are you building with? What errors do you get? One other thing: when you downloaded the ZIP file, did you "unblock" it? Right-click onthe ZIP file, choose PROPERTIES, then click on UNBLOCK in the GENERAL section. You need to do that before unzipping it. Finally, please understand that this blog post was published almost 3 years ago. The ideas in the blog should survive releases, but the code is there for reference purposes and may stop to work if VS makes a change that breaks compatibility. I don't necessarily go back and test all uploaded solutions to make sure they build with the latest versions of VS. But in this particular case, I will help. Let me know what the errors are if you still cannot build successfully when you UNBLOCk the ZIP file.

  • Anonymous
    June 28, 2010
    @Babu: You should be able to just handle WM_MOUSEWHEEL in your Win32ListBoxHost.WndProc. Doesn't that work?

  • Anonymous
    April 29, 2012
    Hi,   Nice article, appreciate for sharing. I have a slight different scenario from the one discussed above. I have a Win32 application from which I launch a WPF window (not hosted). I use a key combination in the win32 window to enable something. I want this to be notified to the WPF window as well. Can this be achieved by using IKeyboardSInk? If so how? Thanks, Uday

  • Anonymous
    May 01, 2012
    Hi Uday, I don't think you'd be able to do that with IKeyboardSink. You'd need to use a different communication mechanism to inform the WPF window of the event. Ivo