Exportar (0) Imprimir
Expandir todo
Expandir Minimizar

Alojamiento de un control nativo de Windows en un control de formulario de .NET Compact Framework

18 de Julio de 2005

Publicado: Octubre de 2004

Peter Foot

Microsoft Embedded MVP

In The Hand Ltd

Este artículo se aplica a:

Microsoft® Windows® CE

Microsoft® Windows® CE .NET

Descargar ejemplo.

Resumen: en este artículo se analizará una técnica para alojar controles nativos de Windows, en este caso el control de explorador Web HTML, dentro de un control personalizado de .NET Compact Framework. El método descrito admite la comunicación bidireccional con el control nativo, de manera que se puedan generar eventos en función de la interacción del usuario. (6 páginas impresas.)

En esta página

Introducción Introducción
Clase ControlEx Clase ControlEx
Identificadores de ventana Identificadores de ventana
Compatibilidad con el diseñador Compatibilidad con el diseñador
Control WebBrowser Control WebBrowser
Comunicación con el control nativo Comunicación con el control nativo
Eventos Eventos
Creación en tiempo de diseño Creación en tiempo de diseño
Aplicación de ejemplo Aplicación de ejemplo
Conclusión Conclusión
Recursos adicionales Recursos adicionales

Introducción

Si ya ha invertido en la creación de un control nativo de Windows y desea utilizarlo en su código de .NET Compact Framework o si desea aprovechar algunos controles de Windows CE intrínsecos que no son compatibles directamente con .NET Compact Framework, necesitará un método para interoperar con los controles de ventana nativos. OpenNETCF Smart Device Framework incluye una nueva implementación personalizada que utiliza la clase MessageWindow. Platform Invoke se emplea para crear una ventana visible que es capaz de alojar controles nativos. En este artículo se explicará cómo crear un control personalizado que aloje un control nativo de Windows: el ejemplo será un control administrado a partir del control HTML disponible en Windows CE.

Clase ControlEx

Al igual que ocurre con todos los controles personalizados de .NET Compact Framework, el desarrollo comienza por una clase derivada de System.Windows.Forms.Control. Sin embargo, para realizar gran parte del trabajo interno necesario para alojar un control nativo, se ha creado una clase OpenNETCF.Windows.Forms.ControlEx.

La clase ControlEx trabaja alojando una clase MessageWindow modificada. Como MessageWindow se crea como una ventana nativa invisible de 0 por 0 píxeles, resulta un destino ideal para los mensajes de Windows, pero no puede desempeñar ninguna función útil como parte de la interfaz de usuario. Por tanto, utilizaremos Platform Invoke para modificar las propiedades de esta ventana, conseguir que sea visible y, de esta manera, hacer posible que hospede otros controles en su interior.

ControlEx se ocupa de la creación de un ControlMessageWindow y del control nativo que se seleccione. En tiempo de ejecución, ControlMessageWindow recibe todos los mensajes de notificación del control nativo y los pasa al método OnNotifyMessage de la clase derivada ControlEx, en este caso WebBrowser. ControlEx responde automáticamente a eventos como cambios de tamaño, de enfoque, etc. y cambia automáticamente el tamaño del control nativo.

Cuando se crea un nuevo ControlMessageWindow, se asigna una referencia al control principal administrado. Mediante Platform Invoke, se llama al método SetWindowParent API para conseguir que ControlMessageWindow sea un secundario de la clase derivada ControlEx. El propio ControlEx crea el control nativo como secundario de ControlMessageWindow. De este modo, MessageWindow recibe los mensajes de notificación procedentes del control nativo y los pasa a la clase derivada administrada ControlEx.

Figura 1. Interacción de los códigos nativo y administrado

Funcionalidad estándar

La clase ControlEx dispone de una implementación integrada para un determinado número de propiedades que son útiles para el control derivado, entre las que se incluyen BorderStyle, Handle, Name y Tag.

Identificadores de ventana

.NET Compact Framework no expone los identificadores de ventana nativos de los controles. La clase ControlEx implementa la interfaz IWin32Window que está presente en la versión completa de .NET Framework, pero que OpenNETCF agrega a .NET Compact Framework. Esta interfaz define una propiedad Handle, que se utiliza para exponer el identificador de ventana del propio control administrado. Este identificador se determina estableciendo Capture en el control y utilizando la función API GetCapture para obtener el identificador de ventana:

/// <summary>
/// Native Window Handle
/// </summary>
public IntPtr Handle
{
   get
   {
      if(m_handle==IntPtr.Zero)
      {
         this.Capture = true;
         m_handle = OpenNETCF.Win32.Win32Window.GetCapture();
         this.Capture = false;
      }
      return m_handle;
   }
}

Igualmente, ControlEx expone una propiedad ChildHandle a las clases derivadas, lo que proporciona un fácil acceso al identificador de ventana (HWND) del control nativo que se está alojando. Se utiliza con gran frecuencia, ya que se invocan las propiedades y los métodos del control nativo enviando mensajes a este identificador.

Compatibilidad con el diseñador

El código ControlEx se ha escrito para proporcionar la compatibilidad en tiempo de diseño más básica, que realiza el cambio del tamaño del control en el formulario y simplemente lo rellena con un rectángulo con el color BackColor de los controles. Si BorderStyle está establecido en FixedSingle o Fixed3D, se dibuja un cuadro negro alrededor del perímetro del control (no se admite el aspecto 3D del borde). Los diferentes controles que se derivan de ControlEx pueden ofrecer un comportamiento adicional en tiempo de diseño, según sea adecuado, omitiendo el método OnPaint.

Control WebBrowser

Al utilizar este código genérico de la clase ControlEx, se reduce la carga de trabajo necesaria para crear un control de contenedor administrado. El único código adicional necesario en el constructor del control WebBrowser consiste en invocar a la función API InitHtmlControl para preparar el control para su uso. A continuación, podemos establecer los CreateParams de ControlEx con el nombre de clase "DISPLAYCLASS".

public WebBrowser() : base(true)
{
   //new stack (for history urls)
   m_history = new Stack();
         
   //load htmlview module
   IntPtr module = Core.LoadLibrary("htmlview.dll");

   //init htmlcontrol
   int result = InitHTMLControl(Instance);

   //set the html specific class name of the native control
   this.CreateParams.ClassName = "DISPLAYCLASS";
}

El nombre de clase del control nativo se puede encontrar en el archivo de encabezado del control: necesitará el SDK de la plataforma de destino que debe contener htmlctrl.h. También será necesario el archivo de encabezado del control nativo como referencia para comenzar a enlazar las propiedades, los métodos y los eventos del control concreto. En el ejemplo WebBrowser, se han incluido todas las constantes y las estructuras necesarias dentro del código WebBrowser.

Comunicación con el control nativo

Con el constructor configurado para crear el control nativo, se puede crear un proyecto de prueba y agregar al mismo el control mediante código (todavía no hemos creado el ensamblado del diseñador), para que se muestre el WebBrowser. No obstante, todavía no se ha incorporado la funcionalidad que permita explorar o establecer otras propiedades del control.

La comunicación entre los controles nativos se consigue mediante mensajes de Windows que se envían al control o mensajes WM_NOTIFY que se reciben del mismo. En algunos casos, se modifican otros parámetros estableciendo un determinado estilo de ventana en el control. En el mundo administrado, los controles tienen propiedades que permiten obtener o establecer valores, métodos que permiten realizar acciones y eventos que se generan cuando ocurre algo en el control. A continuación se muestra cómo se suelen traducir al introducir en un contenedor un control nativo:

Administrado

Nativo

Obtener propiedad

Se envía un mensaje concreto al control y se devuelve el valor. Para datos complejos, se envía junto con el mensaje un puntero a un búfer que se rellena con los datos necesarios.

Establecer propiedad

Se envía un mensaje concreto al control con el valor como un parámetro. Para datos complejos, se envía un puntero de memoria nativa que apunta a la estructura que contiene los datos.

O bien, se envía un determinado estilo de ventana para el control nativo.

Método

Se envía un determinado mensaje al control, si se desea, con parámetros mediante un puntero a una estructura.

Evento

Mensaje WM_NOTIFY recibido del control. Contiene un puntero a información adicional que describe el evento del que se pueden calcular las referencias a una estructura administrada.

En el caso del control WebBrowser, probablemente el mensaje más importante que se puede enviar es la exploración de una determinada dirección URL. Se almacena una copia de la dirección URL en el campo privado m_url. Se calcula la referencia de la cadena a la memoria nativa con un carácter nulo agregado al final para marcar el final de la cadena y el puntero se envía al control junto con el mensaje DTM.NAVIGATE. El método API SendMessage de Windows se utiliza para enviar el mensaje al control alojado. A continuación, se libera la memoria nativa utilizada.

public void Navigate(string url)
{
   //allocate temporary native buffer
   IntPtr stringptr = MarshalEx.StringToHGlobalUni(url + '\0');
            
   //send message to native control
   Win32Window.SendMessage(ChildHandle,(int)DTM.NAVIGATE, 0, (int)stringptr);

   //free native memory
   MarshalEx.FreeHGlobal(stringptr);
}

El valor de los mensajes utilizados se encuentra en el archivo de encabezado correspondiente al control. Para que se pueda obtener acceso a ellos en el código, puede almacenarlos como una enumeración (como se ha realizado en el código de ejemplo), como constantes privadas o puede incluirlos en el código en los bloques correspondientes (no se recomienda). Consulte la enumeración DTM del código para ver todos los mensajes del control HTML admitidos y sus valores.

Todas las implementaciones de propiedades y métodos siguen el mismo patrón básico. Todos los datos enviados junto con el mensaje deberán colocarse en la memoria no administrada y ésta se debe liberar posteriormente. Encontrará un buen número de métodos útiles para asignar y liberar memoria en la clase OpenNETCF.Runtime.InteropServices.MarshalEx.

Eventos

La implementación de ControlEx resulta realmente valiosa cuando hay que reaccionar a los eventos del control nativo. Los controles nativos envían sus notificaciones de vuelta a una ventana principal. La clase base del control no expone un método WndProc para procesar los mensajes entrantes, por lo que no es posible alojar controles nativos directamente y recibir sus eventos. ControlMessageWindow es una capa adicional entre el control administrado y el control nativo de Windows, que es capaz de recibir y procesar mensajes entrantes de Windows.

Para capturar los eventos del control nativo, es necesario proporcionar una implementación del método OnNotifyMessage, que es en realidad nuestro WndProc para el control. El SDK define un número de mensajes de notificación enviados por el control HTML a su principal. En el código fuente de WebBrowser se han definido en la enumeración NM. Todos los mensajes pasados al control tendrán el valor Msg igual a WM_NOTIFY. LParam apunta a una estructura de notificación específica del control, que comenzará con los miembros NMHDR que identifican al remitente, un identificador personalizado (que no se utiliza) y el código de notificación. Hemos configurado una instrucción switch para comprobar el campo de código respecto a los mensajes de notificación conocidos y realizar las acciones adecuadas.

La primera tarea que hay que llevar a cabo es calcular la referencia a los datos adjuntos a esta notificación en la memoria administrada. Estos datos son específicos del control de Windows; en el caso de WebBrowser definen la dirección URL y otros datos relacionados con el evento que se ha producido. Una vez más, la estructura de estos datos queda definida en el archivo de encabezado htmlctrl.h de la estructura NM_HTMLVIEW. A continuación, se utiliza el campo de "código" para determinar de qué tipo de notificación se trata y la acción que hay que realizar.

//get html viewer specific data from the notification message
NM_HTMLVIEW nmhtml = (NM_HTMLVIEW)Marshal.PtrToStructure(m.LParam,typeof(NM_HTMLVIEW));

//marshal the Target string
string target = MarshalEx.PtrToStringAuto(nmhtml.szTarget);

//check the incoming message code and process as required
switch(nmhtml.code)
{
      //hotspot click
      case (int)NM.HOTSPOT:
      case (int)NM.BEFORENAVIGATE:
         OnNavigating(new WebBrowserNavigatingEventArgs(target));
         break;

Esta sección del código muestra el evento que hay que tratar con mayor frecuencia: la exploración que se produce cuando el control comienza a explorar una nueva página. Cuando se produce este evento uno de los miembros de la estructura de datos de notificación apunta a una cadena que indica la dirección URL de la página solicitada. Se calcula la referencia de este puntero a una cadena administrada y se utiliza al crear una instancia de la clase personalizada WebBrowserNavigatingEventArgs. Por último, se pasa al método OnNavigating que se encargará de generar el evento.

protected virtual void OnNavigating(WebBrowserNavigatingEventArgs e)
{
   if(Navigating!=null)
   {
      Navigating(this,e);
   }
}

Este método comprueba primero si hay suscriptores al evento. Si los hay, se genera el evento pasando los argumentos del evento que se han creado. Un consumidor de este evento puede utilizar la dirección URL para determinar la acción que hay que llevar a cabo. Si está tratando un evento del control nativo que necesita devolver información, como se ha hecho con la dirección URL, necesitaremos utilizar una estrategia similar, en la que una clase personalizada EventArgs se pasa junto con el evento.

Creación en tiempo de diseño

La posibilidad de agregar un control a un proyecto y diseñar la interfaz de usuario mediante el diseñador de formularios de Visual Studio es muy importante. Por tanto, hemos proporcionado cierta funcionalidad adicional para implementar este aspecto. En primer lugar, el entorno de escritorio no tiene información acerca del control MessageWindow o de las P/Invokes específicas de la plataforma, como SendMessage, por lo que es necesario ocultar la implementación de la creación en tiempo de diseño. Para ello, se agrega una compilación condicional al código fuente. En todas nuestras clases OpenNETCF utilizamos la constante "DESIGN" para crear versiones de tiempo de diseño de nuestros controles. Al final de este artículo encontrará un vínculo a un magnífico artículo sobre la creación de controles compatibles con el tiempo de diseño escrito por Alex Yakhnin. Por tanto, en las secciones del código en las que se haga referencia a código específico de la plataforma, como en el constructor, agregamos la instrucción #if:

#if !DESIGN
      //load htmlview module
      IntPtr module = Core.LoadLibrary("htmlview.dll");

      //init htmlcontrol
      int result = InitHTMLControl(Instance);

      //set the html specific classname of the native control
      this.CreateParams.ClassName = "DISPLAYCLASS";
#endif

En efecto, el diseñador es completamente ajeno al funcionamiento interno de nuestro control, aunque seguirá mostrando los eventos y las propiedades públicas que hemos definido. Para el WebBrowser no he implementado ninguna compatibilidad más con el tiempo de diseño y heredará su dibujo de la clase ControlEx. No resulta imprescindible que el control de procesamiento HTML funcione completamente en el diseñador de formularios, aunque es muy importante obtener la correcta posición del control en tiempo de ejecución.

Aplicación de ejemplo

Los proyectos Pocket Browser (C#) y Pocket Browser VB son idénticos para Pocket PC que utilizan el control WebBrowser. Estos proyectos muestran cómo se agrega el control al formulario en la vista de diseño de la misma manera que cualquier otro control. Los demás controles del formulario ilustran cómo llamar a ciertas propiedades y métodos del control para visitar una determinada dirección URL o utilizar el método GoBack. La aplicación trata varios eventos generados por el control WebBrowser.

Cuando WebBrowser indica que ha comenzado la exploración, se muestra WaitCursor. Este cursor se borra cuando se genera el evento DocumentCompleted. Con el evento DocumentTitleChanged, se actualiza la propiedad Form.Text para reflejar el título del documento HTML actual.

[C#]
//update the page title
private void webBrowser1_DocumentTitleChanged(object sender, EventArgs e)
{
   this.Text = webBrowser1.DocumentTitle;
}
[VB]
Private Sub WebBrowser1_DocumentTitleChanged(ByVal sender As Object,
   ByVal e As System.EventArgs) Handles WebBrowser1.DocumentTitleChanged

   'update title to document title
   Me.Text = WebBrowser1.DocumentTitle

End Sub

El control WebBrowser no tiene por qué utilizarse únicamente para explorar sitios de Internet. Como se puede proporcionar un formato fuente HTML al control y capturar eventos cuando se puntee en los vínculos, es posible utilizarlo para proporcionar una interfaz de usuario HTML generada de manera dinámica para la aplicación.

Conclusión

WebBrowser implementa varias propiedades, métodos y eventos; todos ellos siguen el modelo básico descrito anteriormente. El código fuente del control se incluye junto con los proyectos de exploradores Web de ejemplo en C# y en VB. Se recomienda encarecidamente que examine el código tanto de ControlEx como de WebBrowser para comprender el proceso y su aplicación a otros controles nativos. Con la clase ControlEx se puede implementar un número de controles que antes no estaban disponibles en .NET Compact Framework y permite trabajar completamente con los eventos generados por el control nativo. Utilizando estas mismas técnicas se han creado los controles InkX y MonthCalendar para Smart Device Framework.

Recursos adicionales

Para obtener más información acerca de la creación de controles de tiempo de diseño para .NET Compact Framework, consulte el artículo http://www.intelliprog.com (en inglés).

Para obtener más detalles sobre Smart Device Framework, consulte http://www.opennetcf.org/sdf/ (en inglés).

Mostrar:
© 2015 Microsoft