Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

HTML dentro de aplicaciones GUI

Mientras esperamos por Avalon, la nueva interfaz gráfica de usuarios que incluirá Longhorn, muchas aplicaciones implementan algunas de sus operaciones mediante HTML. ¿Simple moda? Se me ocurren algunas razones adicionales. Por ejemplo, las hojas de estilo en cascada (CSS) permiten cambiar radicalmente la apariencia de una página sin demasiado esfuerzo. El uso de HTML permite también que el diseño gráfico de la aplicación sea responsabilidad de un verdadero diseñador, y no del programador. Por último, existen determinadas situaciones en las que HTML ofrece una solución más sencilla y flexible. Estoy pensando ahora en la programación de asistentes (wizards). La técnica tradicional exige trabajar con controles de páginas, esconder las solapas de las páginas, y programar la enrevesada lógica de la secuencia de páginas. En cambio, parece más fácil lograr el mismo efecto con varias páginas HTML independientes, mostrándose consecutivamente en un mismo espacio visual.

EL CONTROL TWebBrowser

Si programamos en Delphi, la forma más sencilla de incluir páginas HTML en una aplicación GUI consiste en utilizar el control TWebBrowser, ubicado en la página Internet de la Paleta de Componentes. Este control corresponde en realidad a un control ActiveX importado por Delphi como componente. La clase ActiveX subyacente es utilizada por Internet Explorer, y lo normal es que ya esté presente en cualquier ordenador que tenga instalado Windows.


Existen también controles de visualización HTML que no utilizan Internet Explorer para nada. La mayoría son de pago, pero creo haber visto alguno gratuito.

El uso de TWebBrowser es cosa de niños. Basta con dejar caer uno de estos controles en un formulario, y utilizar alguna de las versiones de su método Navigate para cargar el contenido de una URL. El control es capaz de interpretar automáticamente cualquier script asociado a la página, y soporta automáticamente la mayoría de las extensiones DHTML: hojas de estilo, behaviors, etc. La versión más sencilla de Navigate es la siguiente:

WebBrowser1.Navigate('http://www.marteens.com');

Ahora le contaré un truco sencillo con una explicación complicada: es muy buena idea forzar a TWebBrowser para que cargue, aunque sea, el documento vacío. Para lograrlo, podemos pedir al control que navegue a la URL especial about:blank.

procedure TForm1.FormCreate(Sender: TObject);
begin
  WebBrowser1.Navigate('about:blank');
end;

La explicación consiste en que el control TWebBrowser, cuya clase ActiveX está implementada en shdocvw.dll utiliza en realidad una clase de más bajo nivel, implementada dentro de mshtml.dll. La clase de más bajo nivel se encarga solamente de la lectura y visualización de documentos HTML. Recuerde, sin embargo, que Internet Explorer puede mostrar en su interior otros tipos de contenido: un fichero PDF, por ejemplo, o una hoja de cálculo de Excel. Es esta funcionalidad adicional la que aporta la clase implementada por shdocvw.dll. ¿Qué tiene que ver esto con la carga de about:blank? Pues que mshtml.dll no se cargará mientras TWebBrowser no haya navegado al menos a una dirección. Este control, por ejemplo, no mostraría su borde interno hasta ese momento. Pero hay algo más importante: muchos trucos hacen uso de la propiedad Document de TWebBrowser. Esa propiedad contendrá una referencia nula hasta el momento en que carguemos la primera URL en el control.

Si necesitamos asegurarnos de la instanciación de la propiedad TWebBrowser.Document, no podemos fiarnos solamente de la navegación a about:blank, porque la carga puede tener lugar de forma asíncrona. Podemos resolverlo declarando una variable dentro del formulario:

private
  FCompletado: Boolean;

A continuación, debemos interceptar el evento OnDocumentCompleted de TWebBrowser:

procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  FCompletado := True;
end;

Si el documento que cargamos contiene frames (marcos), hay que tener cuidado, porque este evento se dispará varias veces, una para cada frame. En ese caso esperaríamos a que el segundo parámetro del evento coincidiese con el puntero al propio control ActiveX, disponible en la propiedad TWebBrowser.ControlInterface. Para emitir la orden de navegación al control y esperar a que éste termine la carga, podríamos entonces ejecutar un algoritmo parecido a éste:

procedure TForm1.FormCreate(Sender: TObject);
begin
  FCompletado := False;
  WebBrowser1.Navigate('about:blank');
  while not FCompletado do
    Application.ProcessMessages;
end;

ALMACENANDO FICHEROS HTML COMO RECURSOS

Pasemos ahora a lo que más nos interesa. Normalmente, un TWebBrowser mostrará contenido extraído de páginas en Internet o de ficheros locales. Las URLs que se pasan al método Navigate suelen pertenecer a uno de estos tipos:

http://www.marteens.com            (una página de Internet)
file://c:/directorio/fichero.htm   (un fichero "físico" local)

Ahora bien, si quisiéramos mostrar contenido HTML perteneciente a nuestra aplicación, y tuviésemos que ceñirnos a las dos posibilidades anteriores, tendríamos que distribuir nuestra aplicación junto con plantillas HTML externas... con todos los riesgos que entraña tener esos ficheros al alcance de cualquiera. No es tanto por la posibilidad de que alguien retocase esos ficheros, o averiguase su contenido, sino por el riesgo de que alguien los borrase o moviese inadvertidamente. Antes de dar con el truco que voy a explicar ahora, en algunas aplicaciones tuve que implementar un servidor HTTP muy elemental dentro de la propia aplicación, de modo que recibiese peticiones a través de algún puerto auxiliar (digamos que el 8888). Gracias a este mini servidor, podía navegar entonces a URLs como la siguiente:

http:8888//localhost/pagina_ficticia

Creo que las desventajas de esta solución son evidentes... Por fortuna, TWebBrowser es capaz de trabajar con muchos más tipos de URLs. Por ejemplo:

res://miaplicacion.exe/principal   (¡un recurso de la aplicación!)

He destacado en negritas el fragmento de cadena dentro de la URL que indica el protocolo: en este caso, res, que indica que el contenido se leerá desde un recurso almacenado, con el nombre principal, dentro del fichero ejecutable. He mencionado el ejecutable en este ejemplo, pero podría referirme a cualquier otro módulo de la aplicación, como una DLL de recursos. Para probar el funcionamiento de esta técnica, le sugiero que cree un proyecto vacío en Delphi. Cree también un fichero vacío llamado recursos.rc en el mismo directorio del proyecto. Es posible que Delphi nos pueda ayudar a crear este fichero desde el propio IDE, pero prefiero apostar por lo seguro. Una vez que haya creado el fichero RC, vuelva al IDE y añada el fichero al proyecto (Project|Add to project). Delphi incluirá la siguiente directiva de compilación en el fichero DPR del proyecto:

{$R 'recursos.res' 'recursos.rc'}

Con esto, Delphi recuerda que debe convertir el fichero recursos.rc en otro fichero, recursos.res, y enlazar este último dentro de la aplicación. Delphi conoce la extensión RC, y sabe qué herramientas debe utilizar para obtener un fichero RES a partir de un fichero RC. Esta directiva hace también que el fichero aparezca ahora en el Project Manager. Si hacemos doble clic sobre el mismo, Delphi lo editará dentro de su Editor de Código. Vamos a teclear lo siguiente en su interior:

#define HTML 23
#define JPEG 25

principal   HTML  principal.htm
secundaria  HTML  secundaria.htm
imagen      JPEG  imagen.jpg

Esto supone que tenemos otros tres ficheros externos: principal.htm, secundaria.htm e imagen.jpg. Los dos primeros ficheros corresponden a las plantillas HTML que queremos incrustar dentro de la aplicación, y que el tercer fichero es una imagen JPEG. El contenido de la plantilla principal.htm podría parecerse a lo siguiente:

<html>
<body>
  <b>Página principal</b><br>
  <a href="/secundaria">Página secundaria</a><br>
  <img src="/#25/imagen" border=0>
</body>
</html>

Para la plantilla secundaria, puede teclear algo parecido. Observe la forma en que se han escrito las URLs "relativas" para el enlace y para la etiqueta de imagen:

/secundaria
/#25/imagen

Los identificadores secundaria e imagen corresponden a los nombres que le hemos dado a los correspondientes recursos dentro del fichero RC. En el primer caso, vamos directamente a por la plantilla secundaria. En el segundo caso, no obstante, se trata de una URL que hace referencia a una imagen, y ha sido necesario aclarar el tipo de recurso. Como elegí el número 25, de forma arbitraria, para el tipo del recurso de imagen, he tenido que incluir la cadena #25 al principio de la URL que hace referencia a la imagen.

Traiga al formulario, a continuación, un control TWebBrowser, que encontrará en la página Internet de la Paleta de Componentes. Ajuste su tamaño a su antojo, y escriba la siguiente instrucción en respuesta al evento OnCreate del formulario:

procedure TForm1.FormCreate(Sender: TObject);
begin
  WebBrowser1.Navigate('res://ResourceTest.exe/principal');
end;

Con muy poco esfuerzo, ya tenemos la plantilla almacenada como recurso cargada dentro del control TWebBrowser. Compruebe que la imagen se puede recuperar y mostrar correctamente, y que el enlace a la plantilla secundaria funciona adecuadamente.

Una vez llegados a este punto, puede que le interese seguir personalizando el control de exploración. Por ejemplo, puede que le interese eliminar el menú de contexto que el control mostrará de forma automática, probablemente para impedir la lectura del código fuente HTML. Otra posibilidad interesante sería incluir código JScript dentro de las plantillas para acceder a objetos y métodos pertenecientes a nuestra aplicación Delphi. Todos estos objetivos se pueden lograr haciendo que el site que aloja al control ActiveX (el propio control VCL TWebBrowser), implemente tipos de interfaz como IDocHostUIHandler. Encontrará más información al respecto en la MSDN Library.


Recuerde que para utilizar TWebBrowser, el ordenador debe tener instalada la DLL shdocvw.dll y algunos otros ficheros auxiliares. Si está instalado Internet Explorer, versión 4 o posterior, este requisito estará satisfecho automáticamente. En caso contrario, puede redistribuir gratuitamente los componentes necesarios, siempre que solicite antes una licencia de redistribución a Microsoft. Encontrará también estos detalles en la MSDN.

CARGA DIRECTA DEL CONTENIDO

El truco que acabo de explicar tiene una limitación: el contenido de las plantillas se determina en tiempo de desarrollo. Si necesitamos generar dinámicamente contenido HTML y mostrarlo, y no deseamos crear ficheros temporales, tendremos que recurrir a otra vía: cargar el contenido del "documento HTML" que queremos mostrar dentro del control de visualización a partir de un flujo de datos o stream. Naturalmente, estoy hablando de un stream OLE: una clase que implemente la interfaz IStream definida por Microsoft. En Delphi podemos utilizar la clase TStreamAdapter. Su constructor exige que le pasemos como parámetro un objeto de una clase derivada de TStream (el flujo de datos de Delphi). La clase delega entonces en este flujo de datos interno la implementación de los métodos de IStream. La siguiente función auxiliar nos muestra la técnica necesaria para crear un objeto que implemente IStream y en cuyo interior esté almacenado determinado contenido HTML:

function CrearStream(const HtmlContent: string): IStream;
begin
  Result := TStreamAdapter.Create(
    TStringStream.Create(HtmlContent), soOwned);
end;

Primero creamos un objeto de la clase TStringStream, basado en la cadena HTML que recibimos como parámetro. A continuación, se crea un TStreamAdapter, que asume la pertenencia del TStringStream (lo destruirá cuando él mismo sea destruido). Finalmente, la función devuelve un puntero a la interfaz IStream implementada por el objeto adaptador.

Veamos ahora cómo se puede utilizar la función anterior para cargar contenido HTML en un TWebBrowser:

procedure TForm1.Cargar(const HtmlContent: string);
var
  PSI: IPersistStreamInit;  // Definida en ActiveX
begin
  // Precondición: WebBrowser1.Document <> Unassigned
  OleCheck(WebBrowser1.Document.QueryInterface(
    IPersistStreamInit, PSI));
  OleCheck(PSI.InitNew);
  PSI.Load(CrearStream(HtmlContent));
end;

El truco exige que el objeto interno de documento del control ya esté creado. Recuerde el truco que mencionamos al principio, que forzaba la navegación a la URL about:blank, y que esperaba a que el documento estuviese cargado antes de continuar. El primer paso de Cargar consiste en extraer una implementación de la interfaz IPersistStreamInit del objeto referenciado por WebBrowser1.Document. Luego ejecutamos el método InitNew de este tipo de interfaz, para dejar el documento HTML interno en un estado predecible. Finalmente, cargamos con el método Load el contenido de un flujo de datos creado mediante la función auxiliar CrearStream.

No obstante, tenemos que ser cuidadosos con el contenido que pasamos. La URL que el control utilizará como base para resolver las URLs relativas será la URL que estaba activa antes de la carga; casi siempre, about:blank. Si el contenido HTML contiene imágenes o hiperenlaces basados en URLs relativas, las primeras no se mostrarán correctamente, y los segundos no funcionarán. Una solución más completa sería definir y registrar un nuevo protocolo para que sea reconocido por Internet Explorer. Algo parecido es lo que hace la conocida Ayuda HTML de Microsoft. ¿Adivina dónde encontrará más información al respecto? Efectivamente: en la MSDN Library.

Para terminar, le mostraré un truco muy interesante. Sólo tiene que hacer clic sobre el siguiente enlace:


El "protocolo" view-source viene bloqueado en el Service Pack 2 de Windows XP.