Visual FoxPro como cliente en Internet (III): Web

Por Pablo Almunia
© Copyrights 1997 by FoxPress, All rights reserved
FoxPress, Septiembre 1997

En los dos artículos anteriores describimos como utilizar el Dial-Up Networking (Acceso Telefónico a Redes) y el File Transfer Protocol (FTP) desde Visual FoxPro. Para terminar hemos elegido cómo nuestra aplicación puede utilizar la parte cliente del sistema de páginas Web.

El Web (o la Web en muchos países de habla hispana) está basado en un servidor y un cliente que dialogan por medio del protocolo Hypertext Transfer Protocol (HTTP), que define la forma en que se transmiten los ficheros o se envían los formularios entre estas maquinas. El formato de fichero básico del WEB es el Hypertext Makup Language (HTML) que puede contener referencias a ficheros gráficos (GIF, JPEG, etc.), objetos (ActiveX o Java), etc. y que es visualizado por el Browser, constituyendo la página Web.

Veremos cómo Visual FoxPro puede hacer uso del protocolo HTTP para la transferencia de ficheros y cómo es capaz de utilizar los browser para la visualización de las páginas.

HTTP

El protocolo que se utiliza para acceder a los ficheros incluidos en un servidor Web es el HTTP. Desde Visual FoxPro podemos hacer uso de este protocolo por medio del API de WinInet que explicamos en el número anterior. En una aplicación Visual FoxPro podemos hacer uso de HTTP para obtener ficheros contenidos en servidores Web y no en servidores FTP, práctica cada día más habitual, para obtener y enviar datos por medio de formularios, para obtener ficheros HTML y almacenarlos, etc.

No explicaremos de nuevo el API de WinInet, ya que pudimos observar en el artículo anterior cómo funciona. Sólo mostraremos un sencillo ejemplo que obtiene un fichero del servidor Web:

PROCEDURE VFPHTTP
LPARAMETER cURL, cNameFich
*** Definición de constantes básicas
#DEFINE INTERNET_OPEN_TYPE_PRECONFIG     0
*** Declaración de funciones del API
DECLARE LONG GetLastError ;
	IN WIN32API
DECLARE INTEGER InternetCloseHandle ;
	IN "wininet.dll" ;
	LONG     hInet
DECLARE LONG InternetOpen ;
	IN "wininet.dll" ;
	STRING   lpszAgent, ;
	LONG     dwAccessType, ;
	STRING   lpszProxyName, ;
	STRING   lpszProxyBypass, ;
	LONG     dwFlags
DECLARE LONG InternetOpenUrl ;
	IN "wininet.dll" ;
	LONG    hInet, ;
	STRING  lpszUrl, ;
	STRING  lpszHeaders, ;
    LONG    dwHeadersLength, ;
    LONG    dwFlags, ;
    LONG    dwContext
DECLARE LONG InternetReadFile ;
	IN "wininet.dll" ;
	LONG     hFtpSession, ;
	STRING  @lpBuffer, ;
	LONG     dwNumberOfBytesToRead, ;
	LONG    @lpNumberOfBytesRead
*** Apertura
nInternet = InternetOpen( ;
	"pruebavfp", ;
	INTERNET_OPEN_TYPE_PRECONFIG, ;
	"", "", 0 )
IF nInternet = 0
	MESSAGEBOX( "Error: " ;
		+ LTRIM( STR( GetLastError() ) ) ;
		+ " en InternetOpen.", 16 )
	RETURN
ENDIF
*** Abrir el URL
nFichHTTP = InternetOpenUrl ( ;
	nInternet, ;
	cUrl, ;
	NULL, 0, 0, 0 )
IF nFichHTTP = 0
	MESSAGEBOX( "Error: " ;
		+ LTRIM( STR( GetLastError() ) ) ;
		+ " en InternetOpenUrl.", 16 )
	InternetCloseHandle( nInternet )
RETURN
ENDIF
*** Abir el fichero en el cliente
nFich = FCREATE( cNameFich )
*** Contruir las variables necesarias
nTama = 0
nLen = 1
*** Bucle de lectura
DO WHILE nLen # 0
	cBuffer = REPLICATE( CHR(0), 2048 )
	*** Leer del fichero en el servidor
	InternetReadFile( ;
		nFichHTTP, ;
		@cBuffer, ;
		LEN( cBuffer ), ;
		@nLen ) 
	*** Escribir el fichero en el cliente
	FWRITE( ;
		nFich, ;
		SUBSTR( cBuffer, 1, nLen ) )
	*** Aumentar el tamaño total
	nTama = nTama + nLen
	WAIT WIND "Recibidos " ;
		+ LTRIM( STR( nTama ) ) NOWAIT
ENDDO
WAIT CLEAR
*** Cerrar el fichero local
FCLOSE( nFich )
*** Cierre del uso del API
InternetCloseHandle( nInternet )

Basta llamar a esta función con la dirección que queremos obtener y el fichero que deseamos grabar: vfphttp("http://www.server1.es", "home.htm"). Para más información sobre el API de WinInet, ver la nueva página www.micro soft.com/msdn/sdk/inetsdk/help/.

Lanzar el Browser con ShellExecute

En el menú Ayuda de Visual FoxPro podemos encontrar una opción denominada Microsoft en el Web en la que podemos encontrar enlaces a distintas páginas del sitio de Microsoft. Si seleccionamos alguna de ellas se abre el browser y se accede a la página correspondiente. ¿Cómo podemos hacer algo similar en nuestra aplicación?

El primer problema que tenemos que afrontar es la existencia de distintos browsers: Microsoft, Netscape, Mosaic, etc. No sabremos de antemano cuál tendrá instalado nuestro usuario, qué versión del mismo tendrá, y en caso de tener varios instalados, cuál es su preferido.

Siempre podríamos preguntarle a nuestro usuario cuál es su browser preferido y en qué directorio se encuentra, para después arrancarlo por medio de una orden RUN pasando como parámetro el sitio Web que deseamos abrir, pero existen otras posibilidades más elegantes.

Es posible abrir el browser haciendo uso de una simple función del API de Windows llamada ShellExecute. Cuando un browser es el predeterminado se inscribe en el Registro de Configuraciones (Registry) de la estación de trabajo para encargarse él de abrir una referencia a un fichero HTML o a un sitio Web. Por ello, si decimos a la función ShellExecute que abra un sitio Web el propio sistema buscará y ejecutará el programa encargado de abrir este tipo de enlace.

Veamos una simple función escrita en Visual FoxPro a la que pasándole una referencia a un sitio Web o a un fichero HTML llama a la función ShellExecute para abrirlo:

PROCEDURE OpenHREF
LPARAMETER cHREF
#DEFINE SW_HIDE             0
#DEFINE SW_SHOWNORMAL       1
#DEFINE SW_NORMAL           1
#DEFINE SW_SHOWMINIMIZED    2
#DEFINE SW_SHOWMAXIMIZED    3
#DEFINE SW_MAXIMIZE         3
#DEFINE SW_SHOWNOACTIVATE   4
#DEFINE SW_SHOW             5
#DEFINE SW_MINIMIZE         6
#DEFINE SW_SHOWMINNOACTIVE  7
#DEFINE SW_SHOWNA           8
#DEFINE SW_RESTORE          9
#DEFINE SW_SHOWDEFAULT      10
DECLARE LONG ShellExecute ;
	IN "shell32.dll" ;
	LONG    hwnd, ;
	STRING  lpOperation, ;
	STRING  lpFile, ;
	STRING  lpParameters, ;
	STRING  lpDirectory, ;
	LONG    nShowCmd
IF 32 => ShellExecute( 0, NULL, ;
                       cHREF, NULL, ;
                       NULL, SW_MAXIMIZE )
	ERROR "Error en ShellExecute."
ENDIF

ShellExecute tiene como primer parámetro (hwnd) un manejador de una ventana o bien un 0. En caso de pasar un manejador de ventana, cuando esa ventana se cierre se cerrará la aplicación abierta con esta función. Podemos utilizar la función MainHwnd()de la librería FOXTOOLS.FLL para obtener el de la ventana principal de Visual FoxPro (ver el artículo de Abril de 1996 "Uso del API de Windows : Introducción a las Ventanas (IV)" sobre cómo obtener un manejador de ventana) o utilizar el Visual FoxPro HWND Control que se distribuye con la versión 5.

El segundo parámetro (lpOperation) por defecto (pasando un NULL) es open para abrir el fichero, pero puede ser cualquier operación relacionada con un fichero que se hubiera registrado como print, edit, etc.

Los siguientes parámetros son (lpFile) el fichero que queremos abrir, (lpParameters) posibles parámetros necesarios para el programa que vamos a abrir, (lpDirectory) el directorio activo para esa aplicación o NULL para utilizar el directorio actual, y (nShowCmd) la forma de visualizar el programa según las constantes que se pueden ver al principio del programa de ejemplo.

Para abrir nuestro sitio Web podemos utilizar una simple orden como OpenHREF( "http://www.miempresa.es" ).

Como hemos dicho, este sistema es independiente del programa que tenga configurado el usuario como browser predeterminado, si bien es necesario que tenga alguno. Si queremos saber cuál es el programa que se ha arrancará por medio de ShellExecute podemos utilizar la función FindExecutable.

Aquí podemos ver una función donde pasamos una extensión y nos devuelve el programa asociado a este tipo de fichero:

PROCEDURE GetExe4Ext
LPARAMETERS cExtension
#DEFINE MAX_PATH          260
DECLARE LONG FindExecutable ;
	IN "shell32.dll" ;
	STRING lpFile, ;
	STRING lpDirectory, ;
	STRING @lpResult
*** Crear un fichero con esa extensión
cTmpName = SYS(2023) + "\" ;
           + SUBSTR( SYS(2015), 3, 10 ) ;
           + "." + cExtension
FCLOSE( FCREATE( cTmpName ) )
*** Buscar el programa asociado al fichero
cExecutable = REPLICATE( CHR(0), MAX_PATH )
FindExecutable( cTmpName, ;
                NULL, ;
                @cExecutable )
*** Borrar el fichero temporal
DELETE FILE cTmpName
*** Retornar el nombre del programa
cExecutable = SUBSTR( cExecutable, ;
             1, ;
             AT(CHR(0), cExecutable ) - 1 )
RETURN cExecutable

Como se puede ver se crea un fichero temporal con la extensión pasada como parámetro y después de usar la función FindExecutable se retorna el nombre del programa asociado. Para conocer el programa asociado a los ficheros HTML debemos utilizar: ? GetExe4Ext( "HTM" ).

Manejar el Browser con DDE

Sí, ha leído bien, Dinamic Data Exchange (DDE). En la era de DCOM todavía persiste el DDE. He tenido que realizar hace unas pocas semanas, para mi sorpresa, un servidor DDE en Visual FoxPro y colaborar en otro en Visual C++. Casi todos los browser soportan DDE dentro del entorno Windows y es un sistema bastante interesante para poder manejarlo con algo más de control que con ShellExecute.

Spyglass, la empresa que creó el famoso Mosaic, definió en 1994 el denominado Software Development Interface (SDI) para dar una forma de interactuar con los distintos elementos del Web de forma neutral. Para el entorno Windows y los browser se define una serie de tópicos e Items DDE. Netscape y Microsoft utilizan, al menos en parte, estas interfaces DDE.

No vamos a describir todas las posibilidades de estas Interfaces, pero mostraremos un pequeño programa de ejemplo donde establecemos una sesión DDE con el browser, abrimos una página, lo activamos y preguntamos si queremos cerrar el browser.

PROCEDURE DDEBrowser
LPARAMETER cTipo, cURL
cTipo = LOWER( cTipo )
*** Configurar el DDE
DDESetOption( "SAFETY", .F. )
*** Abrir la página
nChanNum = DDEInitiate( cTipo, ;
           "WWW_OpenURL" )
IF nChanNum = -1
	*** Si hay un error arrancar el browser
	*** Se debería pedir por configuración
	*** la ubicación de los programas
	DO CASE
	CASE cTipo = "netscape"
		RUN /n "C:\ARCHIVOS DE PROGRAMA\" +
          "NETSCAPE\NAVIGATOR\PROGRAM\" +
          "NETSCAPE.EXE"
	CASE cTipo = "iexplore"
		RUN /n "C:\ARCHIVOS DE PROGRAMA\" +
          "PLUS!\MICROSOFT INTERNET\" +
          "IEXPLORE.EXE"
	ENDCASE
	*** Reintentar el inicio de sesión
	nChanNum = DDEInitiate( cTipo,;
             "WWW_OpenURL" )
	IF nChanNum = -1
		ERROR "Error DDE número" + ;
            STR( DDELastError( nChanNum ) )
		RETURN
	ENDIF
ENDIF
lExecute = DDEExecute( nChanNum, cURL )
IF lExecute != .F.
	ERROR "Error DDE número" + ;
         STR( DDELastError( nChanNum ) )
ENDIF
DDETerminate( nChanNum )
*** Activar el último browser abierto
nChanNum = DDEInitiate( cTipo, ;
           "WWW_Activate" )
IF nChanNum != -1
	cRequest = DDERequest( nChanNum,;
             "0xFFFFFFFF" )
	DDETerminate( nChanNum )
ELSE
	ERROR "Error DDE número" + ;
         STR( DDELastError( nChanNum ) )
ENDIF
*** Preguntar y en cerrar el browser
IF 7 = MESSAGEBOX( ;
        "¿Desea cerrar el Browser?", ;
        16+4, ;
        "DDE Browser"  )
	RETURN
ENDIF
nChanNum = DDEInitiate( cTipo, "WWW_Exit" )
IF nChanNum != -1
	cRequest = DDEPoke( nChanNum,;
              "WWW_Exit", "" )
	DDETerminate( nChanNum )
ELSE
	ERROR "Error DDE número" + ;
         STR( DDELastError( nChanNum ) )
ENDIF

El sistema de comunicación DDE tiene la gran ventaja de funcionar correctamente con casi todos los browser (personalmente lo he probado con Netscape, Microsoft y Mosaic, pero es de esperar que pueda funcionar con cualquier otro basado en el viejo Mosaic).

Las Interfaces DDE definidas por Spyglass son bastante extensas (aunque un poco confusas para mi gusto). Para más información se pueden visitar las siguientes páginas:

? http://www.spyglass.com:4040/
newtechnology/integration/iapi.htm
? http://www.microsoft.com/kb/articles/
Q160/9/57.htm
? http://developer.netscape.com/library/
documentation/communicator/DDE/index.htm

Manejar el Browser con OLE

Por medio OLE (o ActiveX, como se dice ahora) podemos manejar de varias formas los browsers de Internet. Tanto Microsoft Internet Explorer como Netscape Navigator disponen de interfaces OLE, pero muy distintos entre sí.

Netscape Navigator dispone, en sus versiones para Windows, de interfaces OLE. La primera de estas interfaces, denominada Netscape. Network.1 está orientada al uso de las capas de red del browser y obtención de ficheros, estando más cerca del API WinInet. Con ella podríamos crear un programa similar al VFPHTTP que hemos escrito más arriba.

La segunda es la denominada Netscape. Registry.1 y nos permite registrar servidores OLE para que se encarguen de visualizar determinados tipos MIME o responder a determinados protocolos y en la práctica sólo se debería programar desde C o C++.

Por otra parte Netscape registra los documentos HTML como documentos OLE manejados por él, lo que posibilita almacenar referencias a este tipo de página dentro de campos GENERAL de tablas Visual FoxPro o ser visualizados dentro de formularios por medio del control OLE Container. El resultado no es muy bueno, pero puede ser interesante para algunas ocasiones. Esta posibilidad no está disponible dentro de Microsoft Internet Explorer.

Para más información sobre el OLE que soporta Netscape se puede consultar la página:

? http://developer.netscape.com/library/
documentation/communicator/OLE/index.htm

Microsoft Internet Explorer dispone de tres modelos de objetos OLE: el primero es interno, para ser utilizado por los ActiveX Scripts como JScript y Visual Basic Script (también existe un Perl Script para IE); el segundo modelo es para manejar la aplicación Internet Explorer desde otra aplicación por medio de OLE Automation; y por último tenemos un Control OLE llamado WebBrowser que además de ser utilizado internamente por IE está a disposición de los programadores. Sobre los dos últimos vamos a tratar en este artículo, el primero sólo se puede utilizar desde los lenguajes de script y no es posible utilizarlo desde Visual FoxPro.

La documentación sobre las interfaces OLE de Internet Explorer que vamos a tratar se puede consultar en la página

? http://www.microsoft.com/workshop/prog/
sdk/docs/iexplore/webrowse.htm

En este caso hay que elegir con qué browser va a trabajar nuestra aplicación. En nuestro caso hemos optado por explorar las posibilidades del Microsoft Internet Explorer:

1. InternetExplorer.Application

Por medio de OLE Automation podemos controlar el Internet Explorer de forma muy sencilla. Basta con crear un objeto OLE denominado InternetExplorer. Application e ir haciendo uso de las propiedades y métodos que pone a nuestra disposición para el control de la aplicación. Como ejemplo podemos ver un programa similar al realizado anteriormente por medio de DDE, pero ahora utilizando el OLE Automation de Internet Explorer:

PROCEDURE OLEBrowser
LPARAMETER cURL
oExplorer = CREATEOBJECT( ;
            "InternetExplorer.Application")
oExplorer.Navigate( cURL )
oExplorer.Visible = .T.
IF 7 = MESSAGEBOX( ;
         "¿Desea cerrar el Browser?", ;
         16+4, ;
         "DDE Browser"  )
	RETURN
ENDIF
oExplorer.Quit

Como podemos ver es mucho más sencillo que el programa que utiliza DDE, sobre todo porque el manejo de errores del OLE Automation lo realiza el gestor de errores de Visual FoxPro directamente. Posiblemente sea un programa mucho más claro, pero, como hemos dicho, lamentablemente sólo puede funcionar con Microsoft Internet Explorer y no puede funcionar con otros browser, como el programa que utiliza DDE.

El objeto OLE Automation nos permite controlar muchos aspectos del Internet Explorer: posición, visualización de la barra de herramientas, barra de estado, menú, el texto de la barra de estado, la navegación entre páginas, etc. Como ejemplo de todas estas posibilidades hemos creado un formulario que maneja la mayoría de las posibilidades que se ponen a nuestra disposición:


 

A la hora de transcribir el código hemos eliminado toda la sección que declara los controles y sus propiedades y sólo hemos incluido el código de los métodos más interesantes (el código completo se puede ver en el disco que acompaña a la revista).

PUBLIC IEAutomation
IEAutomation = CREATEOBJECT( ;
                 "frmieautomation" )
IEAutomation.Show
RETURN

DEFINE CLASS frmieautomation AS form

( el código correspodiente a las propiedades y definición de controles han sido eliminado por problemas de espacio )

PROCEDURE GetInfo
*** Método para obtener y vincular
*** los datos desde el objeto OLE
*** Automation "oExplorer"
  *** Si el objeto no se ha creado
  *** simplemente se retorna
  IF TYPE( "This.oExplorer" ) # "O" ;
    OR ISNULL( This.oExplorer )
    RETURN
  ENDIF
  *** Obtener y vincular los datos
  *** de Internet Explorer
  WITH This
    .txtFullName.Value = ;
      This.oExplorer.FullName
    .txtPath.Value = This.oExplorer.Path
    .txtName.Value = This.oExplorer.Name
    .txtHWND.Value = This.oExplorer.HWND
    .chkVisible.ControlSource = ;
      "ThisForm.oExplorer.Visible"
    .chkFullScreen.ControlSource = ;
      "ThisForm.oExplorer.FullScreen"
    .chkMenuBar.ControlSource = ;
      "ThisForm.oExplorer.MenuBar"
    .chkToolBar.ControlSource = ;
      "ThisForm.oExplorer.ToolBar"
    .chkStatusBar.ControlSource = ;
      "ThisForm.oExplorer.StatusBar"
    .txtStatusText.ControlSource = ;
      "ThisForm.oExplorer.StatusText"
    .txtTop.ControlSource = ;
      "ThisForm.oExplorer.Top"
    .txtLeft.ControlSource = ;
      "ThisForm.oExplorer.Left"
    .txtHeight.ControlSource = ;
      "ThisForm.oExplorer.Height"
    .txtWidth.ControlSource = ;
      "ThisForm.oExplorer.Width"
    .txtLocationName.ControlSource = ;
      "ThisForm.oExplorer.LocationName"
    .txtLocationURL.ControlSource = ;
      "ThisForm.oExplorer.LocationURL"
    .txtType.ControlSource = ;
      "ThisForm.oExplorer.Type"
  ENDWITH
ENDPROC
PROCEDURE ControlsEnabled
*** Activa todos los controles
ThisForm.SetAll( "Enabled", .T. )
ENDPROC
PROCEDURE ControlsDisabled
*** Desactiva todos los controles
*** excepto los botones de abajo
WITH ThisForm
    .LockScreen = .T.
    .SetAll( "Enabled", .F. )
    .cmdAbrirCerrar.Enabled = .T.
    .cmdCerrar.Enabled = .T.
    .LockScreen = .F.
  ENDWITH
ENDPROC
PROCEDURE Error
*** Gestor de errores
  LPARAMETERS nError, cMethod, nLine
  *** Los errores OLE son ignorados
  IF nError = 1426
    RETURN
  ELSE
    ERROR nError
  ENDIF
ENDPROC
PROCEDURE Init
*** Deshabilitar controles y cambiar
*** el gestor de errores
  *** Guardar el actual gestor de errores
  This.Tag = ON("ERROR")
  *** Cambiar el gestor de errores
  ON ERROR IEAutomation.Error( ;
    ERROR(), PROGRAM(), LINE() )
  This.ControlsDisabled()
ENDPROC
PROCEDURE Destroy
*** Si existe un objeto OLE Automation
*** de IE y es visible preguntar si se
*** cierra
  IF TYPE( "ThisForm.oExplorer" ) = "O" ;
    AND NOT ISNULL( ThisForm.oExplorer )
    IF ThisForm.oExplorer.Visible
      IF 6 = MESSAGEBOX( ;
        "¿Desea cerrar la copia de IE ";
          +"que tiene abierta?", ;
        4+16, ;
       "IE Automation" )
        ThisForm.oExplorer.Quit()
      ENDIF
    ENDIF
  ENDIF
  cEjec = "ON ERROR " + This.Tag
  &cEjec
ENDPROC
PROCEDURE cmdAbrirCerrar.Click
*** Botón de abrir IE y cerrar IE
  This.Enabled = .F.
  *** Si existe un objeto OLE Automation
  *** cerrar el IE
  IF TYPE( "ThisForm.oExplorer" ) = "O";
    AND NOT ISNULL( ThisForm.oExplorer )
    ThisForm.oExplorer.Quit()
    ThisForm.oExplorer = .F.
    ThisForm.ControlsDisabled()
    This.Caption = "Abrir IE"
  *** Si no existe un objeto OLE Automation
  *** crear uno
  ELSE
    ThisForm.oExplorer = CREATEOBJECT(;
      "InternetExplorer.Application" )
    ThisForm.ControlsEnabled()
    ThisForm.GetInfo()
    This.Caption = "Cerrar IE"
  ENDIF
  This.Enabled = .T.
ENDPROC

*** Para poder editar los campos
*** txtStatusText, txtTop, txtLeft,
*** txtHeight y txtWidth es necesario
*** desactivar el timer que se ha creado
*** para refrescar el contenido de estos
*** controles
PROCEDURE txtStatusText.LostFocus
  ThisForm.tmrRefresh.Enabled = .T.
ENDPROC
PROCEDURE txtStatusText.GotFocus
  ThisForm.tmrRefresh.Enabled = .F.
ENDPROC
(Se elimina el código similar de los otros
controles por problemas de espacio.)
PROCEDURE tmrRefresh.Timer
*** El timer refresca el formulario cada
*** 1 segundo para poder visualizar
*** rapidamente los cambios en las
*** propiedades de IE que hemos vinculado a
*** campos del formulario
ThisForm.Refresh()
ENDPROC
PROCEDURE cmdGoBack.Click
*** Ir a la página anterior
  ThisForm.oExplorer.GoBack()
ENDPROC
PROCEDURE cmdGoForward.Click
*** Ir a la página siguiente
  ThisForm.oExplorer.GoForward()
ENDPROC
PROCEDURE cmdGoHome.Click
*** Ir a la página Home
  ThisForm.oExplorer.GoHome()
ENDPROC
PROCEDURE cmdGoSearch.Click
*** Ir a la página de búsqueda
  ThisForm.oExplorer.GoSearch()
ENDPROC
PROCEDURE cmdStop.Click
*** Detener si está en proceso
  IF ThisForm.oExplorer.Busy
    ThisForm.oExplorer.Stop()
  ENDIF
ENDPROC
PROCEDURE cmdNavigate.Click
*** Ir a la página indicada y
*** con los Flags indicados
  ThisForm.oExplorer.Navigate( ;
    ThisForm.txtURL.Value, ;
    ThisForm.cboFlags.List( ;
      ThisForm.cboFlags.ListIndex, ;
      2 ) )
ENDPROC
PROCEDURE cmdRefresh.Click
*** Refrescar la página
  ThisForm.oExplorer.Refresh()
ENDPROC
PROCEDURE cmdRefresh2.Click
*** Refrescar la página con el
*** parámetro indicado
  ThisForm.oExplorer.Refresh2( ;
    ThisForm.cboLevel.List( ;
      ThisForm.cboLevel.ListIndex, ;
      2 ) )
ENDPROC
PROCEDURE cmdCerrar.Click
*** Cerrar el formulario
  ThisForm.Release()
ENDPROC
ENDDEFINE

En el método cmdAbrirCerrar.Click crea y destruye el objeto OLE Automation. Para crearlo utilizaremos la ya conocida función CREATEOBJECT() con la referencia InternetExplorer.Application. Para cerrar el Internet Explorer tenemos que tener en cuenta si este es Visible o no. Si no es visible la destrucción de la referencia (ThisForm.oExplorer) cierra la aplicación, pero si el Internet Explorer es Visible, entonces necesitaremos llamar al método Quit para cerrarlo, tal y como puede verse en el método Destroy del formulario.

En el método GetInfo del formulario se obtienen de las propiedades de Internet Explorer los datos que visualizaremos. Aquellos que no cambian son simplemente asignados a la propiedad Value del control, por ejemplo:

ThisForm.txtFullName.Value = ;
  This.oExplorer.FullName

Aquellas propiedades que sí cambian en la ejecución del Internet Explorer son vinculadas dinámicamente a los controles por medio de la propiedad ControlSource, por ejemplo:

ThisForm.chkVisible.ControlSource = ;
      "ThisForm.oExplorer.Visible"

Los controles que visualizan las propiedades FullScreen, Height, Left, MenuBar, StatusBar, StatusText, ToolBar, Top y Visible permiten modificar sus valores. Por medio de estas propiedades podemos cambiar considerablemente la forma en que vemos el Internet Explorer. Yo utilizo este programa para situar el browser a 640x480 para ver cómo se ven mis páginas cuando tengo configurado el equipo con una mayor resolución o FullScreen para hacer pantallas de presentación. El resto son propiedades de sólo lectura.

Por medio de los distintos botones llamamos a varios de los métodos de los que dispone Internet Explorer. Especialmente interesante es la llamada al método Navigate. El primero de este método es obligatorio y es la dirección URL a donde queremos dirigirnos. El segundo, optativo, indica algunas opciones sobre la lectura y escritura del caché, la creación en nuevas ventanas y la inclusión de la referencia en el histórico (estas opciones se pueden combinar, pero aquí se han incluido en un ComboBox que excluye las opciones entre sí a fin de simplificar el ejemplo). Con el tercer parámetro podemos indicar el Frame donde queremos que se abra el documento. Los parámetros cuarto y quinto no se han ejemplificado aquí y nos permiten incluir en las cabeceras HTTP información adicional sobre el POST y el GET de las páginas.

Otros dos métodos interesantes son Refresh y Refresh2. Ambos refrescan la página que se está visualizando en ese momento, pero el segundo método dispone de un parámetro por el cual se indica cómo se desea que se realice la actualización.

Cuando llamamos a una propiedad todavía no disponible, como LocationName cuando todavía no se ha realizado ninguna navegación, o llamamos a un método que no puede realizar su trabajo, como puede ser GoForward cuando no se ha realizado un GoBack o Navigate a una URL que no se encuentra, se produce un error 1426 de Visual FoxPro. Por ello se ha registrado un gestor de errores en el método Init que ignora este número de error. No es un gestor muy sofisticado, pero sirve para este ejemplo.

En la referencia sobre WebBrowser y InternetExplorer.Application que se incluye en este artículo podemos ver que existen una serie de eventos disponibles en el servidor OLE Automation. Lamentablemente Visual FoxPro no es capaz de capturar los FireEvent de un servidor de OLE Automation pues no dispone de los connection point para este tipo de servidor. Esto es realmente una pena, pues sería muy fácil saber que una propiedad ha cambiado de valor por medio de evento ProgressChange. Por ello se ha creado un objeto Timer que cada segundo refresca el formulario y de esta forma las propiedades vinculadas a objetos del formulario se ven reflejadas con rapidez.

Microsoft publicó hace ya algún tiempo un ejemplo de manejo de Internet Explorer desde Visual FoxPro por medio de OLE Automation. Puede buscarse VFPIE3.EXE dentro de www.microsoft.com/kb o en el fórum de Visual FoxPro en Compuserve.

2. WebBrowser

La otra posibilidad que pone a nuestra disposición Microsoft Internet Explorer es la de construir nuestro propio browser utilizando para la visualización de los documentos un control ActiveX denominado WebBrowser que es el mismo que utiliza el Internet Explorer internamente.

Existen algunos controles ActiveX que visualizan documentos HTML, pero con los cambios tan rápidos que se está produciendo en los estándares de Internet se hace muy difícil que estos controles puedan seguirlos. Por el contrario el control WebBrowser de Internet Explorer es exactamente el mismo que utiliza el explorador de Microsoft y es realmente avanzado.

WinCim de Compuserve, el programa que utilizaba la MSN, el Visual Interdev o el nuevo HTMLHelp utilizan el WebBrower dentro de su programa, haciendo que se parezca muy poco al Internet Explorer original.

Para utilizar el Control WebBrower de Internet Explorer basta con tener instalado la versión 3 de este explorador (la próxima versión 4.0 también soporta este control con mejoras considerables para utilizarlo desde programas). Desde Visual FoxPro deberemos incluir un objeto OleControl y seleccionar el Control Explorador de Web de Microsoft. Si no se encuentra podemos añadirlo desde el fichero SHDOCVW.DLL que se encuentra en el directorio SYSTEM o SYSTEM32 del equipo.

Nosotros hemos creado una especie de pequeño browser visible dentro de Visual FoxPro haciendo uso del este control. En una aplicación real podríamos hacer un sistema de ayuda, de información sobre nuestra aplicación o conectarnos con el Web de nuestra empresa de forma personalizada.


 

Esta es la transcripción del código correspondiente a este formulario:

PROCEDURE VFPBrowser
PUBLIC oVFPBrowser
oVFPBrowser=CREATEOBJECT("frmvfpbrowser")
oVFPBrowser.Show
RETURN

DEFINE CLASS frmVFPBrowser AS form

( el código correspodiente a las propiedades y definición de controles han sido eliminado por problemas de espacio )

PROCEDURE Unload
*** Configurar para WebBrowser
	IF This.Tag = "AUTOYIELD_YES"
		_VFP.AutoYield = .T.
	ENDIF
	SYS(2333,1)
ENDPROC
PROCEDURE Load
*** Retornar la configuración original
	This.Tag = IIF( _VFP.AutoYield, ;
"AUTOYIELD_YES", ;
"AUTOYIELD_NO" )
	_VFP.AutoYield = .F.
	SYS(2333,0)
ENDPROC
PROCEDURE Error
*** Gestor de errores genérico
	LPARAMETERS nError, cMethod, nLine
*** Ignorar errores OLE
	IF nError = 1426
		RETURN
	*** Resto de errores
	ELSE
		nInfoSize = AERROR( aInfo )
		MESSAGEBOX( "Error " ;
+ LTRIM(STR(nError))+CHR(13) ;
+ aInfo[nInfoSize, 2], ;
			16, ;
			This.Caption )
		This.Release()
	ENDIF
ENDPROC
PROCEDURE Init
*** Gestionar parámetros si existen
	LPARAMETERS url, flags, ;
				targetframename, ;
				postdata, headers
	IF NOT EMPTY( url )
		ThisForm.oleWebBrowser.Navigate( ;
			url, flags, ;
			targetframename, ;
			postdata, headers )
	ENDIF
ENDPROC
PROCEDURE Resize
*** Gestionar el tamaño de los controles
	WITH This
		.LockScreen = .T.
		.txtURL.Width = .Width - .txtURL.Left
		.oleWebBrowser.Width = .Width
		.oleWebBrowser.Height = .Height - 49
		.LockScreen = .F.
	ENDWITH
ENDPROC
PROCEDURE oleWebBrowser.BeforeNavigate
*** OLE Control Event ***
*** Cambiar el contenido de textbox
*** con el URL a donde se navega
	LPARAMETERS url, flags, ;
				targetframename, ;
				postdata, headers, ;
				cancel
	ThisForm.txtURL.Value = url
ENDPROC
PROCEDURE oleWebBrowser.NavigateComplete
*** OLE Control Event ***
*** Volver a mostrar el URL al 
*** terminar la navegación
	LPARAMETERS url
	ThisForm.txtURL.Value = This.LocationURL
ENDPROC
PROCEDURE oleWebBrowser.StatusTextChange
*** OLE Control Event ***
*** Cambiar el panel con el texto
LPARAMETERS text
	ThisForm.oleStatusBar.Panels[1].Text ;
= text
ENDPROC
PROCEDURE oleWebBrowser.ProgressChange
*** OLE Control Event ***
*** Modificar el panel[2] con la
*** progresión del download
	LPARAMETERS progress, progressmax
	DO CASE
	CASE progress = -1
		ThisForm.oleStatusBar.Panels[2].Text;
= ""
	CASE progress = 0
		ThisForm.oleStatusBar.Panels[2].Text;
= "-"
	OTHERWISE
	ThisForm.oleStatusBar.Panels[2].Text;
= LTRIM( STR( (progress/progressmax);
* 100 ) ) + " %"
	ENDCASE
	DOEVENTS
ENDPROC
PROCEDURE oleWebBrowser.CommandStateChange
*** OLE Control Event ***
*** Actualizar los botones
	LPARAMETERS command, enable
	#define CSC_UPDATECOMMANDS	-1
	#define CSC_NAVIGATEFORWARD	1
	#define CSC_NAVIGATEBACK	2
	DO CASE
	CASE command = CSC_NAVIGATEFORWARD
		ThisForm.cmdForward.Enabled = enable
	CASE command = CSC_NAVIGATEBACK
		ThisForm.cmdBack.Enabled = enable
	ENDCASE
ENDPROC
PROCEDURE oleWebBrowser.NewWindow
*** OLE Control Event ***
*** Crear una nueva instancia del
*** formulario
	LPARAMETERS url, flags, ;
				targetframename, ;
				postdata, headers, ;
				processed
	DO FORM VFPBrowser ;
	WITH 	url, flags, ;
				targetframename, ;
				postdata, headers
	*** Comunicar que se ha procesado
	processed = .T.
ENDPROC
PROCEDURE oleWebBrowser.TitleChange
*** OLE Control Event ***
*** Cambiar el caption del formulario
	LPARAMETERS text
	ThisForm.Caption = text ;
+ " - Internet Browser"
ENDPROC
PROCEDURE oleStatusBar.Init
	This.Panels[1].Width = 330
	This.Panels[1].AutoSize = 1
	This.Panels.Add()
	This.Height = 20
ENDPROC 
PROCEDURE cmdBack.Click
*** Ir a la página anterior
	ThisForm.oleWebBrowser.GoBack()
ENDPROC
PROCEDURE cmdForward.Click
*** Ir a la página siguiente
	ThisForm.oleWebBrowser.GoForward()
ENDPROC
PROCEDURE cmdStop.Click
*** Detener el proceso
	IF ThisForm.oleWebBrowser.Busy
		ThisForm.oleWebBrowser.Stop()
	ENDIF
ENDPROC
PROCEDURE cmdRefresh.Click
*** Refrescar la página
	ThisForm.oleWebBrowser.Refresh()
ENDPROC
PROCEDURE cmdHome.Click
*** Ir a la página Home
	ThisForm.oleWebBrowser.GoHome()
ENDPROC
PROCEDURE cmdSearch.Click
*** Ir a la página de búsqueda
	ThisForm.oleWebBrowser.GoSearch()
ENDPROC
PROCEDURE txtURL.KeyPress
*** Ir a la página indicada
	LPARAMETERS nKeyCode, nShiftAltCtrl
	IF nKeyCode = 13
		ThisForm.oleWebBrowser.Navigate(;
ALLTRIM( This.Value ) )
	ENDIF
ENDPROC
ENDDEFINE

El control WebBrowser se diseñó para ser utilizando fundamentalmente desde programas escritos en C y tiene algunos problemas con aplicaciones como Visual FoxPro. El primero es un mensaje de error que aparece cuando insertas un objeto de este tipo dentro de un formulario y lo ejecutas (se puede consultar una referencia sobre el mismo en: http://www.microsoft.com/kb/ articles/q163/4/12.htm que se refiere a este problema de pasada y se centra en el uso del control con PageFrames). Para solucionarlo simplemente hemos ignorado cualquier error con el código 1426 y de esta forma evitamos también tener que gestionar posibles problemas cuando un método del control no tiene éxito, por ejemplo una llamada a Navigate con URL que no responde.

Para que los eventos del control WebBrowser sean capturados correctamente por Visual FoxPro debemos situar la propiedad AutoYield del objeto Application a .F. y de esta forma evitar que el sistema de múltiples threads de control pueda hacer que el código asociado a un evento no se ejecute. En los eventos Load y UnLoad del formulario hemos cambiado esta propiedad.

En algunas ocasiones hemos recibido algún error cuando se ha realizado una navegación (es decir se pulsa un enlace en un documento HTLM) antes de que todo el documento y sus gráficos hubieran sido bajados del servidor. Para minimizar estos problemas hemos desactivado el soporte de VTABLE de Visual FoxPro por medio de la orden SYS(2333) en los eventos Load y UnLoad del formulario.

También nos despistó una errata en la documentación que dice que el parámetro recibido por el evento CommandStateChange cuando cambia el estado del botón de vuelta atrás tiene un valor 3, cuando CSC_NAVIGATEBACK realmente vale 2. Es un pequeño problema, pero dedicamos algún tiempo a averiguarlo por medio de un inspector de objetos OLE.

Una vez solucionados estos problemas el control funciona bastante bien. Especial interés tiene el uso de los eventos que ahora sí podemos capturar. En el código hemos capturado los eventos BeforeNavigate y NavigateComplete para actualizar el TextBox con la URL en la que nos encontramos. Por medio de StatusTextChange y TitleChange cambiamos la barra de estado y el título del formulario y con ProgressChange se indica el porcentaje de download de cada elemento en la barra de estado. Gracias a CommandStateChange actualizamos el estado de los botones adelante y atrás.

Especial interés tiene también el evento NewWindow que se lanza cuando alguien pulsa un enlace teniendo pulsado el botón de mayúsculas o cuando por medio del menú de contexto elegimos abrir en ventana nueva. Si el último parámetro del evento lo cambiamos a .T. le decimos al control que ya hemos creado nosotros una nueva ventana y de esta forma evitamos que se abra una instancia de Internet Explorer para visualizar ese documento.

Podemos ver en la lista adjunta que existen algunas diferencias con el objeto obtenido por OLE Automation, que dispone de más propiedades, métodos y eventos. También debemos saber que algunas propiedades del control como LocationName y LocationURL no son visibles en tiempo de diseño por medio de la ventana de propiedades del diseñador de formularios, pero podemos hacer uso de ellas en el código.

Conclusión

Llegamos al final de esta serie sobre Visual FoxPro como cliente en Internet. Hemos tratado sobre el Dial-Up Networking y RAS (junio) para establecer conexiones a Internet desde nuestras aplicaciones, de FTP (julio-agosto) para poder subir o bajar ficheros con un servidor y por último de HTTP y browser de páginas Web (septiembre).

Espero que estos artículos hayan servido para acercarnos a este tipo de programación y nuestras aplicaciones mejoren con la incorporación de nuevas funcionalidades.

Pablo Almunia pertenece al grupo de Consultoría y Control de Calidad de la Unidad de Informática de MAPFRE. Se puede entrar en contacto con él por Email en palmun@compuserve.com