Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Actualización: noviembre 2007
Windows Presentation Foundation (WPF) proporciona un entorno avanzado para crear aplicaciones. Sin embargo, cuando se tiene una inversión sustancial en código Win32, puede ser más eficaz reutilizar la menor parte de este código en la aplicación WPF en lugar de reescribirla completamente. WPF proporciona un mecanismo sencillo para hospedar una ventana Win32 en una página de WPF.
En este tutorial se explica cómo crear una aplicación, Ejemplo Hosting a Win32 ListBox Control in Windows Presentation Foundation, que hospede un control de cuadro de lista Win32. Este procedimiento general se puede extender para hospedar cualquier ventana Win32.
Este tema contiene las secciones siguientes.
- Requisitos
- El procedimiento básico
- Implementar el diseño de página
- Implementar una clase para hospedar el control Microsoft Win32
- Hospedar el control en la página
- Implementar la comunicación entre el control y la página
- Temas relacionados
Requisitos
En este tutorial se da por hecho que está familiarizado con la programación básica en WPF y Win32. Para obtener una introducción básica a la programación en WPF, vea Introducción (WPF). Para obtener una introducción a la programación en Win32, puede consultar cualquiera de los numerosos libros sobre el tema, en particular Programming Windows escrito por Charles Petzold.
Como el ejemplo que acompaña a este tutorial se implementa en C#, se utiliza Servicios de invocación de plataforma (PInvoke) para tener acceso a la API de Win32. Puede ser útil tener algún conocimiento sobre PInvoke, pero no esencial.
Nota
En este tutorial se incluyen varios ejemplos de código del ejemplo asociado. Sin embargo, para facilitar la lectura, no se incluye el código de ejemplo completo. Puede obtener o ver el código completo en Ejemplo Hosting a Win32 ListBox Control in Windows Presentation Foundation.
El procedimiento básico
En esta sección se describe el procedimiento básico para hospedar una ventana Win32 en una página de WPF. En las demás secciones se explican los detalles de cada paso.
El procedimiento de hospedaje básico es el siguiente:
Implemente una página de WPF para hospedar la ventana. Una técnica es crear un elemento Border para reservar una sección de la página de la ventana hospedada.
Implemente una clase para hospedar el control que se hereda de HwndHost.
En esta clase, invalide el miembro BuildWindowCore de la clase HwndHost.
Cree la ventana hospedada como un elemento secundario de la ventana que contiene la página de WPF. Aunque la programación de WPF convencional no necesita utilizar esta ventana explícitamente, la página de hospedaje es una ventana con un controlador (HWND). Recibirá la página HWND a través del parámetro hwndParent del método BuildWindowCore. La ventana hospedada se debe crear como un elemento secundario de HWND.
Una vez creada la ventana host, devuelva el HWND de la ventana hospedada. Si desea hospedar uno o varios controles Win32, normalmente creará una ventana host como un elemento secundario de HWND y convertirá los controles en elementos secundarios de esa ventana host. Alojar los controles en una ventana host es un modo simple de permitir que la página WPF reciba las notificaciones de los controles y de abordar algunos de los problemas de Win32 con las notificaciones a través de los límites de HWND.
Controle algunos de los mensajes enviados a la ventana host, como las notificaciones de los controles secundarios. Existen dos formas de lograr esto.
Si prefiere controlar los mensajes en la clase de hospedaje, invalide el método WndProc de la clase HwndHost.
Si prefiere que WPF controle los mensajes, controle el evento MessageHook de la clase HwndHost en el código subyacente. Este evento se produce para cada mensaje recibido por la ventana hospedada. Si elige esta opción, debe invalidar igualmente WndProc, pero sólo necesita una implementación mínima.
Invalide los métodos DestroyWindowCore y WndProc de HwndHost. Debe invalidar estos métodos para cumplir el contrato de HwndHost, pero es posible que sólo necesite proporcionar una implementación mínima.
En su archivo de código subyacente, cree una instancia de la clase que aloja los controles y conviértala en un elemento secundario del elemento Border que va a hospedar la ventana.
Comuníquese con la ventana hospedada enviándole mensajes Microsoft Windows y controlando los mensajes de sus ventanas secundarias, como las notificaciones enviadas por los controles.
Implementar el diseño de página
El diseño de la página WPF que hospeda el control ListBox consta de dos regiones. El margen izquierdo de la página hospeda varios controles WPF que proporcionan una interfaz de usuario (UI) que le permite manipular el control Win32. La esquina superior derecha de la página tiene una región cuadrada para el control ListBox hospedado.
El código para implementar este diseño es bastante simple. El elemento raíz es un DockPanel que tiene dos elementos secundarios. El primero es un elemento Border que hospeda el control ListBox. Ocupa un espacio cuadrado de 200 x 200 en la esquina superior derecha de la página. El segundo es un elemento StackPanel que contiene un conjunto de los controles WPF que muestran información y le permiten manipular el control ListBox estableciendo las propiedades de interoperación expuestas. Para cada uno de los elementos que son elementos secundarios de StackPanel, consulte el material de referencia de los distintos elementos utilizados para obtener detalles sobre qué son estos elemento y qué hacen. Estos elementos se muestran en el código de ejemplo siguiente, pero no se ofrece una explicación de ellos (no son necesarios para el modelo básico de interoperación; se proporcionan para agregar interactividad al ejemplo).
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WPF_Hosting_Win32_Control.HostWindow"
Name="mainWindow"
Loaded="On_UIReady">
<DockPanel Background="LightGreen">
<Border Name="ControlHostElement"
Width="200"
Height="200"
HorizontalAlignment="Right"
VerticalAlignment="Top"
BorderBrush="LightGray"
BorderThickness="3"
DockPanel.Dock="Right"/>
<StackPanel>
<Label HorizontalAlignment="Center"
Margin="0,10,0,0"
FontSize="14"
FontWeight="Bold">Control the Control</Label>
<TextBlock Margin="10,10,10,10" >Selected Text: <TextBlock Name="selectedText"/></TextBlock>
<TextBlock Margin="10,10,10,10" >Number of Items: <TextBlock Name="numItems"/></TextBlock>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Append an Item to the List</Label>
<StackPanel Orientation="Horizontal">
<Label HorizontalAlignment="Left"
Margin="10,10,10,10">Item Text</Label>
<TextBox HorizontalAlignment="Left"
Name="txtAppend"
Width="200"
Margin="10,10,10,10"></TextBox>
</StackPanel>
<Button HorizontalAlignment="Left"
Click="AppendText"
Width="75"
Margin="10,10,10,10">Append</Button>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Delete the Selected Item</Label>
<Button Click="DeleteText"
Width="125"
Margin="10,10,10,10"
HorizontalAlignment="Left">Delete</Button>
</StackPanel>
</DockPanel>
</Window>
Implementar una clase para hospedar el control Microsoft Win32
El núcleo de este ejemplo es la clase que realmente hospeda el control, ControlHost.cs. Se hereda de HwndHost. El constructor toma dos parámetros, el alto y el ancho, que se corresponden con el alto y ancho del elemento Border que hospeda el control ListBox. Estos valores se utilizan después para garantizar que el tamaño del control coincide con el elemento Border.
public class ControlHost : HwndHost
{
IntPtr hwndControl;
IntPtr hwndHost;
int hostHeight, hostWidth;
public ControlHost(double height, double width)
{
hostHeight = (int)height;
hostWidth = (int)width;
}
También hay un conjunto de constantes. Estas constantes se obtienen principalmente de Winuser.h y permiten utilizar nombres convencionales al llamar a las funciones Win32.
internal const int
WS_CHILD = 0x40000000,
WS_VISIBLE = 0x10000000,
LBS_NOTIFY = 0x00000001,
HOST_ID = 0x00000002,
LISTBOX_ID = 0x00000001,
WS_VSCROLL = 0x00200000,
WS_BORDER = 0x00800000;
Invalidar BuildWindowCore para crear la ventana Microsoft Win32
Este método se invalida para crear la ventana Win32 que se hospedará en la página y realizar la conexión entre la ventana y la página. Como este ejemplo implica hospedar un control ListBox Control, se crean dos ventanas. La primera es la ventana que se hospeda realmente en la página WPF. El control ListBox se crea como un elemento secundario de esa ventana.
La razón de este enfoque es simplificar el proceso de recepción de notificaciones desde el control. La clase HwndHost permite procesar los mensajes enviados a la ventana hospedada. Si hospeda directamente un control Win32, recibirá los mensajes enviados al bucle de mensajes interno del control. Puede mostrar el control y enviarle mensajes, pero no recibirá las notificaciones que el control envía a su ventana primaria. Esto significa, entre otras cosas, que no hay forma de detectar cuándo el usuario interactúa con el control. En lugar de ello, cree una ventana host y convierta el control en un elemento secundario de esa ventana. Esto le permite procesar los mensajes de la ventana host incluidas las notificaciones enviadas a ella por el control. Por comodidad, como la ventana host es básicamente un contenedor simple del control, el paquete recibirá el nombre de control ListBox.
Crear la ventana host y el control ListBox
Puede utilizar PInvoke para crear una ventana host para el control creando y registrando una clase de ventana, etc. Sin embargo, un procedimiento mucho más simple es crear una ventana con la clase de ventana "estática" predefinida. De esta forma, obtendrá el procedimiento de la ventana que necesita para recibir las notificaciones del control, sin tener apenas que escribir código.
El HWND del control se expone a través de una propiedad de sólo lectura, que la página host puede utilizar para enviar mensajes al control.
public IntPtr hwndListBox
{
get { return hwndControl; }
}
El control ListBox se crea como un elemento secundario de la ventana host. El alto y ancho de ambas ventanas se establecen en los valores pasados al constructor, descritos anteriormente. Esto garantiza que el tamaño de la ventana host y el control sea idéntico al del área reservada en la página. Una vez creadas las ventanas, el ejemplo devuelve un objeto HandleRef que contiene el HWND de la ventana host.
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
hwndControl = IntPtr.Zero;
hwndHost = IntPtr.Zero;
hwndHost = CreateWindowEx(0, "static", "",
WS_CHILD | WS_VISIBLE,
0, 0,
hostHeight, hostWidth,
hwndParent.Handle,
(IntPtr)HOST_ID,
IntPtr.Zero,
0);
hwndControl = CreateWindowEx(0, "listbox", "",
WS_CHILD | WS_VISIBLE | LBS_NOTIFY
| WS_VSCROLL | WS_BORDER,
0, 0,
hostHeight, hostWidth,
hwndHost,
(IntPtr) LISTBOX_ID,
IntPtr.Zero,
0);
return new HandleRef(this, hwndHost);
}
//PInvoke declarations
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
Implementar DestroyWindow y WndProc
Además de BuildWindowCore, debe invalidar también los métodos WndProc y DestroyWindowCore de HwndHost. En este ejemplo, los mensajes del control se controlan mediante el controlador MessageHook, por lo que la implementación de WndProc y DestroyWindowCore es mínima. En el caso de WndProc, establezca handled en false para indicar que el mensaje no se ha controlado y devuelva 0. Para DestroyWindowCore, simplemente destruya la ventana.
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
handled = false;
return IntPtr.Zero;
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hwnd.Handle);
}
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
Hospedar el control en la página
Para hospedar el control en la página, primero debe crear una nueva instancia de la clase ControlHost. Pase el alto y ancho del elemento de esquina que contiene el control (ControlHostElement) al constructor ControlHost. De esta forma, se asegurará de que el control ListBox tiene el tamaño correcto. A continuación, hospede el control en la página asignando el objeto ControlHost a la propiedad Child del Border host.
En el ejemplo se asocia un controlador al evento MessageHook de ControlHost para recibir los mensajes del control. Este evento se provoca para cada mensaje enviado a la ventana hospedada. En este caso, éstos son los mensajes enviados a la ventana que contiene el control ListBox real, incluidas las notificaciones del control. En el ejemplo se llama a SendMessage para obtener información del control y modificar su contenido. Los detalles sobre cómo la página se comunica con el control se describen en la sección siguiente.
Nota
Observe que hay dos declaraciones PInvoke para SendMessage. Esto es necesario porque una utiliza el parámetro wParam para pasar una cadena y la otra lo utiliza para pasar un valor entero. Necesita una declaración distinta para cada firma con el fin de garantizar que las referencias a los datos se calculan correctamente.
public partial class HostWindow : Window
{
int selectedItem;
IntPtr hwndListBox;
ControlHost listControl;
Application app;
Window myWindow;
int itemCount;
private void On_UIReady(object sender, EventArgs e)
{
app = System.Windows.Application.Current;
myWindow = app.MainWindow;
myWindow.SizeToContent = SizeToContent.WidthAndHeight;
listControl = new ControlHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);
ControlHostElement.Child = listControl;
listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);
hwndListBox = listControl.hwndListBox;
for (int i = 0; i < 15; i++) //populate listbox
{
string itemText = "Item" + i.ToString();
SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, itemText);
}
itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + itemCount.ToString();
}
private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int textLength;
handled = false;
if (msg == WM_COMMAND)
{
switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
{
case LBN_SELCHANGE : //Get the item text and display it
selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);
StringBuilder itemText = new StringBuilder();
SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);
selectedText.Text = itemText.ToString();
handled = true;
break;
}
}
return IntPtr.Zero;
}
internal const int
LBN_SELCHANGE = 0x00000001,
WM_COMMAND = 0x00000111,
LB_GETCURSEL = 0x00000188,
LB_GETTEXTLEN = 0x0000018A,
LB_ADDSTRING = 0x00000180,
LB_GETTEXT = 0x00000189,
LB_DELETESTRING = 0x00000182,
LB_GETCOUNT = 0x0000018B;
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
int wParam,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
String lParam);
Implementar la comunicación entre el control y la página
El control se manipula enviándole mensajes Windows. El control le indica cuándo el usuario interactúa con él enviando notificaciones a su ventana host. En el ejemplo Ejemplo Hosting a Win32 ListBox Control in Windows Presentation Foundation se incluye una interfaz de usuario que proporciona varios ejemplos sobre cómo funciona esto:
Asocie un elemento a la lista.
Elimine el elemento seleccionado de la lista
Muestre el texto del elemento actualmente seleccionado.
Muestre el número de elementos de la lista.
El usuario puede seleccionar también un elemento del cuadro de lista haciendo clic en él, como ocurre en cualquier aplicación Win32 convencional. Los datos mostrados se actualizan cada vez que el usuario cambia el estado del cuadro de lista seleccionándolo, agregando o anexando un elemento.
Para anexar elementos, envíe un mensaje LB_ADDSTRING al cuadro de lista. Para eliminar elementos, envíe LB_GETCURSEL para obtener el índice de la selección actual y, después, LB_DELETESTRING para eliminar el elemento. En el ejemplo se envía también LB_GETCOUNT y se utiliza el valor devuelto para actualizar la pantalla que muestra el número de elementos. Ambas instancias de SendMessage utilizan una de las declaraciones PInvoke descritas en la sección anterior.
private void AppendText(object sender, EventArgs args)
{
if (txtAppend.Text != string.Empty)
{
SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, txtAppend.Text);
}
itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + itemCount.ToString();
}
private void DeleteText(object sender, EventArgs args)
{
selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
if (selectedItem != -1) //check for selected item
{
SendMessage(hwndListBox, LB_DELETESTRING, (IntPtr)selectedItem, IntPtr.Zero);
}
itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + itemCount.ToString();
}
Cuando el usuario selecciona un elemento que cambia su selección, el control se lo notifica a la ventana host enviándole un mensaje WM_COMMAND, que provoca el evento MessageHook para la página. El controlador recibe la misma información que el procedimiento de la ventana principal de la ventana host. Pasa también una referencia a un valor booleano, handled. handled se establece en true para indicar que se ha controlado el mensaje, y ya no hace falta más procesamiento.
WM_COMMAND se envía por varias razones, por lo que debe examinar el identificador de la notificación para determinar si es un evento que desea controlar. El identificador se incluye en los bytes más significativos del parámetro wParam. Como Microsoft .NET no tiene una macro HIWORD, en el ejemplo se utilizan operadores bit a bit para extraer el identificador. Si el usuario ha realizado o cambiado su selección, el identificador será LBN_SELCHANGE.
Cuando se recibe LBN_SELCHANGE, el ejemplo obtiene el índice del elemento seleccionado enviando un mensaje LB_GETCURSEL al control. Para obtener el texto, primero creará StringBuilder. A continuación, enviará un mensaje LB_GETTEXT al control. Pase el objeto StringBuilder vacío como parámetro wParam. Cuando SendMessage termine de procesarse, StringBuilder contendrá el texto del elemento seleccionado. Este uso de SendMessage requiere una nueva declaración PInvoke.
Por último, establezca handled en true para indicar que el mensaje se ha controlado. En el código siguiente se hace hincapié de nuevo en el método ControlMsgFilter, que es donde se implementa este comportamiento.
private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int textLength;
handled = false;
if (msg == WM_COMMAND)
{
switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
{
case LBN_SELCHANGE : //Get the item text and display it
selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);
StringBuilder itemText = new StringBuilder();
SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);
selectedText.Text = itemText.ToString();
handled = true;
break;
}
}
return IntPtr.Zero;
}
Vea también
Conceptos
Información general sobre la interoperabilidad de WPF y Win32
Información general sobre Windows Presentation Foundation