Mostrando las entradas con la etiqueta Artículos. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Artículos. Mostrar todas las entradas

20 de julio de 2022

Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado

Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado

Autor: Carlos Alejandro Perez (QEPD) 1965-2021 (Chaco, Argentina)

Publicado en: http://logica10mobile.blogspot.com/2012/05/como-consumir-datos-de-visual-foxpro-en.html

Carlos Alejandro Perez



Introducción

Nos hemos puesto un poco nostálgicos, así que acá volcamos nuestra experiencia reciente en integrar datos de Visual Foxpro (32 bits) en un entorno de IIS de 64 bits, que no podía reducirse a correr 32 bits por condiciones de borde de la instalación.
Como es sabido, los sistemas operativos han ido migrando lentamente hacia los 64 bits. Entre otras ventajas, se tiene mayor direccionamiento en memoria RAM, y un rendimiento más sólido en los entornos de 64 bits, y lo más importante, el foco en los esfuerzos de las grandes compañías, entre otras ventajas. La migración de 16 a 32 bits demoró casi una década, pero de 32 a 64 está ocurriendo muy rápidamente.
En esta coyuntura, la realidad es que todavía existen muchos sistemas desarrollados en torno a Visual Foxpro, que es una aplicación de 32 bits. Éste puede correr en sistemas operativos Windows de 64 bits bajo la emulación WoW64 (Windows on Windows 64), que es esencialmente un conjunto de tres bibliotecas de enlace dinámico. Estas se encargan de colocar una capa muy liviana de adaptación en el sistema de 64 bits, a fin de traducir las llamadas y las estructuras de los sistemas de 32 bits, sin que éstos sufran modificación alguna. Sin embargo, por diseño, los manejadores de dispositivos se manejan de forma distinta. De esta forma, si por ejemplo tenemos una placa de video de tal marca y modelo, y la queremos usar en un sistema de 64 bits, deberemos indefectiblemente conseguir el driver de 64 bits. Como toda máquina virtual, es sumamente difícil comunicarse fuera de ella. En otras palabras, los drivers de 32 bits se podrán ver sólo dentro del ámbito Wow64, es decir: en un sistema operativo de 64 bits, los drivers de 32 serán visibles para los procesos emulados de 32 bits, pero no lo serán desde fuera de ese ámbito.
Ahora, ¿qué tiene que ver esto con Visual FoxPro? Bueno, fundamentalmente, las estrategias de acceso a datos que corren en Windows son basadas en el modelo ODBC (Open Database Connectivity). ODBC, en la implementación para Windows, no es otra cosa que un manejador de impresora modificado para bases de datos, es decir, funciona y se gestiona de manera muy parecida a un device-driver de impresora, y por lo tanto, pueden coexistir drivers de 32 y 64 en un sistema, pero sólo serán visibles dentro de sus ámbitos respectivos de ejecución. El rol del driver ODBC es análogo al manejador de impresora: así como un controlador de una determinada impresora “entiende” el lenguaje y los comandos de la misma (sean ésos PCL, Postcript, etc.), del mismo modo un manejador ODBC “entiende” las particularidades de una determinada base de datos. Y con OLEDB pasa más o menos lo mismo, recordemos que OLE DB es un derivado de ODBC, que introduce el modelo de objetos COM a ODBC, y copia su modelo de doble búfer, etc., con lo cual hay mucha similitud conceptual en cuanto al driver en sí (y no tanta similitud en cómo expone los datos hacia el cliente).

Escenarios posibles entre cliente y servidor

Suponiendo que tenemos un ejecutable de una aplicación cliente que desea acceder a un servidor de datos. Las combinaciones posibles serían estas, siempre hablando de un sistema operativo de 64 bits:
ClienteServidorDriver de 32 bits instaladoDriver de 64 bits instalado
32 bits32 bitsVisible desde el cliente, conectable.Invisible desde el cliente, innecesario para conectarse
32 bits64 bitsVisible desde el cliente, conectable.Invisible desde el cliente, innecesario para conectarse.
64 bits32 bitsInvisible desde el cliente, no se puede conectar.Visible desde el cliente, conectable
64 bits64 bitsInvisible desde el cliente, no se puede conectarVisible desde el cliente, se puede conectar.
Luego, si de VFP y de SQL Server se tratase, podemos asumir como cliente a VFP, y como servidor a SQL Server, y el cuadro de arriba sería válido igualmente. Si nos preguntamos si un cliente de 64 bits podría conectarse a un servidor que corre en 32 bits, la respuesta es: si, en la medida que exista el driver del lado del cliente. Supongamos que existiera un VFP de 64 bits, y se corre una instancia de SQL Server de 32 bits. Como SQL Server es un “servicio de datos” que funciona un un protocolo binario (esto es, basado en sockets con tráfico bajo un modelo propietario) llamado TDS (Tabular Data Stream), tendremos que este servicio es independiente de la versión (32 o 64 bits) que lo produzca: el protocolo es inmutable entre dichas plataformas. Para que lo veamos mejor, un servidor IIS de 32 bits producirá el mismo HTML que uno de 64 bits, ya que el protocolo de comunicación HTTP y el formato del mensaje HTML es conceptualmente siempre el mismo (un documento de texto), independientemente de la plataforma que lo genere. Análogamente, es posible entonces conectarse desde un cliente de 32 a un servidor de 64, siempre y cuando exista el driver de 32 bits para dicho servicio.

Por ejemplo, si queremos gestionar desde un cliente de 64 bits la creación y el mantenimiento de un driver ODBC, sólo veremos los drivers de 64 bits. Hagamos la siguiente prueba: iniciemos el Panel de Control, vayamos a Herramientas administrativas, Configurar orígenes de datos (ODBC). Pidamos crear una nueva conexión de sistema (DSN de sistema), y veremos sólo los drivers de 64 bits que la PC tenga instalados en ese momento:

image
Fig. 1: drivers de 64 bits visibles desde un cliente de 64 bits (Panel de Control)


Ahora bien, sin cambiar de PC, tratemos de hacer lo mismo desde un cliente de 32 bits, por ejemplo, desde el mismo Visual FoxPro, creando una base de datos .DBC y generando una conexión desde allí.

image
Fig. 2: drivers de 32 bits visibles de un cliente de 32 bits (VFP)


Como se aprecia en la figura 2, la cantidad es mayor, y entre ellos , vemos el driver para Visual FoxPro instalado. Tanto en el cliente de 64 bits (Panel de control) como en el cliente de 32 (VFP) podremos gestionar, por ejemplo, la creación de una conexión a SQL Server (independientemente de si el servidor SQL corre en 32 o en 64), porque tenemos ambos drivers de cliente instalados en la PC. Pero no podremos gestionar una conexión a VFP desde un cliente de 64, porque no existe el driver.

El problema con ASP.NET, VS y Windows de 64 bits.

Ahora bien, desde Visual Studio 2010, ¿qué sucede?. Supongamos este escenario:
  • Sistema operativo Windows de 64 bits –> IIS de 64 bits, que contiene:
    • IDE de Visual Studio 2010 convencional, 32 bits, instalado con ajustes por defecto sobre el Windows anterior.
    • Visual FoxPro, 32 bits
    • OLE-DB para Visual FoxPro, 32 bits
El objetivo es construir una página ASP.NET que acceda a los datos de VFP en ese escenario.
El problema es el siguiente:
  • Al programar la aplicación ASP.NET en VS2010, se puede crear sin problemas una conexión VFPOLEDB, ya que el cliente (justamente la IDE de Visual Studio) y el driver OLE-DB son de 32 bits. Al depurar la aplicación ASP.NET, se podrán consumir los datos de VFP con mucha facilidad, por ejemplo, utilizando un control SQLDataSource desde la misma página.
  • Pero al publicar el sitio en IIS, se transfieren los ensamblados a un entorno de ejecución de 64 bits. Las páginas se ejecutan bajo una máquina virtual .NET, que es un proceso de 64 bits. Este CLR de .NET es “incapaz” de registrar el driver de 32 bits OLE-DB para acceder a VFP, y las páginas fallan en IIS dando un error de “dll no registrada”, la de OLE-DB justamente.
Entonces la pregunta es: ¿cómo publicamos una página ASP.NET en un servidor IIS montado en un Windows de 64 bits, que no puede tener compatibilidad de 32 bits por alguna razón, y que la vez consuma datos de Visual FoxPro?
No existe una respuesta fácil. Las posibilidades que exploramos son las siguientes:

Idea centralQué habría que hacerContras
Correr el sitio ASP.NET en IIS habilitado para 32 bits
  • Activar la compatibilidad de 32 bits en IIS de 64 bits, corriendo en una ventana de comandos la siguiente instrucción:


C:\>cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 true


Si bien se activa la compatibilidad de 32 bits en IIS, al mismo tiempo se inhabilitan las de 64 bits. Luego, deberíamos tener todos los sitios corriendo en IIS en modo de 32 bits, aunque el SO de 64 bits (estamos pagando por algo que no usamos).
Instalar dos IIS en el servidor, uno de 32 y otro de 64La segunda instalación de IIS correría en modo de compatibilidad de 32 bits, sólo afectando a las de 64 en esa instancia, y en la otra podremos correr el sitio en 64 bits. Las páginas que acceden a VFP , al estar publicadas en otro servidor, debería correr en otro port HTTP o bien resolver por nombre (porque tienen una misma IP pública) a nivel del IIS principal.No es posible. No se puede instalar dos IIS en un mismo servidor. Si es posible instalar un IIS + Apache, o bien dos instancias de Apache, pero necesitamos IIS para correr ASP.NET.
Implementar un servidor de automatización COM hecho en VFPCrear un objeto COM (servidor de automatización) con VFP, que reciba las consultas SQL de ASP.NET, y devuelva datasets ADO.NET utilizando el comando CursorToXML()No es posible. No se podrá enlazar el COM en el espacio de 64 de bits del IIS en producción.
Implementar un servicio web con VFP y publicarlo en el mismo IIS que publica las páginas ASP.NET.Utilizar el mismo objeto de automatización COM del punto anterior, pero publicarlo como servicio web en el mismo IIS que sirve el sitio principal.No es posible, el COM de VFP de 32 bits no se podrá enlazar al ISAPI de 64 bits.
Ídem anterior, pero publicar el web service de VFP en un servidor Apache de 32 bits.Instalar en paralelo al IIS un servidor Apache de 32 bits, y publicar el servicio web de VFP de 32 bits por allí. Las páginas principales ASP.NET en IIS de 64 bits consultarán al web service local para acceder a los datos VFPTeóricamente posible, pero complicado. Al tener dos HTTP servers bajo una misma IP, uno de los HTTP servers debe trabajar en otro port, o bien transferir el request HTTP por nombre de dominio en el IIS, derivando al Apache de 32 bits las llamadas al servicio web.
Intentar conseguir un driver de 64 bits.Conseguir un driver OLE-DB de 64 bits para VFP provisto por la empresa Sybase, que sirve para su ETL (extracción, transformación y carga).Licencia de Sybase. ¿Dónde se lo consigue sin tener ningún producto Sybase? ¿funciona standalone o necesita un software de Sybase?

La solución del Servidor vinculado (Linked Server)


Tras dos meses de mucho probar y cavilar, se llegó a la conclusión que la mejor solución sería no tocar la infraestructura de IIS 64 bits preexistente, y utilizar el servicio de “servidor encadenado” que tiene SQL Server. El truco consiste en instalar una instancia vacía de SQL Server Express de 32 bits y activar dentro de ella un servidor vinculado o encadenado (linked server), que oficie de enlace entre los datos de VFP y los clientes de 64 bits.

El servidor vinculado o encadenado es esencialmente una pasarela, es decir, una capa de transformación de mensajes. A continuación mostramos un diagrama de bloques conceptual, donde desde un gran sitio ASP.NET en Windows Server de 64 bits, con SQL Server de 64 bits, se accede a VFP del driver VFPOLEDB de 32 bits:


VFP linked server


Figura 3: esquema de bloques de la solución

Microsoft ha incluido, increíblemente podríamos decir, la capacidad de tener servidores encadenados dentro de la instancia Express. El servidor encadenado puede ser cualquiera que soporte un driver OLE-DB para dicha versión (32 o 64 bits). Así, si debemos conectarnos a VFP, deberemos usar VFPOLEDB, que es un driver de 32 bits, y por lo tanto, descargar e instalar la versión de 32 bits de SQL Server Express, descargar e instalar el driver OLE-DB para Visual FoxPro, y estaríamos listos para lograr el acceso desde las páginas ASP.NET corriendo desde un servidor de 64 bits.

Una vez que el servidor encadenado está definido, se puede acceder desde cualquier cliente que tenga un driver de acceso a SQL, sea éste de 32 o de 64 bits. En la figura, el servidor HTTP instalado es IIS7.x, de 64 bits, que aloja un CLR de 64. Dentro de esa máquina virtual CLR, el proveedor administrado ADO.NET para SQL Server será uno regular, nativo, no existirá ningún problema porque sólo se necesitará la visibilidad del servicio de datos. El proveedor administrado hace abstracción de la plataforma del servidor, porque sólo ve los servicios sin importarle los procesos que lo generan. De este modo, desde .NET y con esta solución, se tendrá acceso indistintamente a SQL Server regular, de producción, que puede ser de 64 bits (alternativa de la figura, donde se asume un SQL server prexistente de 64 bits para el sistema principal), y al mismo tiempo, a SQL Server Express de 32 bits que aloja el servidor encadenado, vacío sin tablas nativas.

Configurando el servidor encadenado para que tome el archivo .DBC de Visual FoxPro, se tendrá acceso a todas las tablas de la base de datos Visual Fox, como si estuviesen residiendo de alguna manera en la instancia SQL Server Express.

Configuración paso a paso

1. Descargar e instalar SQL Server Express de 32 bits en el servidor IIS, o bien en algún lugar de la red que sea visible. Luego, hay dar acceso al proceso de SQL Server a la carpeta que contiene las tablas y el contenedor de base de datos de VFP.

Yendo a la carpeta en cuestión, en el explorador de archivos, hacer clic con botón derecho sobre la que contiene las tablas y el contenedor DBC, y seleccionar Propiedades, luego seleccionar la ficha Seguridad. La cuenta que debemos habilitar para acceso no es ninguna de las consabidas Network Service, ni Local System, ni System, sino la denominada MSSQL$SQLEXPRESS, como se muestra en la figura de abajo. El no hacerlo impedirá que se pueda acceder a dicha base de datos.

image

2. Iniciar el administrador de SQL Server, y configurar un servidor encadenado. Para ello abrir el administrador, y hacer clic con botón derecho sobre la instancia correspondiente. Seleccionar el nodo Server Objects, y dentro de él, la opción Linked Servers

image
Fig. 4

3. Verificar drivers. Expandir dicho nodo, y se verá el listado de drivers disponibles en el ámbito de 32 bits. Debe estar visible la opción VFPOLEDB

image
fig. 5

4. Agregar un nuevo servidor vinculado. Hacer clic con botón derecho sobre el nodo Linked Servers para agregar un nuevo un servidor vinculado a los datos VFP. Aparece el siguiente cuadro de diálogo:

image
Fig. 6

Proveer los siguientes datos:

  • Nombre del servidor encadenado: especificar el nombre o etiqueta para el servidor encadenado. Por ejemplo, MIAPPVFP

  • Proveedor: seleccionar OLE DB para Visual FoxPro

  • Nombre del producto: se puede especificar un identificador del producto, en este caso colocar la misma que el nombre del servidor encadenado.

  • Origen de datos: especificar el archivo .DBC de bases de datos de VFP, con el camino completo al directorio. Por ejemplo, C:\MIAPPVFP\DATOS\datos1.dbc

  • Dejar todos los demás campos en blanco, y hacer clic en Aceptar. Asegurarnos que la pantalla de alta quede así:
image
Fig. 7

5. Probar con una nueva consulta. Conectarse a la instancia de SQL Server Express que aloja el servidor encadenado. Recordemos que al no tener bases de datos de usuario de SQL Server, no existirán más que las predeterminadas (master, etc.). Cuando hagamos una consulta al servidor encadenado MIAPPVFP de este ejemplo, no se trabaja contra ninguna base de datos nativa, sino contra la representación interna de la base de datos VFP. En otras palabras, MIAPPVFP es, al mismo tiempo, servidor y base de datos. Debido a esta particularidad, para hacer una selección SELECT a una tabla de VFP, tendremos dos formas de acceder:
  1. Trabajando directamente con el servidor encadenado, armando consultas en Transact-SQL (en la figura 3, el trazo en azul) desde cualquier cliente de datos administrado de .NET. Esta técnica envía strings de consultas a la base de datos SQL Server Express, y el servidor encadenado, a través de VFPOLEDB, realiza la traducción necesaria al dialecto de VFP. Por este motivo, desde ADO.NET no podremos enviar a ejecutar comandos y funciones propias de VFP, como por ejemplo, SELECT DTOS(fecha) AS fecha… , ya que DTOS() no es una función reconocida por T-SQL. El servidor encadenado recibe la petición en T-SQL , la traduce al SQL de Visual FoxPro, ejecuta la consulta, y al recibir la respuesta, acondiciona el conjunto de resultados como si fuese un resultado nativo de SQL Server para enviárselo al cliente que efectuó la petición de datos.

  2. Utilizando la primitiva OPENQUERY de SQL Server, que permite enviar por paso-a-través un string conteniendo comandos nativos de VFP. En este caso, el servidor encadenado pasa a través de sí mismo la consulta sin modificarla, y sólo se encarga de “adaptar” el resultado de respuesta como una respuesta regular de SQL-Server.
5.1 Prueba con sintaxis T-SQL de SQL Server

Para probar si todo funciona bien, escribamos una consulta sencilla sobre una tabla que sabemos preexistente en la DBC de VFP. Nótese la sintaxis de acceso a la tabla. Se utiliza la notación de cuatro segmentos siempre que se acceda de esta manera a un servidor encadenado. Sin embargo, como aquí se trata en realidad de una carpeta y un contenedor de tablas, no existe el concepto de esquema, etc. por lo que estas etiquetas se omiten, dejando solamente los puntos. De esta forma, si el servidor encadenado se llama MIAPPVFP, y la tabla es clientes.dbf, se referencia como FROM miappvfp…clientes (tres puntos), como se muestra en la siguiente figura:

image
fig. 8

5.2 Prueba de consulta con paso-a-través y OPENQUERY

Cuando sea necesario emitir una consulta que sólo puede resolverse a través de VFP, será necesario utilizar el pasfo-a-través. Para lograr esto, será necesario utilizar la función OPENQUERY, en el siguiente formato de consulta:

SELECT <lista de campos> FROM OPENQUERY(<servidor_vinculado>, <string de consulta nativa VFP>)

Donde el <servidor_vinculado> debe especificarse sin comillas, y la consulta VFP nativa debe estar como una constante de caracteres encerrada entre comillas simples:

SELECT nombre,sexo FROM OPENQUERY(miappvfp,’SELECT * FROM clientes’)

image
fig. 9

Con esto configurado, estaremos en condiciones de consumir los datos desde páginas ASP.NET que se publiquen en IIS de 64 bits, pudiendo aprovechar lo que ya sabemos de ADO.NET, etc.

Conexión desde una aplicación de .NET

Para conectarnos desde Visual Studio, el string de conexión a SQL Server es parecido al que usaríamos con tablas regulares, pero en este caso se omite el catálogo inicial o el nombre de base de datos. Por ejemplo:

Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword

Una vez establecido este origen de datos, en nuestro proyecto podremos generar objetos de datos que accedan a la base contenedora de VFP a través del servidor vinculado. Por ejemplo, si utilizásemos el control sqlDataSource para las páginas ASP.NET, podríamos probar una consulta así

image
fig. 10

Donde el secreto está en configurar el datasource SqlDataSource1 de la siguiente manera:

String de conexión: Por ejemplo,  Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword” reemplazando las credenciales por las correspondientes a nuestra instalación.

Comando SELECT: Al configurar el SqlDataSource1, el asistente nos preguntará cómo recuperar la base de datos. Nótese que no nos permite acceder a ninguna tabla ya que no hemos especificado una base de datos por defecto, y la única opción será especificar una consulta SQL. La opción de procedimiento almacenado no está disponible.

image  image
Fig. 11 y 12

Este comando SELECT debe especificarse con sintaxis T-SQL de SQL Server, no con el dialecto de Visual FoxPro. Nótese que hemos definido un parámetro de consulta en la consulta:

SELECT nombre,doc_nro FROM miappvfp…clientes WHERE doc_nro = @doc_nro

Ajuste del parámetro de la consulta: Como el control SqlDataSource1 acepta varias formas de cargar el parámetro antes de ejecutar la consulta, seleccionamos que lo tome del control del formulario txtDoc, que es el cuadro de texto donde el usuario especifica el número de documento a buscar, colocando esos datos en el asistente:

 image
Fig. 13

Prueba de la consulta:

En el siguiente paso del asistente probaremos la consulta. En nuestro caso, a los fines de verificación solamente, hemos creado varias entradas en la tabla (doc_nro no es clave primaria en este test) con un mismo documento, para ver si recupera correctamente:

image   image
Fig. 14 y 15

Aceptemos todo y el SqlDataSource1 quedará configurado.

Ajuste del control GridView: Para el gridview de la página sólo bastará hacer clic en su smarttag (el pequeño triángulo que aparece en la esquina superior derecha al seleccionar el control con el ratón), y elegir como origen de datos al recién configurado SqlDataSource1.

image
fig. 16

Al hacerlo, inmediatamente quedará configurada la visual del gridview. Podemos cambiar la visual del gridview utilizando la opción AutoFormat, etc. y cambiarle la cabecera a cada columna utilizando la opción Edit Columns. A efectos ilustrativos, vemos cómo cambiar la cabecera de la primera columna, de nombre (que trae de la tabla de datos VFP) a Nombre de cliente.

image
fig. 17

Prueba de la página:

Pulsamos F5 y aguardamos hasta que aparezca la página

image
fig. 18

Colocamos el numero de documento y pulsamos consultar. A los breves instantes tendremos la respuesta:

image
fig. 19

Donde los datos provienen de Visual Foxpro. También podríamos haber utilizado un dataset, etc.

Publicar la página en el servidor IIS de 64 bits.

La prueba de fuego será publicar la página al servidor IIS, de 64 bits. Una vez hecho esto, podremos verificarla para ver si funciona correctamente. Esta es una imagen de la misma página instalada en el servidor de producción:

image
fig. 20

Con lo cual, hemos podido publicar los datos de VFP en un entorno ASP.NET con IIS de 64 bits, consumiendo datos de VFP.

Algunos cuidados a tener

1. Campos de tipo fecha: Cuando se utilicen fechas como parámetros, la consulta utilizando sintaxis de T-SQL no es optimizada en el motor VFP si la columna de filtrado es de tipo fecha. Por algún motivo, OLEBD para VFP ejecuta una exploración secuencial completa de toda la tabla, dando como resultado un rendimiento de consulta sub-óptimo que puede llegar incluso a una decena de segundos, algo inaceptable para una página web. En este caso, si necesitamos optimizar la consulta por parámetros de fecha en la tabla de VFP, la única solución posible será utilizar OPENQUERY como se explica en el punto siguiente.

2. Funciones nativas de VFP. Al utilizar sintaxis T-SQL, no podremos enviar una consulta con funciones propias de VFP, por ejemplo, INSTR() o DTOS() no podrían incluirse en la consulta. Como vimos en el paso anterior, tampoco podríamos optimizar una consulta que se haga sobre fechas de VFP. Para estos casos, debermos utilizar OPENQUERY indefectiblemente, a fin de pasar sólo un string de consulta en dialecto VFP que será procesado desde el mismo motor de VFP, sin pasar por ningun proceso de traducción en el servidor encadenado.

Por ejemplo, supongamos que necesitamos optimizar una consulta por la columna de fecha llamada fechaVenta en una tabla llamada VENTAS debemos seguir estos pasos:
  • indexar la tabla de Visual Foxpro con la función DTOS, y hacer la consulta desde ASP.NET con dicha consulta. Suponiendo que la columna sea fechaVenta, debemos indexarla con INDEX ON DTOS(fechaVenta) TAG fV en Visual Foxpro.

  • en ASP.NET, configurar los parámetros para que coincidan con el patrón YYYYMMDD de VFP que devuelve la función DTOS(). Por ejemplo, si tenemos una variable dFecha as date en .NET, podremos obtener su string equivalente al colocar dim cFechaParam as string = dFecha.ToString(“yyyyMMdd”).

  • Armar la consulta en el SQLDataSource, SQLAdapter, etc. especificando que el SelectCommand sea el sigiuente: 
SELECT * FROM OPENQUERY( miappvfp, ‘SELECT fechaVenta, factura, cliente FROM ventas WHERE DTOS(fechaVenta) = ‘”+cFechaParam+”’)

3. Palabras clave en consulta. Si bien VFP nos deja emitir consultas con nombres de campo o tablas que coincidan con las palabras clave, como por ejemplo llegar al extremo de colocar “SELECT select FROM select”  y que ejecute correctamente en VFP, siempre que haya una tabla llamada SELECT con una columna con nombre SELECT, este tipo de consultas será rechazada por VFPOLEDB en el servidor encadenado, dando un error en en la instancia de traducción si utilizamos las consultas T-SQL. Incluso utilizando OPENQUERY, la consulta podría realizarse correctamente en VFP, pero al rearmar la tabla de resultados, VFPOLEDB dará un error y no se podrá obtener el dataset, etc. desde .NET. Por tanto, debemos asegurarnos que, tanto con consultas en T-SQL como con  las especificadas dentro de OPENQUERY, no se utilicen objetos de la base de datos que coincidan con palabras clave de T-SQL.

Conclusión


Con la facilidad de servidor vinculado (linked server) que tiene SQL Server Express, podremos hacer que los procesos de 64 bits consuman los datos de VFP a través de VFPOLEDB. El SQL Server Express no tiene costo, y el driver OLEDB para VFP es de descarga gratuita. Con esta solución, si bien se agrega una instancia más al servidor principal o a algún otro de la red, tenemos la ganancia es que no se debe alterar el mecanismo en .NET de acceso a los datos, porque seguirá siendo el driver nativo que siempre estará disponible, en sistemas de 32 o de 64 bits. Así, uno puede tener toda una instalación de un gran sistema ASP.NET en Windows Server de 64 bits, y aún así acceder a los datos de Visual FoxPro sin necesidad de alterar en nada la programación ni la instalación en producción del sitio.

2 de julio de 2021

Utilice MDots por la velocidad, no solo por la exactitud

Artículo original: Use MDots for speed, not just for correctness
(Use MDots for speed.pdf)
Autor: Tamar E. Granor
Traducido por: Luis María Guayán


El prefijo de referencias a variables "m." (mdot) no solo hace que su código sea inequívoco, sino que lo hace más rápido.

Puede que no haya ningún tema en el que los desarrolladores de VFP como grupo se sientan mas convencidos que si prefijar o no, todas las referencias a las variables con "m." para evitar ambigüedades. Sin embargo, puede que sea hora de que se termine ese argumento, porque resulta que el uso de mdots también hace que el código se ejecute más rápido.

Desde sus primeros días, FoxPro ha dado preferencia a los nombres de campo en las expresiones. Cuando una expresión incluye un nombre que es tanto un nombre de campo de la tabla abierta en el área de trabajo actual, como una variable, a menos que se indique lo contrario, FoxPro usa el campo. Es decir, cuando tiene un código como el Listado 1, VFP primero busca campos llamados nHeight y nWidth. Solo si no lo encuentra, decide que debe haber querido decir variable.

Listado 1. Cuando se usan nombres en una expresión, VFP da preferencia a los nombres de campos.

 nArea = nHeight * nWidth 

Si desea utilizar la variable en lugar de un campo del mismo nombre, puede precederla con la letra "m" y un punto. Los desarrolladores de VFP suelen llamar a ésta combinación "mdot".

El Listado 2 muestra el ejemplo anterior con las variables claramente indicadas.

Listado 2. Mdots deja en claro que se refiere a una variable.

 nArea = m.nHeight * m.nWidth 

En este ejemplo, mdot no es necesario para nArea porque solo se puede asignar un nuevo valor a las variables mediante el signo igual.

Convenciones de nomenclatura como solución

Debido a este comportamiento, muchos desarrolladores de VFP han adoptado convenciones de nomenclatura destinadas a garantizar que nunca tengan variables y campos con el mismo nombre. La notación más común (recomendada en el archivo de ayuda de VFP y generalmente denominada "húngara") utiliza una letra de alcance ("l" para local, "p" para privado, "g" para global/pública) seguida de una letra tipo ("c" para carácter, "n" para numérico, etc.) al principio de cada nombre de variable. En esa notación, los campos obtienen un tipo de letra, pero no un indicador de alcance. Usando esta notación, un campo que representa height sería nHeight, pero una variable de altura sería lnHeight.

El problema de confiar en una convención de nomenclatura es que VFP no la conoce y no evita todos los conflictos. Por ejemplo, no seria imposible imaginar tener un campo llamado lOrange y una variable llamada loRange. Si bien estos se ven legiblemente diferentes, para el motor VFP, son exactamente iguales y el campo se utilizará siempre que haya ambigüedad.

A estas alturas, probablemente pueda decir que está del lado de "siempre usar mdots", y si está firmemente del lado de "no mdots", probablemente ningún argumento sobre cómo funciona VFP o posibles errores lo convencerá.

MDots es más rápido

Sin embargo, también resulta que el uso de mdots hace que su código se ejecute más rápido. Cuánto más rápido depende del número de referencias a variables y del número de campos de la tabla abiertos en el área de trabajo actual.

Recientemente probé en dos computadoras diferentes, usando dos programas diferentes, uno con solo unas pocas referencias a variables y otro con muchísimas más.

En cada caso, también probé diferentes números de campos en el área de trabajo actual, comenzando sin una tabla abierta, luego con una tabla (en realidad, un cursor) con cinco campos, luego uno con 10 campos, y así sucesivamente hasta 200 campos en la tabla en el área de trabajo actual.

Dada la preferencia de VFP por los campos, no me sorprendió ver que mdot era más rápido y que cuantos más campos en la tabla en el área de trabajo actual, mayor era la ventaja de mdot.

El Listado 3 muestra el primer programa de prueba, el que tiene menos referencias a variables. El código usa variables de altura y ancho para calcular el perímetro y área. Los cálculos se realizan en un ciclo que se ejecuta durante cinco segundos; hay un total de seis referencias a variables en el ciclo y los cálculos.

Listado 3. Este programa compara el uso de variables con mdots con el uso de variables sin mdots. El bloque que se está probando contiene seis referencias a variables.

* Compare speed with and without mdot
#DEFINE SECONDSTORUN 5
LOCAL nCase1Start, nCase1LoopEnd,
nCase2LoopStart, nCase2LoopEnd
LOCAL nCase1Passes, nCase2Passes
LOCAL nLength, nWidth, nPerimeter, nArea
* Test multiple cases from no table open
* to table with many fields open.
* Store results in a cursor in a different
* workarea.
CREATE CURSOR csrMDotSpeeds
(nFields N(3), nNoMDots I, nMDots I)
SELECT 0
LOCAL nFields, nField, cFieldList
* Initialize variables for calculations
nLength = 27.3
nWidth = 13.7
FOR nFields = 0 TO 200 STEP 5
  IF m.nFields <> 0
    cFieldList = ''
    FOR nField = 1 TO m.nFields
      cFieldList = m.cFieldList + "cField" + ;
        TRANSFORM(m.nField) + " C(5), "
    ENDFOR
    cFieldList = TRIM(m.cFieldList, ", ")
    CREATE CURSOR csrDummy (&cFieldList)
  ELSE
    SELECT 0
  ENDIF
  * Now do the test
  nCase1LoopStart = SECONDS()
  nCase1LoopEnd = m.nCase1LoopStart + ;
    SECONDSTORUN
  nCase1Passes = 0
  DO WHILE nCase1LoopEnd > SECONDS()
    nCase1Passes = nCase1Passes + 1
    nPerimeter = 2*nLength + 2*nWidth
    nArea = nLength * nWidth
  ENDDO
  nCase2LoopStart = SECONDS()
  nCase2LoopEnd = m.nCase2LoopStart + ;
    SECONDSTORUN
  nCase2Passes = 0
  DO WHILE m.nCase2LoopEnd > SECONDS()
    nCase2Passes = m.nCase2Passes + 1
    nPerimeter = 2*m.nLength + 2*m.nWidth
    nArea = m.nLength * m.nWidth
  ENDDO
  INSERT INTO csrMDotSpeeds
  VALUES (m.nFields, m.nCase1Passes, ;
    m.nCase2Passes)
  IF m.nFields <> 0
    USE IN csrDummy
  ENDIF
ENDFOR
RETURN

Los resultados de esta prueba en dos máquinas diferentes, fueron bastante similares. Sin tabla abierta en el área de trabajo actual (el caso 0), la versión sin mdots fue un poco más rápida. Después de eso, sin embargo, la versión mdots siempre fue más rápida. Con 30 campos en la tabla, la versión mdots completó más de un 25% más de iteraciones; con 50 campos, la versión mdots completó un 50% más de iteraciones. En el extremo máximo de la prueba, 200 campos, la versión mdots hizo 2,7 veces más pases.

El número de iteraciones completadas por el código usando mdots fue notablemente estable. Para una máquina determinada, la diferencia entre el máximo y el mínimo fue inferior al 0,02% del valor máximo.

Por otro lado, el número de iteraciones completadas por el código sin mdots descendió de manera bastante constante. Con 200 campos, solo se completaron alrededor de un tercio de las iteraciones que sin una tabla abierta.

Es importante tener en cuenta que estamos hablando de millones de iteraciones en cinco segundos, por lo que el efecto es pequeño para cualquier referencia a variable dada. Sin embargo, en una aplicación, es probable que tenga miles o decenas de miles de referencias a variables; en una aplicación típica, es probable que la mayoría de ellos ocurran con una tabla abierta en el área actual.

Una prueba más extensa

Quería ver la diferencia que hace mdot en un programa con muchas más referencias a variables que el ejemplo de perímetro y área. Para hacerlo, adapté un fragmento de código de una aplicación cliente. El núcleo del código es una función que determina si un punto específico está "cerca" de una línea específica. Acepta cuatro parámetros, una línea, un punto (en forma de coordenadas de fila y columna) y una tolerancia. La tolerancia indica qué tan lejos de la línea puede estar algo y aún ser considerado "cerca". El código real no es importante, pero la función contiene casi 60 referencias a variables potencialmente ambiguas.

La prueba, estructurada de la misma manera que la prueba anterior, se muestra en el Listado 4.

Listado 4. Este código prueba la velocidad de un programa con más de 50 referencias a variables con y sin mdots.

* Compare speed with and without mdot
#DEFINE SECONDSTORUN 5
LOCAL nCase1Start, nCase1LoopEnd, nCase2LoopStart, nCase2LoopEnd
LOCAL nCase1Passes, nCase2Passes
* Test multiple cases from no table open
* to table with many fields open.
* Store results in a cursor in a different
* workarea.
CREATE CURSOR csrMDotSpeedsLarge ;
  (nFields N(3), nNoMDots I, nMDots I)
SELECT 0
LOCAL nFields, nField, cFieldList
LOCAL oLine AS LINE
oLine = CREATEOBJECT("Line")
oLine.LEFT = 27
oLine.TOP = 13
oLine.HEIGHT = 152
oLine.WIDTH = 53
FOR nFields = 0 TO 200 STEP 5
  IF m.nFields <> 0
    cFieldList = ''
    FOR nField = 1 TO m.nFields
      cFieldList = m.cFieldList + "cField" + ;
        TRANSFORM(m.nField) + " C(5), "
    ENDFOR
    cFieldList = TRIM(m.cFieldList, ", ")
    CREATE CURSOR csrDummy (&cFieldList)
  ELSE
    SELECT 0
  ENDIF
  * Now do the test
  nCase1LoopStart = SECONDS()
  nCase1LoopEnd = m.nCase1LoopStart + ;
    SECONDSTORUN
  nCase1Passes = 0
  DO WHILE nCase1LoopEnd > SECONDS()
    nCase1Passes = nCase1Passes + 1
    IsPointNearLineNoMDot(oLine, 55, 45, 1)
    IsPointNearLineNoMDot(oLine, 100, 27, 2)
    IsPointNearLineNoMDot(oLine, 0, 0, 1)
    IsPointNearLineNoMDot(oLine, 500, 7, 3)
  ENDDO
  nCase2LoopStart = SECONDS()
  nCase2LoopEnd = m.nCase2LoopStart + ;
    SECONDSTORUN
  nCase2Passes = 0
  DO WHILE m.nCase2LoopEnd > SECONDS()
    nCase2Passes = m.nCase2Passes + 1
    IsPointNearLineMDot(m.oLine, 55, 45, 1)
    IsPointNearLineMDot(m.oLine, 100, 27, 2)
    IsPointNearLineMDot(oLine, 0, 0, 1)
    IsPointNearLineMDot(oLine, 500, 7, 3)
  ENDDO
  INSERT INTO csrMDotSpeedsLarge ;
    VALUES (m.nFields, m.nCase1Passes, ;
    m.nCase2Passes)
  IF m.nFields <> 0
    USE IN csrDummy
  ENDIF
ENDFOR

A esta altura, no debería sorprendernos que cuantos más campos de la tabla abierta en el área de trabajo actual, mayor será la ventaja de la versión con mdots.

En mis pruebas, la versión mdot se ejecutó aproximadamente un 25% más de veces en con 70 campos en el área de trabajo y aproximadamente un 50% más de veces con 150 campos.

Sospecho que la razón por la que la diferencia no es tan extrema como en el ejemplo anterior, es que hay mucho más código que no son referencias de variables en este ejemplo. Es decir, el código general es más complejo. (De hecho, mientras que el ejemplo anterior logró millones de pases en cinco segundos, el ejemplo más extenso completó solo decenas de miles).

Para tener una mejor idea de la diferencia entre las dos pruebas, calculé un "tiempo por referencia a variable" aproximado para cada una. Específicamente, hice el cálculo del Listado 5, dividiendo los cinco segundos de la prueba por el producto del número de referencias a variables y el número de pasadas completadas. Por supuesto, este es solo un tiempo aproximado por referencia a variable porque hay otro código en cada prueba. Sin embargo, me permitió hacer una comparación entre las dos pruebas.

Listado 5. Esta ecuación calcula un "tiempo por referencia a variable" aproximado.

Time = TestTime/((# of variables) * passes)

Lo que encontré fue que la segunda prueba, más compleja, tomó aproximadamente un orden de magnitud más para cada referencia que la prueba más simple. Nuevamente, es probable que sea un reflejo del código adicional en el caso más complejo.

¿Qué pasa con las matrices?

El código que determina si un punto está cerca de una línea usa un par de matrices en sus cálculos. Dado que una referencia a un elemento de matriz no se puede confundir con una referencia a un campo, me pregunté si hace una diferencia usar mdots en esas referencias.

Probé agregando un tercer caso a la prueba más extensa. Está estructurado de la misma manera que las dos pruebas del Listado 4, pero llama a una tercera versión de IsPointNearLine que tiene mdots en referencias a variables escalares, pero no en referencias a elementos de matriz.

Encontré solo una pequeña diferencia entre ésta versión y la que tiene mdots en todas las referencias a variables, incluidas las matrices. La mayoría de las veces (66 de 82 casos), el que no tenía mdots en las referencias a matriz era más rápido, pero a veces el que usaba mdots en las referencias a matriz era más rápido. Eso sugiere que VFP es lo suficientemente inteligente como para no buscar (o no mirar mucho) un campo cuando se le da una referencia a matriz.

Algunas palabras sobre las pruebas de tiempo

Las pruebas en el entorno de Windows son inherentemente defectuosas. Entre el propio Windows y varios servicios que siempre se están ejecutando, cualquier resultado de prueba puede ser inexacto.

Hay dos cosas que puede hacer para obtener mejores resultados. Primero, antes de probar, apague todo lo que pueda que pueda interferir, como un cliente de correo electrónico, escaneo de virus bajo demanda, etc. Si no necesita una red para la prueba, considere desconectarse.

En segundo lugar, realice más de una prueba para cada caso. Ese consejo también es importante porque VFP almacena datos en caché, por lo que la primera vez que ejecuta un proceso que utiliza DBF, es probable que tarde más que las ejecuciones posteriores.

Como mencioné anteriormente, hice mis pruebas en dos máquinas diferentes. En ambos casos, me aseguré de que Outlook y mi cliente de Twitter estuvieran cerrados. Cuando se estaba ejecutando una prueba, no hice nada más con esa computadora, ni siquiera tocar el teclado o mover el mouse. Además, en el transcurso de la redacción, realicé cada una de mis pruebas varias veces.

Incluso con estas medidas, los resultados de las pruebas deben verse más como un indicador que como una respuesta definitiva. En este caso, debido a que la diferencia entre los resultados mdots y no mdots es tan grande, es seguro afirmar que mdots marca la diferencia. Por otro lado, la diferencia entre mdots en todas las referencias a variables y mdots solo en referencias a variables de elementos que no son de matriz es lo suficientemente pequeña como para insinuar la respuesta. Se necesitan más pruebas en un entorno más controlado para confirmar ese resultado.

Sólo tiene que utilizar mdots

Como dije al principio, ya estoy subida al carro de los mdots. Me ha pasado demasiadas veces que el código un campo, cuando me refiero a una variable y no quiero preocuparme por eso nunca más. Además, trabajo a menudo con códigos escritos originalmente por otros, por lo que incluso si adoptara una convención de nomenclatura estricta, es probable que gran parte del código que toco no lo esté usando.

Pero incluso si realmente cree que su convención de nomenclatura lo protegerá de ese problema, el hecho de que omitir mdots hace que su aplicación sea más lenta y debería reconsiderar su elección.


Copyright (C) Tamar E. Granor, Tomorrow’s Solutions, LLC.

24 de marzo de 2021

Enlazar eventos para obtener mejores aplicaciones - Parte 4 de 4

Artículo original: Bind Events for Better Applications
(Bind Events for Better Applications 2017.pdf)
Autor: Tamar E. Granor
Traducido por: Luis María Guayán


... Continuación de: "Enlazar eventos para obtener mejores aplicaciones - Parte 3 de 4"

El uso del método Assign

Encontré más formas de usar métodos Assign que métodos Access. Aunque un método Assign le permite cambiar el valor asignado, rara vez uso esa capacidad. Más a menudo, mi código de método Assign me permite asegurarme de que sucedan cosas adicionales cuando se guarda el valor.

A menudo, el objetivo es la encapsulación; Al usar un método Assign, evito que el código que cambia una propiedad tenga que saber a qué afecta un cambio en esa propiedad.

Actualizar una marca de tiempo

En la aplicación de la que se extrae la Figura 7, hacemos un seguimiento del valor actual (el "Valor real") de cada uno de los elementos que se muestran en el formulario. Estos valores se leen del hardware real y queremos saber no solo el valor, sino también cuándo se leyó (entre otras cosas, para mostrar la última fecha de lectura en el formulario, como en la Figura 7). Un objeto comercial contiene la información de un solo artículo; hay una colección de tales artículos. El objeto comercial tiene una propiedad cActual para el valor actual y una propiedad tLastRead para la marca de tiempo. Un método Assign para actualizaciones cActual tLastRead, como en el Listado 45.

Listado 45. El método Assign para una propiedad que rastrea los valores leídos desde el hardware actualiza la marca de tiempo del valor.

LPARAMETERS tuNewValue
IF NOT (ALLTRIM(THIS.cActual) == ALLTRIM(m.tuNewValue))
 THIS.cActual = m.tuNewValue
 * Set last read time.
 THIS.tLastRead = DATETIME()
ENDIF

Establecer una bandera "sucia" (“dirty” flag)

El código del Listado 45 es en realidad solo una parte de lo que hacemos cuando leemos un nuevo valor de hardware. Necesitamos una forma de saber si los datos han cambiado desde la última vez que se almacenaron, es decir, una bandera "sucia". Los métodos Assign también proporcionan eso. Tenemos una propiedad de aplicación, lIsDirty. Siempre que guardamos datos o abrimos un nuevo archivo de datos, borramos esa propiedad. El método Assign (del Listado 45) incluye la línea adicional de código que se muestra en el Listado 46 dentro del IF; utilizamos el mismo código en los métodos Asignar de todas las propiedades donde un cambio indica que los datos ahora son diferentes de lo que eran en la última vez que se guardaron.

Listado 46. Una línea de código en un método Assign (más un pequeño código a nivel de aplicación) le permite realizar un seguimiento de si los datos han cambiado desde la última vez que se guardaron.

goApp.lIsDirty = .T.

En la aplicación de la biblioteca, ya tenemos una gran parte del marco para mantener una bandera "sucia" en toda la aplicación. Cada formulario tiene la propiedad lDataChanged. Podemos usar esas propiedades para determinar si hay datos sin guardar en la aplicación. Para hacerlo, agregamos un método Assign a lDataChanged en frmBase. Ese método llama a un método de objeto de aplicación para actualizar la bandera "sucia" de toda la aplicación.

Solo para demostrar otra posibilidad, lDataChanged_Assign también actualiza el título del formulario para que siempre que haya datos no guardados, incluya un asterisco, tal como lo hace VFP. El método se muestra en el Listado 47.

Listado 47. Este código en el método Assign de la propiedad lDataChanged del formulario ayuda a mantener un indicador "sucio" en toda la aplicación y tiene el título de cada formulario que indica si actualmente tiene cambios sin guardar.

LPARAMETERS tuNewValue
This.lDataChanged = tuNewValue
* Set app-level dirty flag
IF VARTYPE("goApp") = "O" AND NOT ISNULL(goApp) AND ;
 PEMSTATUS(goApp, "SetDirtyFlag", 5)
 goApp.SetDirtyFlag(m.tuNewValue)
ENDIF
* Update form caption
IF m.tuNewValue
 IF RIGHT(This.Caption, 1) <> "*"
 This.Caption = This.Caption + " *"
 ENDIF
ELSE
 IF RIGHT(This.Caption, 1) = "*"
 This.Caption = LEFT(This.Caption, LEN(This.Caption)-2)
 ENDIF
ENDIF

El método SetDirtyFlag del objeto de aplicación, junto con una propiedad del objeto de aplicación, lUnsavedData, hace el resto del trabajo. SetDirtyFlag, que se muestra en el Listado 48, verifica el valor que se le pasa; si es .T., algún formulario tiene datos sin guardar, por lo que la bandera se establece en .T. Si el parámetro es .F., Sabemos que al menos un formulario acaba de guardar datos o restaurar los datos anteriores, pero no conocemos el estado de los otros formularios, por lo que los recorremos hasta que encontramos uno sin guardar. datos.

Listado 48. Este método gestiona una bandera "sucia" en toda la aplicación, de modo que podamos saber con sólo comprobar una propiedad si hay datos no guardados.

PROCEDURE SetDirtyFlag(lNewValue)
* Set the application dirty flag. If the parameter
* is true, then some form has changed data, and we can
* just set this flag. If the parameter is false, some
* form has just saved or reverted its changed data
* and we need to look at all open forms.
IF m.lNewValue
 This.lUnsavedData = .T.
ELSE
 This.lUnsavedData = .F.
 FOR EACH oForm IN _VFP.Forms FOXOBJECT
 IF PEMSTATUS(oForm, "lDataChanged", 5)
 IF oForm.lDataChanged
 This.lUnsavedData = .T.
 * No need to find more than one
 EXIT
 ENDIF
 ENDIF
 ENDFOR
ENDIF
RETURN

En la mayoría de las aplicaciones de ingreso de datos, el siguiente paso sería marcar la bandera "sucia" al salir y pedirle al usuario que guarde los cambios si hay datos no guardados. La aplicación Biblioteca se diseñó para que cuando cierre un formulario, los datos se guarden automáticamente, por lo que no es necesario preguntar.

Delegar el manejo de un nuevo valor

Para una aplicación, necesitaba la capacidad de administrar un conjunto complejo de preferencias de usuario. Los elementos de preferencia se correlacionan más o menos directamente con las propiedades del objeto de la aplicación o con las propiedades de los objetos gestionados por el objeto de la aplicación. Por ejemplo, una preferencia aborda la frecuencia con la que se realiza la recolección de basura; esta propiedad debe convertirse en el intervalo de un temporizador. Al crear una propiedad de objeto de aplicación para cada preferencia y dándoles métodos Assign, me aseguré de que las actualizaciones apropiadas ocurrieran automáticamente.

La aplicación Biblioteca tiene sólo un par de elementos en su formulario de Preferencias, que se muestra en la Figura 10. Utilice mapas de barras de herramientas grandes directamente a una propiedad de la aplicación; utiliza BindEvent() para activar el cambio de tamaño de los botones en la barra de herramientas. (Tenga en cuenta que este código es incompatible con el código de cambio de tamaño de la barra de herramientas descrito anteriormente en este documento).

La casilla de verificación que controla si la aplicación se apaga después de un período específico de inactividad también se asigna a una propiedad de la aplicación. Sin embargo, el temporizador que rastrea la inactividad (descrito en "Monitoreo de la actividad del usuario" anteriormente en este documento) debe habilitarse o deshabilitarse de manera apropiada cuando el usuario cambia la casilla de verificación. De manera similar, la ruleta que determina cuánto tiempo esperar para la actividad necesita establecer el intervalo para ese temporizador. (Nada de esto sucede hasta que el usuario cierra el formulario de Preferencias). Ambos elementos utilizan métodos Assign para garantizar que se produzcan los cambios adecuados.

Figura 10. El formulario de preferencias de la aplicación de la biblioteca se basa en los métodos Assign para garantizar que el temporizador de inactividad se establezca correctamente.

Cuando el usuario cierra el formulario, los valores de control se almacenan en las propiedades de la aplicación correspondiente. El valor de la ruleta se multiplica por 60000, la cantidad de milisegundos en un minuto, primero. El valor de ruleta ajustado se almacena en una propiedad de la aplicación llamada nActivityTimerInterval. El método nActivityTimerInterval_Assign establece el intervalo del temporizador, como se muestra en el Listado 49. Tenga en cuenta que necesitamos almacenar el valor en la propiedad de la aplicación, así como establecer el intervalo del temporizador. De lo contrario, la próxima vez que abramos el cuadro de diálogo Preferencias, no configurará la ruleta en el valor actual.

Listado 49. Este método Assign establece el intervalo para el temporizador de actividad cuando el usuario cambia la configuración en Preferencias.

PROCEDURE nActivityTimerInterval_Assign(nNewValue)
* Something changed interval for activity timer. Propagate to timer
IF VARTYPE(m.nNewValue) = "N" AND m.nNewValue > 0 AND ;
 m.nNewValue <> This.oActivityTimer.Interval
 This.nActivityTimerInterval = m.nNewValue
 This.oActivityTimer.Interval = m.nNewValue
ENDIF
RETURN

El valor de la casilla de verificación se almacena en la propiedad lTrackUserActivity. Su método Assign habilita o deshabilita el temporizador, como se muestra en el Listado 50.

Listado 50. Cuando el usuario cambia la preferencia para rastrear la inactividad del usuario, se activa el método Assign de lTrackUserActivity.

PROCEDURE lTrackUserActivity_Assign(lNewValue)
* Tracking decision changed. Set up or disable timer
IF VARTYPE(m.lNewValue) = "L" AND m.lNewValue <> This.lTrackUserActivity

 This.lTrackUserActivity = m.lNewValue
 IF This.lTrackUserActivity
 This.SetupActivityTimer()
 ELSE
 This.DisableActivityTimer()
 ENDIF
ENDIF
RETURN

Compruebe la validez

Una de las cosas que puede hacer con un método Assign es comprobar la validez del nuevo valor de una propiedad y rechazar los valores no válidos. Por ejemplo, escribí un juego de Sudoku en VFP hace unos años (como demostración de objetos comerciales; se describe en http://tinyurl.com/y84fj5p3). El objeto bizGame, que representa el juego en su conjunto, tiene una propiedad llamada nSize que contiene el tamaño del tablero (el número de celdas en cualquier dirección). Dado que Sudoku requiere que el tamaño del juego sea un cuadrado perfecto, nSize tiene un método Assign que verifica; se muestra en el Listado 51.

Listado 51. El método nSize_Assign del objeto bizGame de una aplicación de Sudoku asegura que el tamaño de juego especificado sea válido.

* Ensure that size is a perfect square
LPARAMETERS tuNewValue
IF SQRT(m.tuNewValue) = INT(SQRT(m.tuNewValue))
 This.nSize = tuNewValue
ENDIF
RETURN

Propagar datos dentro de un contenedor

Una de las principales formas en que uso los métodos Assign es empujar los datos hacia abajo en una jerarquía dentro de un contenedor, de modo que solo el contenedor esté expuesto al mundo. Hay una variedad de comportamientos en este sentido que son útiles.

Anteriormente en este documento, mostré cómo usar los métodos de Access para proporcionar ToolTips dinámicos y tener el mismo ToolTip para un contenedor y su contenido. Assign proporciona una forma alternativa de hacer esto último, en situaciones en las que no necesita sugerencias dinámicas. Es decir, este enfoque funciona cuando desea asignar un ToolTip fijo a un contenedor y hacer que todos los controles internos muestren la misma sugerencia.

Mi código para esto usa una propiedad lógica personalizada, lPropagateTooltipsDown, en la clase base Contenedor. El método ToolTipText_Assign contiene el código del Listado 52. Si el contenedor tiene el indicador establecido en .T., Cuando cambia el ToolTip, recorremos los objetos en el contenedor y cambiamos ToolTipText para cada uno de ellos.

Listado 52. Este código en el método ToolTipText_Assign le permite propagar el ToolTipText de un contenedor a los objetos contenidos.

LPARAMETERS tuNewValue
This.ToolTipText = tuNewValue
LOCAL oObject
IF THIS.lPropagateToolTipDown
 FOR EACH oObject IN THIS.OBJECTS FOXOBJECT
 IF PEMSTATUS(m.oObject, "ToolTipText", 5)
 m.oObject.ToolTipText = m.tuNewValue && don't use ToolTipText on the right
 && in case it also has an access method
 ENDIF
 ENDF
ENDIF

Tenga en cuenta que no tenemos que hacer esto de forma recursiva, incluso si el contenedor contiene otros} contenedores. El método ToolTipText_Assign para los objetos contenidos se activará y, siempre que tengan lPropogateToolTipDown establecido en .T., Manejará sus objetos contenidos.

Ajustar una etiqueta

En una aplicación, necesitaba tener una etiqueta que se ejecutara verticalmente dentro de una forma. En la forma correspondiente (que se muestra en la Figura 11), los títulos de la etiqueta se determinan dinámicamente en función de los datos. Creé una subclase de etiqueta y le di a Caption un método Assign. Ese método llama a un método TurnCaption personalizado para reconstruir el título y agregar la puntuación adecuada para que se muestre verticalmente. Para las formas de doble ancho en la fila superior, el mismo método divide el título en dos cadenas de aproximadamente la misma longitud y crea la cadena de subtítulos adecuada. TurnCaption se muestra en el Listado 53. (Tenga en cuenta que CHR (160) es un espacio sin interrupciones).

Figura 11. Las etiquetas verticales dentro de los cuadros aquí se configuran mediante un método Assign.

Listado 53. Este código es llamado por el método Caption_Assign de las etiquetas verticales que se muestran en la Figura 11.

LPARAMETERS cCaption, lTwoColumns
#DEFINE LF CHR(10)
LOCAL cTurnedCaption, nLength, nChar
m.cCaption = CHRTRAN(CHRTRAN(m.cCaption, CHR(160), " "), LF, "")
nLength = LEN(m.cCaption)
IF m.lTwoColumns
 * Find a dividing point
 nWords = GETWORDCOUNT(m.cCaption)
 IF m.nWords > 1
 * Break on a word break
 cColumn1 = GETWORDNUM(m.cCaption, 1)
 cColumn2 = ALLTRIM(SUBSTR(m.cCaption, LEN(m.cColumn1) + 1))
 cNextWord = GETWORDNUM(m.cColumn2, 1)
 nWord = 2
 DO WHILE m.nWord <= m.nWords-1 AND ;
 LEN(m.cColumn1) + LEN(m.cNextWord) > LEN(m.cColumn2) - LEN(m.cNextWord)
 cColumn1 = m.cColumn1 + " " + m.cNextWord
 cColumn2 = ALLTRIM(SUBSTR(m.cColumn2, LEN(m.cNextWord) + 1))
 cNextWord = GETWORDNUM(m.cColumn2, 1)
 nWord = nWord + 1
 ENDDO

 nLength = MAX(LEN(m.cColumn1), LEN(m.cColumn2))
 ELSE
 * Just break it in half
 cColumn1 = LEFT(m.cCaption, FLOOR(m.nLength/2))
 cColumn2 = RIGHT(m.cCaption, CEILING(m.nLength/2))

 * Pad shorter string
 cColumn1 = PADR(m.cColumn1, LEN(m.cColumn2))
 ENDIF
ELSE
 cColumn1 = m.cCaption
 cColumn2 = ""
ENDIF
cTurnedCaption = ""
FOR nChar = 1 TO nLength
 cTurnedCaption = m.cTurnedCaption + EVL(SUBSTR(m.cColumn1, nChar, 1),CHR(160))
 IF m.lTwoColumns
 cTurnedCaption = m.cTurnedCaption + CHR(160) + CHR(160) + ;
 SUBSTR(m.cColumn2, nChar, 1)
 ENDIF
 cTurnedCaption = m.cTurnedCaption + LF
ENDFOR
RETURN m.cTurnedCaption

Para la aplicación Biblioteca, puede usar este código si desea mostrar libros de forma gráfica con títulos en el lomo.

Depurar con BindEvent(), Access y Assign

Como puede imaginar, tener eventos enlazados y métodos Access y Assign hace que la depuración sea más complicada. A veces, un método parece agotarse de la nada, sin señales de que se esté llamando.

No existen fórmulas mágicas para facilitar este tipo de depuración. Sin embargo, las mismas buenas prácticas de depuración que funcionan para el código VFP generalmente son útiles aquí. En particular, considero que el comando DEBUGOUT, los puntos de interrupción y el rastreo son las herramientas más efectivas.

Cuando la secuencia de eventos no parezca tener sentido para usted, distribuya DEBUGOUT PROGRAM() a través de su código, colocándolo en cada método que parezca involucrado. Luego, cuando ejecute el código con la ventana de salida de depuración disponible, verá la secuencia de métodos llamados. (Mejor aún, no tiene que eliminar estas declaraciones antes de distribuir su aplicación. DEBUGOUT se ignora en el entorno de ejecución). Una vez que haya reducido las cosas hechas a una sección particular de código, establezca un punto de interrupción para detener la ejecución al principio de esa sección y recorra el código una línea a la vez para ver exactamente qué está sucediendo.

En algunos casos, he descubierto que ni siquiera el rastreo me resuelve el misterio, o que rastrear el código interfiere con cualquier proceso que esté viendo. En tales casos, utilizo la herramienta Registro de cobertura. Enciendo el inicio de sesión en el momento en que creo que comienza el problema, luego ejecuto el código hasta que haya ejecutado la sección del problema. Luego utilizo el registro de cobertura generado para mostrarme exactamente qué líneas de código se ejecutaron en qué orden.

La otra herramienta que es útil al depurar problemas de BindEvent() es la función AEvents(). Puedo usarlo en cualquier punto de interrupción para ver exactamente lo que está vinculado, así como para ver si un evento vinculado me llevó a este punto.

Para obtener más consejos de depuración generales, consulte mi artículo en http://tinyurl.com/ycqrjufe.

Configúrelo y olvídelo

Mientras escribía este artículo, me sorprendió la cantidad de formas que encontré para usar BindEvent(), Access y Assign. Mirando los ejemplos aquí, uno de los temas principales es "configúrelo y olvídelo". Es decir, a menudo lo que estas características le permiten hacer es configurar un comportamiento deseable en sus clases base y luego usarlo una y otra vez sin tener que recordar cómo lo hizo. En algunos casos, debe establecer una propiedad o dos, pero en general, las técnicas de este artículo reducen la cantidad de código que debe escribir. ¿No es ese el objetivo de la Programación Orientada a Objetos?







Enlace a todas las partes de este artículo:

Enlazar eventos para obtener mejores aplicaciones: Parte 1 de 4 - Parte 2 de 4 - Parte 3 de 4 - Parte 4 de 4


Copyright 2017, Tamar E. Granor