Tutorial: Hosting Visual Objects in a Win32 Application

Windows Presentation Foundation (WPF) provides a rich environment for creating applications. However, when you have a substantial investment in Win32 code, it might be more effective to add WPF functionality to your application rather than rewrite your code. To provide heterogeneous support for Win32 and WPF graphics subsystems used concurrently in an application, WPF provides a mechanism for hosting WPF objects in a Win32 window.

This tutorial describes how to write a sample application, Visual Hit Test Sample, that hosts WPF visual objects in a Win32 window.

This topic contains the following sections.

  • Requirements
  • Creating the Host Win32 Window
  • Adding Visual Objects to the Host Win32 Window
  • Implementing the Win32 Message Filter
  • Processing the Win32 Messages
  • Related Topics

Requirements

This tutorial assumes a basic familiarity with both WPF and Win32 programming. For a basic introduction to WPF programming, see Get Started Using Windows Presentation Foundation. For an introduction to Win32 programming, see any of the numerous books on the subject, in particular Programming Windows by Charles Petzold.

NoteNote:

This tutorial includes a number of code examples from the associated sample. However, for readability, it does not include the complete sample code. For the complete sample code, see Visual Hit Test Sample.

Creating the Host Win32 Window

The key to hosting WPF objects in a Win32 window is the HwndSource class. This class wraps the WPF objects in a Win32 window, allowing them to be incorporated into your user interface (UI) as a child window.

The following example shows the code for creating the HwndSource object as the Win32 container window for the visual objects. To set the window style, position, and other parameters for the Win32 window, use the HwndSourceParameters object.

// Constant values from the "winuser.h" header file.
internal const int WS_CHILD = 0x40000000,
                   WS_VISIBLE = 0x10000000;

internal static void CreateHostHwnd(IntPtr parentHwnd)
{
    // Set up the parameters for the host hwnd.
    HwndSourceParameters parameters = new HwndSourceParameters("Visual Hit Test", _width, _height);
    parameters.WindowStyle = WS_VISIBLE | WS_CHILD;
    parameters.SetPosition(0, 24);
    parameters.ParentWindow = parentHwnd;
    parameters.HwndSourceHook = new HwndSourceHook(ApplicationMessageFilter);

    // Create the host hwnd for the visuals.
    myHwndSource = new HwndSource(parameters);

    // Set the hwnd background color to the form's background color.
    myHwndSource.CompositionTarget.BackgroundColor = Brushes.OldLace.Color;
}
NoteNote:

The value of the ExtendedWindowStyle property cannot be set to WS_EX_TRANSPARENT. This means that the host Win32 window cannot be transparent. For this reason, the background color of the host Win32 window is set to the same background color as its parent window.

Adding Visual Objects to the Host Win32 Window

Once you have created a host Win32 container window for the visual objects, you can add visual objects to it. You will want to ensure that any transformations of the visual objects, such as animations, do not extend beyond the bounds of the host Win32 window's bounding rectangle.

The following example shows the code for creating the HwndSource object and adding visual objects to it.

NoteNote:

The RootVisual property of the HwndSource object is set to the first visual object added to the host Win32 window. The root visual object defines the top-most node of the visual object tree. Any subsequent visual objects added to the host Win32 window are added as child objects.

public static void CreateShape(IntPtr parentHwnd)
{
    // Create an instance of the shape.
    MyShape myShape = new MyShape();

    // Determine whether the host container window has been created.
    if (myHwndSource == null)
    {
        // Create the host container window for the visual objects.
        CreateHostHwnd(parentHwnd);

        // Associate the shape with the host container window.
        myHwndSource.RootVisual = myShape;
    }
    else
    {
        // Assign the shape as a child of the root visual.
        ((ContainerVisual)myHwndSource.RootVisual).Children.Add(myShape);
    }
}

Implementing the Win32 Message Filter

The host Win32 window for the visual objects requires a window message filter procedure to handle messages that are sent to the window from the application queue. The window procedure receives messages from the Win32 system. These may be input messages or window-management messages. You can optionally handle a message in your window procedure or pass the message to the system for default processing.

The HwndSource object that you defined as the parent for the visual objects must reference the window message filter procedure you provide. When you create the HwndSource object, set the HwndSourceHook property to reference the window procedure.

parameters.HwndSourceHook = new HwndSourceHook(ApplicationMessageFilter);

The following example shows the code for handling the left and right mouse button up messages. The coordinate value of the mouse hit position is contained in the value of the lParam parameter.

// Constant values from the "winuser.h" header file.
internal const int WM_LBUTTONUP = 0x0202,
                   WM_RBUTTONUP = 0x0205;

internal static IntPtr ApplicationMessageFilter(
    IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    // Handle messages passed to the visual.
    switch (message)
    {
        // Handle the left and right mouse button up messages.
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
            Point pt = new Point();
            pt.X = (uint)lParam & (uint)0x0000ffff;  // LOWORD = x
            pt.Y = (uint)lParam >> 16;               // HIWORD = y
            MyShape.OnHitTest(pt, message);
            break;
    }

    return IntPtr.Zero;
}

Processing the Win32 Messages

The code in the following example shows how a hit test is performed against the hierarchy of visual objects contained in the host Win32 window. You can identify whether a point is within the geometry of a visual object, by using the HitTest method to specify the root visual object and the coordinate value to hit test against. In this case, the root visual object is the value of the RootVisual property of the HwndSource object.

// Constant values from the "winuser.h" header file.
public const int WM_LBUTTONUP = 0x0202,
                 WM_RBUTTONUP = 0x0205;

// Respond to WM_LBUTTONUP or WM_RBUTTONUP messages by determining which visual object was clicked.
public static void OnHitTest(Point pt, int msg)
{
    // Clear the contents of the list used for hit test results.
    hitResultsList.Clear();

    // Determine whether to change the color of the circle or to delete the shape.
    if (msg == WM_LBUTTONUP)
    {
        MyWindow.changeColor = true;
    }
    if (msg == WM_RBUTTONUP)
    {
        MyWindow.changeColor = false;
    }

    // Set up a callback to receive the hit test results enumeration.
    VisualTreeHelper.HitTest(MyWindow.myHwndSource.RootVisual,
                             null,
                             new HitTestResultCallback(CircleHitTestResult),
                             new PointHitTestParameters(pt));

    // Perform actions on the hit test results list.
    if (hitResultsList.Count > 0)
    {
        ProcessHitTestResultsList();
    }
}

For more information on hit testing against visual objects, see Hit Testing in the Visual Layer.

See Also

Concepts

Hit Testing in the Visual Layer

Other Resources

Visual Hit Test Sample