28 de abril de 2018

Incluir una imagen centrada en la pantalla (revisado)

Me vi en la necesidad de incluir una imagen en el escritorio (pantalla) de VFP, para ello Luis María nos ofrece una solución.

_SCREEN.ADDOBJECT("oImg", "Image") 
_SCREEN.oImg.PICTURE = "miImagen.png" 
_SCREEN.oImg.TOP = (_SCREEN.HEIGHT- _SCREEN.oImg.HEIGHT)/2 
_SCREEN.oImg.LEFT = (_SCREEN.WIDTH - _SCREEN.oImg.WIDTH)/2 
_SCREEN.oImg.VISIBLE = .T. 

La imagen debe estar en un directorio donde VFP pueda encontrarla, mejor aún si se incluye en el ejecutable.

Esta solución me presentó algunos problemas.

El primer problema es que tarda en cargarse la imagen, entonces recurrí a un comado del viejo FoxPro que muestra la imagen de inmediato.

CLEAR
@0,0 SAY 'miImagen.png' BITMAP CENTER

Este comando en VFP9 muestra una imagen de los formatos más utilizados, no sólo BMP; pero tiene el inconveniente que si se redimensiona la pantalla a un tamño menor de la imagen, esta quedará permanentemente recortada, por cual se hace necesario utilizar el objeto Image, que se sobrepone a la imagen cargada con el comando SAY.

El segundo problema es que al redimensionar la pantalla, la imagen no se redimensiona, pero VFP ofrece una solución muy sencilla que es la propiedad ANCHOR. Anclando relativamente todos los bordes de la imagen a la Pantalla se obtiene un buen resultado. Para esto se incluye la siguiente línea:

_SCREEN.oImg.anchor = 240

Anclar de forma fija los bordes de la Imagen a la Pantalla (ANCHOR = 15), crea problemas con el siguiente punto de mantener el aspecto de la imagen.

Finalmente deseaba que la imagen conservara su aspecto sin importar el tamaño, para lo cual el objeto Image tiene la propiedad STRETCH, agregamos la siguiente línea:

_SCREEN.oImg.stretch = 1

El código completo queda así:

CLEAR
@0,0 SAY 'miImagen.png' BITMAP CENTER
_SCREEN.ADDOBJECT("oImg", "Image") 
_SCREEN.oImg.PICTURE = "miImagen.png" 
_SCREEN.oImg.TOP = (_SCREEN.HEIGHT- _SCREEN.oImg.HEIGHT)/2 
_SCREEN.oImg.LEFT = (_SCREEN.WIDTH - _SCREEN.oImg.WIDTH)/2 
_SCREEN.oImg.anchor = 240
_SCREEN.oImg.stretch = 1
_SCREEN.oImg.VISIBLE = .T. 

Este código se debe colocar en el programa Principal.

Espero que les sea de utilidad.

Germán Giraldo

23 de abril de 2018

Copiar una grafica de Excel y pegarla en una diapositiva de PowerPoint

El Objetivo del siguiente código es abrir un libro de Excel y una presentación de PowerPoint, copiar un Grafico (Gráfico 2) el cual se encuentra en la primera hoja del libro y luego pegarlo en la diapositiva 19 de la presentación.

*!* Abrimos el archivo de Excel y de power point
lcArchivoExcel=GETFILE("xls","Abrir archivo de Excel")
lcArchivoPower=GETFILE("ppt","Abrir archivo de Power")

*!* Comprobamos que existan
IF FILE(lcArchivoExcel)==.F. OR FILE(lcArchivoPower)==.F.
  =MESSAGEBOX("Los archivo o alguno no existe",16,"File==.f.")
  RETURN
ENDIF

*!* Obejtios de Excel
loXlsApp = CREATEOBJECT("Excel.Application")
loXlsBook=loXlsApp.Workbooks.OPEN(lcArchivoExcel)
loXlsApp.VISIBLE = .T.
loXlsSheet = loXlsBook.Sheets(1)

*!* Seleccionando Grafica y Copiando
loGraficoXls = loXlsSheet.ChartObjects("Gráfico 2")
loGraficoXls.COPY()

*!* Objetos de Power Point
loPptApp = CREATEOBJECT("Powerpoint.Application")
loPptApp.VISIBLE= .T.
loPptPresentacion = loPptApp.Presentations.OPEN(lcArchivoPower)
loPptSlide=loPptPresentacion.Slides(19)
loPptSlide.SELECT()

*!* Pegamos Objeto
loGraficoPpt=loPptSlide.Shapes.Paste()

*!* Movemos el Grafico al centro
WITH loPptSlide.Shapes(loGraficoPpt.NAME)
  .IncrementLeft(-521.75)
  .IncrementTop(104.12)
ENDWITH

Jose Guillermo Ortiz Hernandez

21 de abril de 2018

Copiar celdas de una Hoja de calculo (Excel) y pegarlas en una diapositiva (PowerPoint)

El siguiente ejemplo muestra como copiar celdas existentes en un libro de excel y copiarlas en una diapositiva de una presentacion de Power Point. En este ejmplo se supone que el libro y la presentacion ya existen.

LOCAL loXlsApp, loXlsBook, loXlsSheet, loPptApp, loPptPresentacion, loPptSlide

*!* ARCHIVOS ORIGEN
*!* Archivo de Excel en donde estan los datos
*!* Comprobamos que exista el archivo, de no existir lo abrimos
lcArchivoExcel="datos.xls"
lcArchivoExcel=IIF(!FILE(lcArchivoExcel),GETFILE("xls"),lcArchivoExcel)

*!* Archivo de Power Point donde deseamos pegar los datos
*!* Nuevamente comprobamos si existe el archivo o si hay que buscarlo
lcArchivoPower="presentacion.ppt"
lcArchivoPower=IIF(!FILE(lcArchivoPower),GETFILE("ppt"),lcArchivoPower)

*!* INICIANDO PROCESO DE COPY -> PASTE
IF FILE(lcArchivoExcel) AND FILE(lcArchivoPower)

  *!* COPIANDO DATOS DE EXCEL AL CLIPBOARD
  loXlsApp = CREATEOBJECT("Excel.Application")
  loXlsApp.VISIBLE = .T.
  loXlsBook=loXlsApp.Workbooks.OPEN(lcArchivoExcel)
  loXlsSheet = loXlsBook.Sheets(3)
  loXlsSheet.RANGE("A1:I23").COPY()

  *!* PEGANDO DATOS DESDE EL CLIPBOARD A DIAPOSITIVA
  loPptApp = CREATEOBJECT("Powerpoint.Application")
  loPptApp.VISIBLE= .T.
  loPptPresentacion = loPptApp.Presentations.OPEN(lcArchivoPower)
  loPptSlide=loPptPresentacion.Slides(18)
  loPptSlide.SELECT()
  loPptApp.ActiveWindow.VIEW.Paste()
ELSE
  =MESSAGEBOX("Los archvios de Excel y Power no existen",0+64+0,"No existen")
ENDIF

Jose Guillermo Ortiz Hernandez

16 de abril de 2018

Mensaje tipo Messenger

Antes de comenzar cambiamos la propiedad ShowWindow A 2 (Formulario de nivel superior)

Ahora declaramos la API que vamos a utilizar la colocamos en el evento Load del formulario:

DECLARE integer SetWindowPos IN "user32";
  integer hwnd, integer hWndInsertAfter,;  
  integer x,integer y,integer cx,integer cy,integer wFlags 

Si queremos darle un grado de transparencia declaramos estas.

Declare Integer SetWindowLong In "user32" ;
  Integer HWnd, Integer nIndex, Integer dwNewLong

Declare Integer SetLayeredWindowAttributes In "user32" ;
  Integer HWnd, Integer crey, ;
  Integer bAlpha, Integer dwFlags

Bueno ahora en el evento Init colocamos lo siguiente.

*- esto nos permitira abrir el formulario sin que nos afecte otra ventana.
=SetWindowPos(this.HWnd, -1, 0, 0, 0, 0, 1 + 2 )

*-- Con estas define el grado de transparencia del formulario
SetWindowLong(THISFORM.hWnd, -20, 0x00080000)
*-- Cambia el valor (200) para ajustar el nivel de transparencia. 
SetLayeredWindowAttributes(THISFORM.hWnd, 0, 200, 2) 

Bien ahora vamos a darle una pequeña animación.

En el evento Active del form colocamos lo siguiente.

*-- Ubico el formulario 
tleft = (_screen.Width -this.Width)
ttlef = (tleft + this.Width)
this.Move (ttlef,ttop,this.Width,this.Height)
FOR i = 1 TO tleft  && muevo el form 
  ttlef = ttlef - 1
  this.Move (ttlef,ttop,this.Width,this.Height)
  IF ttlef = tleft 
    EXIT 
  ENDIF  
ENDFOR

Ahora el sonido en el mismo evento.

lcWaveFile =""
*-- defino la ruta del sonido a emitir  
lcWaveFile = ruta + "Librerias\newemail.wav" 

DECLARE INTEGER PlaySound ;
  IN WINMM.dll  ;
  STRING cWave, ;
  INTEGER nModule, ;
  INTEGER nType

PlaySound(lcWaveFile,0,1)

Y listo ya tenemos nuestro mensaje tipo Messenger el diseño corre por cuenta de ustedes.

by FreeCalls

12 de abril de 2018

SQLXMLBulkLoad funciona

Artículo original: SQLXMLBulkLoad Rocks!
http://doughennig.blogspot.com/2006/07/sqlxmlbulkload-rocks.html
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


Por razones que serán obvias en septiembre, he estado trabajando últimamente en actualizar los datos a SQL Server 2005. Existen varios mecanismos que se pueden utilizar para hacerlo; pero todos tienen sus pequeños inconvenientes bajo ciertas condiciones. Por ejemplo, es lento al utilizar una serie de instrucciones INSERT, mientras que insertar un volumen grande no trabaja con campos memo. Al buscar otra cosa en los "SQL Server Books Online", pasé por un tema sobre carga masiva a XML. Luego de jugar un poco, parecía fácil de hacer, hice algunas pruebas en un archivo relativamente grande (365,741 registros) que contiene archivos memo (lo que significa que no podía usar INSERT masivo). Utilizando otros mecanismos, me tomó más de dos horas cargar los datos a una tabla SQL Server. Utilizando la carga masiva XML, me tomó 11 minutos. Eso es un 90 % de mejora en la velocidad. ¡ Me encanta hacer las cosas más rápido !

Utilizar la carga masiva XML es sencillo: instancie SQLXMLBulkLoad.SQLXMLBulkload.4.0, configure su propiedad ConnectionString a una cadena de conexión OLE DB, establezca adecuadamente algunas propiedades (por ejemplo, KeepNulls = .T. para insertar nulos en lugar de los valores predeterminados para los valores de las columnas), y llame a Execute, pasando el nombre del archivo de esquema y el nombre para el archivo XML. Execute lanza un error si hay algo mal con alguno de los archivos, y los errores al importar se guardan en un archivo cuyo nombre se almacena en la propiedad ErrorLogFile.

Hay algo un poco complejo al trabajar con datos VFP: mientras la función CURSORTOXML() puede crear el archivo de esquema, necesita ser afinado cuando se trabaja con carga masiva XML. Además, los campos DateTime tienen que ser señalizados con el atributo sql:datatype="dateTime" (esto último fue documentado, lo demás me costó solucionarlo tras algunas pruebas y errores).

He aquí el programa genérico que hace la carga desde un cursor VFP abierto a una tabla SQL abierta.

*==============================================================================
* Función: BulkXMLLoad
* Objetivo: Realizar una carga masiva de XML a SQL Server
* Autor: Doug Hennig
* Última revisión: 07/06/2006
* Parámetros: tcAlias - alias del cursor a exportar
* tcTable - nombre de la tabla hacia la que se importa
* tcDatabase - base de datos a la que pertenece la tabla
* tcServer - nombre del servidor SQL 
* tcUserName - nombre de usuario para la conexión (opcional:
* si no se especifica, se utiliza, la Seguridad Integrada de Windows 
* tcPassword - palabra clave para la conexión (opcional:
* si no se especifica, se utiliza, la Seguridad Integrada de Windows 
* Devuelve: cadena vacía si la carga masiva ha sido exitosa 
* o texto con un mensaje de error si ha fallado
* Entorno de entrada: debe estar abierto el alias especificada en tcAlias 
* deben existir la base de datos y tabla especificadas
* el servidor especificado debe estar accesible
* debe existir espacio de disco suficiente  para los archivos XML 
* Entorno de salida: si es devuelta una cadena vacía, los datos fueron 
* importados a la tabla especificada 
*==============================================================================
lparameters tcAlias, ;
  tcTable, ;
  tcDatabase, ;
  tcServer, ;
  tcUserName, ;
  tcPassword
  local lnSelect, ;
  lcSchema, ;
  lcData, ;
  lcReturn, ;
  loException as Exception, ;
  lcXSD, ;
  loBulkLoad
* Crea los archivos de dato y de esquema para el XML.
lnSelect = select()
select (tcAlias)
lcSchema = forceext(tcTable, 'xsd')
lcData = forceext(tcTable, 'xml')
try
  cursortoxml(alias(), lcData, 1, 512 + 8, 0, lcSchema)
  lcReturn = ''
catch to loException
  lcReturn = loException.Message
endtry

* Convierte el formato XSD en un formato aceptable por SQL Server.
* Añade el espacio de nombre SQL,
* convierte las etiquetas inicio y fin ,
* utiliza el atributo sql:datatype pra campos DateTime fields,
* y especifica la tabla importada con el atributo sql:relation.
if empty(lcReturn)
  lcXSD = filetostr(lcSchema)
  lcXSD = strtran(lcXSD, ':xml-msdata">', ;
    ':xml-msdata" xmlns:sql="urn:schemas-microsoft-com:mapping-schema">')
  lcXSD = strtran(lcXSD, 'IsDataSet="true">', ;
    'IsDataSet="true" sql:is-constant="1">')
  lcXSD = strtran(lcXSD, '<xsd:choice maxOccurs="unbounded">', ;
    '<xsd:sequence>')
  lcXSD = strtran(lcXSD, '</xsd:choice>', ;
    '</xsd:sequence>')
  lcXSD = strtran(lcXSD, 'type="xsd:dateTime"', ;
    'type="xsd:dateTime" sql:datatype="dateTime"')
  lcXSD = strtran(lcXSD, 'minOccurs="0"', ;
    'sql:relation="' + lower(tcTable) + '" minOccurs="0"')
  strtofile(lcXSD, lcSchema)

  * Instancia el objeto SQLXMLBulkLoad, configura su propiedad ConnectionString
  * y otras propiedades, y llama a Execute para realizar la importación masiva.
  try
    loBulkLoad = createobject('SQLXMLBulkLoad.SQLXMLBulkload.4.0')
    lcConnString = 'Provider=SQLOLEDB.1;Initial Catalog=' + tcDatabase + ;
      ';Data Source=' + tcServer + ';Persist Security Info=False;'
    if empty(tcUserName)
      lcConnString = lcConnString + 'Integrated Security=SSPI'
    else
      lcConnString = lcConnString + 'User ID=' + tcUserName + ;
        ';Password=' + tcPassword
    endif empty(tcUserName)
    loBulkLoad.ConnectionString = lcConnString
    *** Puede configurar la propiedad ErrorLogFile con el nombre 
    *** del archivo para escribir errores de importación
    loBulkLoad.KeepNulls = .T.
    loBulkLoad.Execute(lcSchema, lcData)
    lcReturn = ''
  catch to loException
    lcReturn = loException.Message
  endtry
  * Código de limpieza y cierre.
  erase (lcSchema)
  erase (lcData)
endif empty(lcReturn)
select (lnSelect)
return lcReturn

8 de abril de 2018

Seguimiento de los archivos gráficos de su aplicación

Artículo original: Keep track of your applications graphic files
http://www.ml-consult.co.uk/foxst-41.htm
Autor: Mike Lewis
Traducido por: Ana María Bisbé York


Una sencilla utilidad para ayudarle a controlar los iconos, bitmaps y otros archivos gráficos que utiliza en su aplicación.

Una de las tareas más difíciles en el desarrollo de proyectos en Visual FoxPro es seguir la pista de muchos iconos, bitmaps y otros gráficos que utiliza en la aplicación. En un proyecto típico, utiliza probablemente cientos, incluso miles, de esos archivos. Los emplea para la propiedad Icon de sus formularios, para la propiedad Picture de los botones de comando, y en otros muchos lugares de su interfaz, donde desee tener efecto visual. Algunos de estos archivos serán tipo ICOs, otros, BMPs, GIFs, JPGs u otro de los 17 formatos gráficos que soporta nativamente VFP.

Controlar estos datos es un dolor de cabeza. En nuestro propio proyecto, típicamente verificamos diferentes tipos de iconos para una función particular, entonces olvidamos borrar los que hemos rechazado, resultando un archivo no deseado que desordena la carpeta. Al trabajar con un equipo de programadores, encontramos con frecuencia que cada programador escoge sus propios gráficos, por tanto, al final tenemos diferentes imágenes que serán empleadas para lo mismo. Y si el archivo gráfico se ha perdido, a veces sólo nos damos cuenta cuando el usuario se queja.

Para ayudar a solucionar este y otros problemas de este estilo, hemos creado una sencilla utilidad Review Bitmaps que compara el archivo gráfico en su carpeta de proyecto con aquellos que en realidad se emplean en la aplicación. Es una herramienta excelente que nos ayuda a controlar los archivos no utilizados que desorganizan la carpeta de archivos "bitmaps". Es también muy útil para detectar los archivos perdidos y para no perder de vista qué archivo bitmap pertenece a qué imagen. (Empleamos el término "bitmap" para referirnos a cualquier icono o archivo de imagen. No es exactamente relativo al formato de Windows Bitmap [BMP].)

Como todas las utilidades y componentes de Foxstuff, la herramienta Review Bitmaps es totalmente gratis. Solamente siga el enlace que aparece más abajo, para la descarga. Review Bitmaps requiere VFP 8.0 ó superior.

Para comenzar

La instalación de Review Bitmaps es trivial. Sólo hay que descomprimir el archivo descargado en una carpeta necesaria del PC de desarrollo. Una vez hecho esto, haga lo siguiente:

  1. Lance VFP (8.0 ó superior).
  2. Establezca el directorio predeterminado o la ruta de búsqueda al directorio en que descomprimió el archivo descargado.
  3. En la ventana de comandos escriba, DO FORM Review_Bitmaps. Aparecerá el formulario mostrado en la Figura 1.


Figura 1: Formulario principal de la utilidad

Utilizar Referencias de código

Para que Review Bitmaps pueda hacer algo útil, necesita saber qué archivos gráficos se referencian desde el código de la aplicación. En lugar de buscar los programas, formularios y clases por sí mismo para obtener esta información, parar hacer este trabajo, confía en la herramienta de VFP Referencias de Código.

Entonces, el primer paso en el uso de Review Bitmaps es lanzar la herramienta Referencia de códigos. Se puede hacer desde el menú Herramientas de VFP; pero es más fácil de hacer si se hace clic sobre el enlace que se encuentra cerca del borde superior del formulario (Launch Code Reference = Cargar Referencias de Código). Cuando lo haga , Review Bitmaps introducirá automáticamente la cadena de búsqueda necesaria en el cuadro Look for (Buscar por), como se muestra en la Figura 2.


Figura 2: La ventana de búsqueda de la herramienta Referencia de Código, con el cuadro de texto Look for ya relleno.

Luego, configure la herramienta Referencia de Código de la siguiente manera:

  1. Especifique el proyecto o carpeta de su aplicación..
  2. Establezca File types (Tipos de archivos) igual a Common (Comunes) o All Source (Todas las fuentes).
  3. Seleccione Use regular expressions  (Empleo de expresiones regulares) (esto es importante).
  4. No seleccione Match Case o Match Whole Word. (Coincidir mayúsculas/minúsculas o Coincidir con palabra completa)
  5. Establezca Comments (Comentarios) igual a Ignore Comments (Ignorar comentarios).

Si ha lanzado la referencia de código desde el menú Herramientas, entonces además tendrá que entrar la cadena de búsqueda que es la siguiente:

(\.ani)|(\.bmp)|(\.cur)|(\.dib)|(\.emf)|(\.exif)|
(\.gif)|(\.gfa)|(\.ico)|(\.jpg)|(\.jpeg)|(\.jpe)|
(\.jfif)|(\.png)|(\.tif)|(\.tiff)|(\.wmf)

Esto es una expresión regular que localiza nombres de archivos con cualquiera de estas extensiones aplicables a archivos gráficos. Vea que, la cadena de búsqueda se ha cortado en tres líneas para hacerla más legible: Debe entrarla como una única cadena al cuadro de texto Look for (Buscar por).

Ya puede seguir adelante con la búsqueda.

Cuando finaliza la búsqueda, exporte los resultados al archivo DBF que desee. Para hacer esto, haga clic en el botón Export (Exportar) de la barra de herramientas de la Referencia de Código, como tipo de salida, seleccione Visual FoxPro Table (DBF) y especifique el nombre del archivo para guardar los resultados exportados.

En este punto, puede cerrar la herramienta Referencias de código

Volviendo al formulario Review Bitmaps, introduzca la ruta y el nombre de archivo del archivo exportado que acaba de crear, y la ruta del directorio que contiene los archivos gráficos de su aplicación. Si se aplica, marque la casilla "Include subfolders" (Incluir Subcarpetas).

Seleccione la salida

Review Bitmaps ofrece tres posibles salidas:

  1. Una lista de archivos gráficos inutilizados, es decir, aquellos archivos que están en su carpeta de gráficos; pero no están referenciados desde una aplicación. Estos archivos son candidatos a ser eliminados.
  2. Una lista de archivos gráficos, es decir, aquellos ficheros que son referenciados en la aplicación pero que no están en la carpeta de archivos gráficos. Esta lista le ayudará a asegurarse de que todos los archivos requeridos están disponibles en tiempo de compilación.
  3. Una lista de todos los archivos en la carpeta, mostrando su imagen. Este informe es una herramienta excelente para determinar exactamente las imágenes disponibles. También es útil para determinar archivos con imágenes duplicadas en diferentes sub carpetas (Vea que esta opción no requiere el archivo exportado de Referencias de código, si lo único que desea es este informe, puede saltarse el paso de búsqueda y exportación de la Referencias de código.

Estas tres salidas están disponibles como Informes de VFP, los que se pueden visualizar o imprimir directamente desde la formulario Review Bitmaps. Las dos primeras salidas se pueden ver como listas que se pueden copiar al portapapeles o a un archivo.

Para obtener cualquiera de estas salidas, presione el botón adecuado del formulario.

Interprete con cuidado

Hemos visto que Review Bitmaps nos brinda una ayuda muy valiosa para nuestros proyectos; pero no es perfecto. En algunos casos, la referencia de códigos falla al identificar los archivos gráficos - frecuentemente debido a que su nombre se construye dinámicamente en el código de programa. En aquellos casos, Review Bitmaps, informará incorrectamente de que el archivo en cuestión no se utiliza.

La Referencia de Código puede también indicar lo que cree que es un archivo gráfico. Por ejemplo, si su código se refiere a un objeto llamado Report que tiene una propiedad llamada BMP, cualquier referencia a esa propiedad como Report.BMP se va a tratar como un nombre de archivo. Entonces, Review Bitmaps va a informar erróneamente sobre un archivo inexistente.

Por estas razones, debe interpretar las salidas desde Review Bitmaps con mucho cuidado. En particular, tenga cuidado de no eliminar permanentemente ningún archivo a menos de que esté seguro de que no lo necesita.

¡ A por ello !

Para descargar la utilidad, simplemente haga clic en un enlace:

Descarge ahora - http://www.ml-consult.co.uk/download/review_bitmaps.zip

El archivo descargado se llama review_bitmaps.zip, y ocupa 26 KB.

Mike Lewis Consultants Ltd. Agosto 2006.

4 de abril de 2018

Interfaces para objetos COM

En ciertas ocasiones, cuando se diseña con Orientación a Objetos, es útil separar las responsabilidades de una clase en dos o más clases distintas. Con esto se logra que una entidad exponga los lineamientos a seguir, pero, pueda desligarse de la forma de llevarlos a cabo, delegando esas responsabilidades en otras clases. Es más, existe un Patrón de Diseño que así lo aconseja: El Puente.

Otro caso típico, por ejemplo, se da con los servidores COM. Un objeto compilado en una DLL puede realizar sus funciones básicas o capturar sus eventos, pero, es útil que otro objeto encapsule las implementaciones de su funcionamiento para adaptar esa funcionalidad a los proyectos clientes de ese servicio, a la plataforma donde corre, etc. Luego, el objeto COM debe permitir delegar cierta funcionalidad a un objeto externo (no compilado en la misma DLL).

Para llevar esto adelante es necesario que se cumplan ciertas condiciones: Un contrato entre los objetos en cuestión. Esto se logra mediante las interfaces. En Programación Orientada a Objetos (POO), las interfaces son tipos de datos abstractos (clases abstractas), que exponen o publican los miembros públicos que deben contener otras clases para implementar dicha interfaz. Es decir, las interfaces son como listas de mensajes a los cuales debe responder un objeto de determinada clase, sin indicar cómo responder (no implementan funcionalidad).

Por ejemplo, se puede contar con una clase que encapsule un Array de objetos, a la cual podríamos llamar Arreglo, y con una interfaz llamada Pila que expone los métodos push (apilar) y pop (retirar o desapilar). Luego, podríamos querer definir una nueva clase basada en Arreglo: ArregloPila. Si a esta nueva clase se le definen los métodos push y pop, luego, podemos decir que ArregloPila "implementa" la interfaz Pila.

En el ejemplo anterior, se muestra como una de las funcionalidades de las interfaces es la de simular herencia múltiple, aspecto que algunos lenguajes, entre ellos VFP, no soportan. ArregloPila pareciera ser una clase heredera de Arreglo y de Pila, y en realidad es solamente heredera de la primera. Por otro lado, las interfaces sirven para validar que un objeto de una clase implemente lo necesario para cumplir con determinada funcionalidad. Por ejemplo, si se desea administrar las acciones que se vayan realizando en un programa para dar la posibilidad de deshacer esas acciones (siempre comenzando desde la última realizada), es conveniente que la estructura donde se almacenen esas acciones sea una pila. Es decir, será necesario verificar que un posible objeto llamado oAcciones sea de una clase que "implemente" la interfaz Pila.

VFP no soporta el uso de interfaces de forma nativa. Es un buen momento para aclarar que esta premisa se refiere a las interfaces puras de la POO, definidas con la misma categoría que las clases. En realidad, cada vez que se está definiendo una clase, todos aquellos miembros públicos son la interfaz de ella. Sin embargo, las interfaces declaradas como clases abstractas pueden ser útiles a la hora de documentar para especificar determinado comportamiento o clasificación de clases.

El uso de interfaces en VFP se puede simular mediante el análisis de la estructura interna de las clases, como lo ha hecho Víctor Espina, en un artículo publicado recientemente: Interfaces en VFP

Existe una excepción a estas restricciones que estamos nombrando: Los objetos COM. VFP incluye una serie de métodos y cláusulas que permiten implementar interfaces definidas en objetos COM, dependiendo de la forma en que se haya diseñado. Un ejemplo de esto se encuentra en Visual FoxPro Callback Design (Microsoft Visual FoxPro and Advanced COM)

La idea es definir 3 clases:

  • La clase principal del objeto COM.
  • Una clase que funcione como interfaz de los métodos delegados por la clase principal. Debe definir, sin implementar, los métodos que se delegarán.
  • Una clase externa que implemente los métodos que publica la interfaz.

A continuación se muestra un ejemplo implementado como dos PRG, de los cuales, el primero debe incluirse en un proyecto y compilarlo como DLL, mientras que el segundo PRG puede ejecutarse de cualquier forma posible. El alcance de este artículo no contempla la compilación de proyectos como DLL, por lo que si necesita ayuda con ese paso, se recomienda el siguiente artículo: Crear Servidores de Automatización

*------- Primer PRG: Incluir en un proyecto y compilarlo como DLL -------*
*************************
******* Clase COM *******
*************************

DEFINE CLASS ClaseOLE AS Session OLEPUBLIC
HIDDEN oImplementador
HIDDEN cPropiedad as String

PROCEDURE Init()
 this.oImplementador = .Null.
ENDPROC

*** Métodos para setear y recuperar la propiedad ***
PROCEDURE setPropiedad(cProp as Variant)
 IF ISNULL(this.oImplementador) ;
  OR (!ISNULL(this.oImplementador) AND this.oImplementador.ValidarAntesDeSetear(cProp))

  this.cPropiedad = cProp
 ELSE
  COMRETURNERROR("Valor incorrecto", "El valor ingresado no cumple con la validación")
 ENDIF
ENDPROC

PROCEDURE getPropiedad() as Variant
 IF !ISNULL(this.oImplementador)
  this.oImplementador.mostrar(this.cPropiedad)
 ENDIF

 RETURN this.cPropiedad
ENDPROC

*** Asociación del objeto COM con un objeto externo que implementa la interfaz InterfazImplementador ***
PROCEDURE setImplementador(oImplementadorExterno as Variant)
this.oImplementador = GETINTERFACE(oImplementadorExterno, "iInterfazImplementador", "interfacesCOM.ClaseOLE")
ENDPROC

ENDDEFINE


************************************************
******* Clase Interfaz con el objeto COM *******
************************************************
DEFINE CLASS InterfazImplementador as session OLEPUBLIC

PROCEDURE mostrar(cProp as String)
ENDPROC

PROCEDURE validarAntesDeSetear(cProp as Variant) as Boolean
ENDPROC

ENDDEFINE

*------- Segundo PRG: Cliente del objeto COM -------*
* Verificar, de ser necesario, las rutas.

LOCAL loImp as ImplementadorExterno
LOCAL loObjetoCOM as ClaseOLE

loImp = CREATEOBJECT("ImplementadorExterno")
loObjetoCOM = CREATEOBJECT("interfacesCOM.ClaseOLE")
loObjetoCOM.setImplementador(loImp)

TRY
 * Ejecución exitosa. Muestra "Hola Mundo"
 loObjetoCOM.setPropiedad("Hola Mundo")
 loObjetoCOM.getPropiedad()

 * Ejecución errónea. Muestra un error.
 loObjetoCOM.setPropiedad(DATE())
 loObjetoCOM.getPropiedad()
 
CATCH TO loError
 MESSAGEBOX(loError.message, 16, "Error")
ENDTRY

RETURN


********************************************************************************************************************************
******* Objeto que implementa parte de la funcionalidad de la clase ClaseOLE (Implementa la interfaz InterfazImplementador) *******
********************************************************************************************************************************
DEFINE CLASS ImplementadorExterno AS Custom
IMPLEMENTS iInterfazImplementador IN "interfacesCOM.dll"

*** Método validarAntesDeSetear ***
PROCEDURE iInterfazImplementador_validarAntesDeSetear(cProp as Variant) as Boolean
 RETURN VARTYPE(cProp) $ "CN"  && La propiedad debe ser caracter o numérica.
ENDPROC

*** Método mostrar ***
PROCEDURE iInterfazImplementador_mostrar(cProp as Variant)
 MESSAGEBOX(cProp)
ENDPROC

ENDDEFINE

Como se puede ver, la clase ClaseOLE está diseñada para delegar parte de su funcionamiento a un objeto externo, de la clase ImplementadorExterno en este caso, que implementa los métodos definidos por la interfaz InterfazImplementador. Con todo esto se logran ciertas ventajas:

  • Es fácil extender la funcionalidad y utilización de ClaseOLE. En el ejemplo, el procedimiento validarAntesDeSetear() permite cambiar las validaciones sin tener que recompilar la DLL. Además, los objetos externos pueden ser escritos en cualquier lenguaje, lo que permitiría que dichas validaciones se ajusten al lenguaje.
  • Se logra adaptar el funcionamiento del objeto COM a la plataforma que lo consume. En el caso del ejemplo, la función MESSAGEBOX() generaría un error si se intentara ejecutar desde la DLL. Luego, esa parte se delega.
  • Es útil este modelo para procesar eventos que captura el objeto COM.

Ahora, algunas preguntas y respuestas rápidas sobre el modelo:

  1. ¿Se puede omitir la escritura de alguno de los métodos que expone la interfaz en la clase que la implementa? No, deben escribirse todos los métodos publicados (aunque se dejen vacíos en su implementación). La cláusula IMPLEMENTS obliga a cumplir con el protocolo de la interfaz. Si alguno de los métodos se omite, al intentar realizar la creación del objeto externo (CREATEOBJECT("ImplementadorExterno") en este caso), se lanzaría un error indicando los métodos faltantes.
  2. ¿Qué pasa si no se incluye la cláusula IMPLEMENTS en la definición de la clase externa? La invocación a loObjetoCOM.setImplementador() arrojaría error (al llegar a la función GETINTERFACE()) ya que se valida que el objeto implemente la interfaz solicitada de forma explícita.
  3. Luego, ¿se podría omitir el uso de IMPLEMENTS y GETINTERFACE(), y hacer la asociación de los objetos directamente? Sí, y parecería, incluso, mucho más sencillo de implementar porque no sería necesario explicitar ciertas líneas de código y se podría desarrollar una clase menos. Sin embargo, incluyendo este manejo de interfaces se logra mayor orden:
    • Por un lado, se incluye implícitamente una validación de que el objeto externo cumple con el protocolo impuesto por la interfaz. Esto asegura que los métodos llamados por el objeto COM existen y no se va a generar un error.
    • Por otro lado, es la forma más eficiente de indicar qué métodos se deben escribir. En el ejemplo se ve que los métodos a escribir no precisamente concuerdan con los nombres de los métodos de ClaseOLE.

Pablo Lissa


Artículos relacionados: