Tutorial: Hospedar objetos visuales en una aplicación Win32

Windows Presentation Foundation (WPF) proporciona un entorno enriquecido para crear aplicaciones. Sin embargo, si ya ha realizado una inversión importante en código Win32, quizá sea más eficaz agregar la funcionalidad WPF a la aplicación en lugar de reescribir el código. Para proporcionar compatibilidad con los subsistemas gráficos de Win32 y WPF usados simultáneamente en una aplicación, WPF proporciona un mecanismo para hospedar objetos en una ventana de Win32.

En este tutorial se describe cómo escribir una aplicación de ejemplo, Ejemplo de prueba de posicionamiento con interoperabilidad Win32, que hospeda objetos visuales de WPF en una ventana de Win32.

Requisitos

En este tutorial se da por supuesto un conocimiento básico de la programación en WPF y Win32. Para obtener una introducción básica a la programación en WPF, consulte Tutorial: Mi primera aplicación de escritorio de WPF. Para obtener una introducción a la programación en Win32, consulte cualquiera de los numerosos libros publicados sobre el tema, en particular Programación en Windows, escrito por Charles Petzold.

Nota:

En el tutorial se incluye una serie de ejemplos de código del ejemplo asociado. Sin embargo, para una mejor lectura, no se incluye el código de ejemplo completo. Para obtener el código de ejemplo completo, consulte Ejemplo de prueba de posicionamiento con interoperabilidad Win32.

Crear la ventana host de Win32

La clave para hospedar contenido WPF en una ventana de Win32 es la clase HwndSource. Esta clase encapsula los objetos de WPF en una ventana de Win32, lo que le permite incorporarlos en su interfaz de usuario como una ventana secundaria.

En el ejemplo siguiente se muestra el código para crear el objeto HwndSource como la ventana contenedora de Win32 para los objetos visuales. Para establecer el estilo de la ventana y otros parámetros para la ventana de Win32, use el objeto HwndSourceParameters.

// 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 = System.Windows.Media.Brushes.OldLace.Color;
}
' Constant values from the "winuser.h" header file.
Friend Const WS_CHILD As Integer = &H40000000, WS_VISIBLE As Integer = &H10000000

Friend Shared Sub CreateHostHwnd(ByVal parentHwnd As IntPtr)
    ' Set up the parameters for the host hwnd.
    Dim parameters As New HwndSourceParameters("Visual Hit Test", _width, _height)
    parameters.WindowStyle = WS_VISIBLE Or WS_CHILD
    parameters.SetPosition(0, 24)
    parameters.ParentWindow = parentHwnd
    parameters.HwndSourceHook = New HwndSourceHook(AddressOf 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 = System.Windows.Media.Brushes.OldLace.Color
End Sub

Nota:

El valor de la propiedad ExtendedWindowStyle no se puede establecer en WS_EX_TRANSPARENT. Esto significa que la ventana host de Win32 no puede ser transparente. Por este motivo, el color de fondo de la ventana host de Win32 está establecido en el mismo color de fondo que la ventana primaria.

Agregar objetos visuales a la ventana host de Win32

Una vez haya creado una ventana host contenedora de Win32 para los objetos visuales, puede agregar objetos visuales a la misma. Es conveniente asegurarse de que ninguna transformación de los objetos visuales, como las animaciones, se extienda más allá de los límites del rectángulo delimitador de la ventana host de Win32.

En el ejemplo siguiente se muestra el código para crear el objeto HwndSource y agregarle objetos visuales.

Nota:

La propiedad RootVisual del objeto HwndSource se establece en el primer objeto visual agregado a la ventana host Win32. El objeto visual raíz define el nodo de nivel superior del árbol de objetos visuales. Los objetos visuales agregados posteriormente a la ventana host de Win32 se agregan como objetos secundarios.

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);
    }
}
Public Shared Sub CreateShape(ByVal parentHwnd As IntPtr)
    ' Create an instance of the shape.
    Dim myShape As New MyShape()

    ' Determine whether the host container window has been created.
    If myHwndSource Is Nothing Then
        ' 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.
        CType(myHwndSource.RootVisual, ContainerVisual).Children.Add(myShape)
    End If
End Sub

Implementar el filtro de mensajes de Win32

La ventana host de Win32 para los objetos visuales requiere un procedimiento de filtro de mensajes de ventana para controlar los mensajes que se envían a la ventana desde la cola de la aplicación. El procedimiento de ventana recibe mensajes del sistema Win32. Pueden ser mensajes de entrada o mensajes de administración de ventanas. Si lo desea, puede administrar un mensaje en el procedimiento de ventana o bien pasar el mensaje al sistema para su procesamiento predeterminado.

El objeto HwndSource, que se ha definido como el elemento primario de los objetos visuales, debe hacer referencia al procedimiento de filtro de mensajes de ventana que proporcione. Cuando cree el objeto HwndSource, establezca la propiedad HwndSourceHook para que haga referencia al procedimiento de ventana.

parameters.HwndSourceHook = new HwndSourceHook(ApplicationMessageFilter);
parameters.HwndSourceHook = New HwndSourceHook(AddressOf ApplicationMessageFilter)

En el ejemplo siguiente se muestra el código para controlar los mensajes que se emiten al soltar el botón primario y secundario del ratón. El valor de las coordenadas de la posición donde se hace clic con el ratón se encuentra en el valor del parámetro lParam.

// 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:
            System.Windows.Point pt = new System.Windows.Point();
            pt.X = (uint)lParam & (uint)0x0000ffff;  // LOWORD = x
            pt.Y = (uint)lParam >> 16;               // HIWORD = y
            MyShape.OnHitTest(pt, message);
            break;
    }

    return IntPtr.Zero;
}
' Constant values from the "winuser.h" header file.
Friend Const WM_LBUTTONUP As Integer = &H202, WM_RBUTTONUP As Integer = &H205

Friend Shared Function ApplicationMessageFilter(ByVal hwnd As IntPtr, ByVal message As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
    ' Handle messages passed to the visual.
    Select Case message
        ' Handle the left and right mouse button up messages.
        Case WM_LBUTTONUP, WM_RBUTTONUP
            Dim pt As New System.Windows.Point()
            pt.X = CUInt(lParam) And CUInt(&HFFFF) ' LOWORD = x
            pt.Y = CUInt(lParam) >> 16 ' HIWORD = y
            MyShape.OnHitTest(pt, message)
    End Select

    Return IntPtr.Zero
End Function

Procesar mensajes de Win32

El código del ejemplo siguiente muestra cómo se realiza una prueba de posicionamiento en la jerarquía de objetos visuales de la ventana host de Win32. Puede identificar si un punto está dentro de la geometría de un objeto visual mediante el método HitTest para especificar el objeto visual raíz y el valor de las coordenadas con las que se comparará la prueba de posicionamiento. En este caso el objeto visual raíz es el valor de la propiedad RootVisual del objeto HwndSource.

// 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(System.Windows.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();
    }
}
' Constant values from the "winuser.h" header file.
Public Const WM_LBUTTONUP As Integer = &H0202, WM_RBUTTONUP As Integer = &H0205

' Respond to WM_LBUTTONUP or WM_RBUTTONUP messages by determining which visual object was clicked.
Public Shared Sub OnHitTest(ByVal pt As System.Windows.Point, ByVal msg As Integer)
    ' 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 Then
        MyWindow.changeColor = True
    End If
    If msg = WM_RBUTTONUP Then
        MyWindow.changeColor = False
    End If

    ' Set up a callback to receive the hit test results enumeration.
    VisualTreeHelper.HitTest(MyWindow.myHwndSource.RootVisual, Nothing, New HitTestResultCallback(AddressOf CircleHitTestResult), New PointHitTestParameters(pt))

    ' Perform actions on the hit test results list.
    If hitResultsList.Count > 0 Then
        ProcessHitTestResultsList()
    End If
End Sub

Para obtener más información acerca de la prueba de posicionamiento en objetos visuales, consulte Realizar pruebas de posicionamiento en la capa visual.

Vea también