Interoperabilidad de WPF y Win32

En este tema se proporciona información general sobre cómo interactuar con código de Windows Presentation Foundation (WPF) y Win32. WPF proporciona un entorno enriquecido para crear aplicaciones. Pero, si ha hecho una inversión sustancial en código de Win32, podría resultar más efectivo reutilizar parte de ese código.

Conceptos básicos de interoperación de WPF y Win32

Hay dos técnicas básicas de la interoperación entre el código de WPF y Win32.

  • Hospedar contenido de WPF en una ventana de Win32. Con esta técnica puede usar las capacidades gráficas avanzadas de WPF en el marco de una ventana y aplicación estándar de Win32.

  • Hospedar una ventana de Win32 en contenido de WPF. Con esta técnica puede usar un control personalizado de Win32 existente en el contexto de otro contenido de WPF y pasar datos a través de los límites.

Todas estas técnicas se presentan conceptualmente en este tema. Para ver una explicación más orientada al código sobre cómo hospedar WPF en Win32, consulte Tutorial: Hospedar contenido de WPF en Win32. Para ver una explicación más orientada al código sobre cómo hospedar Win32 en WPF, consulte Tutorial: Hospedar un control de Win32 en WPF.

Proyectos de interoperación de WPF

Las API de WPF son código administrado, pero la mayoría de los programas existentes de Win32 están escritos en C++ no administrado. No puede llamar a las API de WPF desde un auténtico programa no administrado. Pero puede usar la opción /clr con el compilador de Microsoft Visual C++ para crear un programa mixto (administrado-no administrado), en el que puede combinar perfectamente llamadas de API administradas y no administradas.

Una desventaja a nivel de proyecto es que no puede compilar archivos de lenguaje XAML en un proyecto de C++. Existen varias técnicas de división de proyectos para compensarlo.

  • Cree un archivo DLL de C# que contenga todas las páginas XAML como un ensamblado compilado y, luego, haga que el ejecutable de C++ incluya ese DLL como referencia.

  • Cree un ejecutable de C# para el contenido de WPF y cree una referencia entre este y un archivo DLL de C++ que incluya el contenido de Win32.

  • Use Load para cargar cualquier XAML en tiempo de ejecución, en lugar de compilar el XAML.

  • No use XAML y escriba todo lo relacionado con WPF en el código, para crear el árbol de elementos desde Application.

Use el método que se adapte mejor a sus necesidades.

Nota:

Si no ha usado C++/CLI antes, observará algunas palabras clave "nuevas", como gcnew y nullptr, en los ejemplos de código de interoperación. Estas palabras clave reemplazan la anterior sintaxis de doble subrayado (__gc) y proporcionan una sintaxis más natural para el código administrado en C++. Para obtener más información sobre las características administradas de C++/CLI, consulte Extensiones de componentes para plataformas de tiempo de ejecución.

Cómo WPF usa los HWND

Para aprovechar al máximo la "interoperación de HWND" de WPF, debe comprender cómo WPF usa los HWND. En ningún HWND se puede mezclar la representación de WPF con la representación de DirectX o la representación de GDI / GDI+. Esto tiene varias implicaciones. En primer lugar, para poder mezclar estos modelos de representación, debe crear una solución de interoperación y usar segmentos de interoperación designados para cada modelo de representación que quiera usar. Además, el comportamiento de representación crea una restricción de "espacio aéreo" de lo que puede llevar a cabo su solución de interoperación. El concepto "espacio aéreo" se describe más detalladamente en el tema Información general sobre áreas de la tecnología.

Todos los elementos de WPF de la pantalla están respaldados en última instancia por un HWND. Al crear una Window de WPF, WPF crea un HWND de nivel superior y usa una clase HwndSource para colocar la Window y su contenido de WPF dentro del HWND. El resto del contenido de WPF de la aplicación comparte ese HWND en concreto. Una excepción son los menús, los menús desplegables de cuadro combinado y otros elementos emergentes. Estos elementos crean su propia ventana de nivel superior, razón por la cual un menú de WPF puede exceder el borde del HWND de ventana que lo contiene. Al usar HwndHost para colocar un HWND dentro de WPF, WPF informa a Win32 sobre cómo se debe colocar el nuevo HWND secundario con relación al HWND Window de WPF.

Un concepto relacionado con HWND es la transparencia dentro de cada HWND y entre cada uno de ellos. Esto también se describe en el tema Información general sobre áreas de la tecnología.

Hospedar contenido de WPF en una ventana de Microsoft Win32

La clave para hospedar WPF en una ventana de Win32 es la clase HwndSource. En esta clase se encapsula el contenido de WPF en una ventana de Win32, de manera que el contenido de WPF se puede incorporar a la interfaz de usuario como una ventana secundaria. En el siguiente enfoque se combina Win32 y WPF en una única aplicación.

  1. Implemente su contenido de WPF (el elemento raíz del contenido) como una clase administrada. Normalmente, la clase se hereda de una de las clases que pueden contener varios elementos secundarios o que se usan como elemento raíz, como DockPanel o Page. En los siguientes pasos se hace referencia a esta clase como "clase de contenido de WPF", mientras que las instancias de la clase se denominan "objetos de contenido de WPF".

  2. Implemente una aplicación Windows con C++/CLI. Si está empezando con una aplicación existente de C++ no administrada, normalmente puede habilitarla para llamar al código administrado cambiando la configuración del proyecto para incluir el indicador del compilador /clr (en este tema no se describe el ámbito completo de lo que podría necesitarse para admitir la compilación de /clr).

  3. Establezca el modelo de subprocesos en Contenedor uniproceso (STA). WPF usa este modelo de subprocesamiento.

  4. Controle la notificación WM_CREATE en el procedimiento de ventana.

  5. En el controlador (o en una función a la que llama el controlador), haga lo siguiente:

    1. Cree un nuevo objeto HwndSource con la ventana primaria HWND como parámetro parent.

    2. Cree una instancia de su clase de contenido WPF.

    3. Asigne una referencia al objeto de contenido WPF para la propiedad RootVisual del objeto HwndSource.

    4. La propiedad Handle del objeto HwndSource contiene el identificador de ventana (HWND). Para obtener un HWND que se pueda usar en la parte de la aplicación no administrada, convierta Handle.ToPointer() en un HWND.

  6. Implemente una clase administrada que contenga un campo estático para contener una referencia al objeto de contenido de WPF. Esta clase le permite obtener una referencia al objeto de contenido de WPF desde el código de Win32, pero lo más importante es que impide que la clase HwndSource se recopile involuntariamente como un elemento no utilizado.

  7. Adjunte un identificador a uno o varios de los eventos del objeto de contenido de WPF para recibir notificaciones del objeto de contenido WPF.

  8. Comuníquese con el objeto de contenido de WPF mediante la referencia que almacenó en el campo estático para establecer propiedades, llamar a métodos, etc.

Nota:

Puede hacer una parte o toda la definición de clase de contenido de WPF para el primer paso en XAML usando la clase parcial predeterminada de la clase de contenido. Para ello, debe crear un ensamblado independiente y hacer referencia a este. Aunque los objetos Application se suelen incluir como parte de la compilación del XAML en un ensamblado, al final no se usa dicho objeto Application como parte de la interoperación, sino que se usa únicamente una o varias de las clases raíz de los archivos XAML referenciados por la aplicación y se hace referencia a sus clases parciales. El resto del procedimiento es muy similar al descrito anteriormente.

Todos estos pasos se describen mediante código en el tema Tutorial: Hospedar contenido de WPF en Win32.

Hospedar una ventana de Microsoft Win32 en WPF

La clave para hospedar una ventana de Win32 dentro de otro contenido de WPF es la clase HwndHost. Esta clase encapsula la ventana en un elemento de WPF que se puede agregar a un árbol de elementos de WPF. HwndHost también admite las API con las que puede llevar a cabo estas tareas como mensajes de proceso para la ventana hospedada. El procedimiento básico es el siguiente:

  1. Cree un árbol de elementos para una aplicación de WPF (se puede efectuar mediante código o mediante marcado). Busque un punto adecuado y permitido en el árbol de elementos donde se pueda agregar la implementación HwndHost como un elemento secundario. En el resto de estos pasos, este elemento se conoce como el "elemento de reserva".

  2. Haga una derivación de HwndHost para crear un objeto que incluya el contenido de Win32.

  3. En esa clase de host, invalide el método HwndHostBuildWindowCore. Devuelva el HWND de la ventana hospedada. Tal vez quiera encapsular los controles reales como una ventana secundaria de la ventana devuelta; el encapsulamiento de los controles en una ventana host es una manera sencilla que tiene el contenido de WPF para recibir notificaciones de los controles. Esta técnica ayuda a corregir algunos problemas de Win32 relacionados con el control de los mensajes en el límite del control hospedado.

  4. Invalide los métodos HwndHostDestroyWindowCore y WndProc. Aquí, la intención es procesar una limpieza y quitar las referencias al contenido hospedado, sobre todo si creó referencias a objetos no administrados.

  5. En el archivo de código subyacente, cree una instancia del control que hospeda la clase y conviértalo en un elemento secundario del elemento de reserva. Los controladores de eventos se suelen usar como Loaded o bien suelen usar el constructor de clase parcial, pero también podría agregar el contenido de interoperación mediante un comportamiento en tiempo de ejecución.

  6. Procese los mensajes de la ventana seleccionada, como las notificaciones de control. Existen dos enfoques. Ambos proporcionan el mismo acceso a la secuencia de mensajes, por lo que su elección es más bien una cuestión de comodidad a la hora de programar.

    • Implemente el procesamiento de todos los mensajes (no solo de los mensajes de cierre) en la invalidación del método HwndHostWndProc.

    • Haga que el elemento de WPF de hospedaje procese los mensajes administrando el evento MessageHook. Este evento se genera para cada mensaje que se envía al procedimiento de ventana principal de la ventana hospedada.

    • No puede procesar mensajes de ventanas que están fuera del proceso mediante WndProc.

  7. Comuníquese con la ventana hospedada usando una invocación de plataforma para llamar a la función SendMessage no administrada.

Si sigue estos pasos, creará una aplicación que funcionará con la entrada del ratón. Puede agregar compatibilidad de tabulación a la ventana hospedada implementando la interfaz IKeyboardInputSink.

Todos estos pasos se describen mediante código en el tema Tutorial: Hospedar un control de Win32 en WPF.

HWND dentro de WPF

Puede considerar HwndHost como un control especial. (Técnicamente, HwndHost es una clase derivada FrameworkElement, no una clase derivada Control, pero se puede considerar como un control a efectos de interoperación). HwndHost abstrae la naturaleza subyacente de Win32 del contenido hospedado, de manera que el resto de WPF considera el contenido hospedado como otro objeto de tipo de control, que debería representar y procesar la entrada. HwndHost se suele comportar como cualquier otra clase FrameworkElement de WPF, aunque existen algunas diferencias importantes relacionadas con la salida (dibujos y gráficos) y la entrada (ratón y teclado), en función de las limitaciones de compatibilidad de los HWND subyacentes.

Diferencias notables en el comportamiento de salida

  • FrameworkElement, que es la clase base HwndHost, tiene varias propiedades que implican cambios en la interfaz de usuario. Entre ellas hay propiedades como FrameworkElement.FlowDirection, que cambia el diseño de los elementos que se encuentran dentro de ese elemento como elemento primario. Pero la mayoría de estas propiedades no están asignadas a posibles equivalentes de Win32, aunque dichos equivalentes existieran. Demasiadas de estas propiedades y de sus significados son tan específicos de la tecnología de representación que las asignaciones no resultan prácticas. Por este motivo, el hecho de establecer propiedades como FlowDirection en HwndHost no tiene ningún efecto.

  • HwndHost no se puede girar, escalar, sesgar ni verse afectada por una transformación.

  • HwndHost no admite la propiedad Opacity (combinación alfa). Si el contenido que se encuentra dentro de la clase HwndHost lleva a cabo operaciones System.Drawing que incluyen información alfa, no representa ninguna infracción, pero la clase HwndHost como un todo solo admite una opacidad de 1,0 (100 %).

  • HwndHost aparecerá por encima de otros elementos de WPF en la misma ventana de nivel superior. Pero un menú generado ToolTip o ContextMenu es una ventana de nivel superior independiente y, de esta manera, se comportará correctamente con HwndHost.

  • HwndHost no respeta la zona de recorte de su elemento primario UIElement. Puede llegar a ser un problema si intenta colocar una clase HwndHost dentro de una zona de desplazamiento o de un Canvas.

Diferencias notables en el comportamiento de entrada

  • En general, mientras que los dispositivos de entrada tienen el ámbito dentro de la zona hospedada de Win32 HwndHost, los eventos de entrada van directamente a Win32.

  • Mientras el ratón esté situado encima de la clase HwndHost, la aplicación no recibirá eventos de ratón de WPF y el valor de la propiedad IsMouseOver de WPF será false.

  • Mientras la clase HwndHost tenga el foco del teclado, la aplicación no recibirá eventos de teclado de WPF y el valor de la propiedad IsKeyboardFocusWithin de WPF será false.

  • Si el foco está en la clase HwndHost y cambia a otro control ubicado dentro de la clase HwndHost, la aplicación no recibirá los eventos GotFocus o LostFocus de WPF.

  • Las propiedades y los eventos de lápiz relacionados son análogos y no proporcionan información mientras el lápiz esté situado encima de HwndHost.

Tabulación, teclas de acceso y aceleradores

Las interfaces IKeyboardInputSink y IKeyboardInputSite le permiten crear una experiencia perfecta con el teclado para las aplicaciones de WPF y Win32 combinadas:

  • Tabulación entre componentes de Win32 y WPF

  • Las teclas de acceso y los aceleradores que funcionan tanto si el foco está dentro de un componente de Win32 como si está dentro de un componente de WPF.

Las clases HwndHost y HwndSource proporcionan implementaciones de IKeyboardInputSink, pero es posible que no puedan gestionar todos los mensajes de entrada que quiera para los escenarios más avanzados. Invalide los métodos adecuados para obtener el comportamiento de teclado que quiera.

Las interfaces solo proporcionan compatibilidad para lo que ocurre en la transición entre las zonas de WPF y Win32. Dentro de la zona de Win32, el comportamiento de la tabulación está totalmente controlado por la lógica implementada de Win32 de la tabulación, en el caso de que la haya.

Vea también