tag:blogger.com,1999:blog-88472312383481319122024-03-18T09:05:36.078-03:00Comunidad de Visual FoxPro en EspañolNada corre como un zorroLuis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.comBlogger963125tag:blogger.com,1999:blog-8847231238348131912.post-53435490024148955242022-07-20T00:00:00.009-03:002023-12-16T11:27:46.638-03:00Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado<h4 style="text-align: left;"><span style="font-family: arial;"><span style="color: #333333; text-align: justify;"><b>Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado</b></span></span></h4><h4 style="text-align: left;"><span style="font-family: arial;"><b>Autor: Carlos Alejandro Perez (QEPD) 1965-2021 (Chaco, Argentina)</b></span></h4><h4 style="text-align: left;"><span style="font-family: arial;"><b>Publicado en: <a href="http://logica10mobile.blogspot.com/2012/05/como-consumir-datos-de-visual-foxpro-en.html" target="_blank">http://logica10mobile.blogspot.com/2012/05/como-consumir-datos-de-visual-foxpro-en.html</a></b></span></h4><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody><tr><td style="text-align: center;"><span style="font-family: arial; font-size: x-small;"><b><img data-original-height="1301" data-original-width="916" height="200" src="https://blogger.googleusercontent.com/img/a/AVvXsEixSWzsw7U1Is2YKjT96Z094Nl2pE98SvQ_XMIIvI6LyxeAgie5ZalQLbar0HIvYpsXC_pV0m89eQUhJfTGsi4HjtOtwsLyPTV0H_BHt0zli8gmvJtijUCIgEs9N6bcL_F96Vai7XNH76qdb6FyA1RuF3BFglAnpJSFPQDNDsTlNL8SBJUJ4dFdS4jmIorE=w141-h200" width="141" /></b></span><tr><td class="tr-caption" style="text-align: center;"><span style="font-family: arial; font-size: x-small;"><b>Carlos Alejandro Perez</b></span></td></tr></tbody></table>
<span style="font-family: arial;"><br /><br /></span></div><div class="article-content entry-content" itemprop="articleBody" style="clear: both; color: #333333; font-family: "Helvetica Neue Light", HelveticaNeue-Light, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 1.4; margin: 10px auto 5px; outline: none; padding: 0px; text-align: justify;">
<hr>
<h3 style="font-weight: bolder; margin: 0.83em 0px; padding: 0px; position: relative;"><span style="font-size: 14px;"></span></h3>
<h3 style="font-weight: bolder; margin: 0.83em 0px; padding: 0px; position: relative;"><span style="font-size: 14px;">Introducción</span></h3><span style="font-size: 14px;">Nos hemos puesto </span><strong style="font-size: 14px;">un poco nostálgicos</strong><span style="font-size: 14px;">, 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.</span><br /><span style="font-size: 14px;">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.</span><br /><span style="font-size: 14px;">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.</span><br /><span style="font-size: 14px;">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 </span><em style="font-size: 14px;">device-driver</em><span style="font-size: 14px;"> 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).</span><br /><br /><strong style="font-size: 14px;">Escenarios posibles entre cliente y servidor</strong><br /><br /><span style="font-size: 14px;">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:</span><br /><table border="1" cellpadding="2" cellspacing="0" style="font-size: 14px; width: 866px;"><tbody><tr><td valign="top" width="143"><strong>Cliente</strong></td><td valign="top" width="163"><strong>Servidor</strong></td><td valign="top" width="308"><strong>Driver de 32 bits instalado</strong></td><td valign="top" width="250"><strong>Driver de 64 bits instalado</strong></td></tr><tr><td valign="top" width="143">32 bits</td><td valign="top" width="163">32 bits</td><td valign="top" width="308">Visible desde el cliente, conectable.</td><td valign="top" width="250">Invisible desde el cliente, innecesario para conectarse</td></tr><tr><td valign="top" width="143">32 bits</td><td valign="top" width="163">64 bits</td><td valign="top" width="308">Visible desde el cliente, conectable.</td><td valign="top" width="250">Invisible desde el cliente, innecesario para conectarse.</td></tr><tr><td valign="top" width="143">64 bits</td><td valign="top" width="163">32 bits</td><td valign="top" width="308">Invisible desde el cliente, no se puede conectar.</td><td valign="top" width="250">Visible desde el cliente, conectable</td></tr><tr><td valign="top" width="143">64 bits</td><td valign="top" width="163">64 bits</td><td valign="top" width="308">Invisible desde el cliente, no se puede conectar</td><td valign="top" width="250">Visible desde el cliente, se puede conectar.</td></tr></tbody></table><span style="font-size: 14px;">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,</span><strong style="font-size: 14px;"> siempre y cuando exista el driver de 32 bits para dicho servicio</strong><span style="font-size: 14px;">.</span><br /><br /><span style="font-size: 14px;">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:</span><br /><br /><a href="http://lh4.ggpht.com/-qK7_ZhIGqF4/T7BX9LVvM-I/AAAAAAAAAqM/BQpYrMa57F0/s1600-h/image3.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="386" src="http://lh5.ggpht.com/-ZhfFSdmvDyo/T7BX-tr-rCI/AAAAAAAAAqU/QfEAlLMJME4/image_thumb1.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="477" /></a><br /><em style="font-size: 14px;">Fig. 1: drivers de 64 bits visibles desde un cliente de 64 bits (Panel de Control)</em><br /><em style="font-size: 14px;"><br /></em><br /><span style="font-size: 14px;">Ahora bien,</span><strong style="font-size: 14px;"> sin cambiar de PC</strong><span style="font-size: 14px;">, 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í.</span><br /><br /><a href="http://lh3.ggpht.com/-kE3LbIY-01k/T7BYAfp1glI/AAAAAAAAAqc/81kDw2fiSdI/s1600-h/image7.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="432" src="http://lh3.ggpht.com/-Nnla2xNtHo4/T7BYCMvCnsI/AAAAAAAAAqk/-f1Icps9ghQ/image_thumb3.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="458" /></a><br /><em style="font-size: 14px;">Fig. 2: drivers de 32 bits visibles de un cliente de 32 bits (VFP)</em><br /><em style="font-size: 14px;"><br /></em><br /><span style="font-size: 14px;">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.</span><br /><br /><strong style="font-size: 14px;">El problema con ASP.NET, VS y Windows de 64 bits.</strong><br /><br /><span style="font-size: 14px;">Ahora bien, desde Visual Studio 2010, ¿qué sucede?. Supongamos este escenario:</span><br /><ul style="font-size: 14px; list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">Sistema operativo Windows de 64 bits –> IIS de 64 bits, que contiene:<ul style="list-style: disc; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">IDE de Visual Studio 2010 convencional, 32 bits, instalado con ajustes por defecto sobre el Windows anterior.</li><li style="margin: 0px; outline: none; padding: 0px;">Visual FoxPro, 32 bits</li><li style="margin: 0px; outline: none; padding: 0px;">OLE-DB para Visual FoxPro, 32 bits</li></ul></li></ul><span style="font-size: 14px;">El objetivo es construir una página ASP.NET que acceda a los datos de VFP en ese escenario.</span><br /><span style="font-size: 14px;">El problema es el siguiente:</span><br /><ul style="font-size: 14px; list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">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.</li><li style="margin: 0px; outline: none; padding: 0px;">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.</li></ul><span style="font-size: 14px;">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?</span><br /><span style="font-size: 14px;">No existe una respuesta fácil. Las posibilidades que exploramos son las siguientes:</span><br /><br /><table border="1" cellpadding="2" cellspacing="0" style="font-size: 14px;"><tbody><tr><td valign="top" width="20%"><strong>Idea central</strong></td><td valign="top" width="60%"><strong>Qué habría que hacer</strong></td><td valign="top" width="20%"><strong>Contras</strong></td></tr><tr><td valign="top">Correr el sitio ASP.NET en IIS habilitado para 32 bits</td><td valign="top"><ul style="list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">Activar la compatibilidad de 32 bits en IIS de 64 bits, corriendo en una ventana de comandos la siguiente instrucción:</li></ul><br /><br /><table border="0" cellpadding="2" cellspacing="0" style="width: 804.503px;"><tbody><tr><td valign="top" width="100%"><pre>C:\>cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 true</pre><br /></td></tr></tbody></table><br /></td><td valign="top">Si bien se activa la compatibilidad de 32 bits en IIS, al mismo tiempo<strong> se inhabilitan las de 64 bits</strong>. 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).</td></tr><tr><td valign="top">Instalar dos IIS en el servidor, uno de 32 y otro de 64</td><td valign="top">La 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.</td><td valign="top"><strong>No es posible</strong>. 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.</td></tr><tr><td valign="top">Implementar un servidor de automatización COM hecho en VFP</td><td valign="top">Crear 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 <span style="font-family: "Courier New";">CursorToXML()</span></td><td valign="top"><strong>No es posible</strong>. No se podrá enlazar el COM en el espacio de 64 de bits del IIS en producción.</td></tr><tr><td valign="top">Implementar un servicio web con VFP y publicarlo en el mismo IIS que publica las páginas ASP.NET.</td><td valign="top">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.</td><td valign="top"><strong>No es posible</strong>, el COM de VFP de 32 bits no se podrá enlazar al ISAPI de 64 bits.</td></tr><tr><td valign="top">Ídem anterior, pero publicar el web service de VFP en un servidor Apache de 32 bits.</td><td valign="top">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 <em>web service</em> local para acceder a los datos VFP</td><td valign="top"><strong>Teóricamente posible</strong>, 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.</td></tr><tr><td valign="top">Intentar conseguir un driver de 64 bits.</td><td valign="top">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).</td><td valign="top"><strong>Licencia de Sybase</strong>. ¿Dónde se lo consigue sin tener ningún producto Sybase? ¿funciona standalone o necesita un software de Sybase?</td></tr></tbody></table><br /><h3 style="font-size: 14px; font-weight: bolder; margin: 0.83em 0px; padding: 0px; position: relative;">La solución del Servidor vinculado (Linked Server)</h3><br /><span style="font-size: 14px;">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.</span><br /><br /><span style="font-size: 14px;">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:</span><br /><br /><br /><div align="center" style="font-size: 14px; margin: 0px; outline: none; padding: 0px;"><a href="http://lh3.ggpht.com/-Ty8kY-Ms1us/T7BYDOGGcqI/AAAAAAAAAqs/cl9_fARGM6Q/s1600-h/VFP%252520linked%252520server%25255B9%25255D.png" style="color: #009eb8; display: inline; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="VFP linked server" border="0" height="589" src="http://lh5.ggpht.com/-g1Etcvc99is/T7BYEpcu2tI/AAAAAAAAAq0/vHUwwjUJ_CA/VFP%252520linked%252520server_thumb%25255B5%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="VFP linked server" width="857" /></a></div><br /><br /><div align="center" style="font-size: 14px; margin: 0px; outline: none; padding: 0px;"><em>Figura 3: esquema de bloques de la solución</em></div><br /><span style="font-size: 14px;">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.</span><br /><br /><span style="font-size: 14px;">Una vez que el servidor encadenado está definido, </span><strong style="font-size: 14px;">se puede acceder desde cualquier cliente que tenga un driver de acceso a SQL</strong><span style="font-size: 14px;">, 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.</span><br /><br /><span style="font-size: 14px;">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.</span><br /><br /><h3 style="font-size: 14px; font-weight: bolder; margin: 0.83em 0px; padding: 0px; position: relative;">Configuración paso a paso</h3><strong style="font-size: 14px;">1. Descargar e instalar SQL Server Express de 32 bits</strong><span style="font-size: 14px;"> 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.</span><br /><br /><span style="font-size: 14px;">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.</span><br /><br /><a href="http://lh4.ggpht.com/-G7DLBd1cj6c/T7BYFTFORgI/AAAAAAAAAq8/bEoh_P1Cjhw/s1600-h/image%25255B26%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="455" src="http://lh5.ggpht.com/-DAzeEOQ6GKc/T7BYGTebYzI/AAAAAAAAArE/uCeOqGWSPds/image_thumb%25255B13%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="346" /></a><br /><br /><strong style="font-size: 14px;">2. Iniciar el administrador de SQL Server, y configurar un servidor encadenado</strong><span style="font-size: 14px;">. 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</span><br /><br /><a href="http://lh5.ggpht.com/-zogavTF7dqs/T7BYHKkQR8I/AAAAAAAAArM/13fHpoEff24/s1600-h/image%25255B4%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="265" src="http://lh4.ggpht.com/-TD-kvZI6CJk/T7BYIPZ7-cI/AAAAAAAAArU/oX8B1TATYvs/image_thumb%25255B1%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="360" /></a><br /><em style="font-size: 14px;">Fig. 4</em><br /><br /><strong style="font-size: 14px;">3. Verificar drivers</strong><span style="font-size: 14px;">. Expandir dicho nodo, y se verá el listado de drivers disponibles en el ámbito de 32 bits. Debe estar visible la opción VFPOLEDB</span><br /><br /><a href="http://lh5.ggpht.com/-boy2gQTlvG0/T7BYIgUSiZI/AAAAAAAAArc/jbU0YaBSaHA/s1600-h/image%25255B8%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="388" src="http://lh5.ggpht.com/-M83F7f9cTsE/T7BYJ8c12kI/AAAAAAAAArk/UnZj14iMJbc/image_thumb%25255B3%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="311" /></a><br /><em style="font-size: 14px;">fig. 5</em><br /><br /><strong style="font-size: 14px;">4. Agregar un nuevo servidor vinculado</strong><span style="font-size: 14px;">. 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:</span><br /><br /><a href="http://lh3.ggpht.com/-jVC5QHu6CYg/T7BYKb4N8cI/AAAAAAAAArs/GJWLQNftp_U/s1600-h/image%25255B12%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="291" src="http://lh3.ggpht.com/-9G9uP8zRcGQ/T7BYLIleyVI/AAAAAAAAAr0/a8IS1Y5MEtA/image_thumb%25255B5%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="538" /></a><br /><em style="font-size: 14px;">Fig. 6</em><br /><br /><span style="font-size: 14px;">Proveer los siguientes datos:</span><br /><ul style="font-size: 14px; list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><br /><li style="margin: 0px; outline: none; padding: 0px;">Nombre del servidor encadenado: especificar el nombre o etiqueta para el servidor encadenado. Por ejemplo, MIAPPVFP</li><br /><li style="margin: 0px; outline: none; padding: 0px;">Proveedor: seleccionar OLE DB para Visual FoxPro</li><br /><li style="margin: 0px; outline: none; padding: 0px;">Nombre del producto: se puede especificar un identificador del producto, en este caso colocar la misma que el nombre del servidor encadenado.</li><br /><li style="margin: 0px; outline: none; padding: 0px;">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</li><br /><li style="margin: 0px; outline: none; padding: 0px;">Dejar todos los demás campos en blanco, y hacer clic en Aceptar. Asegurarnos que la pantalla de alta quede así:</li></ul><a href="http://lh5.ggpht.com/-RZ1xhJIUBgY/T7BYL7jRDWI/AAAAAAAAAr8/Zr4zohtQKwU/s1600-h/image%25255B16%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="281" src="http://lh4.ggpht.com/-y7n3E4OAqC8/T7BYM2iswbI/AAAAAAAAAsE/GTIqKcoBUCM/image_thumb%25255B7%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="523" /></a><br /><em style="font-size: 14px;">Fig. 7</em><br /><br /><strong style="font-size: 14px;">5. Probar con una nueva consulta.</strong><span style="font-size: 14px;"> 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:</span><br /><ol style="font-size: 14px; list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">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.</li><br /><li style="margin: 0px; outline: none; padding: 0px;">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.</li></ol><strong style="font-size: 14px;">5.1 Prueba con sintaxis T-SQL de SQL Server</strong><br /><br /><span style="font-size: 14px;">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 </span><span style="font-family: Consolas; font-size: small;">MIAPPVFP</span><span style="font-size: 14px;">, y la tabla es </span><span style="font-family: Consolas; font-size: small;">clientes.dbf</span><span style="font-size: 14px;">, se referencia como </span><span style="font-family: Consolas; font-size: small;">FROM miappvfp…clientes</span><span style="font-size: 14px;"> (tres puntos), como se muestra en la siguiente figura:</span><br /><br /><a href="http://lh3.ggpht.com/-ZCTZD2HYxP4/T7BYNYJlf2I/AAAAAAAAAsM/G2M-FhMnfas/s1600-h/image%25255B22%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="519" src="http://lh3.ggpht.com/-TvG0Li1eWHU/T7BYPdS95TI/AAAAAAAAAsU/nnzkidBfoRg/image_thumb%25255B11%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="679" /></a><br /><em style="font-size: 14px;">fig. 8</em><br /><br /><strong style="font-size: 14px;">5.2 Prueba de consulta con paso-a-través y OPENQUERY</strong><br /><br /><span style="font-size: 14px;">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:</span><br /><br /><span style="background-color: white; font-family: Consolas; font-size: small;">SELECT <lista de campos> FROM OPENQUERY(<servidor_vinculado>, <string de consulta nativa VFP>)</span><br /><br /><span style="font-size: 14px;">Donde el <servidor_vinculado> debe especificarse sin comillas, y la consulta VFP nativa debe estar como una constante de caracteres encerrada entre comillas simples:</span><br /><br /><span style="background-color: white; font-family: Consolas; font-size: small;">SELECT nombre,sexo FROM OPENQUERY(miappvfp,’SELECT * FROM clientes’)</span><br /><br /><a href="http://lh6.ggpht.com/-vGWj1UZ06IM/T7BYQKpN0yI/AAAAAAAAAsc/2ml-5OKY1xc/s1600-h/image%25255B30%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="419" src="http://lh6.ggpht.com/-s6nd3C5VQdI/T7BYRZoIKgI/AAAAAAAAAsk/H4aYz7H6Q9c/image_thumb%25255B15%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="539" /></a><br /><em style="font-size: 14px;">fig. 9</em><br /><br /><span style="font-size: 14px;">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.</span><br /><br /><strong style="font-size: 14px;">Conexión desde una aplicación de .NET</strong><br /><br /><span style="font-size: 14px;">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:</span><br /><br /><span style="background-color: white; font-family: Consolas; font-size: small;">Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword</span><br /><br /><span style="font-size: 14px;">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í</span><br /><br /><a href="http://lh3.ggpht.com/-uao5Qz5OoEQ/T7BYR4g7CpI/AAAAAAAAAss/c_15e5GsOZI/s1600-h/image%25255B34%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="371" src="http://lh5.ggpht.com/-L66c9ijjauI/T7BYSuSiXbI/AAAAAAAAAs0/-C-ZV1mTLL0/image_thumb%25255B17%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="318" /></a><br /><em style="font-size: 14px;">fig. 10</em><br /><br /><span style="font-size: 14px;">Donde el secreto está en configurar el datasource </span><strong style="font-size: 14px;">SqlDataSource1</strong><span style="font-size: 14px;"> de la siguiente manera:</span><br /><br /><strong style="font-size: 14px;">String de conexión</strong><span style="font-size: 14px;">: Por ejemplo, </span><span style="background-color: white; font-size: 14px;">“</span><span style="font-family: Consolas; font-size: small;"><span style="background-color: white;">Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword”</span> <span style="font-family: Verdana; font-size: x-small;">reemplazando las credenciales por las correspondientes a nuestra instalación.</span></span><br /><br /><strong style="font-size: 14px;">Comando SELECT:</strong><span style="font-size: 14px;"> Al configurar el </span><strong style="font-size: 14px;">SqlDataSource1</strong><span style="font-size: 14px;">, 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.</span><br /><br /><a href="http://lh5.ggpht.com/-6yaBsMdx8Vs/T7BYTK6IuHI/AAAAAAAAAs8/72FGUXzosOc/s1600-h/image%25255B38%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="290" src="http://lh4.ggpht.com/-DwQgYJgezCI/T7BYT1808vI/AAAAAAAAAtE/bi79WOJgCZc/image_thumb%25255B19%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="498" /></a><span style="font-size: 14px;"> </span><a href="http://lh6.ggpht.com/-oCIXEqr6-No/T7BYUbugHeI/AAAAAAAAAtM/zKmkrGWjqW4/s1600-h/image%25255B50%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="289" src="http://lh3.ggpht.com/-8wg6sIMp1dU/T7BYVHQ2ywI/AAAAAAAAAtU/ckJRtHWzAUM/image_thumb%25255B25%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="475" /></a><br /><em style="font-size: 14px;">Fig. 11 y 12</em><br /><br /><span style="font-size: 14px;">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:</span><br /><br /><span style="background-color: white; color: black; font-family: Consolas; font-size: small;">SELECT nombre,doc_nro FROM miappvfp…clientes WHERE doc_nro = @doc_nro</span><br /><br /><strong style="font-size: 14px;">Ajuste del parámetro de la consulta:</strong><span style="font-size: 14px;"> 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:</span><br /><br /><span style="font-size: 14px;"> </span><a href="http://lh6.ggpht.com/-DxAEl_gzBhg/T7BYVgyTqRI/AAAAAAAAAtc/yX0UDCzXOcg/s1600-h/image%25255B54%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="337" src="http://lh3.ggpht.com/-CzRKUoSLlow/T7BYWsA8_wI/AAAAAAAAAtk/UmShngxdO_o/image_thumb%25255B27%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="567" /></a><br /><em style="font-size: 14px;">Fig. 13</em><br /><br /><strong style="font-size: 14px;">Prueba de la consulta:</strong><br /><br /><span style="font-size: 14px;">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 (</span><span style="font-family: Consolas; font-size: small;">doc_nro</span><span style="font-size: 14px;"> no es clave primaria en este test) con un mismo documento, para ver si recupera correctamente:</span><br /><br /><a href="http://lh6.ggpht.com/-LuB63duB75Q/T7BYY-yWAeI/AAAAAAAAAts/o4oED_BnlHU/s1600-h/image%25255B58%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="307" src="http://lh4.ggpht.com/-juVOXc9qYi0/T7BYZh4dewI/AAAAAAAAAt0/4cugt3K3QNI/image_thumb%25255B29%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="429" /></a><span style="font-size: 14px;"> </span><a href="http://lh6.ggpht.com/-_mJPZDmsBzQ/T7BYaD7lcXI/AAAAAAAAAt8/uC1FHyvMRBk/s1600-h/image%25255B62%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="303" src="http://lh4.ggpht.com/-pzJQuYYWmSc/T7BYbdkqmII/AAAAAAAAAuE/2bfS0ZvykqM/image_thumb%25255B31%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="479" /></a><br /><em style="font-size: 14px;">Fig. 14 y 15</em><br /><br /><span style="font-size: 14px;">Aceptemos todo y el </span><strong style="font-size: 14px;">SqlDataSource1</strong><span style="font-size: 14px;"> quedará configurado.</span><br /><br /><strong style="font-size: 14px;">Ajuste del control GridView:</strong><span style="font-size: 14px;"> Para el </span><strong style="font-size: 14px;">gridview</strong><span style="font-size: 14px;"> 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 </span><strong style="font-size: 14px;">SqlDataSource1</strong><span style="font-size: 14px;">.</span><br /><br /><a href="http://lh4.ggpht.com/-51f34xQ1ryc/T7BYbxNQ9XI/AAAAAAAAAuM/yme3e-K1Aho/s1600-h/image%25255B67%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="317" src="http://lh3.ggpht.com/-ul4e5-UTEe0/T7BYdHUy5XI/AAAAAAAAAuU/oKRdN6c-YOc/image_thumb%25255B34%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="501" /></a><br /><em style="font-size: 14px;">fig. 16</em><br /><br /><span style="font-size: 14px;">Al hacerlo, inmediatamente quedará configurada la visual del </span><strong style="font-size: 14px;">gridview</strong><span style="font-size: 14px;">. Podemos cambiar la visual del </span><strong style="font-size: 14px;">gridview</strong><span style="font-size: 14px;"> utilizando la opción </span><strong style="font-size: 14px;">AutoFormat</strong><span style="font-size: 14px;">, etc. y cambiarle la cabecera a cada columna utilizando la opción </span><strong style="font-size: 14px;">Edit Columns</strong><span style="font-size: 14px;">. A efectos ilustrativos, vemos cómo cambiar la cabecera de la primera columna, de </span><span style="font-family: Consolas; font-size: small;">nombre</span><span style="font-size: 14px;"> (que trae de la tabla de datos VFP) a</span><span style="font-family: Consolas; font-size: small;"> Nombre de cliente</span><span style="font-size: 14px;">.</span><br /><br /><a href="http://lh3.ggpht.com/-IlFznIhzZ74/T7BYdvKsaSI/AAAAAAAAAuc/3S7yXQOn6dY/s1600-h/image%25255B72%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="407" src="http://lh5.ggpht.com/-UUujuJWy77Q/T7BYe06pVlI/AAAAAAAAAuk/gtW39I3xeQA/image_thumb%25255B37%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="484" /></a><br /><em style="font-size: 14px;">fig. 17</em><br /><br /><strong style="font-size: 14px;">Prueba de la página:</strong><br /><br /><span style="font-size: 14px;">Pulsamos F5 y aguardamos hasta que aparezca la página</span><br /><br /><a href="http://lh5.ggpht.com/-jaMkEYi1eb8/T7BYfk32qSI/AAAAAAAAAus/hUsohHfo62s/s1600-h/image%25255B76%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="197" src="http://lh3.ggpht.com/-icp314ZAflI/T7BYg5jvI6I/AAAAAAAAAu0/4-dE-AicJEo/image_thumb%25255B39%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="358" /></a><br /><em style="font-size: 14px;">fig. 18</em><br /><br /><span style="font-size: 14px;">Colocamos el numero de documento y pulsamos consultar. A los breves instantes tendremos la respuesta:</span><br /><br /><a href="http://lh4.ggpht.com/-OCKx8x7aQgc/T7BYhRXCj4I/AAAAAAAAAu8/8N-FN4XJ3H0/s1600-h/image%25255B80%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="297" src="http://lh3.ggpht.com/-l8AFbJfp22w/T7BYiERmTJI/AAAAAAAAAvE/gtRFboTciZ4/image_thumb%25255B41%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="297" /></a><br /><em style="font-size: 14px;">fig. 19</em><br /><br /><span style="font-size: 14px;">Donde los datos provienen de Visual Foxpro. También podríamos haber utilizado un dataset, etc.</span><br /><br /><h4 style="font-size: 14px; font-weight: bolder; margin: 1.12em 0px; padding: 0px; position: relative;">Publicar la página en el servidor IIS de 64 bits.</h4><span style="font-size: 14px;">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:</span><br /><br /><a href="http://lh6.ggpht.com/-TS3PxAI_hX0/T7BYi6OqzvI/AAAAAAAAAvM/5N10zOw0gxg/s1600-h/image%25255B87%25255D.png" style="color: #009eb8; display: inline; font-size: 14px; outline: none; text-decoration-line: none; transition: color 0.3s ease 0s;"><img alt="image" border="0" height="275" src="http://lh3.ggpht.com/-q6uV9DpHH4w/T7BYj-n3IeI/AAAAAAAAAvU/z3ulnekDwAE/image_thumb%25255B46%25255D.png?imgmax=800" style="-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 fill / 9px stretch; background-image: none; border-color: initial; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 9; border-image-width: 9px; border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAOVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///+8yHYvAAAAEnRSTlMAAgEDBAUJBwYzCw0UChARDghnBteEAAAAhElEQVR4XnWRSxJDIQgEEx1Axd/L/Q8bKCuuyOzsohHxdZLS25LSOV2ULQdflgEiIDu9KqiIFILXXpeEx2ChXwdnKNxUGxc4dZbNfXjPufkp5H0Nwtymq/dltUIweNy9qmXt08EgydDZ6+dT+9Qh9AeGenhROFI0fPjMaCHx6uIlh9/xBSJuB3l0A/6JAAAAAElFTkSuQmCC") 9 / 9px / 0 stretch; border-style: none; border-width: 0px; box-sizing: border-box; display: inline; height: auto; margin: 10px auto; max-width: 100%; padding: 0px 0px 8px; position: relative;" title="image" width="304" /></a><br /><em style="font-size: 14px;">fig. 20</em><br /><br /><strong style="font-size: 14px;">Con lo cual, hemos podido publicar los datos de VFP en un entorno ASP.NET con IIS de 64 bits, consumiendo datos de VFP.</strong><br /><br /><h4 style="font-size: 14px; font-weight: bolder; margin: 1.12em 0px; padding: 0px; position: relative;">Algunos cuidados a tener</h4><strong style="font-size: 14px;">1. Campos de tipo fecha:</strong><span style="font-size: 14px;"> 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.</span><br /><br /><strong style="font-size: 14px;">2. Funciones nativas de VFP</strong><span style="font-size: 14px;">. 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.</span><br /><br /><span style="font-size: 14px;">Por ejemplo, supongamos que necesitamos optimizar una consulta por la columna de fecha llamada </span><strong style="font-size: 14px;">fechaVenta</strong><span style="font-size: 14px;"> en una tabla llamada </span><strong style="font-size: 14px;">VENTAS</strong><span style="font-size: 14px;"> debemos seguir estos pasos:</span><br /><ul style="font-size: 14px; list-style-image: initial; list-style-position: initial; margin: 0.5em 0px; outline: none; padding: 0px 0px 0px 2em;"><li style="margin: 0px; outline: none; padding: 0px;">indexar la tabla de Visual Foxpro con la función <strong>DTOS</strong>, y hacer la consulta desde ASP.NET con dicha consulta. Suponiendo que la columna sea <strong>fechaVenta</strong>, debemos indexarla con <span style="background-color: white;">INDEX ON DTOS(fechaVenta) TAG fV</span> en Visual Foxpro.</li><br /><li style="margin: 0px; outline: none; padding: 0px;">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 <span style="font-size: small;">dFecha as date</span> en .NET, podremos obtener su string equivalente al colocar <span style="background-color: white; font-family: Consolas;">dim cFechaParam as string = dFecha.ToString(“yyyyMMdd”)</span>.</li><br /><li style="margin: 0px; outline: none; padding: 0px;">Armar la consulta en el SQLDataSource, SQLAdapter, etc. especificando que el SelectCommand sea el sigiuente: </li></ul><span style="background-color: white; font-family: Consolas; font-size: 14px;">SELECT * FROM OPENQUERY( miappvfp, ‘SELECT fechaVenta, factura, cliente FROM ventas WHERE DTOS(fechaVenta) = ‘”+cFechaParam+”’)</span><br /><div style="font-size: 14px; margin: 0px; outline: none; padding: 0px;"><strong><br /></strong></div><div style="font-size: 14px; margin: 0px; outline: none; padding: 0px;"><strong>3. Palabras clave en consulta</strong>. 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 “<span style="background-color: white; font-family: Consolas; font-size: small;">SELECT select FROM select</span>” 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.<br /><br /><h3 style="font-weight: bolder; margin: 0.83em 0px; padding: 0px; position: relative;">Conclusión</h3><br />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.</div></div>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-824508063977927172022-02-05T20:56:00.004-03:002023-01-08T20:59:41.722-03:00Números Arábigos a Romanos<pre>
? NUM2ROMANO(1980)
FUNCTION Num2Romano(tnNum)
DIMENSION laNum(13),laRom(13)
LOCAL lnI, lcRom
laNum(1) = 1
laNum(2) = 4
laNum(3) = 5
laNum(4) = 9
laNum(5) = 10
laNum(6) = 40
laNum(7) = 50
laNum(8) = 90
laNum(9) = 100
laNum(10) = 400
laNum(11) = 500
laNum(12) = 900
laNum(13) = 1000
laRom(1) = "I"
laRom(2) = "IV"
laRom(3) = "V"
laRom(4) = "IX"
laRom(5) = "X"
laRom(6) = "XL"
laRom(7) = "L"
laRom(8) = "XC"
laRom(9) = "C"
laRom(10) = "CD"
laRom(11) = "D"
laRom(12) = "CM"
laRom(13) = "M"
lcRom = ""
FOR lnI = 13 TO 1 STEP -1
DO WHILE tnNum >= laNum(lnI)
tnNum = tnNum - laNum(lnI)
lcRom = lcRom + laRom(lnI)
ENDDO
ENDFOR
RETURN lcRom
ENDFUNC
</pre>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-2475824076273761012022-01-13T00:30:00.001-03:002022-01-13T00:30:00.201-03:00Edad de una persona<p>Una (de tantas) funciones para calcular la edad de una persona.</p>
<pre>* Ejemplo:
? Edad(DATE(1998,06,03))
*-----------------------------------------------------
* FUNCTION Edad(tdNac, tdHoy)
*-----------------------------------------------------
* tdNac = Fecha de nacimiento
* tdHoy = Fecha a la cual se calcula la edad (Por defecto toma la fecha actual)
*-----------------------------------------------------
FUNCTION Edad(tdNac, tdHoy)
IF EMPTY(tdHoy)
tdHoy = DATE()
ENDIF
RETURN FLOOR((VAL(DTOC(tdHoy,1)) - VAL(DTOC(tdNac,1))) / 10000)
ENDFUNC
*-----------------------------------------------------</pre>
<p><i><a href="https://cetinbasoz.blogspot.com/" target="_blank">Cetin Basoz</a>, Izmir, Turkey</i><br>Publicado en <a href="www.foxite.com" target="_blank">www.foxite.com</a></p>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-42985148377677902722022-01-08T02:00:00.001-03:002022-01-08T02:00:00.202-03:00Función PUTFILE() como se escribió originariamente (no en mayúsculas)<p><b><i>Artículo original: PUTFILE function as originally typed (not in uppercase)<br>
<a href="http://vfpimaging.blogspot.com/2021/01/putfile-function-as-originally-typed.html">http://vfpimaging.blogspot.com/2021/01/putfile-function-as-originally-typed.html</a><br>
Autor: Cesar Ch.<br>
Traducido por: <a href="https://translate.google.com">Google Translate</a></i></b></p>
<hr>
<p>Como ya se ha discutido en Fox.Wikis: <i>"VFP siempre ha sido un poco más gracioso con los casos de nombres de archivo. Más específicamente, no está documentado cómo funciona con el caso de nombres de archivo. Se traducirá los nombres de archivo a minúsculas en algunos casos, a mayúsculas en otros, y dejarlo igual en otros."</i></p>
<p>PUTFILE() está en esa lista de funciones extrañas, siempre devolviendo los nombres de archivo en MAYÚSCULAS. Así que aquí hay una pequeña función que he estado usando que devuelve el nombre de archivo elegido de la forma en que el usuario lo escribió.</p>
<p>Los parámetros y el uso son exactamente los mismos que los de la función PUTFILE() original de Visual FoxPro:</p>
<pre>
FUNCTION XPUTFILE(tcCustomText, tcFileName, tcFileExt)
* Usage:
* ? PUTFILE("Save file as...", "MyFile.PDF", "PDF;TXT;*")
#DEFINE COMMDLOG_DEFAULT_FLAG 0x00080000
#DEFINE COMMDLOG_RO 4
#DEFINE COMMDLOG_MULTFILES 512
LOCAL lcSetDefa
m.lcSetDefa = SET("Default") + CURDIR()
LOCAL loDlgForm AS "Form"
m.loDlgForm = CREATEOBJECT("Form")
m.loDlgForm.ADDOBJECT("oleObject1", "oleComDialObject")
LOCAL loDlg
m.loDlg = m.loDlgForm.OleObject1
LOCAL lcFilter, lcFileExt, lnExtCount, N
IF NOT EMPTY(tcFileExt)
lnExtCount = GETWORDCOUNT(m.tcFileExt, ";")
lcFilter = ""
FOR N = 1 TO lnExtCount
lcFileExt = GETWORDNUM(m.tcFileExt, N, ";")
IF lcFileExt = "*"
lcFilter = lcFilter + "All files|*.*"
ELSE
lcFilter = lcFilter + lcFileExt + " files|*." + lcFileExt && EVL(tcFileExt, "All files|*.*")
ENDIF
IF N < lnExtCount
lcFilter = lcFilter + "|"
ENDIF
ENDFOR
ELSE
lcFilter = "*.*|*.*" && EVL(tcFileExt, "All files|*.*")
ENDIF
m.loDlg.FILTER = lcFilter
m.loDlg.FileName = EVL(m.tcFileName, "")
m.loDlg.DialogTitle = EVL(m.tcCustomText, "Save file as...")
m.loDlg.FLAGS = COMMDLOG_RO + COMMDLOG_DEFAULT_FLAG
m.loDlg.MaxFileSize = 256
LOCAL lnResult AS INTEGER, lcFileName
* lnResult = loDlg.ShowOpen()
m.lnResult = m.loDlg.ShowSave()
* Restore the original directory
SET DEFAULT TO (m.lcSetDefa)
IF EMPTY(m.loDlg.FileTitle) && Clicked 'Cancel'
m.lcFileName = ""
ELSE
m.lcFileName = m.loDlg.FileName
ENDIF
m.loDlgForm = NULL
RETURN m.lcFileName
DEFINE CLASS oleComDialObject AS OLECONTROL
OLECLASS ="MSComDlg.CommonDialog.1"
ENDDEFINE
</pre>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-79235282517810147252021-12-18T01:00:00.000-03:002021-12-18T01:00:00.232-03:00Guardar un informe de VFP a una resolución más alta con FoxyPreviewer<p><b>Articulo Original: Saving a VFP Report at a higher resolution with FoxyPreviewer<br>
<a href="http://vfpimaging.blogspot.com/2021/03/saving-vfp-report-at-higher-resolution.html">http://vfpimaging.blogspot.com/2021/03/saving-vfp-report-at-higher-resolution.html</a><br>
Autor: Cesar Ch.<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p><img alt="" border="0" data-original-height="65" data-original-width="774" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYt3iO_1KNKoxXU9UUlyXYoqiJwlhguaAYqv1DIFCJPA8lRZW4pofM6iKRTefpv5WqwctETY9OpuWEkyHbUpKA7b3A1GxBZa4iQyfro4QOoeKKZLfp7OyhD3WDTR_0i4X3X0f2TX5VBQs/s1600/ClosedToolbar.png"/></p>
<p>Visual FoxPro 9 trajo la posibilidad de guardar nuestros informes como imágenes, utilizando la clase ReportListener y el método OutputPage:</p>
<pre>loListener.OutputPage(lnPage, "\MyReportImage.PNG", 104) && PNG file type</pre>
<p>Esto nos trae algunas imágenes útiles, pero pésimas en cuanto a calidad. Por ejemplo, la muestra "COLORS.FRX" me brinda una imagen de 816 x 1056 píxeles, una imagen de muy mala calidad, y mas aun si estamos pensando en imprimirla o manipularla.</p>
<p>Pero el método "OutputPage" también nos permite dibujar la página del informe en cualquier tamaño de imagen deseado, pasando un identificador GDI+ Graphics en lugar del nombre de archivo ampliamente utilizado.</p>
<p>Aquí está la muestra de trabajo: observe que el motor de informes funciona solo con 96 DPI, por lo que para tener una mejor calidad, debe gurdar en dimensiones más grandes.</p>
<p>Use la función GETREPORTPAGEEX para obtener sus informes con mayor resolución, aquí está la lista de parámetros</p>
<ul>
<li>tcFile: el nombre del archivo de la imagen de destino</li>
<li>toListener: el ReportListener asociado con el informe actual</li>
<li>tnPage: el número de página del informe</li>
<li>tnEncoder: 100=EMF, 101=TIFF, 102=JPEG, 103=GIF, 104=PNG, 105=BMP</li>
<li>tnScale: el factor de escala que se aplicará a la imagen. 1=Predeterminado (baja calidad), 10=Super alta calidad</li>
<li>tnWidth: el ancho de la imagen de salida (opcional, si se usa "tnScale")</li>
<li>tnHeight: la altura de la imagen de salida (opcional, si se usa "tnScale")</li>
</ul>
<pre>
DO FoxyPreviewer.App
LOCAL loListener AS REPORTLISTENER
LOCAL lcFile, lnPage, lnFileType
m.loListener = CREATEOBJECT("FoxyListener")
m.loListener.LISTENERTYPE = 3
REPORT FORM (ADDBS(_Samples) + "Solution\Reports\Colors.FRX") OBJECT m.loListener
m.lnFileType = 104 && PNG
&& 100 - EMF
&& 101 - TIFF
&& 102 - JPEG
&& 103 - GIF
&& 104 - PNG
&& 105 - BMP
FOR m.lnPage = 1 TO m.loListener.PAGETOTAL
m.lcFile = "c:\temp\Test__" + SYS(2015) + "__" + ALLTRIM(STR(m.lnPage)) + ".png"
GetReportPageEx(m.lcFile, m.loListener, m.lnPage, m.lnFileType, 5) && 5 times bigger image than default
* For the default lower quality image, use:
* loListener.OutputPage(lnPage, "c:\Test" + ALLTRIM(STR(lnPage)) + ".png", lnFileType)
ENDFOR
m.loListener = NULL
RETURN
PROCEDURE GetReportPageEx(tcFile, toListener AS REPORTLISTENER, tnPage, tnEncoder, tnScale, tnWidth, tnHeight)
LOCAL lhGfx
*!* 100 - image type EMF
*!* 101 - image type TIFF
*!* 102 - image type JPEG
*!* 103 - image type GIF
*!* 104 - image type PNG
*!* 105 - image type BMP
m.tnEncoder = EVL(m.tnEncoder, 104) && Default = 104-PNG
m.tnScale = EVL(m.tnScale, 1)
IF EMPTY(m.tnWidth)
m.tnWidth = m.toListener.GETPAGEWIDTH() / 10 * m.tnScale
m.tnHeight = m.toListener.GETPAGEHEIGHT() / 10 * m.tnScale
ENDIF
#DEFINE Gdiplus_PixelFormat_32BppArgb 0x0026200a
#DEFINE OUTPUTDEVICETYPE_GDIPLUS 1
LOCAL loBMP AS GpBitmap OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
m.loBMP = NEWOBJECT("GpBitmap", ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
m.loBMP.CREATE(m.tnWidth, m.tnHeight, Gdiplus_PixelFormat_32BppArgb)
LOCAL loGfx AS GpGraphics OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
m.loGfx = NEWOBJECT('GpGraphics', ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
m.loGfx.CreateFromImage(m.loBMP)
m.lhGfx = m.loGfx.GetHandle()
m.toListener.OUTPUTPAGE(m.tnPage, m.lhGfx, OUTPUTDEVICETYPE_GDIPLUS, 0, 0, m.tnWidth, m.tnHeight, 0, 0, m.tnWidth, m.tnHeight)
m.loBMP.SaveToFile(m.tcFile, "image/png")
ENDPROC
</pre>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-87238136168643520252021-12-04T02:00:00.000-03:002021-12-04T02:00:00.200-03:00TRY ... CATCH ... FINNALY<p>A partir de Visual FoxPro 8 existe una nueva estructura de control TRY...CATCH...FINALLY para capturar errores o excepciones que ocurren en tiempo de ejecución.</p>
<p>La estructura TRY...CATCH...FINALLY comienza con una cláusula TRY que marca el inicio de un bloque TRY. En este bloque se puede especificar un grupo de cláusulas que pueden producir un error en tiempo de ejecucion. Si el programa completa el bloque TRY sin generar ninguna excepción, este salta el bloque CATCH y busca el bloque FINALLY y ejecuta las sentencias de ese bloque. Si el bloque FINALLY no existe, el programa ejecuta la primera sentencia despues de la clausula ENDTRY que marca el final de la estructura TRY...CATCH...FINALLY</p>
<p><b>Capturando el error</b></p>
<p>Cuándo el error ocurre, el código lanza (THROW) una excepción, el programa ejecuta la primera sentencia del bloque CATCH que maneja dicha excepción.</p>
<p>El programa examina las sentencias CATCH en la orden que estas aparecen para ver si una de ellas pueden manejar la excepción. Si el programa encuentra una sentencia CATCH que maneja la excepción, el programa ejecuta el código correspondiente.</p>
<p>La sentencia CATCH puede contener las clausuals opcionales TO y WHEN. Uno puede especificar una variable de memoria en la cláusula TO para almacenar una referencia a un objeto Exception (nuevo en Visual FoxPro 8), que se crea sólo cuando una excepción ocurre. Si se quiere establecer una condición para ejecutar un bloque CATCH, se puede especificar una expresión en la cláusula WHEN que se debe evaluar Verdadero (.T.) antes de que el bloque CATCH se ejecute.</p>
<p>Las sentencias CATCH trabajan similarmente como una sentencia DO CASE en el que la cláusula WHEN debe evaluar a una expresión lógica. Si las cláusulas TO y WHEN no existen, la sentencia CATCH se evalúa como CATCH WHEN .T. (Verdadero).</p>
<p>Después que el programa ejecuta las sentencias en un bloque CATCH, no vuelve a la sentencia TRY, y no busca otras declaraciones CATCH. El programa busca el bloque FINALLY, si este existe. Si no existe el programa ejecuta la sentencia inmediatemente despues de la clausula ENDTRY. El bloque FINNALY se ejecuta se haya o no generado una excepción.</p>
<p><b>Sintaxis y ejemplo</b></p>
<p>Esta es la sintaxis de la estructura de control TRY...CATCH...FINALLY</p>
<pre>
TRY
[tryCommands]
[CATCH [TO VarName] [WHEN lExpression]
[catchCommands]]
[THROW eUserExpression]
[EXIT]
[FINALLY
[finallyCommands]]
ENDTRY
</pre>
<p>Un breve ejemplo del uso de TRY...CATCH...FINALLY</p>
<pre>
CLEAR
LOCAL ln AS Integer, lc AS Character
LOCAL lo AS Exception
TRY
? "-- TRY --"
SELECT 0
USE "TablaNoExistente" ALIAS "MiTabla"
GO BOTT
ln = MiTabla.nCampo
lc = MiTabla.cCampo
? ln
? lc
CATCH TO lo && objeto Exception
? "-- CATCH --"
lo.UserValue = "Aquí hay un error"
? ' Comment: ', lo.COMMENT
? ' Details: ', lo.Details
? ' ErrorNo: ', lo.ErrorNo
? ' LineContents: ', lo.LineContents
? ' LineNo: ', lo.LINENO
? ' Message: ', lo.MESSAGE
? ' Procedure: ', lo.PROCEDURE
? ' UserValue: ', lo.UserValue
? ' StackLevel: ', lo.StackLevel
FINALLY
? "-- FINALLY --"
IF USED("MiTabla")
USED IN "MiTabla"
ENDIF
ENDTRY
? "-- ENDTRY --"
</pre>
<p><b>Prioridad de los manejos de errores</b></p>
<p>Si un error ocurre en el método de un objeto que se llama en un bloque TRY, Visual FoxPro sigue el procedimiento del manejo del error para ese objeto particular. Este protocolo proporciona una manera para mantener una encapsulation y control de los componentes.</p>
<p>Por ejemplo, si la línea MiForm.Refresh() en un bloque TRY genera un error, si el método Error existe para manejar el error, entonces el método Error toma la precedencia. Si ningún método Error existe, entonces la sentencia CATCH procura manejar el error.</p>
<p>Veamos algunos ejemplos de estas prioridades:</p>
<p>Ejemplo 1: La clase MiClase posee un método Error, y este toma precedencia al generarse un error.</p>
<pre>
CLEAR
lo = CREATEOBJECT("MiClase")
lo.MiMetodo1()
DEFINE CLASS MiClase AS CUSTOM
PROCEDURE MiMetodo1
TRY
THIS.MiMetodo2()
CATCH TO loErr
? "-- CATCH --"
? "ErrorNo:", loErr.ErrorNo
? "LineNo:", loErr.LINENO
? "Procedure:", loErr.PROCEDURE
? "Message:", loErr.MESSAGE
ENDTRY
ENDPROC
PROCEDURE MiMetodo2
? PROGRAM()
*-- La variable luY no existe. El evento ERROR maneja este error.
luX = luY
ENDPROC
PROCEDURE ERROR(nError, cProcedure, nLine)
? "-- ERROR --"
? "ErrorNo:", nError
? "LineNo:", nLine
? "Procedure:", cProcedure
? "Message:", MESSAGE()
ENDPROC
ENDDEFINE
</pre>
<p>Ejemplo 2: La clase MiClase no posee un método Error, en esta caso el manejo de error lo maneja la claúsula CATCH.</p>
<pre>
CLEAR
lo = CREATEOBJECT("MiClase")
lo.MiMetodo1()
DEFINE CLASS MiClase AS CUSTOM
PROCEDURE MiMetodo1
TRY
THIS.MiMetodo2()
CATCH TO loErr
? "-- CATCH --"
? "ErrorNo:", loErr.ErrorNo
? "LineNo:", loErr.LINENO
? "Procedure:", loErr.PROCEDURE
? "Message:", loErr.MESSAGE
ENDTRY
ENDPROC
PROCEDURE MiMetodo2
? PROGRAM()
*-- La variable luY no existe y
*-- el método ERROR no existe. CATCH maneja este error.
luX = luY
ENDPROC
ENDDEFINE
</pre>
<p><b>Mas información</b></p>
<p>Pueden ver mas información de la estructura de control TRY...CATCH...FINNALY en:</p>
<p>Fox.Wikis: <a href="http://fox.wikis.com/wc.dll?Wiki~TryCatch">http://fox.wikis.com/wc.dll?Wiki~TryCatch</a></p>
<hr>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-46318775836067862452021-11-20T00:00:00.000-03:002021-11-20T00:00:00.199-03:00Controlar la configuración de un informe en tiempo de ejecución<p><b>Articulo original: Controlling report settings at run time<br />
<a href="http://www.ml-consult.co.uk/foxst-12.htm" target="_blank">http://www.ml-consult.co.uk/foxst-12.htm</a><br />
Autor: Mike Lewis<br />
Traducido por: Luis María Guayán</b></p>
<hr />
<p><b>Cómo permitir que los usuarios elijan opciones de impresora para informes VFP.</b></p>
<p>Este artículo se aplica a Visual Foxpro 8.0 y versiones anteriores. Si está utilizando una versión posterior, consulte la nota al final del artículo.</p>
<p>Probablemente sepa que, al diseñar un informe de Visual FoxPro, puede seleccionar el tamaño de página, la orientación y el origen del papel en el cuadro de diálogo "Configurar impresión". Este es el cuadro de diálogo al que llega cuando elige "Archivo" - "Configuración de página" y luego hace clic en el botón "Configurar impresión" dentro del diseñador de informes.</p>
<p>Pero hay un inconveniente. Si realiza algún cambio en la configuración de impresión de esta manera, sus usuarios no podrán anularlos en tiempo de ejecución.</p>
<p>Por ejemplo, si configura el origen del papel en "Bandeja superior", el informe siempre intentará seleccionar esa bandeja. No puede usar el comando <code>REPORT FORM ... PROMPT</code> para anularlo en tiempo de ejecución, porque el cuadro de diálogo PROMPT no incluye una configuración para el origen del papel. (Pero puede usar el cuadro de diálogo PROMPT para seleccionar una impresora diferente).</p>
<p><code>SYS(1037)</code> tampoco ayudará. Normalmente, esa función muestra el cuadro de diálogo "Configurar página" y permite al usuario cambiar la configuración dentro de él. Los cambios afectarán a todos los resultados impresos de la sesión actual. Pero si cambió alguna de las configuraciones de página predeterminadas dentro del diseñador de informes, el cuadro de diálogo <code>SYS(1037)</code> no tendrá ningún efecto en ese informe. Ni siquiera puede usarlo para enviar el informe a una impresora diferente.</p><p>
</p><p><b>Hackear el FRX</b></p>
<p>Afortunadamente, existe una solución. Implica una pequeña triquiñuela tocando el archivo del informe.</p>
<p>Como ya se sabe, el formato interno de un informe de VFP, es decir, el archivo FRX, es el mismo que el de una DBF. De ello se deduce que se puede abrir el archivo como si fuera una tabla de FoxPro. También puede examinarlo y editarlo. Ahora, no es necesario que le digamos que editar un archivo de informe de esta manera es peligroso. Si cambia un campo incorrecto por un valor incorrecto, podría arruinar su diseño cuidadosamente planificado. Así que tenga cuidado con lo que sigue, y asegúrese de tener una copia de seguridad antes de comenzar.</p>
<p>El primer paso es deshacerse de la configuración de la impresora que se guardó en el diseñador de informes. Para hacerlo, abra y explore el informe desde la Ventana de Comandos. Por ejemplo, si el informe se llama Cliente, escriba lo siguiente:</p>
<pre>USE Customer.frx
BROWSE
</pre>
<p>En la Ventana Examinar, busque un registro donde el campo Objtype sea igual a 1 y el campo Objcode igual a 53 (generalmente es el primer registro). En este registro, busque los campos Expr, Tag y Tag2. Estos son campos Memos que contienen la configuración que realizó en el cuadro de diálogo "Configuración de impresión" del diseñador de informes. Tag y Tag2 contienen valores binarios. Expr contiene texto sin formato, que se verá así:</p>
<pre>DRIVER=WINSPOOL
DEVICE=HP LaserJet 6P/6MP - Enhanced
OUTPUT=LPT1:
ORIENTATION=0
PAPERSIZE=9
PAPERLENGTH=2794
PAPERWIDTH=2159
COPIES=1
DEFAULTSOURCE=4
PRINTQUALITY=-4
COLOR=2
</pre>
<p>Ahora siga adelante y elimine el contenido de estos tres campos Memo. Esto devolverá todas las configuraciones guardadas a sus valores predeterminados. Si desea permitir que el usuario elija el tamaño de la página, el origen del papel o la orientación en tiempo de ejecución, ahora puede llamar a <code>SYS(1037)</code> para hacer el trabajo.</p>
<p><b>Todo o nada</b></p>
<p>Pero, ¿qué sucede si desea permitir que el usuario cambie algunas de las configuraciones pero no otras? ¿O desea almacenar configuraciones no predeterminadas en el informe, pero desea permitir que el usuario las anule en determinadas circunstancias? Llamar a <code>SYS(1037)</code> no ayudará, porque es todo o nada. No puede usarlo para permitir que el usuario cambie algunas configuraciones, pero evitar que cambie otras. En cualquier caso, dicha función no funcionará si ha realizado algún cambio en la configuración desde el diseñador de informes, incluso si son diferentes de los que desea que el usuario configure.</p>
<p>Esto es lo que se haría. Supongamos que siempre desea que el informe se imprima en orientación horizontal en papel A5, pero desea permitir que el usuario cambie el origen del papel entre "Alimentación manual" y "Bandeja inferior".</p>
<p>Comience seleccionando la orientación y el tamaño de la página en el cuadro de diálogo "Configurar impresión" desde el diseñador de informes. Mientras esté allí, configure el origen del papel en el valor predeterminado que desee. Guarde el informe como de costumbre.</p>
<p>En su aplicación, proporcione al usuario un mecanismo para alternar el origen del papel. Puede ser un botón de comando, una casilla de verificación, un comando del menú o lo que sea. Cuando el usuario elija, ejecute un código similar al siguiente:</p>
<pre>USE Customer.frx
LOCATE FOR Objtype = 1 AND Objcode = 53
REPLACE Tag WITH ""
REPLACE Tag2 WITH ""
IF "DEFAULTSOURCE=2" $ Expr
lcNew = "DEFAULTSOURCE=4"
lcOld = "DEFAULTSOURCE=2"
ELSE
lcNew = "DEFAULTSOURCE=2"
lcOld = "DEFAULTSOURCE=4"
ENDIF
REPLACE expr WITH STRTRAN(Expr,lcOld,lcNew)
USE
</pre>
<p>La próxima vez que se imprima el informe, seleccionará el origen de papel que elija el usuario.</p>
<p>Esto es lo que hace el código. Primero, abre el archivo del informe como una tabla y localiza el registro que contiene la configuración. Luego borra los valores binarios de la configuración. Eso no es un problema, el informe funcionará bien sin ellos.</p>
<p>A continuación, comprueba la configuración existente del origen de papel. Si es 2, lo cambia a 4 y viceversa. Como puede imaginar, esta configuración se especifica mediante el valor DEFAULTSOURCE: 4 significa "Alimentación manual", 2 es "Bandeja inferior". Finalmente, el campo Expr se actualiza con el valor modificado y se cierra el archivo.</p>
<p>¿Cómo supimos qué valores usar para DEFAULTSOURCE? Desde la pantalla de ayuda de la función PrtInfo(). La función PrtInfo(), devuelve la configuración de la impresora actual, utiliza los mismos valores que el campo Expr. Entonces, si desea comprender el contenido del campo, ahí es donde debe buscar.</p>
<p>Una última palabra: cuando distribuya su aplicación, asegúrese de excluir el archivo FRX y su archivo FRT asociado de la compilación. En otras palabras, no vincule estos dos archivos al archivo EXE. Si lo hiciera, el código anterior no podría actualizar los archivos y su usuario vería un error en tiempo de ejecución. En su lugar, déle al usuario una copia independiente de los dos archivos.</p>
<p><b>Nota para los usuarios de VFP 9.0:</b></p>
<p>En VFP 9.0, toda la cuestión de la configuración de la impresora en los archivos de informe se ha vuelto mucho más fácil. De hecho, nunca necesitará manipular el archivo FRX de la forma descrita en este artículo. De forma predeterminada, la configuración de la impresora ya no se guarda en el FRX. Independientemente de las configuraciones que estuvieran vigentes en el momento del diseño del informe, puede usar <code>SET PRINTER TO NAME</code> o <code>SYS(1037)</code> para establecer las configuraciones de tiempo de ejecución. </p>
<p>Si desea anular el comportamiento por defecto, es decir, si no desea guardar la configuración del informe en el archivo FRX, abra el menú Informe en el diseñador de informes, y seleccione la opción entorno de la impresora. Puede hacer esto si desea forzar la impresión de un informe en una impresora en particular, por ejemplo. Si desea que la configuración de la impresora se guarde en todos los informes de forma predeterminada, vaya a Herramientas - Opciones - Informes y habilite la opción "Guardar entorno de impresora".</p>
<hr>
<p><i>Mike Lewis Consultants Ltd.</i></p><p></p><p></p><p></p><p></p>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-64998064609525312702021-11-10T01:00:00.000-03:002021-11-10T01:00:00.205-03:00Separar párrafos en líneas de "n" caracteres<p>La función recursiva CortarParrafo() prepara una cadena para luego separarla con la función ALINES() en varias lineas de "n" o menos caracteres</p>
<p><i>Ejemplo:</i></p>
<pre>
lcCadena = "SON PESOS: TRES MILLONES NOVECIENTOS CINCUENTA Y CUATRO MIL " + ;
"TRESCIENTOS OCHENTA Y NUEVE CON SETENTA Y CINCO CENTAVOS."
FOR ln = 1 TO ALINES(la,CortarParrafo(lcCadena,40))
? la(ln)
ENDFOR
FUNCTION CortarParrafo(tc,tn)
LOCAL lc, ln
tc = ALLTRIM(tc) + " "
lc = SUBSTR(tc,1,tn)
ln = RAT(" ",lc)
lc = SUBSTR(lc,1,ln-1)
RETURN IIF(EMPTY(lc),lc, lc + CHR(13) + CortarParrafo(SUBSTR(tc,ln+1),tn))
ENDFUNC
</pre>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-23252486850101034022021-10-23T02:00:00.000-03:002021-10-23T02:00:00.231-03:00Expresiones regulares en Visual FoxPro - Parte 3<p><b>Artículo original: Regular Expressions for Visual FoxPro Part 3<br><a href="http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,9a28ad22-b41b-434a-a540-40e6197a099e.aspx">http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,9a28ad22-b41b-434a-a540-40e6197a099e.aspx</a><br>Autor: Craig Boyd<br>Traducido por: Ana María Bisbé York</b></p><hr><h4>FLL actualizada con más funcionalidad</h4>
<p><img alt="" border="0" data-original-height="150" data-original-width="150" src="https://1.bp.blogspot.com/-Yu9qO6GkV0I/YSba-oaxcTI/AAAAAAAADgQ/eB-3BwvaQnk3WHRjAyY2_QYAAuW8jzqLgCLcBGAsYHQ/s0/RegExp-Icono-www.Jarroba.com_.png"/></p>
<p>No tengo tiempo (es tarde) para comentar mucho sobre los cambios que he hecho en regexp.fll. He mejorado significativamente las funcionalidades de la función regexp() cuando corta la cadena. Ahora se pueden guardar los pedazos cortados en una matriz o en un cursor. He mejorado además un par de errores en el código que tenían que ver con la fragmentación de la cadena. La documentación de mi entrada de blog anterior es válida para cada evento, vea (<a href="https://www.sweetpotatosoftware.com/blog/index.php/2006/01/11/regular-expressions-in-visual-foxpro-part-2/">https://www.sweetpotatosoftware.com/blog/index.php/2006/01/11/regular-expressions-in-visual-foxpro-part-2/</a>). <i>(Nota del Editor: Traducido en este blog en <a href="https://comunidadvfp.blogspot.com/2016/02/expresiones-regulares-en-visual-foxpro.html">Expresiones regulares en Visual FoxPro - Parte 2</a>)</i>. Ahora se puede asignar 4 al parámetro nFunctionType y se obtendrá un cursor. Además, si está utilizando los valores 3 ó 4 para nFunctionType, puede especificar un comando de Visual FoxPro al motor de expresiones regulares para que lo ejecute cada vez que encuentre una coincidencia (pensado como una especie de función de rellamada -callback function )</p><h4>Un ejemplo de análisis XML</h4><p>Esta biblioteca ha tomado forma; pero pienso que existen muchas otras posibilidades que no se han explorado aun (Coméntenme en el blog o envíenme cualquier idea de funcionalidad que pueda tener -me encanta recibirlas). Algo nuevo que he pensado para la biblioteca puede ser análisis de XML. Utilicé alguna expresión regular que encontré disponible libremente en internet, creé un código de ejemplo (vea debajo) para mostrar las maravillosas habilidades de las expresiones regulares así como la nueva funcionalidad en la FLL.</p><h4>Más por venir</h4><p>Aun no he hecho justicia a las expresiones regulares ni a esta FLL. Cuando tenga más tiempo, regresaré a actualizar la documentación y mostrar cómo esta FLL ofrece posibilidades en Visual FoxPro que aun no hemos visto. He aquí un archivo de descarga y algunos ejemplos de código (la función ParseXML es lo fundamental que necesita la regexp.fll más reciente para poderla ejecutar). ¡¡ A disfrutar !!</p><p><b>Descarga de regexp.fll (63 KB aprox)</b> <a href="http://www.sweetpotatosoftware.com/files/regexp.zip">haciendo clic aquí</a> </p>
<pre>*!* Ejemplo de <font color="#0000FF">empleo</font> de la función ParseXML
#DEFINE CREATEANARRAY 2
#DEFINE CREATEACURSOR 3
LOCAL lcArrayToCreate, lcCursorToCreate, lnTotal, lnCounter
*!* He aquí un ejemplo de XML
*!* sólo como demostración
TEXT TO lcSampleXML NOSHOW PRETEXT 7
<?xml version="1.0"?>
<samples>
<sample>Visual FoxPro Rocks!</sample>
<sample>Hace mucho más antes de desayunar</sample>
<sample>que otros lenguajes <font color="#0000FF">hacen</font> durante todo el día</sample>
</samples>
ENDTEXT
*!* O puede <font color="#0000FF">intentarlo utilizando</font> un archivo XML
*!* Puede obtener algún ejemplo de XML desde
*!* <font color="#008000">ftp://sunsite.unc.edu/pub/sun-info/standards/xml/eg/shakespeare.1.01.xml.zip
</font>*!* Una vez extraído <font color="#0000FF">comente </font>del código encima TEXT/ENDTEXT
*!* finalmente puede hacer algo como lo siguiente
*!* para cargar el ejemplo de archivos XML
*!* lcSampleXML = FILETOSTR(GETFILE())
*!* Crea una matriz
DIMENSION aryXMLDoc(1)
lcArrayToCreate = "aryXMLDoc"
lnTotal = ParseXML(lcSampleXML, lcArrayToCreate, CREATEANARRAY)
CLEAR
FOR lnCounter = 1 TO lnTotal
? aryXMLDoc(lnCounter)
ENDFOR
MESSAGEBOX("El XML analizado se mostrará en pantalla desde la matriz." ;
+ CHR(13) + "Oprima OK para continuar con el siguiente ejemplo.", ;
64, "Análisis del XML terminado")
*!* Crea un cursor
lcCursorToCreate = "crsXMLDoc"
lnTotal = ParseXML(lcSampleXML, lcCursorToCreate, CREATEACURSOR)
IF lnTotal > 0
GO TOP IN (lcCursorToCreate)
BROWSE
ENDIF
MESSAGEBOX("Oprima OK para ejecutar el ejemplo final.", ;
64, "Inicio del ejemplo final de Análisis del XML")
*!* Crea un cursor; pero además ejecuta código
*!* VFP <font color="#0000FF">que</font> ejecuta el analizador C++.
*!* Puede ser utilizado para manipular el dato
*!* <font color="#0000FF">como es</font> recuperado, o lo que sea.
*!* El analizador C++ <font color="#0000FF">en</font> la FLL
*!* ejecutará cualquier comando que se le indique
lcCursorToCreate = "crsXMLDoc"
lcVFPCommand = "DO AppendVFPRocks"
lnTotal = ParseXML(lcSampleXML, lcCursorToCreate, CREATEACURSOR, lcVFPCommand)
IF lnTotal > 0
CLEAR
GO TOP IN (lcCursorToCreate)
SCAN ALL
? Splits
ENDSCAN
ENDIF
**************************
PROCEDURE AppendVFPRocks
**************************
Replace splits WITH "VISUAL FOXPRO ROCKS!" + splits
ENDPROC
*******************************
FUNCTION ParseXML (tcXML, tcName, tnType, tcCommand)
*******************************
*!* Esta función <font color="#0000FF">existe</font> gracias
*!* al trabajo de Robert Cameron...
*******************************
*!* REX/Perl 1.0
*!* Robert D. Cameron "<font color="#008000">REX: XML Shallow Parsing with Regular Expressions</font>",
*!* Technical Report TR 1998-17, School of Computing Science, Simon Fraser
*!* University, November, 1998.
*!* Copyright (c) 1998, Robert D. Cameron.
*!* El código siguiente puede ser utilizado libremente y distribuido indicando este
*!* copyright y citar en una nota que se mantiene intacta y si tiene modificaciones
*!* o agregados han de ser debidamente identificados.
*!* <font color="#008000">http://www.cs.sfu.ca/~cameron/REX.html</font>
*******************************
*!* 01-14-2006: Traducidas y modificadas sus expresiones
*!* <font color="#0000FF">para</font> uso <font color="#0000FF">en</font> Visual FoxPro con regexp.fll
*!* por Craig Boyd <font color="#008000">http://www.sweetpotatosoftware.com/spsblog</font>
*******************************
LOCAL lcTextSE, lcUntilHyphen, ;
lcUntil2Hyphens, lcCommentCE, lcUntilRSBs, ;
lcCDATA_CE, lcS, lcNameStrt, lcNameChar, ;
lcName, lcQuoteSE, lcDT_IdentSE, ;
lcMarkupDeclCE, lcS1, lcUntilQMs, ;
lcPI_Tail, lcDT_ItemSE, lcDocTypeCE, ;
lcDeclCE, lcPI_CE, lcEndTagCE, lcAttValSE, ;
lcElemTagCE, lcMarkupSPE, lcXML_SPE, ;
lcExpression, lvReturn
IF !("\REGEXP.FLL" $ SET("Library"))
SET LIBRARY TO LOCFILE("regexp.fll", "FLL")
ENDIF
lcTextSE = "([^<]+"
lcUntilHyphen = "[^-]*-"
lcUntil2Hyphens = lcUntilHyphen + "(?:[^-]" + lcUntilHyphen + ")*-"
lcCommentCE = lcUntil2Hyphens + ">?"
lcUntilRSBs = "[^\]]*](?:[^\]]+])*]+"
lcCDATA_CE = lcUntilRSBs + "(?:[^\]>]" + lcUntilRSBs + ")*>"
lcS = "[ \n\t\r]+"
lcNameStrt = "[A-Za-z_:]|[^\x00-\x7F]"
lcNameChar = "[A-Za-z0-9_:.-]|[^\x00-\x7F]"
lcName = "(?:" + lcNameStrt + ")(?:" + lcNameChar + ")*"
lcQuoteSE = '"[^"]*"|' + "'[^']*'"
lcDT_IdentSE = lcS + lcName + "(?:" + lcS + "(?:" + lcName + "|" + lcQuoteSE + "))*"
lcMarkupDeclCE = '(?:[^\]"' + "'><]+|" + lcQuoteSE + ")*>"
lcS1 = "[\n\r\t ]"
lcUntilQMs = "[^?]*\?+"
lcPI_Tail = "\?>|" + lcS1 + lcUntilQMs + "(?:[^>?]" + lcUntilQMs + ")*>"
lcDT_ItemSE = "<(?:!(?:--" + lcUntil2Hyphens + ">|[^-]" + lcMarkupDeclCE + ")|\?" ;
+ lcName + "(?:" + lcPI_Tail + "))|%" + lcName + ";|" + lcS
lcDocTypeCE = lcDT_IdentSE + ;
"(?:" + lcS + ")?(?:\[(?:" + lcDT_ItemSE + ")*](?:" + lcS + ")?)?>?"
lcDeclCE = "--(?:" + lcCommentCE + ;
")?|\[CDATA\[(?:" + lcCDATA_CE + ")?|DOCTYPE(?:" + lcDocTypeCE + ")?"
lcPI_CE = lcName + "(?:" + lcPI_Tail + ")?"
lcEndTagCE = lcName + "(?:" + lcS + ")?>?"
lcAttValSE = '"[^<"]*"|' + "'[^<']*'"
lcElemTagCE = lcName + "(?:" + lcS + lcName + ;
"(?:" + lcS + ")?=(?:" + lcS + ")?(?:" + lcAttValSE + "))*(?:" + lcS + ")?/?>?"
lcMarkupSPE = "<(?:!(?:" + lcDeclCE + ")?|\?(?:" + lcPI_CE + ;
")?|/(?:" + lcEndTagCE + ")?|(?:" + lcElemTagCE + ")?))"
lcXML_SPE = lcTextSE + "|" + lcMarkupSPE
lcExpression = lcXML_SPE
IF VARTYPE(tcCommand) = "C"
lvReturn = RegExp(tcXML, lcExpression, tnType, tcName, tcCommand)
ELSE
lvReturn = RegExp(tcXML, lcExpression, tnType, tcName)
ENDIF
RETURN (lvReturn)
ENDFUNC</pre><hr>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-23465092117245253972021-10-02T02:00:00.001-03:002021-10-02T02:00:00.230-03:00Mejoras en el control Grid de Visual FoxPro<p><b>Artículo original: Visual FoxPro Grid Enhancements<br>
<a href="https://www.sweetpotatosoftware.com/blog/index.php/2008/11/25/visual-foxpro-grid-enhancements">https://www.sweetpotatosoftware.com/blog/index.php/2008/11/25/visual-foxpro-grid-enhancements</a><br>
Autor: Craig Boyd<br>
Traductor: Luis María Guayán</b></p>
<h3>El control grid de Visual FoxPro</h3>
<p>A menudo me han escuchado decir que el Grid de Visual FoxPro es uno de los mejores controles jamás ideados. Sigo sintiéndome así, pero ¿no sería bueno si algunas de las funciones que nuestros clientes quieren implementar en el control Grid ya estuvieran disponibles? Ya sabes, características como: ordenado, filtrado, búsqueda incremental, guardar las preferencias del usuario y exportar a Excel. Sería aún mejor si este tipo de funcionalidades pudiera implementarse en cualquier control Grid de Visual FoxPro (independientemente del origen de los registros) simplemente colocando una clase en un formulario, estableciendo una sola propiedad y escribiendo una sola línea de código. Bueno, eso es lo que me he propuesto crear con la clase que presento en esta publicación.</p>
<p>Pueden ver la funcionalidad básica proporcionada por GridExtras en las imágenes que aparecen al final de la publicación.</p>
<h3>Cómo usar la clase</h3>
<p>Les proporciono la aplicación Sample.exe en la descarga para que puedan probarla y ver cómo se implementa. Sin embargo, los pasos básicos son:</p>
<ul>
<li>Coloque una instancia de GridExtras en su formulario o contenedor (un GridExtras para cada control Grid que desee mejorar).</li>
<li>Establezca la propiedad GridExpression de GridExtras en una cadena que se evaluará en tiempo de ejecución al control Grid que está mejorando: "Thisform.Grid1" es el valor predeterminado para esta propiedad.</li>
<li>Llame al método Setup() de GridExtras cuando su control Grid esté listo para funcionar. No tengo forma de saber si está configurando el origen de registros en tiempo de ejecución, o si está agregando columnas en el código, por lo que este método se proporciona como una forma de controlar cuándo GridExtras comenzará a interactuar con el control Grid<li>.
</ul>
<p>Eso es todo lo que hay que hacer para poner GridExtras en funcionamiento para un control Grid en su aplicación. Como digo, he incluido un ejemplo en la descarga, así que si tiene alguna pregunta sobre cómo se hace esto, simplemente revise el ejemplo.</p>
<h3>Otras propiedades de la clase</h3>
<p>Hay algunas otras propiedades de GridExtras de las que es posible que desee tomar nota y utilizar, como:</p>
<ul>
<li>CompanyName y ProductName: estas propiedades se utilizan para determinar dónde guardar el archivo de preferencias del control Grid del usuario que está separado por perfil (Ej: "C:\Users\Craig\AppData\Roaming\MyCompany\MyProduct\gridprefs.tmp").</li>
<li>AllowGridExport, AllowGridFilter, AllowGridPreferences, AllowGridSort: estas propiedades le permiten activar o desactivar ciertas características de la clase según sus necesidades.</li>
<li>TemplateTable: esta propiedad determina dónde GridExtras busca gridextras.dbf que se usa para guardar las plantillas de Grid que crea el usuario (el usuario puede guardar sus filtros y ordenaciones para poder recrearlos fácilmente en un momento posterior). Es posible que deba establecer esta propiedad si planea mantener la tabla gridextras en una ruta de red, como con la carpeta de base de datos compartida para su aplicación.</li>
</ul>
<h3>Que sigue</h3>
<p>Simplemente juegue con GridExtras y vea si es útil para usted y sus aplicaciones. En un mundo perfecto, encontraría alguna forma de mejorarlo (agregar formatos de exportación adicionales, proporcionar una función de bloqueo de columna, etc.). Si mejora GridExtras, le agradecería que se ponga en contacto conmigo y comparta las mejoras que ha implementado. Gracias de antemano a aquellos de ustedes que decidan hacerlo.</p>
<p>Hasta la próxima!</p>
<p>Descargar <a href="https://drive.google.com/u/0/uc?id=1ihjc9gMby-d3lEk83e0WxhOQnPphCoCM&export=download">GridExtras y ejemplos</a> (aproximadamente 223 KB)</p>
<p><img alt="" border="0" data-original-height="508" data-original-width="677" src="https://1.bp.blogspot.com/-CATThC2Ahxk/YI9Cb9CSTWI/AAAAAAAADbE/xqLbXy5_nnAPzNDzDqnJXRR2PNds2StGQCLcBGAsYHQ/s0/gridextras11.png"/><br>
Figura 1: GridExtras proporciona capacidades de ordenamiento, búsqueda incremental y filtrado a cualquier control Grid de Visual FoxPro.</p>
<p><img alt="" border="0" data-original-height="508" data-original-width="677" src="https://1.bp.blogspot.com/-OqUL-RydTgo/YI9ClXR91UI/AAAAAAAADbI/qmsQFZpFwn4nMwuHjECwgAchj8TS4Eb0gCLcBGAsYHQ/s0/gridextras21.png"/><br>
Figura 2: GridExtras tiene características adicionales como la capacidad de volverse semitransparente y la capacidad de guardar las preferencias del usuario (orden de columna y ancho).</p>
<p><img alt="" border="0" data-original-height="508" data-original-width="677" src="https://1.bp.blogspot.com/-HvfmPgMpNBU/YI9CtAu6XTI/AAAAAAAADbQ/wBNv_W8GRDM3wKwCg5sSVfrsZh88F01bACLcBGAsYHQ/s0/gridextras31.png"/><br>
Figura 3: Además de las funciones proporcionadas por GridExtras a través de los encabezados de columna de un coltrol Grid, también hay un icono agregado en la parte inferior derecha de la clase que permite al usuario acceder a la pantalla "Plantillas de cuadrícula y exportación" que se ve en la Figura 4.</p>
<p><img alt="" border="0" data-original-height="278" data-original-width="442" src="https://1.bp.blogspot.com/-o8ZFJUQzNpU/YI9C1_KNm8I/AAAAAAAADbY/5WNCrD-BvzsMpKkkOqEUgmRADvY_ZtrjwCLcBGAsYHQ/s0/gridextras41.png"/><br>
Figura 4: Las plantillas de Grids ayudan al usuario a guardar las vistas del Grid para poder recrearlas rápidamente en el futuro. La función de exportación de GridExtras puede crear formatos de Excel XLS, XLSX, XLB y XLM. Los títulos de encabezado del control Grid se utilizan para nombrar las columnas de Excel cuando se exportan los datos.</p>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com5tag:blogger.com,1999:blog-8847231238348131912.post-1147262584146756442021-09-19T00:00:00.001-03:002021-09-19T00:00:00.224-03:00Regenerar todos los índices compuestos de una base de datos<p><img alt="" border="0" data-original-height="267" data-original-width="279" src="https://1.bp.blogspot.com/-Q-lrYepmQQU/YSbOhSFm6uI/AAAAAAAADgA/iIaquYuJ27wuqaMcjvqgcF59fNpX3dLEQCLcBGAsYHQ/s0/reindex-from-backend.png" /></p>
<p>Cuando se dañan los archivos índices de una tabla, ejecutar el comando REINDEX a veces no es suficiente. Hay casos en que hay que generar nuevamente cada uno de los índices con el comando INDEX ON y/o ALTER TABLE.</p>
<p>El siguiente código lo utilizo como una herramienta para regenerar rápidamente todos los índices de todas las tablas de una base de datos, y aparte me genera un archivo PRG con nombre "REG_CDX_????????.PRG" (donde "????????" es el nombre de la base de datos) que lo puedo ejecutar posteriormente</p>
<p>Si la base de datos contiene relaciones persistentes e integridad referencial <b>NO</b> ejecuten el código publicado, ni el archivo .PRG generado ya que éstos eliminan todas las etiquetas de índices de la tabla (<code>DELETE TAG ALL</code>) y esta acción también elimina el archivo .CDX y quita las relaciones persistentes entre las tablas y la integridad referencial existentes en la base de datos. Si éste es su caso, puede utilizar la herramienta <b>GenDBC.PRG</b> que está en la carpeta <b>\Tools\Gendbc\</b> en la carpeta raíz de instalación de Visual FoxPro, que si respalda y restaura las relaciones y la IR.</p>
<p>Usen este código bajo sus propios riesgos. Hagan una copia de seguridad de la base de datos antes de probarlo.</p>
<pre>
* -----
* Configurando la variable llRegenera en:
* .T. = si desea que se regeneren todos los índices al momento de ejecutar el código
* .F. = solo genera un archivo PRG con el código para generarlos posteriormente
*-----
LOCAL laDBF[1], laTag[1], llRegenera, lnI, lnJ, lnTAG
LOCAL lcCmd, lcDBC, lcPRG, lcTabla, lnDBF, loExc
CLOSE TABLES ALL
CLOSE DATABASES ALL
CLEAR ALL
SET MEMOWIDTH TO 128
SET SAFETY OFF
#DEFINE CR_LF CHR(13)+CHR(10)
*-- Selecciono DBC
m.lcDBC = GETFILE([DBC])
*-- Solo genera PRG o genera PRG y regenera índices
m.llRegenera = .F.
*-- Nombre del PRG generado
m.lcPRG = [REG_CDX_] + FORCEEXT(JUSTFNAME(m.lcDBC), [PRG])
IF EMPTY(m.lcDBC) OR NOT FILE(m.lcDBC)
MESSAGEBOX([Debe seleccionar un archivo DBC], 16)
RETURN
ENDIF
CLEAR
STRTOFILE([*--- Regenera los índices compuestos de las tablas de la base de datos ] + ;
JUSTFNAME(m.lcDBC) + CR_LF, m.lcPRG, 0)
STRTOFILE([*--- ] + CHRTRAN(TTOC(DATETIME(), 3), [T], [ ]) + CR_LF + CR_LF, m.lcPRG, 1)
OPEN DATABASE (m.lcDBC) && EXCLUSIVE VALIDATE
SET DATABASE TO (JUSTSTEM(m.lcDBC))
STRTOFILE([OPEN DATABASE ("] + m.lcDBC + [") EXCLUSIVE VALIDATE] + CR_LF, m.lcPRG, 1)
STRTOFILE([SET DATABASE TO ] + JUSTSTEM(m.lcDBC) + CR_LF + CR_LF, m.lcPRG, 1)
m.lnDBF = ADBOBJECTS(laDBF, "TABLE")
ASORT(m.laDBF)
FOR m.lnI = 1 TO m.lnDBF
*-- Recorro todas las tablas de la DBC
m.lcTabla = ADDBS(JUSTPATH(DBC())) + m.laDBF(m.lnI)
TRY
IF m.llRegenera && Necesito abrir en modo EXCLUSIVE
USE (m.lcTabla) EXCLUSIVE IN SELECT(m.laDBF(m.lnI))
ELSE
USE (m.lcTabla) SHARED IN SELECT(m.laDBF(m.lnI))
ENDIF
STRTOFILE([USE ("] + m.lcTabla + [") EXCLUSIVE ] + CR_LF, m.lcPRG, 1)
m.lnTAG = ATAGINFO(laTag)
m.lcCmd = ""
FOR m.lnJ = 1 TO m.lnTAG
*-- Recorro todas las etiquetas de índices
IF m.laTag(m.lnJ, 2) = [PRIMARY]
m.lcCmd = m.lcCmd + ;
[ALTER TABLE ] + m.laDBF(m.lnI) + [ ADD PRIMARY KEY ] + m.laTag(m.lnJ, 3) + ;
[ TAG ] + m.laTag(m.lnJ, 1) + ;
[ COLLATE "] + m.laTag(m.lnJ, 6) + ["] + CR_LF
ELSE
m.lcCmd = m.lcCmd + ;
[INDEX ON ] + m.laTag(m.lnJ, 3) + [ ] + ;
[TAG ] + m.laTag(m.lnJ, 1) + [ ] + ;
IIF(m.laTag(m.lnJ, 2) = "BINARY", m.laTag(m.lnJ, 2) + [ ], m.laTag(m.lnJ, 5) + [ ]) + ;
IIF(EMPTY(m.laTag(m.lnJ, 4)), [], [FOR ] + m.laTag(m.lnJ, 4) + [ ]) + ;
IIF(m.laTag(m.lnJ, 2) = "CANDIDATE", m.laTag(m.lnJ, 2) + [ ], []) + ;
[COLLATE "] + m.laTag(m.lnJ, 6) + ["] + CR_LF
ENDIF
ENDFOR
IF m.llRegenera
DELETE TAG ALL
PACK
EXECSCRIPT(m.lcCmd)
ENDIF
STRTOFILE([DELETE TAG ALL] + CR_LF, m.lcPRG, 1)
STRTOFILE([PACK] + CR_LF + CR_LF, m.lcPRG, 1)
STRTOFILE(m.lcCmd + CR_LF, m.lcPRG, 1)
CATCH TO m.loExc
STRTOFILE([* ERROR:] + CR_LF, m.lcPRG, 1)
STRTOFILE([* ] + m.loExc.MESSAGE + CR_LF, m.lcPRG, 1)
FINALLY
USE IN SELECT(m.laDBF(m.lnI))
STRTOFILE([USE IN SELECT("] + m.laDBF(m.lnI) + [")] + CR_LF + CR_LF, m.lcPRG, 1)
ENDTRY
ENDFOR
MODIFY FILE (m.lcPRG)
</pre>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-2921675084949752872021-09-04T01:00:00.010-03:002021-09-04T01:00:00.188-03:00Configurando una aplicación usando JSON<p><b>Título original: Application Configuration using JSON<br>
<a href="https://doughennig.blogspot.com/2020/10/application-configuration-using-json.html">https://doughennig.blogspot.com/2020/10/application-configuration-using-json.html</a><br>
Autor: Doug Hennig<br>
Traducido por Luis María Guayán</b></p>
<hr>
<p>Después de ver una la presentación de <b>Andrew MacNeil</b>l sobre JSON, me inspiré para ver <a href="https://github.com/VFPX/nfJson">nfJSON</a>, un proyecto <a href="https://github.com/VFPX/nfJson">VFPx</a> de <b>Marco Plaza</b>. Este gran proyecto agrega soporte JSON a las aplicaciones VFP. Lo que modificó mi interés fue la capacidad de convertir una cadena JSON en un objeto VFP y viceversa con una sola línea de código.</p>
<p>Mi primer pensamiento fue usar esto para los parámetros de configuración. Casi todas las aplicaciones necesitan parámetros de configuración: es mejor leer los parámetros como la información de conexión de la base de datos, ubicaciones de archivos, configuración de correo electrónico, etc. desde una fuente de configuración en lugar de "hardcodearlos" en la aplicación. He usado el Registro de Windows, archivos DBF, INI y XML en varias ocasiones, pero todos requieren codificar manualmente la lectura y escritura entre la fuente y los objetos VFP que contienen la configuración. Con nfJSON, es solo una línea de código.</p>
<p>He creado una clase contenedora llamada SFConfiguration. Solo tiene tres métodos:</p>
<ul>
<li><b>Load</b> devuelve un objeto con propiedades que coinciden con los pares de nombre/valor en el JSON contenido en el archivo de configuración especificado. Si el archivo no existe o está vacío (como la primera vez que se ejecuta la aplicación), llama a GetDefaultSettings (que se describe a continuación) para obtener la configuración predeterminada.</li>
<li><b>Save</b> guarda las propiedades del objeto de configuración especificado en el archivo especificado en el nombre de archivo pasado o en la propiedad cSettingsFile si no se pasa un nombre de archivo.</li>
<li><b>GetDefaultSettings</b> devuelve JSON para la configuración predeterminada. Puede usar esto de dos maneras: subclase SFConfiguration y anular GetDefaultSettings para devolver el JSON deseado, o establecer la propiedad oSettings en un objeto de configuración que contenga la configuración predeterminada.</li>
</ul>
<p>A continuación, se muestra un ejemplo del uso de esta clase para obtener la configuración del correo electrónico:</p>
<pre>
loConfig = createobject('SFConfiguration')
loConfig.cSettingsFile = 'email.json'
loConfig.oSettings = createobject('EmailSettings')
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario;
* si email.json no existe, la configuración de la clase
* EmailSettings se utiliza como predeterminada.
* Después de que el usuario ingrese la configuración deseada en algún
* cuadro de diálogo, guárdelos usando:
loConfig.Save(loSettings)
define class EmailSettings as Custom
Email = 'dhennig@stonefield.com'
MailServer = 'mail.stonefield.com'
Port = 25
UseSSL = .F.
UserName = 'dhennig'
Password = 'mypw'
enddefine
</pre>
<p>Así es como se ve el objeto de configuración:</p>
<p><img alt="" border="0" data-original-height="107" data-original-width="327" src="https://1.bp.blogspot.com/-iabu8s-m58Y/YGh4BS3zhWI/AAAAAAAADZE/4BEnIG-STI0Q1K2IsqMGMngXSaPy-tmxgCLcBGAsYHQ/s0/settings.png"/></p>
<p>Así es como se ve el JSON guardado:</p>
<p><img alt="" border="0" data-original-height="128" data-original-width="388" src="https://1.bp.blogspot.com/-Cr2bw0KL2CQ/YGh4GZ0-xDI/AAAAAAAADZI/-y27E-kON8AZ1i7GgtZhPIZdCYwqZfSPwCLcBGAsYHQ/s0/json.png"/></p>
<p>Aquí hay otro ejemplo, esta vez usando una subclase de SFConfiguration para lo mismo:</p>
<pre>
loConfig = createobject('SFEmailConfiguration')
loConfig.cSettingsFile = 'email.json'
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario;
* si email.json no existe, la configuración de la clase
* SFEmailConfiguration a continuación se usa como predeterminada.
define class SFEmailConfiguration as SFConfiguration
function GetDefaultSettings
text to lcSettings noshow
{
"email":"dhennig@stonefield.com",
"mailserver":"mailserver.stonefield.com",
"password":"mypw",
"port":25,
"username":"dhennig",
"usessl":false
}
endtext
return lcSettings
endfunc
enddefine
</pre>
<p>Aquí está el código para SFConfiguration. Requiere <a href="https://github.com/VFPX/nfJson/blob/master/nfJson/nfjsonread.PRG">nfJSONRead.prg</a> y <a href="https://github.com/VFPX/nfJson/blob/master/nfJson/nfjsoncreate.PRG">nfJSONCreate.prg</a>, que puede obtener del repositorio <a href="https://github.com/VFPX/nfJson">nfJSON</a> de GitHub:</p>
<pre>
define class SFConfiguration as Custom
cSettingsFile = ''
&& the name and path for the settings file
cErrorMessage = ''
&& the text of any error that occurs
oSettings = ''
&& a settings object
* Cargue la configuración del archivo especificado en el parámetro
* o en This.cSettingsFile y devuelva un objeto de configuración.
* Si el archivo no existe (como la primera vez que nos llaman),
* se retorna un objeto de configuración que contiene
* la configuración predeterminada.
function Load(tcSettingsFile)
local lcSettingsFile, ;
lcSettings, ;
loSettings, ;
loException as Exception
try
lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
if not empty(lcSettingsFile) and file(lcSettingsFile)
lcSettings = filetostr(lcSettingsFile)
endif not empty(lcSettingsFile) ...
if empty(lcSettings)
lcSettings = This.GetDefaultSettings()
endif empty(lcSettings)
loSettings = nfJSONRead(lcSettings)
This.cErrorMessage = ''
catch to loException
This.cErrorMessage = loException.Message
loSettings = NULL
endtry
This.oSettings = loSettings
return loSettings
endfunc
* Guarde la configuración en el objeto especificado en el
* archivo especificado en el parámetro o This.cSettingsFile.
function Save(toSettings, tcSettingsFile)
local lcSettingsFile, ;
lcSettings, ;
loException as Exception
lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
if not empty(lcSettingsFile)
try
lcSettings = nfJSONCreate(toSettings, .T.)
strtofile(lcSettings, lcSettingsFile)
This.cErrorMessage = ''
catch to loException
This.cErrorMessage = loException.Message
endtry
else
This.cErrorMessage = 'Settings file not specified.'
endif not empty(lcSettingsFile)
return empty(This.cErrorMessage)
endfunc
* Obtiene el conjunto de configuraciones predeterminado como una
* cadena JSON; anule esto en una subclase si es necesario.
function GetDefaultSettings
local lcSettings
if vartype(This.oSettings) = 'O'
lcSettings = nfJSONCreate(This.oSettings, .T.)
else
lcSettings = '{"text":"some text"}'
endif vartype(This.oSettings) = 'O'
return lcSettings
endfunc
enddefine
</pre>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-3614371729309139382021-08-28T04:00:00.005-03:002021-08-28T04:00:00.219-03:00Olvídese de TXTWIDTH - use GdipMeasureString<p><b>Articulo original: Forget TXTWIDTH - use GdipMeasureString<br>
<a href="https://doughennig.blogspot.com/2006/04/forget-txtwidth-use-gdipmeasurestring.html" target="_blank">https://doughennig.blogspot.com/2006/04/forget-txtwidth-use-gdipmeasurestring.html</a><br>
Autor: Doug Hennig<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p><img alt="" border="0" data-original-height="156" data-original-width="150" src="https://1.bp.blogspot.com/-4IAMtbri1L8/YSbaJnGKbII/AAAAAAAADgI/3KLjvk2_L_kenyXOzuowccCaAtlp4zEBgCLcBGAsYHQ/s0/cuadro-de-texto.png"/></p>
<p>Durante años, hemos utilizado código como el siguiente para determinar el ancho de una cadena:</p>
<pre>
lnWidth = txtwidth(lcText, lcFontName, lnFontSize, ;
lcFontStyle)
lnWidth = lnWidth * fontmetric(6, lcFontName, ;
lnFontSize, lcFontStyle)
</pre>
<p>Este código funciona bien en muchas situaciones, pero no en una en particular: cuando se define el ancho de un objeto en un informe.</p>
<p>El valor calculado anteriormente está en píxeles, por lo que debe convertir el valor a FRU (las unidades utilizadas en los informes, que son 1/10000 de pulgada); debe multiplicar por 104,166 (10000 FRU por pulgada / 96 píxeles por pulgada). En lugar de hacer todo ese trabajo, puede utilizar el método GetFRUTextWidth del objeto auxiliar FFC _FRXCursor:</p>
<pre>
loFRXCursor = newobject('FRXCursor', ;
home() + 'FFC\_FRXCursor.vcx')
lnWidth = loFRXCursor.GetFRUTextWidth(lcText, ;
lcFontName, lnFontSize, lcFontStyle)
</pre>
<p>El problema es que esto en realidad no le da el valor correcto. El motivo es que los informes usan GDI + para la representación y GDI + representa los objetos un poco más grandes de lo esperado.</p>
<p>Para ver este problema, haga lo siguiente:</p>
<pre>
use home() + 'samples\data\customer'
loFRXCursor = newobject('FRXCursor', ;
home() + 'FFC\_FRXCursor.vcx')
select max(loFRXCursor.GetFRUTextWidth(trim(company), ;
'Arial', 10)) from customer into array laWidth
wait window laWidth[1]
</pre>
<p>Obtengo 22500. Ahora cree un informe, agregue un campo, ingrese "empresa" como expresión y hágalo 2.25 pulgadas de ancho (22500 FRU / 10000 FRU por pulgada). Obtenga una vista previa del informe. La elipsis reveladora al final de algunos valores indica que el tamaño del campo no era lo suficientemente amplio.</p>
<p>Esto me volvió loco durante años. Descubrí un factor empírico "fudge" para agregar al ancho calculado; 19 píxeles (1979.154 FRU) parecían funcionar la mayor parte del tiempo, pero ocasionalmente encontraba que no era suficiente para algunos valores.</p>
<p>Afortunadamente, dado que los informes usan GDI +, podemos usar una función GDI + para calcular con precisión el ancho. GdipMeasureString determina varias cosas sobre la cadena especificada, incluido el ancho. Aún mejor, VFP 9 viene con un objeto contenedor de GDI + para que no tenga que comprender la API de GDI + para llamar a GdipMeasureString.</p>
<p>Para mostrar un ejemplo del uso de las clases contenedoras de GDI +, eche un vistazo a esta función:</p>
<pre>
function GetWidth(tcText, tcFontName, tnFontSize)
local loGDI, ;
loFont, ;
lnChars, ;
lnLines, ;
loSize
loGDI = newobject('GPGraphics', ;
home() + 'FFC\_GDIPlus.vcx')
loFont = newobject('GPFont', ;
home() + 'FFC\_GDIPlus.vcx', '', tcFontName, ;
tnFontSize, 0, 3)
loGDI.CreateFromHWnd(_screen.HWnd)
lnChars = 0
lnLines = 0
loSize = loGDI.MeasureStringA(tcText, loFont, , , ;
@lnChars, @lnLines)
lnWidth = loSize.W
release loGDI, loFont, loSize
return lnWidth
</pre>
<p>Ahora intente lo siguiente:</p>
<pre>
select max(GetWidth(trim(company), ;
'Arial', 10)) from customer into array laWidth
wait window ceiling(laWidth[1] * 104.166)
</pre>
<p>Esto da 23838. Cambie el ancho del campo en el informe a 2,384 pulgadas y vuelva a obtener una vista previa. Esta vez los valores encajan correctamente.</p>
<p>El único problema ahora es que este código puede tardar mucho en ejecutarse si hay muchos registros porque para cada llamada, se crean un par de objetos contenedores de GDI + y se realiza alguna configuración de GDI +. Creé una clase contenedora para GdipMeasureString llamada SFGDIMeasureString que funciona de manera mucho más eficiente.</p>
<p>Veamos esta clase en secciones. Aquí está el comienzo: define algunas constantes, la clase y sus propiedades:</p>
<pre>
* Estos #DEFINEs se toman de
* home() + 'ffc\gdiplus.h'
#define GDIPLUS_FontStyle_Regular 0
#define GDIPLUS_FontStyle_Bold 1
#define GDIPLUS_FontStyle_Italic 2
#define GDIPLUS_FontStyle_BoldItalic 3
#define GDIPLUS_FontStyle_Underline 4
#define GDIPLUS_FontStyle_Strikeout 8
#define GDIPLUS_STATUS_OK 0
#define GDIPLUS_Unit_Point 3
define class SFGDIMeasureString as Custom
oGDI = .NULL.
&& a reference to a GPGraphics object
oFormat = .NULL.
&& a reference to a GPStringFormat object
oFont = .NULL.
&& a reference to a GPFont object
oSize = .NULL.
&& a reference to a GPSize object
nChars = 0
&& the number of characters fitted in the
&& bounding box
nLines = 0
&& the number of lines in the bounding box
nWidth = 0
&& the width of the bounding box
nHeight = 0
&& the height of the bounding box
nStatus = 0
&& the status code from GDI+ functions
</pre>
<p>El método Init crea una instancia de algunos objetos auxiliares y declara la función GdipMeasureString. Destruye los objetos miembros con armas nucleares:</p>
<pre>
function Init
This.oGDI = newobject('GPGraphics', ;
home() + 'ffc\_gdiplus.vcx')
This.oFormat = newobject('GPStringFormat', ;
home() + 'ffc\_gdiplus.vcx')
This.oFont = newobject('GPFont', ;
home() + 'ffc\_gdiplus.vcx')
This.oSize = newobject('GPSize', ;
home() + 'ffc\_gdiplus.vcx')
declare integer GdipMeasureString ;
in gdiplus.dll ;
integer nGraphics, string cUnicode, ;
integer nLength, integer nFont, ;
string cLayoutRect, integer nStringFormat, ;
string @cRectOut, integer @nChars, ;
integer @nLines
endfunc
function Destroy
store .NULL. to This.oGDI, This.oFormat, ;
This.oFont, This.oSize
endfunc
</pre>
<p>MeasureString determina las dimensiones del cuadro delimitador para la cadena especificada:</p>
<pre>
function MeasureString(tcString, tcFontName, ;
tnFontSize, tcStyle)
local lcStyle, ;
lnStyle, ;
lnChars, ;
lnLines, ;
lcBoundingBox, ;
lnGDIHandle, ;
lnFontHandle, ;
lnFormatHandle, ;
lcRectF, ;
lnStatus, ;
llReturn
with This
* Asegúrese de que los parámetros se pasen correctamente.
do case
case vartype(tcString) <> 'C' or ;
empty(tcString)
error 11
return .F.
case pcount() > 1 and ;
(vartype(tcFontName) <> 'C' or ;
empty(tcFontName) or ;
vartype(tnFontSize) <> 'N' or ;
not between(tnFontSize, 1, 128))
error 11
return .F.
case pcount() = 4 and ;
(vartype(tcStyle) <> 'C' or ;
empty(tcStyle))
error 11
return .F.
endcase
* Configure el objeto Font si se especificaron la fuente y el tamaño.
if pcount() > 1
lcStyle = iif(vartype(tcStyle) = 'C', ;
tcStyle, '')
.SetFont(tcFontName, tnFontSize, lcStyle)
endif pcount() > 1
* Inicializar las variables de salida utilizadas en GdipMeasureString.
lnChars = 0
lnLines = 0
lcBoundingBox = replicate(chr(0), 16)
* Obtenga los identificadores de GDI + que necesitamos.
lnGDIHandle = .oGDI.GetHandle()
if lnGDIHandle = 0
.oGDI.CreateFromHWnd(_screen.HWnd)
lnGDIHandle = .oGDI.GetHandle()
endif lnGDIHandle = 0
lnFontHandle = .oFont.GetHandle()
lnFormatHandle = .oFormat.GetHandle()
* Obtenga el tamaño del cuadro de diseño.
lcRectF = replicate(chr(0), 8) + ;
.oSize.GdipSizeF
* Llame a la función GdipMeasureString para obtener las dimensiones
* del cuadro delimitador para la cadena especificada.
.nStatus = GdipMeasureString(lnGDIHandle, ;
strconv(tcString, 5), len(tcString), ;
lnFontHandle, lcRectF, lnFormatHandle, ;
@lcBoundingBox, @lnChars, @lnLines)
if .nStatus = GDIPLUS_STATUS_OK
.nChars = lnChars
.nLines = lnLines
.nWidth = ctobin(substr(lcBoundingBox, ;
9, 4), 'N')
.nHeight = ctobin(substr(lcBoundingBox, ;
13, 4), 'N')
llReturn = .T.
else
llReturn = .F.
endif .nStatus = GDIPLUS_STATUS_OK
endwith
return llReturn
endfunc
</pre>
<p>GetWidth es un método de utilidad que devuelve el ancho de la cadena especificada:</p>
<pre>
function GetWidth(tcString, tcFontName, ;
tnFontSize, tcStyle)
local llReturn, ;
lnReturn
with This
do case
case pcount() < 2
llReturn = .MeasureString(tcString)
case pcount() < 4
llReturn = .MeasureString(tcString, ;
tcFontName, tnFontSize)
otherwise
llReturn = .MeasureString(tcString, ;
tcFontName, tnFontSize, tcStyle)
endcase
if llReturn
lnReturn = .nWidth
endif llReturn
endwith
return lnReturn
endfunc
</pre>
<p>SetSize establece las dimensiones del cuadro de diseño para la cadena:</p>
<pre>
function SetSize(tnWidth, tnHeight)
if vartype(tnWidth) = 'N' and ;
tnWidth >= 0 and ;
vartype(tnHeight) = 'N' and tnHeight >=0
This.oSize.Create(tnWidth, tnHeight)
else
error 11
endif vartype(tnWidth) = 'N' ...
endfunc
</pre>
<p>SetFont establece el nombre, el tamaño y el estilo de la fuente que se utilizará:</p>
<pre>
function SetFont(tcFontName, tnFontSize, tcStyle)
local lcStyle
do case
case pcount() <= 2 and ;
(vartype(tcFontName) <> 'C' or ;
empty(tcFontName) or ;
vartype(tnFontSize) <> 'N' or ;
not between(tnFontSize, 1, 128))
error 11
return .F.
case pcount() = 3 and ;
vartype(tcStyle) <> 'C'
error 11
return .F.
endcase
lcStyle = iif(vartype(tcStyle) = 'C', tcStyle, '')
lnStyle = iif('B' $ lcStyle, ;
GDIPLUS_FontStyle_Bold, 0) + ;
iif('I' $ lcStyle, ;
GDIPLUS_FontStyle_Italic, 0) + ;
iif('U' $ lcStyle, ;
GDIPLUS_FontStyle_Underline, 0) + ;
iif('-' $ lcStyle, ;
GDIPLUS_FontStyle_Strikeout, 0)
This.oFont.Create(tcFontName, tnFontSize, ;
lnStyle, GDIPLUS_Unit_Point)
endfunc
</pre>
<p>Probemos el ejemplo anterior usando esta clase:</p>
<pre>
loGDI = newobject('SFGDIMeasureString', ;
'SFGDIMeasureString.prg')
select max(loGDI.GetWidth(trim(company), 'Arial', 10)) ;
from customer into array laWidth
wait window laWidth[1] * 10000/96
</pre>
<p>Esto es mucho más rápido que la función GetWidth presentada anteriormente. Lo siguiente se ejecutaría aún más rápido porque el objeto de fuente no tiene que inicializarse en cada llamada:</p>
<pre>
loGDI = newobject('SFGDIMeasureString', ;
'SFGDIMeasureString.prg')
loGDI.SetFont('Arial', 10)
select max(loGDI.GetWidth(trim(company))) ;
from customer into array laWidth
wait window laWidth[1] * 10000/96
</pre>
<p>Lo bueno de esta clase es que puede hacer mucho más que calcular el ancho de una cuerda. Es cy también determina la altura o el número de líneas que tomará una cadena en un cierto ancho (piense en establecer MEMOWIDTH en un cierto ancho y luego usar MEMLINES (), pero más rápido, más preciso y fuentes de apoyo).</p>
<p>Por ejemplo, tengo una clase de diálogo de mensaje genérico que utilizo para mostrar advertencias, errores y otros tipos de mensajes al usuario. No uso MESSAGEBOX () para esto porque mi clase admite varios botones con subtítulos personalizados. El problema es que los botones aparecen debajo de un cuadro de edición utilizado para mostrar el mensaje. Entonces, ¿cuánto espacio tengo que asignar para la altura del cuadro de edición? Si no especifico lo suficiente, el usuario debe desplazarse para ver el mensaje. Si especifico demasiado, los mensajes cortos se ven ridículos porque hay mucho espacio en blanco antes de los botones. Ahora, puedo hacer que el cuadro de edición tenga un tamaño arbitrario y usar SFGDIMeasureString para determinar la altura necesaria para el cuadro de edición para un mensaje dado, ajustando las posiciones de los botones dinámicamente. Para hacerlo, llamo al método SetSize para decirle a SFGDIMeasureString el ancho del cuadro de edición (paso un valor muy grande, como 10000, para la altura, por lo que no es un factor), luego llamo a MeasureString y uso el valor de la propiedad nHeight para la altura del cuadro de edición.</p>
<p>Estoy encontrando muchos más usos para esta clase. Espero que también te resulte útil.</p>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-82885926830260709732021-08-15T00:00:00.001-03:002021-08-15T00:00:00.207-03:00Cuadro de controles de la barra de título en el lado izquierdo<p><b><i>Artículo original: CtrlBox on Left Side<br>
<a href="http://sandstorm36.blogspot.com/2018/08/ctrlbox-on-left-side.html">http://sandstorm36.blogspot.com/2018/08/ctrlbox-on-left-side.html</a><br>
Autor: Jun Tangunan<br>
Traducido por: <a href="https://translate.google.com">Google Translate</a></i></b></p>
<hr>
<p>Dado que algunos utilizan árabe/urdu, que se ocupa de la lectura y la entrada de datos de derecha a izquierda, este truco podría resultarles útil. Que es transponer también la posición del cuadro de controles de la barra de título en el lado izquierdo.</b>
<p>Este truco es realmente bastante simple y requiere solo 3 líneas de códigos que involucran GetWindowLong y SetWindowLong. Acabo de agregar algunos códigos para mostrar cómo se ve. Vea si esto puede resultarle útil.</p>
<p>Salud!</p>
<pre>
loTest = CREATEOBJECT("Form1")
loTest.SHOW(1)
READ EVENTS
DEFINE CLASS form1 AS FORM
AUTOCENTER = .T.
CAPTION = "ControlBox en el lado izquierdo"
SHOWWINDOW = 2
ADD OBJECT label1 AS LABEL WITH ;
TOP = 20,;
LEFT = 10,;
FONTSIZE = 16,;
WIDTH = THISFORM.WIDTH -20,;
HEIGHT = THISFORM.HEIGHT - 20,;
WORDWRAP = .T.,;
CAPTION = "Esto muestra cómo invertir las posiciones de los objetos de la barra "+;
"de título, como el cuadro de control, el icono y las etiquetas, dejando el interior "+;
"del formulario en las posiciones normales de izquierda a derecha."
PROCEDURE LOAD
DECLARE INTEGER SetWindowLong IN user32 INTEGER HWND, INTEGER nIndex, INTEGER dwNewLong
DECLARE INTEGER GetWindowLong IN user32 INTEGER HWND, INTEGER nIndex
SetWindowLong(THISFORM.HWND, -20, BITOR(GetWindowLong(THISFORM.HWND, -16), 0x80000))
ENDPROC
PROCEDURE DESTROY
CLEAR EVENTS
ENDPROC
ENDDEFINE
</pre>
<p><img src="https://1.bp.blogspot.com/-crA6FeG9uNI/YQP1MFPos0I/AAAAAAAADe0/Uz7mULFJ_5wTnkKgy6ZYdPnPFWZaP62KQCLcBGAsYHQ/s0/leftcontrolbox.png"/></p>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-70702077309930417632021-08-06T02:00:00.000-03:002021-08-06T02:00:00.216-03:00Iconos de Segoe MDL2 Assets en VFP9 con GDI+<p><b>Articulo original: Segoe MDL2 Assets Icons in VFP9 with Gdi+<br>
<a href="http://vfpimaging.blogspot.com/2021/04/segoe-mdl2-assets-icons-in-vfp9-with-gdi.html">http://vfpimaging.blogspot.com/2021/04/segoe-mdl2-assets-icons-in-vfp9-with-gdi.html</a><br>
Autor: Cesar Ch.<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p>Como se discutió anteriormente en este blog, VFP no puede mostrar de forma nativa ningún carácter que tenga su CHR() mayor que 0xFF (decimal 255).</p>
<p>Hay varias fuentes muy interesantes que traen íconos muy interesantes y actualizados que podríamos usar en nuestras aplicaciones, como SEGOE MDL2 ASSETS, utilizado por Windows 10 en todas partes.</p>
<p>Los Unicodes se pueden obtener directamente a través de CharMap.EXE o en toda la web. Aquí hay un excelente punto de partida: <a href="https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font">https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font</a></p>
<p><img alt="" border="0" data-original-height="540" data-original-width="936" src="https://1.bp.blogspot.com/-pdFs9Ix1IjU/YKMRZ7EHXLI/AAAAAAAADcA/Oq0CReZBRZcVhDLT9Mrwbdzv2x27Dzv0QCLcBGAsYHQ/s0/predefined-icon.png"/></p>
<p>Los ejemplos a continuación usan GDI+ para guardar cualquier carácter deseado como una imagen, lo que nos permitirá usar esas imágenes geniales en nuestras aplicaciones. Usan las clases _GDIPLUS.VCX FFC, pero también es muy fácil de adaptar a GdiPlusX, si es necesario.</p>
<p>¡Adáptalo a tus necesidades!</p>
<p>Básicamente, una función que recupera un solo carácter Unicode y lo guarda como un archivo de imagen.</p>
<p><b>Uso:</b><br>Para obtener el icono "Imprimir":</p>
<p><img alt="" border="0" data-original-height="64" data-original-width="64" src="https://1.bp.blogspot.com/-RVuwNO-mJZY/YKMRr4KeMxI/AAAAAAAADcI/zQiEyIrkXjskQh-4-GCPCFw_x_tzzxCwACLcBGAsYHQ/s0/print.jpg"/></p>
<p><b>EXTRAIGA UN ÚNICO ICONO</b></p>
<pre>
lcFile = "Imprimir.bmp"
lcUnicode = "e749"
lcFont = "ACTIVOS SEGOE MDL2"
lnSize = 32 && píxeles
lnForeColor = RGB (0, 0, 255) && Negro
lnBackColor = RGB (255, 255, 255) && Blanco
= MakeImageFromUnicode (m.lcFile, lcUnicode, lcFont, lnSize, lnForeColor, lnBackColor)
</pre>
<p>Guarde el siguiente código como "MakeImageFromUnicode.prg":</p>
<pre>
FUNCTION MakeImageFromUnicode(tcFileName, tcUnicode, tcFontName, tnImgSize, tnForeColor, tnBackColor)
*!* tcUnicode allows up to 2 characters, that will be drawn one over the other
*!* Par1: Main Unicode
*!* Par2: Socondary Unicode
*!* Par3: Mode, where 0=Center, 1=TopLeft, 2=TopRight, 3=BottLeft, 4=BottRight
*!* Par4: Size of the 2nd character
LOCAL lnChars, lnFactor, lnFontHeight, lnFontSize, lnHeight, lnLines, lnNewFontSize, lnWidth
LOCAL lqUnicode
LOCAL lcUnicode1, lcUnicode2, lnMode, lnSize2
IF EMPTY(m.tcFileName) OR EMPTY(m.tcUnicode) OR EMPTY(m.tcFontName) OR EMPTY(m.tnImgSize)
RETURN
ENDIF
m.lnFontSize = 48
m.lnWidth = m.tnImgSize
m.lnHeight = m.tnImgSize
* Create a font object using the text object's settings.
LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont0.CREATE(m.tcFontName, m.lnFontSize, 0, 3) && 0 = Font Style
LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
m.loGfx0.CreateFromHWnd(_SCREEN.HWND)
m.lnChars = 0
m.lnLines = 0
LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.loSize = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
* lnFontWidth = loSize.W
m.lnFontHeight = m.loSize.H
m.lnFactor = m.lnFontHeight / m.tnImgSize
m.lnNewFontSize = INT(m.lnFontSize / m.lnFactor)
* Create a font object using the text object's settings.
LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style
LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
#DEFINE GdiPlus_PixelFormat_32BPPARGB 0x0026200a
m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)
LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
m.loGfx.CreateFromImage(m.loBMP)
* Setting the Backcolor
LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.tnBackColor)
m.loBackColor = 0xFFFFFFFF && White background
ELSE
m.loBackColor = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loBackColor.FoxRGB = m.tnBackColor
ENDIF
m.loGfx.CLEAR(m.loBackColor) && Background
* Create a rectangle
LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
m.loRect = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
m.loRect.Y = m.loRect.Y + 1
* Setting the Forecolor
LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.tnForeColor)
m.tnForeColor = 0 && Black
ENDIF
m.loColor = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loColor.FoxRGB = m.tnForeColor
LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)
* The character need to be drawn at the center of the image object
* Get a basic string format object
* StringAlignment enumeration
* Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
#DEFINE GDIPLUS_STRINGALIGNMENT_Near 0 && in Left-To-Right locale, this is Left
#DEFINE GDIPLUS_STRINGALIGNMENT_Center 1
#DEFINE GDIPLUS_STRINGALIGNMENT_Far 2 && in Left-To-Right locale, this is Right
LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
m.loStringFormat.CREATE()
m.loStringFormat.ALIGNMENT = GDIPLUS_STRINGALIGNMENT_Center
m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center
* Prepare the Unicode
m.lcUnicode1 = GETWORDNUM(m.tcUnicode, 1, ",")
m.lqUnicode = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode1), "4RS"), 2)
* Draw the string
m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
m.lcUnicode2 = GETWORDNUM(m.tcUnicode, 2, ",")
IF NOT EMPTY(m.lcUnicode2)
m.lqUnicode = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode2), "4RS"), 2)
m.lnMode = VAL(GETWORDNUM(m.tcUnicode, 3, ","))
m.lnSize2 = VAL(GETWORDNUM(m.tcUnicode, 4, ","))
m.lnSize2 = EVL(m.lnSize2, 100)
lnNewFontSize = CEILING(m.lnNewFontSize * (lnSize2/100))
m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style
m.loStringFormat.ALIGNMENT = GDIPLUS_STRINGALIGNMENT_Center
m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center
m.loRect.w = INT(m.lnWidth * (m.lnSize2 / 100))
m.loRect.H = INT(m.lnHeight * (m.lnSize2 / 100))
DO CASE
CASE m.lnMode = 0 && No transformation, the 2nd image will be drawn over the original
m.loRect.x = INT((m.lnWidth - m.loRect.w) / 2)
m.loRect.Y = INT((m.lnHeight - m.loRect.H) / 2)
CASE m.lnMode = 1 && Top-Left
m.loRect.x = 0
m.loRect.Y = 0
CASE m.lnMode = 2 && Top-Right
m.loRect.x = m.lnWidth - m.loRect.w
m.loRect.Y = 0
CASE m.lnMode = 3 && Bottom-Left
m.loRect.x = 0
m.loRect.Y = m.lnHeight - m.loRect.H
CASE m.lnMode = 4 && Bottom-Right
m.loRect.x = m.lnWidth - m.loRect.w
m.loRect.Y = m.lnHeight - m.loRect.H
OTHERWISE
ENDCASE
m.loRect.Y = m.loRect.Y + 1
m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
ENDIF
* Save as image
m.loBMP.SaveToFile(m.tcFileName, "image/bmp")
RETURN
ENDFUNC
</pre>
<p>La función también le permite crear nuevos íconos fusionando dos, en este caso, el ícono de Impresora y Configuración en la parte inferior derecha:</p>
<p><img alt="" border="0" data-original-height="64" data-original-width="64" src="https://1.bp.blogspot.com/-YVbOiQgdnXo/YKMR111g9_I/AAAAAAAADcM/cc1_OusOVK03zPdEwr47fLUtEEadljObgCLcBGAsYHQ/s0/Print_Settings.jpg"/></p>
<p><b>PERSONALIZA TUS ICONOS</b></p>
<pre>
* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName = "SEGOE MDL2 ASSETS"
m.lnImgSize = 64 && The desired bmp size in pixels
m.lnForeColor = RGB(0, 0, 0) && the ForeColor
m.lnBackColor = RGB(255, 255, 255) && the BackColor
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png
</pre>
<p><b>EXTRACCIÓN DE TODOS LOS ICONOS DE UNA FUENTE</b></p>
<p>La función anterior se puede adaptar para extraer todos los caracteres de una fuente determinada, utilizando un bucle.</p>
<p>Las fuentes suelen tener algunos códigos que no se utilizan, por lo que en el siguiente código utilicé un truco simple para detectar las dimensiones vacías de la fuente, y cada vez que se cumplan las mismas condiciones en el bucle, se descartará el Unicode.</b>
<p>Simplemente ejecute el siguiente código para extraer todos los íconos de cualquier fuente determinada, con el tamaño y los colores de imagen deseados. ¡Ajuste las variables iniciales para que se adapten a sus necesidades!</p>
<pre>
* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName = "SEGOE MDL2 ASSETS"
m.lnImgSize = 64 && The desired bmp SIZE IN PIXELS
m.lnForeColor = RGB(0, 0, 0) && the FORECOLOR
m.lnBackColor = RGB(255, 255, 255) && the BACKCOLOR
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png
* Let's start
LOCAL lcEmptyUnicode, lcFileName, lcHex, lcUnicode, lnChars, lnEmptyH, lnEmptyW, lnFactor
LOCAL lnFontHeight, lnFontSize, lnFontWidth, lnHeight, lnLines, lnNewFontSize, lnWidth, loSizeReal, N
m.lnFontSize = 48
m.lnWidth = m.lnImgSize
m.lnHeight = m.lnImgSize
m.lcImageType = LOWER(EVL(m.lcImageType, "bmp"))
* Create a rectangle
LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
m.loRect = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
m.loRect.Y = m.loRect.Y + 1
* The character need to be drawn at the center of the image object
* Get a basic string format object
* StringAlignment enumeration
* Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
#DEFINE GDIPLUS_STRINGALIGNMENT_Near 0 && IN LEFT-TO-RIGHT locale, THIS IS LEFT
#DEFINE GDIPLUS_STRINGALIGNMENT_Center 1
#DEFINE GDIPLUS_STRINGALIGNMENT_Far 2 && IN LEFT-TO-RIGHT locale, THIS IS RIGHT
LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
m.loStringFormat.CREATE()
m.loStringFormat.ALIGNMENT = GDIPLUS_STRINGALIGNMENT_Center
m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center
* Create a font object using the text object's settings.
LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont0.CREATE(m.lcFontName, m.lnFontSize, 0, 3) && 0 = FONT STYLE
LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
m.loGfx0.CreateFromHWnd(_SCREEN.HWND)
LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.lnChars = 0
m.lnLines = 0
m.loSize = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
m.lnFontWidth = m.loSize.W
m.lnFontHeight = m.loSize.H
m.lnFactor = m.lnFontHeight / m.lnImgSize
m.lnNewFontSize = INT(m.lnFontSize / m.lnFactor)
* Create a font object using the text object's settings.
LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont.CREATE(m.lcFontName, m.lnNewFontSize, 0, 3) && 0 = FONT STYLE
* Get the measure of the empty character, that will be used to avoid saving it several times
m.lcEmptyUnicode = CHR(0) + CHR(0)
LOCAL loSizeEmpty AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.loSizeEmpty = m.loGfx0.MeasureStringW(m.lcEmptyUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
m.lnEmptyW = m.loSizeEmpty.W
m.lnEmptyH = m.loSizeEmpty.H
LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
#DEFINE GdiPlus_PixelFormat_32BPPARGB 0x0026200a
m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)
LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
m.loGfx.CreateFromImage(m.loBMP)
* Setting the Backcolor
LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnBackColor)
m.loBackColor = 0xFFFFFFFF && White background
ELSE
m.loBackColor = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loBackColor.FoxRGB = m.lnBackColor
ENDIF
* Setting the Forecolor
LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnForeColor)
m.lnForeColor = 0 && Black
ENDIF
m.loColor = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loColor.FoxRGB = m.lnForeColor
LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)
FOR m.n = 0xe001 TO 0xf8b3 && the LAST available FOUND IN charmap
m.lcHex = TRANSFORM(m.n, "@0")
m.lcHex = STRTRAN(m.lcHex, "0x0000", "")
m.lcFileName = FORCEEXT(m.lcHex, m.lcImageType)
* Prepare the Unicode
m.lcUnicode = LEFT(BINTOC(EVALUATE("0x" + m.lcHex), "4RS"), 2)
m.loSizeReal = m.loGfx0.MeasureStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
IF m.loSizeReal.W == m.pnEmptyW AND m.loSizeReal.H == m.pnEmptyH
LOOP
ENDIF
m.loGfx.CLEAR(m.loBackColor) && Background
* Draw the string
m.loGfx.DrawStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
* Save as image
m.loBMP.SaveToFile(m.lcFileName, "image/" + m.lcImageType)
ENDFOR
* Clear GDI+ objects
m.loRect = NULL
m.loStringFormat = NULL
m.loColor = NULL
m.loBackColor = NULL
m.loBrush = NULL
m.loSize = NULL
m.loSizeEmpty = NULL
m.loGfx0 = NULL
m.loGfx = NULL
m.loBMP = NULL
m.loFont0 = NULL
m.loFont = NULL
RETURN
</pre>
<p><b>IMPORTANTE</b></p>
<p>No olvide que todas las fuentes tienen licencia. Eso significa que primero debe verificar si está autorizado a distribuir las imágenes generadas. Asegúrese de leer el EULA y ver qué puede o no puede hacer con ellos, ¿de acuerdo?</p>
<p><b>VEA TAMBIEN</b></p>
<ul>
<li><a href="https://comunidadvfp.blogspot.com/2020/11/iconos-unicode-en-botones-de-visual.html">Iconos Unicode en botones de Visual FoxPro</a></li>
<li><a href="https://comunidadvfp.blogspot.com/2020/12/controles-unicode-en-visual-foxpro-un.html">Controles Unicode en Visual FoxPro: Un nuevo enfoque más rápido y eficiente</a></li>
<li><a href="https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font#using-the-icons">Iconos de Segoe MDL2 Assets</a></li>
</ul>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-36739909070265199962021-07-30T00:30:00.001-03:002021-07-30T00:30:00.195-03:00Capturando pantallas con GdiPlus-X<p><b>Artículo original: CAPTURING SCREENS WITH GDIPLUS-X<br>
Autor: Cesar Ch.<br>
Traducido por: Luis María Guayán</b><br></p>
<hr>
<p>Capturar una pantalla con Gdiplus-X es una tarea muy fácil también.</p>
<p>Básicamente todo lo que tenemos que hacer, es llamar al método FromScreen() de la clase de Bitmap. Para facilitar esta tarea, este método brinda diferentes posibilidades.</p>
<p><font color="#FF0000"><b>IMPORTANTE:</b></font></p>
<p>Todas los ejemplos siguientes utilizan la nueva librería GDIPlus-X, que está todavía en la versión ALFA, pero es estable y confiable para hacer la mayoría de las tareas de GDI+. Descargue la última versión estable de VFPx:</p>
<p><a target="_blank" href="https://github.com/VFPX/GDIPlusX">https://github.com/VFPX/GDIPlusX</a></p>
<p><b>1 - Capturar una pantalla de un Formulario enviando el hWnd del formulario o el formulario como un objeto</b></p>
<pre>_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL loCaptureBmp AS xfcBitmap
WITH _Screen.System.Drawing
loCaptureBmp = .Bitmap.FromScreen(Thisform.HWnd)
* Could be also:
* loCaptureBmp = _screen.system.Drawing.Bitmap.FromScreen(Thisform)
loCaptureBmp.Save("c:\Captured.png", .Imaging.ImageFormat.Png)
ENDWITH</pre>
<p><b>2 - Capturar la pantalla entera</b></p>
<p>En este caso no es necesario pasar parámetros</p>
<pre>_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL loCaptureBmp AS xfcBitmap
WITH _Screen.System.Drawing
loCaptureBmp = .Bitmap.FromScreen()
loCaptureBmp.Save("c:\CapturedScreen.png", .Imaging.ImageFormat.Png)
ENDWITH</pre>
<p><b>3 - Capturar la pantalla de un formulario recortando sus bordes y título.</b></p>
<p>Para esta tarea utilizamos la función SYSMETRIC() para obtener la medida de los elementos de la pantalla, tales como la altura del título, borde superior e izquierdo. Entonces utilizamos otra posibilidad, enviando el hWnd, y las coordenadas del formulario que será capturado.</p>
<pre>_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL lnTitleHeight, lnLeftBorder, lnTopBorder
lnTitleHeight = SYSMETRIC(9)
lnLeftBorder = SYSMETRIC(3)
lnTopBorder = SYSMETRIC(4)
LOCAL loCaptureBmp AS xfcBitmap
WITH _Screen.System.Drawing
loCaptureBmp = .Bitmap.FromScreen(;
Thisform.HWnd, ;
lnLeftBorder, ;
lnTitleHeight + lnTopBorder, ;
Thisform.Width, ;
Thisform.Height)
loCaptureBmp.Save("c:\Captured.png", .Imaging.ImageFormat.Png)
ENDWITH</pre>
<p><b>4 - Capturar todos los formularios de la pantalla</b></p>
<p>Esto también es muy fácil. Sólo cree un ciclo por todas los formularios de _Screen, y capture cada uno de ellos enviando el Form.hWnd como parámetro.</p>
<pre>_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL loCaptureBmp AS xfcBitmap
LOCAL n
LOCAL loForm AS Form
n = 1
WITH _Screen.System.Drawing
FOR EACH loForm IN _Screen.Forms
loCaptureBmp = .Bitmap.FromScreen(loForm.HWnd)
loCaptureBmp.Save("c:\CapturedForm" + TRANSFORM(n) + ".png", .Imaging.ImageFormat.Png)
n = n + 1
ENDFOR
ENDWITH</pre>
<hr>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-79186604288643128362021-07-16T00:30:00.003-03:002021-07-16T00:30:00.214-03:00Información de imágenes con GDI+<p><b>Artículo original: Image Info with GdiPlus X<br>
<a target="_blank" href="http://vfpimaging.blogspot.com/2007/04/image-info-with-gdiplus-x.html">http://vfpimaging.blogspot.com/2007/04/image-info-with-gdiplus-x.html</a><br>
Autor: Cesar Ch<br>
Traducido por: Ana María Bisbé York</b></p>
<hr>
<p>Otra sencilla tarea para GDI+</p>
<p>Para obtener alguna información básica de imágenes, tal como, Ancho, Alto, Resolución y Formato de pixeles, todo lo que necesitamos es Iniciar un objeto Image de GDI+ y tomar algunos valores de las propiedades, como se muestra debajo.</p>
<p><font color="#FF0000"><b>IMPORTANTE:</b></font></p>
<p>Todos los ejemplos que se muestran a continuación utilizan la nueva biblioteca GDIPlus-X, que está aún en versión ALPHA, pero es realmente estable y fiable para hacer la gran mayoría de las tareas de GDI+. Descargue la versión más estable de VFPx:</p>
<p><a target="_blank" href="https://github.com/VFPX/GDIPlusX">https://github.com/VFPX/GDIPlusX</a></p>
<p><img alt="Image Properties" src="https://1.bp.blogspot.com/-3vpnGywf6mU/YF4Bo7tBIiI/AAAAAAAADYU/N8N4EkNP2GYSGiU07-rQUyfbsOaFJVf9wCLcBGAsYHQ/s0/ImgProperties.PNG" width="283" height="135"></p>
<pre>LOCAL lcImage
lcImage = GETPICT()
IF EMPTY(lcImage)
RETURN
ENDIF
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx")))
LOCAL loImg AS xfcImage
WITH _SCREEN.System.Drawing
loImg = .Image.FromFile(lcImage)
IF ISNULL(loImg)
MESSAGEBOX("No se pudo cargar el archivo imagen")
RETURN
ENDIF
* Obtener el nombre de formato de pixeles (PixelFormat )
LOCAL lnPix, lcPixFormat
lnPix = loImg.PixelFormat
DO CASE
CASE lnPix = .Imaging.PixelFormat.Format1bppIndexed
lcPixFormat = "1bppIndexed"
CASE lnPix = .Imaging.PixelFormat.Format4bppIndexed
lcPixFormat = "4bppIndexed"
CASE lnPix = .Imaging.PixelFormat.Format8bppIndexed
lcPixFormat = "8bppIndexed"
CASE lnPix = .Imaging.PixelFormat.Format16bppGrayScale
lcPixFormat = "16bppGrayScale"
CASE lnPix = .Imaging.PixelFormat.Format16bppRGB555
lcPixFormat = "16bppRGB555"
CASE lnPix = .Imaging.PixelFormat.Format16bppRGB565
lcPixFormat = "16bppRGB565"
CASE lnPix = .Imaging.PixelFormat.Format16bppARGB1555
lcPixFormat = "16bppARGB1555"
CASE lnPix = .Imaging.PixelFormat.Format24bppRGB
lcPixFormat = "24bppRGB"
CASE lnPix = .Imaging.PixelFormat.Format32bppRGB
lcPixFormat = "32bppRGB"
CASE lnPix = .Imaging.PixelFormat.Format32bppARGB
lcPixFormat = "32bppARGB"
CASE lnPix = .Imaging.PixelFormat.Format32bppPARGB
lcPixFormat = "32bppPARGB"
CASE lnPix = .Imaging.PixelFormat.Format48bppRGB
lcPixFormat = "48bppRGB"
CASE lnPix = .Imaging.PixelFormat.Format64bppPARGB
lcPixFormat = "64bppPARGB"
OTHERWISE
lcPixFormat = "No identificado"
ENDCASE
ENDWITH
LOCAL lcInfo
lcInfo = ;
"Ancho : " + TRANSFORM(loImg.Width) + SPACE(25) +;
"Alto : " + TRANSFORM(loImg.Height) + CHR(13) +;
"Resolución - Vertical : " + TRANSFORM(loImg.VerticalResolution) + SPACE(6) +;
"Horizontal : " + TRANSFORM(loImg.HorizontalResolution) + CHR(13) +;
"Formato de pixeles : " + lcPixFormat
MESSAGEBOX(lcInfo, 64, "Propiedades de imagen para " + JUSTFNAME(lcImage))</pre>
<hr>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-71456328563016147432021-07-02T00:00:00.065-03:002021-07-02T00:00:00.220-03:00Utilice MDots por la velocidad, no solo por la exactitud<p><b>Artículo original: Use MDots for speed, not just for correctness<br>
(<a href="http://www.tomorrowssolutionsllc.com/Articles/Use%20MDOTs%20for%20speed.pdf" target="_blank">Use MDots for speed.pdf</a>)<br>
Autor: Tamar E. Granor<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><b><i><font size=-1>Listado 1. Cuando se usan nombres en una expresión, VFP da preferencia a los nombres de campos.</font></i></b></p>
<pre> nArea = nHeight * nWidth </pre>
<p>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". </p>
<p>El Listado 2 muestra el ejemplo anterior con las variables claramente indicadas.</p>
<p><b><i><font size=-1>Listado 2. Mdots deja en claro que se refiere a una variable.</font></i></b></p>
<pre> nArea = m.nHeight * m.nWidth </pre>
<p>En este ejemplo, mdot no es necesario para nArea porque solo se puede asignar un nuevo valor a las variables mediante el signo igual.<p>
<p><b>Convenciones de nomenclatura como solución</b></p>
<p>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.</p>
<p>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.<p>
<p>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á.</p>
<p><b>MDots es más rápido</b></p>
<p>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.<p>
<p>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.</p>
<p>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.<p>
<p>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.</p>
<p>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.</p>
<p><b><i><font size=-1>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.</font></i></b></p>
<pre>
* 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
</pre>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><b>Una prueba más extensa</b></p>
<p>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.</p>
<p>La prueba, estructurada de la misma manera que la prueba anterior, se muestra en el Listado 4.</p>
<p><b><i><font size=-1>Listado 4. Este código prueba la velocidad de un programa con más de 50 referencias a variables con y sin mdots.</font></i></b></p>
<pre>
* 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
</pre>
<p>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.</p>
<p>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.</p>
<p>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).</p>
<p>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.</p>
<p><b><i><font size=-1>Listado 5. Esta ecuación calcula un "tiempo por referencia a variable" aproximado.</font></i></b></p>
<pre>Time = TestTime/((# of variables) * passes)</pre>
<p>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.</p>
<p><b>¿Qué pasa con las matrices?</b></p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><b>Algunas palabras sobre las pruebas de tiempo</b></p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><b>Sólo tiene que utilizar mdots</b></p>
<p>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.</p>
<p>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.</p>
<hr>
<p><i><b>Copyright (C) Tamar E. Granor, Tomorrow’s Solutions, LLC.</b></i></p>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com2tag:blogger.com,1999:blog-8847231238348131912.post-49203532209976211692021-06-25T00:00:00.000-03:002021-06-25T00:00:00.231-03:00Arrastrar, soltar y restringir<p><b>Artículo original: Drag, Drop and Restrict<br>
<a href="http://sandstorm36.blogspot.com/2020/06/drag-drop-and-restrict.html">http://sandstorm36.blogspot.com/2020/06/drag-drop-and-restrict.html</a><br>
Autor: Jun Tangunan<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p>Acabo de leer un problema dentro de <a href="https://www.foxite.com/">Foxite.com</a> en el que cuando un usuario mueve accidentalmente el mouse más allá de los límites del formulario, la función de arrastrar y soltar falla porque los objetos desaparecen en las áreas más allá del formulario.</p>
<p>La solución a eso es restringir los movimientos de arrastrar y soltar dentro de su formulario, o la dimensión dentro de los objetos en su formulario. Aquí hay dos ejemplos que muestran cómo lograrlo:</p>
<p>Ejemplo 1:</p>
<pre>* Restricting drag and drop within the form
Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return
Define Class TestForm As Form
AutoCenter = .T.
Width = 900
Height = 440
Caption = 'Drag, Drop & Restrict Inside Form'
Add Object container1 As Mycontainer With Top = 30, Left = 50
Enddefine
Define Class Mycontainer As Container
Height = 100
Width = 100
Procedure MouseMove
Lparameters nButton, nShift, nXCoord, nYCoord
If m.nButton = 1 And Between(m.nYCoord,0,Thisform.Height-This.Height) And ;
BETWEEN(m.nXCoord,0,Thisform.Width-This.Width)
This.Move(m.nXCoord, m.nYCoord)
Endif
Endproc
Enddefine</pre>
<p>Ejemplo 2:</p>
<pre>* Restricing within objects on form, in this case above or below the lines
Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return
Define Class TestForm As Form
AutoCenter = .T.
Width = 900
Height = 440
Caption = 'Drag, Drop & Restrict'
Add Object Shape1 As shape With Top = 30, Left = 0, Width = 900, height = 1
Add Object Shape2 As shape With Top = 200, Left = 0, Width = 900, height = 1
Add Object Command1 As MyButton With Caption='Move Me outside of the lines', Top = 35, Left = 5, width = 200, height = 30
Enddefine
Define Class MyButton As CommandButton
Procedure MouseMove
Lparameters nButton, nShift, nXCoord, nYCoord
If m.nButton = 1 AND BETWEEN(m.nYCoord,30,171)
This.Move(m.nXCoord, m.nYCoord)
WAIT WINDOW m.nYCoord nowait
Endif
Endproc
Enddefine</pre>
<p>Por si lo necesitas. Saludos!</p>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-58987014478215512392021-06-16T01:00:00.000-03:002021-06-16T01:00:00.231-03:00Rotar y voltear imágenes con GdiPlusX<p><b>Artículo original: Rotate and Flip images with GdiPlusX<br>
<a target="_blank" href="https://vfpimaging.blogspot.com/2007/06/vfppaint-flexible-drawing-and-paint.html">https://vfpimaging.blogspot.com/2007/06/vfppaint-flexible-drawing-and-paint.html</a><br>
Autor: Cesar Ch.<br>
Traducido por: Ana María Bisbé York</b></p>
<hr>
<p>Alguna gente ha estado preguntando sobre Rotar/ Voltear imágenes con GdiPlusX.</p>
<p>He aquí un código adaptado de un artículo anterior que utilizaba _GdiPlus.vcx; pero esta vez utilizando GdiPlusX.</p>
<p>Rotar y/o voltear imágenes es una tarea muy sencilla para Gdi+. Para ver los diferentes resultados posibles, cambie el valor de la constante en la variable lnEnumRotateFlip del código que se muestra a continuación.</p>
<p><b><font color="#FF0000">IMPORTANTE</font></b></p>
<p>Requiere VFP9 y GdiPlusX para ejecutarse <img border="0" src="https://1.bp.blogspot.com/-_yBi0tpInHI/X_WyIRDt02I/AAAAAAAADQA/GxALN5cHYSYseiCGpCyzgq403FtCQbiSgCLcBGAsYHQ/s0/1vfpxpoweredby_alternative.gif"></p>
<p>Asegúrese por favor de que tiene la última versión</p>
<p><a target="_blank" href="https://github.com/VFPX/GDIPlusX">https://github.com/VFPX/GDIPlusX</a></p>
<pre>* Iniciar GdiPlusX
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
LOCAL loBMP as xfcBitmap
LOCAL lnEnumRotateFlip
WITH _SCREEN.System.Drawing
loBMP = .Bitmap.FromFile(GETPICT())
* Pruebe cambiando este valor por los que se muestran debajo
lnEnumRotateFlip = .RotateFlipType.Rotate90FlipNone
loBmp.RotateFlip(lnEnumRotateFlip)
* Guardar la imagen como PNG
loBMP.Save("C:\RotateFlip.png", .Imaging.ImageFormat.Png)
ENDWITH
RUN /N explorer.EXE RotateFlip.png </pre>
<p>RotateNoneFlipNone 0<br><img border="0" src="https://lh3.googleusercontent.com/-xVA1h-1aE6s/X_WzNACPffI/AAAAAAAADQI/9YnHSdmIl0wjv-Oa6UhfCq99fZ5AjBaFQCLcBGAsYHQ/a295x107.jpg"> </p>
<p>RotateNoneFlipNone 1<br><img border="0" src="https://lh3.googleusercontent.com/-wzTzvqwe-Bs/X_Wzee2L3EI/AAAAAAAADQU/QPYHFHEDDMIFkJRAc_ru49oKyDzO3y6TQCLcBGAsYHQ/b107x295.jpg"></p>
<p>RotateNoneFlipNone 2<br><img border="0" src="https://lh3.googleusercontent.com/-ibDlQIwWauY/X_Wzyz7rphI/AAAAAAAADQg/_drhNtLg14UvxC6Ckw339FAUR5wjQOfPgCLcBGAsYHQ/c295x107.jpg"></p>
<p>RotateNoneFlipNone 3<br><img border="0" src="https://lh3.googleusercontent.com/-Xk6CFlO03aY/X_Wz-kqCrgI/AAAAAAAADQk/zVfPGDOA_OcOd-Gv-HULSiLR55o9K0M-ACLcBGAsYHQ/d107x295.jpg"></p>
<p>RotateNoneFlipNone 4<br><img border="0" src="https://lh3.googleusercontent.com/-Lqwjb_pq6bw/X_W0Iw6pgGI/AAAAAAAADQs/Pmqxszp2HUcM0y_5xp0YuNwC2bTIJSw9QCLcBGAsYHQ/e295x107.jpg"></p>
<p>RotateNoneFlipNone 5<br><img border="0" src="https://lh3.googleusercontent.com/-is78z1b00gQ/X_W0U7ylehI/AAAAAAAADQ0/irRvzpSrum4Up00M8l7im0YR16a46tt4wCLcBGAsYHQ/f107x295.jpg"></p>
<p>RotateNoneFlipNone 6<br><img border="0" src="https://lh3.googleusercontent.com/-BuiRy_mf9Zg/X_W0e1YHZXI/AAAAAAAADQ8/5dnF6WjfVrkCNCrrwMaBhn5fPEAFcerPwCLcBGAsYHQ/g295x107.jpg"></p>
<p>RotateNoneFlipNone 7<br><img border="0" src="https://lh3.googleusercontent.com/-ioSrsyfwDXE/X_W0qfPqVZI/AAAAAAAADRE/_8-r5dpELec5VU-WoB3QdFmmNxhwERbWACLcBGAsYHQ/h107x295.jpg"></p>
<hr>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-65782678545561844462021-06-03T01:00:00.000-03:002021-06-03T01:00:00.245-03:00Alcance (rango) de propiedades<p><font><b>Titulo: Ver o no ver<br>
Original: To See or Not To See<br>
Autor: Jim Booth<br>
Traducido por: Roberto Alfredo Moré</b></font></p>
<hr>
<p><font>En esta columna investigaremos la visibilidad de
propiedades y métodos. Visual FoxPro nos da tres: Pública, Protegida y Oculta.
¿Que son exactamente cada una de estas y dónde deberíamos usar una en lugar
de la otra?.
</font></p>
<p><font><b>Una pequeña clase</b>.</font></p>
<p>
Uno de los objetivos del desarrollo orientado a Objetos es aislar los datos y el
comportamiento requerido en una clase particular a esa clase misma, para reducir
el nivel de dependencia que esa clase particular tiene sobre otras clases no
relacionadas. Estas dependencias incluso pueden ser un problema dentro del mismo
árbol de la clase, afectando como la superclase (parent) y las subclases
interactúan.</p>
<p>Para examinar la visibilidad de propiedades y métodos usaremos un pequeño
ejemplo de una estructura de clases. El siguiente diagrama describe el ejemplo.</p>
<p><img alt="" border="0" src="https://1.bp.blogspot.com/-iLDs1yEcTX0/X_RrWZxSMBI/AAAAAAAADOc/kX47IT7tiDMOjd3C32CWKMZ6KxuSAbfGwCLcBGAsYHQ/s0/veronover.png"/></p>
<p>En este ejemplo hay dos definiciones de clase txtBase y txtSubClass.
TxtSubClass es una subclase de txtBase. Hay allí un formulario con una
instancia de txtSubClass, txtSubClass1, en él. La clase txtBase tiene una
propiedad, Prop1, agregada en ella y marcada como Oculta. TxtSubClass tiene
agregada una propiedad, Prop2, marcada como protegida.</p>
<p>
<font><b>Pública</b></font></p>
<p>
La primer visibilidad a discutir es el valor de Pública. Al crear una propiedad
o método que es Público causa que aquella propiedad o método sea visible, o
sea que se la pueda leer, escribir o ejecutar desde cualquier otro objeto o
clase sin limitación.</p>
<p>
<font><b>Protegida</b></font></p>
<p>
Un método o propiedad Protegida es accesible solamente por la clase que creó
dicha propiedad o método y para las subclases de esa clase. La visibilidad se
extiende a lo largo del árbol de subclases si hay varios niveles hacia abajo
(Prop2 en el diagrama de arriba es visible a txtSubClass, pero no puede ser
direccionada en el código que esté en el objeto txtSubClass1 o en el objeto
Formulario).</p>
<p>
<b>
<font color="#008000">Nota del Traductor: se ha respetado en forma literal al autor, pero
en realidad la propiedad Prop2 es visible en txtSubClass1 y se puede hacer
referencia a ella en el formulario, pero no se puede modificar. Únicamente la
clase que creó la propiedad lo puede hacer.</font></b></p>
<p>
<font><b>
Oculta</b></font></p>
<p>
Un método o propiedad Oculta es visible ÚNICAMENTE para la clase que la ha
creado. Las subclases no ven esta propiedad/método en su hoja de propiedades y
no pueden acceder a la misma (ocurre un error de VFP). (Prop1 en el diagrama de
arriba es accesible SÓLO por la clase txtBase, txtSubClass no puede direccionar
la propiedad y el nombre de la propiedad no aparece en la hoja de propiedades de
la clase txtSubClass)</p>
<p>
<font>
<b>¿Por qué debería preocuparme?</b></font></p>
<p>
Una de las características subyacentes del Diseño Orientado a Objetos es
"Ocultar la implementación de un objeto". Esto significa ocultar
completamente el mecanismo exacto que un objeto particular o clase usa para
cumplir con su responsabilidad.</p>
<p>
A menudo, un objeto puede necesitar datos internos o funcionalidad que no tiene
ningún significado fuera del alcance de dicho objeto. Si estas porpiedades y
métodos fueran visibles para el mundo exterior, existe la posibilidad de que
puedan ser accedidos por algún código que no debería estar haciendo eso.
Usando la visibilidad se puede controlar absolutamente qué puede y qué no
puede acceder a propiedades y métodos.</p>
<p>
Nota: También se puede cambiar la visibilidad de las propiedades y métodos de
Visual FoxPro usando el diálogo de Edición de Métodos y Propiedades.</p>
<p>
<font><b>Un ejemplo para Oculta.</b></font></p>
<p>
Usted está diseñando una clase formulario que será utilizada por otros
desarrolladores a través de subclases. Uno de los comportamientos de la clase
se cumpliría mejor si estuviera repartido en múltiples métodos en lugar de
uno grande. No obstante, estos sub-métodos no son útiles fuera de la clase y
un desarrollador que sobreescriba uno de ellos convertiría en no funcional a la
clase. Usted puede definir un médodo como Público, aquel que será llamado por
los desarrolladores para iniciar el comportamiento, pero los restantes
sub-métodos como Oculto de manera que los otros desarrolladores ni siquiera
sabrán que existen.</p>
<p><font>
<b>Un ejemplo para Protegida.</b></font></p>
<p>
Usted está creando una clase utilitaria que necesita algún almacenamiento
interno para su estado. Para ello crea un conjunto de propiedades. Si algún
objeto fuera de la clase modifica estas propiedades, el estado del objeto se
pierde. Usted puede definir estas propiedades como Protegidas y nada fuera del
objeto puede direccionarlas.</p>
<p>
<font><b>Usando los métodos Access y Assign.</b></font></p>
<p>
Usted podría estar pensando que usando los métodos Access y Assign se hace
esencialmente la misma cosa que con popiedades protegidas. No exactamente. Los
métodos Access y Assign permiten responder a algo que se direcciona a una
propiedad, pero no previenen el acceso. En algunas situaciones, usted podría
querer permitir a objetos externos actualizar o leer una propiedad y necesita
hacer algo basado en el valor asignado o leído. En ese caso, los médodos
Access y Assign son la vía para hacerlo.</p>
<p>No obstante, creando un método Assign que siempre descarte la asignación, es
como ayudar a una persona que se está quemando manteniéndola bajo agua por 30
minutos.</p>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-55204271668727453542021-05-28T01:30:00.000-03:002021-05-28T01:30:00.234-03:00Extender el IDE de VFP 9 con MENUHIT y MENUCONTEXT<p><b>Original en Inglés: Extending the VFP 9 IDE with MENUHIT and MENUCONTEXT<br><a target="_blank" href="https://doughennig.com/papers/Pub/200409dhen.pdf">https://doughennig.com/papers/Pub/200409dhen.pdf</a> <br>
Autor: Doug Hennig<br>Traducido por: Ana María Bisbé York</b></p>
<hr>
<p>Uno de los temas claves en VFP 9 es la extensibilidad. Puede extender el Diseñador de informes a través de eventos de informes y el motor de informes a través de la clase ReportListener. Y ahora, puede extender incluso el IDE, específicamente las llamadas de menú contextual y de sistema. Este mes, Doug Hennig muestra cómo personalizar el IDE de VFP 9 IDE de forma que nunca antes pensó posible.</p><p>VFP ofrece vías para entrar en varios aspectos del entorno de desarrollo interactivo (IDE), y lo hace mejor con cada nueva versión. FoxPro 2.x nos permitía sustituir la funcionalidad de ciertos componentes Xbase al cambiar el nombre de las aplicaciones que apuntan a variables de sistema, tales como _GENSCRN y _GENMENU. VFP 3 nos proporcionó generadores, con los que creamos formularios y clases.</p><p>VFP 6 añadió ganchos de proyecto (hooks), permitiendo recibir eventos cuando los usuarios realizan varias acciones en el Administrador de proyectos, tales como agregar o eliminar un archivo. La novedad más importante en VFP 7 fue IntelliSense, con un procedimiento conducido por datos que pudimos extender fácilmente. VFP 8 añadió Toolbox personalizable y otras mejoras del IDE.</p><p>En VFP 9, sin embargo, Microsoft ha superado las ligas anteriores sobre el IDE. Debido a que el Diseñador de informes provoca eventos de informe, podemos cambiar completamente la apariencia y comportamiento de sus diálogos. La nueva clase base ReportListener nos permite recibir eventos en la medida que se ejecuta el informe, proporcionando posibilidades como son rotación de etiquetas, formateo dinámico y generación dinámica de objetos por ejemplo, gráficos. Pronto podremos reaccionar a eventos de Windows a través de las mejoras en BINDEVENT(), esta característica no está disponible al momento de este escrito; pero tendremos mejor acceso a eventos del sistema operativo Windows, incluidos aquellos relacionados con el IDE de VFP donde están involucradas ventanas con hWnds.</p><p>(<i>Nota de la traductora:</i> Sobre las nuevas posibilidades que ofrece VFP 9.0 para control de eventos a través de BINDEVENT() ver <b>"Windows Event Binding Made Easy"</b> en: <a target="_blank" href="https://doughennig.com/papers/Pub/200501dhen.pdf">https://doughennig.com/papers/Pub/200501dhen.pdf</a>, o su versión en español <b>"Enlazar eventos de ventana"</b>, que aparece publicada en: <a target="_blank" href="https://comunidadvfp.blogspot.com/2016/04/enlazar-eventos-de-ventana.html">https://comunidadvfp.blogspot.com/2016/04/enlazar-eventos-de-ventana.html</a>)</p><p>Este artículo está enfocado, sin embargo, a penetrar en los hits de menú (esto es, cuando el usuario selecciona un elemento desde la barra de menú del sistema de VFP) y los menús contextuales del sistema (aquellos que se muestran por el clic derecho en varios lugares, tales como la ventana Propiedades o el Diseñador de Bases de datos). Comenzaré examinando cómo hacer esto, seguido de algunos ejemplos prácticos.</p><h4>MENUHIT</h4><p>Para atrapar hit de menú, creo un registro script en la tabla IntelliSense con TYPE = "S" (por "script"), ABBREV = "MENUHIT", y el código para ejecutar en el campo memo DATA. (La tabla IntelliSense se indica por la variable de sistema _FOXCODE; de forma predeterminada es FOXCODE.DBF en el directorio HOME(7)) El código debe aceptar un objeto como parámetro. Este objeto tiene varias propiedades; pero las importantes, en lo que concierne a MENUHIT, se muestran en la Tabla 1.</p>
<table border="1" cellpadding="4" width="100%" cellspacing="0" bgcolor="#FFFFFF" id="table1">
<tr> <th>Propiedad<th>Descripción</tr>
<tr> <td>UserTyped<td>Texto del pad del menú del que procede el hit.</tr>
<tr> <td>MenuItem<td>Texto del elemento del menú seleccionado por el usuario.</tr>
<tr> <td>ValueType<td>Un valor devuelto a VFP – en blanco significa que continúa con el comportamiento predeterminado y "V" o "L" significan evitar el comportamiento predeterminado (similar a utilizar NODEFAULT en código de clases).</tr>
</table>
<h5>Tabla 1. Las propiedades del parámetro de objeto pasado al script MENUHIT proporcionan información sobre el hit de menú.</h5><p>He aquí un ejemplo sencillo (tomado de SimpleMenuHit.PRG, incluido en el archivo Download de este mes):</p><pre>text to lcCode noshow
lparameters toParameter
wait window 'Pad: ' + toParameter.UserTyped + ;
chr(13) + 'Bar: ' + toParameter.MenuItem
endtext
delete from (_foxcode) where TYPE = 'S' and ;
ABBREV = 'MENUHIT'
insert into (_foxcode) ;
(TYPE, ABBREV, DATA) ;
values ;
('S', 'MENUHIT', lcCode)</pre><p>Esto muestra simplemente el texto del pad y la barra para el elemento de menú seleccionado y ejecuta el comportamiento predeterminado para ese elemento. Esto es bueno para pruebas; pero en código "real" probablemente desee deshabilitar el comportamiento predeterminado y sustituirlo por el suyo propio. En este caso, establezca la propiedad ValueType del objeto parámetro a "V" o "L". (En caso de que se sorprenda porqué hay dos valores posibles, es debido a que esos valores se emplean para otros propósitos por el controlador de IntelliSense y el equipo de VFP decidió utilizar el mismo mecanismo para MENUHIT. No tiene importancia cuál de ello se utilice.)</p><p>He aquí un ejemplo, tomado de DisableExit.PRG, que inhabilita la función Exit en el menú File (Archivo) (es posible que desee utilizarlo para hacerle la broma del tonto de Abril a un compañero de trabajo) (<i>Nota de la traductora:</i> April fool Persona a la que burlan tradicionalmente el 1ro. de Abril en los Estados Unidos.)</p><pre>text to lcCode noshow
lparameters toParameter
if toParameter.UserTyped = 'File' and ;
toParameter.MenuItem = 'Exit'
toParameter.ValueType = 'V'
endif toParameter.UserTyped = 'File' ...
endtext
delete from (_foxcode) where TYPE = 'S' and ;
ABBREV = 'MENUHIT'
insert into (_foxcode) ;
(TYPE, ABBREV, DATA) ;
values ;
('S', 'MENUHIT', lcCode)</pre><p>Mientras esto parece correcto, al seleccionar Exit desde el menú File, VFP aun existe. Además de establecer ValueType igual a "V" o "L", algunas funciones requieren que el código MENUHIT devuelva .T. para evitar el comportamiento predeterminado. Añade RETURN .T. justo antes del ENDIF para evitar que Exit cierre VFP.</p><p>Otro aspecto: ¿Qué pasa si está empleando una versión local de VFP, por ejemplo la versión en Alemán? En ese caso, al comprobar que "File" y "Exit" no van a funcionar porque estos textos no aparecen en el menú. Esto es algo que tiene que tener en cuenta si distribuye sus códigos script para MENUHIT para otros desarrolladores.</p><h5>Enseguida hablamos del MENUHITs</h5><p>Mientras estamos en el tema de la distribución de scripts de MENUHIT a otros, ¿Qué ocurre si tiene scripts que hacen algo realmente bueno y otros que hacen otras cosas y quiere utilizar ambos? El problema es que puede haber un solo registro MENUHIT (si existe más de uno, VFP va a utilizar sólo el primero que encuentre en la tabla IntelliSense). Por esta razón, pienso que lo mejor es tener el registro MENUHIT que delegue en otra cosa en lugar de configurar la personalización del IDE directamente.</p><p>La manera más sencilla de hacer esto es agregar registros adicionales a la tabla IntelliSense y hacer que el registro MENUHIT los utilice para configurar las tareas actuales. Aunque esto es arbitrario, me parece que un registro con TYPE = "M" servirá para esto. Debido a que "M" se interpreta como "menú" y no hay un tipo de registro actualmente utilizado en la tabla. Por ejemplo, para controlar la función Exit, puede agregar un registro con TYPE = "M", ABBREV = "Exit", y el código a ejecutar en DATA.</p><p>Para hacer este trabajo, necesitamos un registro estándar MENUHIT. El código para este registro se muestra en la tabla para el registro con TYPE = "M" y ABBREV se iguala al texto de la barra que seleccione el usuario, y si existe, ejecuta el código en el campo memo DATA. He aquí el código para controlarlo:</p><pre>lparameters toParameter
local lnSelect, ;
lcCode, ;
llReturn
try
lnSelect = select()
select 0
use (_foxcode) again shared order 1
if seek('M' + padr(upper(toParameter.MenuItem), ;
len(ABBREV)))
lcCode = DATA
endif seek('M' ...
use
select (lnSelect)
if not empty(lcCode)
llReturn = execscript(lcCode, toParameter)
if llReturn
toParameter.ValueType = 'V'
endif llReturn
endif not empty(lcCode)
catch
endtry
return llReturn</pre><p>Ejecute StandardMenuHit.PRG para instalar este código en la tabla IntelliSense. Existen algunas cosas interesantes sobre este código. Primero, yo utilizo normalmente ORDER <nombre de etiqueta> en lugar de ORDER <n> para establecer el orden en una tabla. Sin embargo, la tabla IntelliSense es inusual: Si abre la tabla y hace ATAGINFO() para recuperar información sobre los índices de la tabla, verá que existen dos etiquetas, ambas marcadas como Primary y ambas sin nombre de etiquetas. Entonces es necesario emplear ORDER 1 u ORDER 2 para establecer el orden para esta tabla.</p><p>Lo segundo a tener en cuenta es que este código está envuelto en una estructura TRY para prevenir cualquier error, tales como problemas con la apertura de tablas o errores que puedan existir en el código en otros registros.</p><p>El tercer aspecto es que este código no va a verificar el texto del pad que corresponde al elemento del menú seleccionado, sino solamente el texto de la barra de menú. Esto es debido a que he decidido hacer un SEEK por razones de rendimiento y la etiqueta utilizada es UPPER(TYPE + ABBREV). Teniendo en cuenta que es una tabla pequeña, podría probablemente conseguirlo colocando el texto para el pad en una columna EXPANDED y usando LOCATE FOR TYPE = "M" AND ABBREV = toParameter.MenuItem AND EXPANDED = toParameter.UserTyped para asegurarse de que ha sido encontrado el registro exacto.</p><p>Como conclusión de estos aspectos, Microsoft no ha decidido si iba a existir algo como un registro estándar MENUHIT en la tabla IntelliSense de forma predeterminada. Si no, van a proporcionar una forma sencilla para agregar como un registro o a través de los ejemplos Solution. (Los ejemplos Solution son ejemplos que muestran varias de las características de VFP, y a los que se accede fácilmente desde el Administrador de Panel de tareas).</p><p>Una característica final de MENUHIT. Si el código en el campo memo DATA tiene cualquier error de compilación, no se obtendrán mensajes de error de VFP – VFP simplemente ignora el código y aplica el comportamiento predeterminado. Esto pudiera ser molesto, por supuesto, ya que puede no estar totalmente seguro de por qué su código falla al hacer lo que espera.</p><h5>¿Para qué nos sirve esto?</h5><p>Muy bien, podemos entrar en un elemento del menú de VFP. ¿Para qué podemos utilizarlo?</p><p>Lo primero que vino a mi mente fue sustituir los diálogos para New Property (Nueva propiedad) y New Method (Nuevo método). Ya que ahora tenemos la posibilidad de preservar la capitalización de letras para propiedades y métodos de usuarios (vea mi artículo de junio 2004 en la revista FoxTalk 2.0, "MemberData and Custom Property Editors"), En lugar de tener el control de VFP lo introduzco en los diálogos New Nueva propiedad y Nuevo método. De esta forma evito pasos extras: saltar al editor de MemberData y cambiar allí el tipo de letra. Además, siempre me molestó tener que hacer clic en el botón Add (Agregar) para agregar una nueva propiedad o método y luego hacer clic en el botón Close (Cerrar) para cerrar el diálogo. Frecuentemente agrego solamente una propiedad o método cada vez, Deseaba tener un botón que hiciera ambas cosas, Agregar la propiedad y Cerrar el diálogo.</p><p>(<i>Nota de la traductora:</i>. El artículo mencionado por el autor fue traducido al español y aparece publicado bajo el título <b>"MemberData y los editores de propiedades de usuario"</b> en: <a target="_blank" href="https://comunidadvfp.blogspot.com/2017/06/memberdata-y-los-editores-de.html">https://comunidadvfp.blogspot.com/2017/06/memberdata-y-los-editores-de.html</a>)</p><p>Debido a que el enfoque dado por MENUHIT nos permite ahora atrapar los elementos de menú New Property (Nueva propiedad) y New Method (Nuevo método), para los menús Form (Formulario) y Class (Clase), debíamos ser capaces de crear un diálogo que se comporte exactamente como deseamos. La Figura 1 muestra un diálogo con las siguientes características:</p><ul> <li>Actualiza automáticamente la propiedad _MemberData (agregando la propiedad si es necesario), entonces la capitalización para cada letra será utilizada (incluso para los métodos Access y Assign si fuesen creados también) y el nuevo miembro se mostrará en la ficha Favorites (Favoritos) si la opción se selecciona en este diálogo.</li> <li>No es una ventana modal. Lo que significa que se puede mantener abierta, agregar otras propiedades o métodos, activar otras ventanas, retornar a esta, y agregar nuevos miembros.</li> <li>Se puede anclar la ventana. Intente anclarla con la ventana Propiedades. Es fantástico !!</li> <li>Se puede redimensionar y se almacena su tamaño y posición en su archivo de recursos (FOXUSER).</li> <li>Tiene un botón Add & Close (Agregar y cerrar) para realizar ambas tareas en un solo clic. </li> <li>El valor predeterminado de la propiedad se establece automáticamente al valor basado en el tipo de dato de la propiedad (si utiliza notación húngara). Por ejemplo lTest debe ser lógica, entonces .F. es su valor predeterminado. Para nTest su valor predeterminado es 0.</li> <li>Oculta en lugar de inhabilitar los controles no aplicables. Debido a que este diálogo se emplea para ambos elementos de menú New Property (Nueva propiedad) y New Method (Nuevo método), en ambos menús Form (Formulario) y Class (Clase), algunas opciones pueden no ser aplicables para una instancia dada.</li> <li>No permite nombres no válidos en el momento de ser introducidos en lugar de comprobarlo al hacer Clic en los botones Add (Agregar) o Add & Close (Agregar y cerrar).</li> <li>Los botones Add (Agregar) se activan sólo si se introduce un nombre.</li></ul><p><img border="0" src="https://lh3.googleusercontent.com/-rG94kEJFGqY/X_W94qKDi9I/AAAAAAAADRQ/HbLwElljt3oVwbrYXJ1PZtW_EkzeQ0sfwCLcBGAsYHQ/fig1.png"></p><h5>Figura 1</h5><p>No veremos el código que contiene NewPropertyDialog.APP. No es complicado, en su núcleo llama a los métodos AddProperty y WriteMethod del objeto que está siendo editado en el Diseñador de clase o formulario para agregar una nueva propiedad o método. Estos dos métodos aceptan dos nuevos parámetros en VFP 9: la visibilidad (1 para Public, 2 para Protected, ó 3 para Hidden) y la descripción de la nueva propiedad o método.</p><p>Para utilizar los diálogos sustituidos, haga DO NewPropertyDialog.APP para registrarlo en la tabla IntelliSense. Agrega el registro MENUHIT que ha sido descrito antes y dos registros (uno para "New Property" y uno para "New Method") que tienen el mismo código en DATA que utiliza la clase NewPropertyDialog. En ese código, "<path>" representa la ruta (path) a NewPropertyDialog.APP en su sistema (la APP agrega automáticamente la ruta correcta al ejecutarla).</p><pre>lparameters toParameter
local llReturn, ;
llMethod, ;
llClass
try
llMethod = toParameter.MenuItem = 'New Method'
llClass = toParameter.UserTyped = 'Class'
release _oNewProperty
public _oNewProperty
_oNewProperty = newobject('NewPropertyDialog', ;
'NewProperty.vcx', ;
'<path>NewPropertyDialog.app', llMethod, llClass)
_oNewProperty.Show()
llReturn = .T.
catch
endtry
return llReturn</pre><p>Ahora, puede seleccionar New Property o New Method desde los menús Form o Class, tendrá el nuevo menú en lugar del nativo.</p><h4>MENUCONTEXT</h4><p>Además de entrar en la selección desde el menú de sistema de VFP puede además atrapar menús contextuales como el que se muestra cuando se hace clic derecho en la ventana Propiedades. Esto se ha hecho utilizando el mismo mecanismo que MENUHIT, excepto el campo ABBREV en la tabla IntelliSense contiene "MENUCONTEXT” en lugar de "MENUHIT".</p><p>Como con MENUHIT, el código para el registro MENUCONTEXT debe aceptar un parámetro de objeto. En este caso, existen tres propiedades de interés: Item, una matriz de los textos mostrados en el menú contextual; ValueType, que tiene el mismo propósito y así lo hace para MENUHIT; y el ID para el menú contextual.</p><p>Algunos de los valores para MenuItem son "24446" para el menú contextual en la ventana Comandos, "24447" para el menú contextual del Administrador de proyectos, y "24456" para el menú contextual de la ventana Propiedades. ¿Cómo he descubierto estos valores? He creado un registro MENUCONTEXT con el código que mostró simplemente el valor para toParameter.MenuItem, y luego hice clic derecho en varios lugares. Como con MENUHIT, debe establecer ValueType como "V" o "L" y devuelve .T. para evitar comportamiento nativo (mostrar el menú contextual).</p><p>MENUCONTEXT no es tan fácil de usar cómo MENUHIT, por varias razones. Primero, al cambiar el contenido de la matriz Ítems, que no cambia mostrado por el menú. Por ejemplo, este código no parece tener ningún efecto sobre el menú contextual del todo:</p><pre>lparameters toParameter
toParameter.Items[1] = 'My Bar'</pre><p>De forma similar, el siguiente código no resulta en una nueva barra en el menú:</p><pre>lparameters toParameter
lnItems = alen(toParameter.Items) + 1
dimension toParameter.Items[lnItems]
toParameter.Items[lnItems] = 'My Bar'</pre><p>Segundo, no hay forma de cambiar lo que ocurre cuando es seleccionada una barra. Por ejemplo, puede desear que la barra Propiedades en el acceso directo a la ventana Comando para mostrar su diálogo en lugar del nativo. El problema es que ninguna de las propiedades del objeto parámetro especifica qué hacer cuándo es escogido un elemento del menú, que es generado dentro del menú como tal.</p><p>Entonces, parece que la única forma que podemos cambiar el menú es utilizar nuestro propio menú contextual y que evitan el comportamiento nativo. Sin embargo, hay una complicación con este proceder: Tal y como tiene que sustituir el menú entero con el suyo propio, ¿cómo le dice a VFP que utilice el comportamiento nativo cuando algunos de sus elementos de menú son seleccionados? En el momento que se realiza este escrito no parece haber ninguna forma para hacer esto, entonces yo sospecho que MENUCONTEXT será utilizado sólo moderadamente.</p><h5>Sustituir el menú contextual del Administrador de proyecto</h5><p>Incluso con estas características, tratemos de hacer un ejemplo. En mi artículo de Mayo 2000 de la revista FoxTalk, "Zip it, Zip it Good," presenté una utilidad que compactaba todos los archivos basados en tablas en un proyecto (tales como archivos VCX, SCX, y FRX) y otras utilidades que cierran (zips) todos los archivos referenciados en un archivo ZIP. En aquel artículo, se llamaban las utilidades desde una barra de herramientas mostrada por un gancho al proyecto. Vamos ahora a colocarlos en un menú contextual que lo hará disponible para cualquier objeto.</p><p>MENUCONTEXT tiene el mismo conflicto potencial que con MENUHIT, entonces, vamos a utilizar la misma solución: un registro "standard" que delegue simplemente en otro registro para un menú contextual particular. De hecho, el código para este registro estándar es exactamente el mismo que para el registro MENUHIT (ejecuta StandardMenuContext.PRG para crear el registro MENUCONTEXT. Sin embargo, en lugar de colocar el texto de la barra en el campo ABBREV para el registro controlador, colocaremos el ID del menú. (Puede ser que quiera el propósito del menú, como "ventana de comandos", en el campo EXPANDED, para hacer obvio el objetivo de ese registro.)</p><p>Después de crear el registro MENUCONTEXT, he creado un registro con TYPE = "M", ABBREV = "24447" (el ID para el menú contextual del Administrador de proyecto), y el siguiente código en DATA:</p><pre>lparameters toParameter
local loMenu
loMenu = newobject('_ShortcutMenu', ;
home() + 'ffc\_menu.vcx')
loMenu.AddMenuBar('\<Pack Project', ;
'do PACKPROJ with _vfp.ActiveProject.Name')
loMenu.AddMenuBar('\<Zip Project', ;
'do ZIPPROJ with _vfp.ActiveProject.Name')
loMenu.AddMenuBar('Project \<Info...', ;
'keyboard "{CTRL+J}" plain')
loMenu.ShowMenu()</pre><p>Este código utiliza las FFC (FoxPro Foundation Classes) la clase _ShortcutMenu para proporcionar el menú contextual. He descrito esta clase en mi artículo de Febrero 1999 de la revista FoxTalk, "A Last Look at the FFC." ("Una última mirada a las FFC."). Ejecute ProjectMenu.PRG para crear este registro en la tabla IntelliSense.</p><p>Al hacer clic derecho en el Administrador de proyecto, el nuevo menú personalizado muestra tres barras (vea la Figura 2): Pack Project, que llama a PACKPROJ.PRG que el nombre del archivo de proyecto actual; Zip Project, que llama a ZIPPROJ.PRG con el nombre del archivo de proyecto actual; y Project Info, que utiliza el comando KEYBOARD para invocar el diálogo Project Info desde el menú Project (Proyecto). (La función anterior muestra que puede reproducir la funcionalidad de una barra en el menú contextual nativo as long as existe una función de sistema que lo llama.)</p><p><img border="0" src="https://lh3.googleusercontent.com/-PfxOdlTeGig/X_W-FGVCqWI/AAAAAAAADRU/zch2v4l5xqQJbq8dh8VPAfqF2bgifnnuQCLcBGAsYHQ/fig2.png"></p><h5>Figura 2</h5><h4>Resumen</h4><p>Las nuevas ventajas MENUHIT y MENUCONTEXT nos permiten entrar en el IDE de VFP mucho más que antes. Además para utilizar los ejemplos que he presentado en este artículo, puedo ver reemplazos para los diálogos Edit Property/Method y las funciones New, Import, y Export en el menú File. Estoy seguro de que puede pensar que las funciones del IDE que desea implementar de forma diferente en VFP 9; por favor, háganme saber que ideas tiene para esta novedad tan útil.</p><h4>Download</h4><p><a target="_blank" href=https://doughennig.com/Papers/Pub/200409dhensc.zip">https://doughennig.com/Papers/Pub/200409dhensc.zip</a></p>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-21487721614173831592021-05-17T00:00:00.000-03:002021-05-17T00:00:00.201-03:00Dígitos significativos en Visual FoxPro<p><b>
Artículo original: Significant digits in Visual FoxPro<br>
<a href="http://www.foxpert.com/foxpro/knowlbits/files/knowlbits_201102_1.html">http://www.foxpert.com/foxpro/knowlbits/files/knowlbits_201102_1.html</a><br>
Autor: Christof Wollenhaupt<br>
Traductor: Luis María Guayán
</p></b>
<h>
<p><i>Steve Bodnar</i> hizo una pregunta interesante en <i>Twitter</i>: ¿Por qué obtiene resultados diferentes para las siguientes dos líneas?</p>
<pre>
? Round(512.9250000000,2) && 512.92
? Round(512.925000000,2) && 512.93
</pre>
<p>Nuestra educación matemática nos dice que no debería importar con cuántos ceros se rellene el número. El resultado tiene que ser el mismo. Las computadoras comúnmente no comparten la opinión de los usuarios, ni usan la misma base que los humanos. Vivimos en un mundo decimal (a excepción de unas pocas medidas no métricas, algunos países simplemente se niegan a darse por vencidos), las computadoras viven en un mundo binario. Lo que es un número casi perfecto para usar, 512,925, es de hecho 512,924999999999 para la computadora.</p>
<p>Visual FoxPro, como la mayoría de los otros lenguajes, usa números de punto flotante para expresar fracciones. Esos números no tienen una precisión fija, a diferencia de los números decimales que usamos. Trabajar con números de punto flotante de 15 dígitos sería engorroso rápidamente, ni coinciden con lo que usamos en Visual FoxPro en realidad. Nadie quiere una factura con la cantidad indicada hasta el millonésimo de centavo.</p>
<p>Visual FoxPro encontró varias soluciones a lo que es básicamente un problema de presentación. Por un lado, tenemos comandos oscuros que casi nadie puede explicar correctamente, como <code>SET DECIMALS</code> y <code>SET FIXED</code>.</p>
<p>Para simplificar las cosas, hasta el punto de que ya no vemos lo que sucede detrás de escena, Visual FoxPro realiza un seguimiento de la cantidad de dígitos que tiene cada valor flotante. Esta es una información adicional que no forma parte del valor numérico en sí.</p>
<p>512,9250000000 y 512,925000000 no son los mismos valores. Ambos tienen el mismo valor de coma flotante 512,924999999999. El primero tiene una precisión de 13, el segundo tiene una precisión de 12. Con un número como este, es bastante fácil averiguar cuántos lugares significativos tiene el número. Solo cuente sus dígitos. Pero, ¿qué hay de usar este número en cualquier tipo de cálculo? ¿Cómo sabe Visual FoxPro cuántos lugares digitales tendrá el resultado? Cuando se ejecuta el siguiente código:</p>
<pre>
? 512.925000000 font "Courier new"
? 512.925000000+0 font "Courier new"
? 512.925000000*1 font "Courier new"
</pre>
<p>Ud. notará que el número sigue siendo el mismo (afortunadamente, de lo contrario tendríamos cosas más serias de las que preocuparnos), sin embargo, sigue desplazándose hacia la derecha. ¿Porque es eso?</p>
<p>Cuando Visual FoxPro imprime un valor en la pantalla, lo hace de acuerdo con la precisión almacenada con el valor. No hay forma de pedir directamente a Visual FoxPro esos valores.</p>
<p>Alguien, probablemente un aprendiz deficiente hace 25 años, especificó para cada operación por cuántos dígitos el resultado podría variar de los valores que participan en la operación. El simple hecho de imprimir el número no tiene ningún efecto, por lo tanto, se imprime alineado con el borde izquierdo. Cuando agrega dos números, nunca puede agregar más de un dígito a la izquierda. 9 + 9 es 18. Por lo tanto, la operación de suma deja espacio para un desbordamiento potencial y agrega un dígito. Visual FoxPro examina ambos lados del separador decimal por separado. Agregar un número 4,1 y 1,4 da como resultado un número 5,4, que es el número máximo de dígitos en cada lado más uno para el desbordamiento. Las multiplicaciones combinan el número de dígitos:</p>
<pre>
? 1.11*1.1 font "Courier new"
? 1.11*1.10 font "Courier new"
? 01.11*01.10 font "Courier new"
</pre>
<p>Las multiplicaciones también dejan espacio para el carácter del signo. En la primera línea multiplicamos un valor de 1,2 y 1,1. El resultado tiene un total de 6 dígitos significativos: uno para el signo, dos para los números enteros y tres para la fracción. Al agregar ceros, cambia la precisión de la expresión. Sumar cero a la fracción da como resultado cuatro lugares decimales (2 + 2), pero no mueve el número a la derecha. Sin embargo, agregar ceros al número entero da como resultado que la parte del número entero ahora sea cuatro en lugar de dos dígitos. El número entero se desplaza dos caracteres a la derecha.</p>
<p>Teniendo esto en cuenta, resulta fácil comprender el problema del redondeo. La última pieza que necesita saber, como señaló amablemente <i>Jody Meyer</i> en <i>Twitter</i>, es que Visual FoxPro usa una precisión interna de 15 dígitos. <code>ROUND()</code>, como cualquier otra operación, cambia el número de lugares significativos de su resultado. Si pasa un valor entero, obtiene el mismo valor más dos lugares decimales.</p>
<p>Comencemos con el segundo valor del ejemplo de Steve para mostrar cómo funciona Visual FoxPro. El operando tiene 12 posiciones decimales. <code>ROUND()</code> agregará otros 2. El resultado será un valor de punto flotante con 14 lugares significativos. Cada valor que participa en la operación se redondea a esa precisión. 512,924999999999, el valor almacenado con un máximo de 15 lugares significativos, se redondea a 512,92500000000, un valor con 14 dígitos. Por lo tanto, el resultado de la operación <code>ROUND()</code> es 512,93.</p>
<p>La primera línea, aunque tiene 13 dígitos. Sumar los dos dígitos de las operaciones <code>ROUND()</code> da como resultado un enorme número de 15 dígitos. Esto maximiza el número de dígitos que admite Visual FoxPro. Siguiendo el mismo patrón, el valor de 512,924999999999 se redondea a 15 dígitos. En otras palabras, permanece sin cambios. El tercer lugar decimal es ahora un cuatro en lugar de un cinco. Como resultado, Visual FoxPro redondea hacia abajo a 512,92.</p>
<hr>
Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com0tag:blogger.com,1999:blog-8847231238348131912.post-48534650370930006532021-05-08T02:00:00.000-03:002021-05-08T02:00:00.224-03:00Lecciones de malabarismo<p>Con tanto tiempo en casa, tengo que mantenerme ocupado.</p>
<p>Esto es totalmente inútil a menos que quieras aprender a hacer malabares.</p>
<p>Cuando estaba en la escuela, aprendí a hacer malabares, un pariente me enseñó. Aquí están las 3 lecciones para practicar.</p>
<p><i><b>Tony Vignone</b><br>FL, USA</i></p>
<p><img alt="" border="0" data-original-height="410" data-original-width="479" src="https://1.bp.blogspot.com/-vE0VtjCPXIg/YHIaV4mfw6I/AAAAAAAADZw/Rl4W2TUJsCshBxBFoFQ_iM7ET3OmbbnwACLcBGAsYHQ/s0/malabares.png"/></p>
<Pre>
PUBLIC oForm
oForm = CREATEOBJECT('form1')
oForm.SHOW(1)
DEFINE CLASS form1 AS FORM
HEIGHT = 369
WIDTH = 461
DOCREATE = .T.
AUTOCENTER = .T.
CAPTION = "Lecciones de malabarismo"
ad = 0
NAME = "form1"
ADD OBJECT b1 AS SHAPE WITH ;
TOP = 252, ;
LEFT = 36, ;
HEIGHT = 24, ;
WIDTH = 24, ;
CURVATURE = 99, ;
BACKCOLOR = RGB(0,128,255), ;
NAME = "b1"
ADD OBJECT b2 AS SHAPE WITH ;
TOP = 252, ;
LEFT = 372, ;
HEIGHT = 24, ;
WIDTH = 24, ;
CURVATURE = 99, ;
BACKCOLOR = RGB(255,0,0), ;
NAME = "b2"
ADD OBJECT b3 AS SHAPE WITH ;
TOP = 252, ;
LEFT = 408, ;
HEIGHT = 24, ;
WIDTH = 24, ;
CURVATURE = 99, ;
BACKCOLOR = RGB(128,255,128), ;
NAME = "b3"
ADD OBJECT Line1 AS LINE WITH ;
BORDERWIDTH = 3, ;
HEIGHT = 0, ;
LEFT = 12, ;
TOP = 276, ;
WIDTH = 444, ;
NAME = "Line1"
ADD OBJECT Command1 AS COMMANDBUTTON WITH ;
TOP = 318, ;
LEFT = 108, ;
HEIGHT = 25, ;
WIDTH = 60, ;
FONTSIZE = 9, ;
CAPTION = "Malabar 1", ;
TABSTOP = .F., ;
BACKCOLOR = RGB(255,179,179), ;
NAME = "Command1"
ADD OBJECT Command2 AS COMMANDBUTTON WITH ;
TOP = 318, ;
LEFT = 192, ;
HEIGHT = 25, ;
WIDTH = 60, ;
CAPTION = "Malabar 2", ;
TABSTOP = .F., ;
BACKCOLOR = RGB(255,179,179), ;
NAME = "Command2"
ADD OBJECT Command3 AS COMMANDBUTTON WITH ;
TOP = 318, ;
LEFT = 276, ;
HEIGHT = 25, ;
WIDTH = 60, ;
CAPTION = "Malabar 3", ;
TABSTOP = .F., ;
BACKCOLOR = RGB(255,179,179), ;
NAME = "Command3"
ADD OBJECT Spinner1 AS SPINNER WITH ;
ALIGNMENT = 2, ;
HEIGHT = 25, ;
KEYBOARDHIGHVALUE = 6, ;
KEYBOARDLOWVALUE = 0, ;
LEFT = 420, ;
SPINNERHIGHVALUE = 6.00, ;
SPINNERLOWVALUE = 0.00, ;
TABSTOP = .F., ;
TOP = 318, ;
WIDTH = 37, ;
CONTROLSOURCE = "Thisform.ad", ;
NAME = "Spinner1"
ADD OBJECT Label2 AS LABEL WITH ;
FONTNAME = "Tahoma", ;
FONTSIZE = 8, ;
ALIGNMENT = 2, ;
CAPTION = "Para con X, FIN, Barra Espaciadora", ;
HEIGHT = 13, ;
LEFT = 154, ;
TOP = 348, ;
WIDTH = 169, ;
NAME = "Label2"
ADD OBJECT Label1 AS LABEL WITH ;
FONTNAME = "Tahoma", ;
FONTSIZE = 8, ;
WORDWRAP = .T., ;
ALIGNMENT = 2, ;
CAPTION = "Ajustar tiro", ;
HEIGHT = 31, ;
LEFT = 372, ;
TOP = 318, ;
WIDTH = 40, ;
NAME = "Label1"
ADD OBJECT Label3 AS LABEL WITH ;
FONTSIZE = 8, ;
WORDWRAP = .T., ;
ALIGNMENT = 2, ;
BACKSTYLE = 0, ;
CAPTION = "Practica en este orden", ;
HEIGHT = 48, ;
LEFT = 36, ;
TOP = 309, ;
WIDTH = 48, ;
NAME = "Label3"
ADD OBJECT Label4 AS LABEL WITH ;
FONTNAME = "Wingdings 3", ;
FONTSIZE = 14, ;
CAPTION = "u", ;
HEIGHT = 25, ;
LEFT = 84, ;
TOP = 321, ;
WIDTH = 18, ;
NAME = "Label4"
ADD OBJECT LblMsg AS LABEL WITH ;
FONTSIZE = 8, ;
ALIGNMENT = 2, ;
BACKSTYLE = 0, ;
CAPTION = "No mires tus manos. Mira hacia adelante.", ;
HEIGHT = 16, ;
LEFT = 96, ;
TOP = 301, ;
WIDTH = 252, ;
NAME = "lblMsg"
ADD OBJECT Image1 AS IMAGE WITH ;
HEIGHT = 25, ;
LEFT = 24, ;
TOP = 278, ;
WIDTH = 61, ;
NAME = "Image1"
ADD OBJECT Image2 AS IMAGE WITH ;
HEIGHT = 25, ;
LEFT = 382, ;
TOP = 278, ;
WIDTH = 61, ;
NAME = "Image2"
PROCEDURE INIT
DECLARE Sleep IN kernel32 INTEGER dwMilliseconds
WITH THISFORM
PUBLIC nspeed
nspeed = 100
PUBLIC x1,x2,x3,y1,y2,y3
PUBLIC esclist
PUBLIC i1,i2,i3
PUBLIC w,b1p,b2p,b3p
PUBLIC side1,side2,side3,msg1,msg2
w = 30 &&30*12
esclist = "6,120,32"
side1 = "L"
side2 = "R"
side3 = "R"
msg1 = "No mires tus manos. Mira hacia adelante."
msg2 = "Tira debajo de uno entrante para que no choquen"
SET CURSOR OFF
.image1.PICTUREVAL = .handpicL()
.image2.PICTUREVAL = .handpicR()
ENDWITH
ENDPROC
PROCEDURE DESTROY
SET CURSOR ON
ENDPROC
PROCEDURE Command1.CLICK
WITH THISFORM
nspeed = 70
IF .b1.TOP < 252
.RESET()
sleep(1000)
ELSE
.RESET()
ENDIF
LOCAL i
x1 = .ad
FOR i=1 TO 30*8
.throw1()
IF INLIST(INKEY(),&esclist) THEN && END
.RESET()
EXIT
ENDIF
NEXT
ENDWITH
ENDPROC
PROCEDURE Command2.CLICK
WITH THISFORM
nspeed = 50
IF .b1.TOP < 252
.RESET()
sleep(1000)
ELSE
.RESET()
ENDIF
.lblMsg.CAPTION = msg2
LOCAL i
x1 = 0
b1p = 0
DO WHILE b1p = 0
.throw1(0.6)
ENDDO
x2 = w-.ad
FOR i=1 TO 30*8
.throw2()
.throw1()
IF INLIST(INKEY(),&esclist) THEN && END
.RESET()
EXIT
ENDIF
NEXT
ENDWITH
ENDPROC
PROCEDURE Command3.CLICK
WITH THISFORM
nspeed = 25
IF .b1.TOP < 252
.RESET()
sleep(1000)
ELSE
.RESET()
ENDIF
.lblMsg.CAPTION = msg2
LOCAL i
x1 = 0
b1p = 0
DO WHILE b1p = 0
.throw1(.2)
ENDDO
x2 = w
b1p = 0
DO WHILE b1p = 0
.throw2()
.throw1(.6)
ENDDO
x3 = w-.ad
FOR i=1 TO 30*12
.throw3()
.throw2()
.throw1()
IF INLIST(INKEY(),&esclist) THEN && END
.RESET()
EXIT
ENDIF
NEXT
ENDWITH
ENDPROC
PROCEDURE throw1
LPARAMETER npart
npart = EVL(npart,0.2)
WITH THISFORM
y1 = MAX(0,(x1-.ad)*(w-x1))
.b1.LEFT = 36+12*x1
.b1.TOP = 252-y1
sleep(nspeed)
IF x1 = INT(npart*w) THEN
b1p = 1
ENDIF
IF side1 = "L" THEN
x1 = x1+1
IF x1 = w THEN
side1 = "R"
ENDIF
ELSE
x1 = x1-1
IF x1 = .ad THEN
side1 = "L"
ENDIF
ENDIF
ENDWITH
ENDPROC
PROCEDURE throw2
WITH THISFORM
y2 = MAX(0,x2*(w-.ad-x2))
.b2.LEFT = 36+12*x2
.b2.TOP = 252-y2
sleep(nspeed)
IF x2 = INT(.8*w) THEN
b2p = 1
ENDIF
IF side2 = "L" THEN
x2 = x2+1
IF x2 = w-.ad THEN
side2 = "R"
ENDIF
ELSE
x2 = x2-1
IF x2 = 0 THEN
side2 = "L"
ENDIF
ENDIF
ENDWITH
ENDPROC
PROCEDURE throw3
WITH THISFORM
y3 = MAX(0,x3*(w-.ad-x3))
.b3.LEFT = 36+12*x3
.b3.TOP = 252-y3
sleep(nspeed)
IF side3 = "L" THEN
x3 = x3+1
IF x3 = w-.ad THEN
side3 = "R"
ENDIF
ELSE
x3 = x3-1
IF x3 = 0 THEN
side3 = "L"
ENDIF
ENDIF
ENDWITH
ENDPROC
PROCEDURE RESET
WITH THISFORM
.b1.LEFT = 26
.b2.LEFT = 372
.b3.LEFT = 408
STORE 252 TO .b1.TOP,.b2.TOP,.b3.TOP
side1 = "L"
side2 = "R"
side3 = "R"
.lblMsg.CAPTION = msg1
ENDWITH
ENDPROC
PROCEDURE handpicL
LOCAL un
* 1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
un = 'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
un = m.un+'726F756E642E0000626C61636B7265640000FFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F63620726B32E35491837D9235655FF968E4E0FD00EB59C902A01E6C6A'
un = m.un+'7E6E371CFE95688F9368C01818F415C92AEDAD343A634527A9565D66770639E7F2D8F4585305BD81C935368B61702F5AF648'
un = m.un+'7ECE850AED6FBD2127AB7FF5F9AA92DAEC6DCF18652796001AB114925B465ADEEA555FEEB8DC31F422A233D6F3349434B40E'
un = m.un+'887D296B9F6D52F72163916427D23E9FF8F556FB7EA1792BDB4734924A07DD83680BFEF3F6AE9F6CBA1CFEC5EED9D4F14561'
un = m.un+'C3A46ADE52EED6E543DD5630C07E2DC9FC68AD2E4D97728CD6BA9B49E53B5B1FF8137F854C965A8C49BCB5BE3D0337F85145'
un = m.un+'70F2A3AEECAD26A72C0712C487D76B1A92D2E66D4D8C76E91A05EA64E7F414515296A53DAE599344BA9CED9B50DB1F758A32'
un = m.un+'33ED9CD6CD9D9C1636EB05BC61117D3BFB9F7A28AEBA492392726F727A28A2B5323FFFD9'
RETURN STRCONV(un,16)
ENDPROC
PROCEDURE handpicR
LOCAL un
* 1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
un = 'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
un = m.un+'726F756E642E0073626C61636B72656400FFFFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F66A2B99B84D5749324D35E5C4B175F3906E03FDE43D3EA38A747ABEA0'
un = m.un+'515CC91BAB747550C3F422B275527666DEC9B574CD7D5ED25BED364821601DB070C786C1CE0FB1AC186F26D33F76E25B361F'
un = m.un+'76265CA37D074FC8D5B37D753E50DE155C7263400FE7CD537815A4DA8AF2B9FBC5F93F99AC2A544DDE3B9B53834AD2D8D0B6'
un = m.un+'D5EF9CEE78A1914765CA1FE6456B5B5D457516F8C9F4653D54FA115896F118536923E83B524D1C6D20CA2973DFA1E9EB442B'
un = m.un+'496FA8A74A2F63A2A2B94366E4E4A7FE3D455FD63C89F61E6755D6B1AE7C3ABF6933D85C1B466E5902E509FA64628A2B7945'
un = m.un+'495998464E2F420B9B4BEB084CEF24132AF5F94AB7F5ACF8F5A131C470F3DF71C514571D48A8CAC8EDA6F9A3765D861D42F9'
un = m.un+'498FC841EEE7FC2A096C75281B7E6DB3D321DBFC28A29F246D71733BD8B51E9DAC4881C4D6A011D32DFE1451455AA71239E4'
un = m.un+'7FFFD9'
RETURN STRCONV(un,16)
ENDPROC
ENDDEFINE
</Pre>
<p><i><font size="-1">Publicado en el foro de <a href="https://www.foxite.com/" target="_blank">Foxite</a> por Tony Vignone: <a href="https://www.foxite.com/archives/for-fun-only-0000481735.htm" target="_blank">FOR FUN ONLY</a></font></i></p>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com1tag:blogger.com,1999:blog-8847231238348131912.post-12794766103041401232021-04-27T00:00:00.002-03:002021-05-20T09:06:03.900-03:00Gráficos de barras simples a través de Grid y BackStyle_Access<p><b>Artículo original: Simple Bar Graphs via Grid and Backstyle_Access<br>
<a href="http://www.sweetpotatosoftware.com/spsblog/2005/12/10/SimpleBarGraphsViaGridAndBackstyleAccess.aspx">http://www.sweetpotatosoftware.com/spsblog/2005/12/10/SimpleBarGraphsViaGridAndBackstyleAccess.aspx</a><br>
Autor: Craig Boyd<br>
Traducido por: Luis María Guayán</b></p>
<hr>
<p><b>Me llegó un correo electrónico</b></p>
<p>Sue Cunningham me envió un correo electrónico y me contó sobre un uso muy bueno que había encontrado para la técnica BackStyle_Access que había explicado en una publicación de mi blog. Si no tienes tiempo para leer esa entrada de blog, aquí tienes la versión resumida: Al usar el método BackStyle_Access de un control contenido en la columna de un Grid, se puede hacer casi cualquier cosa que se desee, ya que ese método se activará cuando se pinte cada fila visible en el Grid. Realmente ni siquiera hay una necesidad de todos esos métodos DynamicLoQueSea que el Team FoxPro de Microsoft agregó al objeto Columna, cuando usas BackStyle_Access. También permite realizar operaciones mucho más complejas y efectos visuales dentro de las filas del Grid. De todos modos, Sue me envió un correo electrónico y me dijo que había ideado una forma relativamente simple para producir gráficos de barras. Algo que se parece mucho a las barras de degradado recientes sobre las que Calvin Hsia escribió en su blog, pero mucho más simple, sin necesidad de GDI+.</p>
<p><b>Yo decidí probarlo</b></p>
<p>Sue no me proporcionó el código para su solución, pero según la explicación que me había dado en su correo electrónico, sentí que tenía la idea básica y decidí intentarlo y ver qué podía crear. El resultado (ver la imagen a continuación) fue tan agradable como fácil. Sue también me dijo que estaba encontrando algunos resultados impredecibles al colocar el Grid dentro de un control contenedor, por lo que también produje un ejemplo (Formulario Example2 que se puede descargar al final) que también usa esta técnica para mostrar gráficos en dos páginas separadas de un marco de página. No encontré ninguno de los resultados inconsistentes (usando VFP 9.0 SP1) que comentó Sue, así que no estoy seguro de qué le está sucediendo a ella. La técnica parece funcionar perfectamente (Nota: las barras se truncarán y/o tendrán problemas de pintura si permito que el Grid muestre ambos paneles al mismo tiempo y el panel izquierdo es un Change Panel. El Change Panel es el único lugar que vi este comportamiento no deseado, aparte de eso, todo está bien).</p>
<p><b>Todo esta involucrado</b></p>
<p>Tengo una subclase Grid que contiene dos columnas. La primer columna se usa para la descripción de lo que sea el elemento que estoy graficando ... puedes pensar en ello como la etiqueta del eje, supongo. La segunda columna contiene una subclase Container bastante simple que contiene un Shape y una subclase Label. el control Shape se usa para producir las barras y el control Label se usa para mostrar el valor que fue graficado. La magia de todo esto está en el método BackStyle_Access del control Container (que está en la columna2). Aquí está el código actual que es bastante pequeño ...</p>
<pre>
LOCAL lnTotalTicks, lnValue, lnWidth
lnTotalTicks = (this.Parent.Parent.MaxValue - This.Parent.Parent.MinValue)
lnValue = EVALUATE(this.Parent.Parent.recordsource + ".value")
lnWidth = (This.Parent.Width - 35) * (lnValue/lnTotalTicks) && - 35 to leave room for the value caption
This.cshape1.Width = lnWidth
This.clabel1.Caption = TRANSFORM(lnValue)
This.cLabel1.Left = lnWidth + 5 && leave a five pixel margin
RETURN THIS.BackStyle</pre>
<p>Eso es básicamente todo lo que hay que hacer. Las barras serán del color del Shape en el contenedor. Sin duda, podría mejorar lo que estoy mostrando aquí agregando una propiedad de color de barra para el Grid o tal vez mostrando las barras en diferentes filas y en diferentes colores basado en algunos criterios, o llamando a un método del Grid desde el método BackStyle_Access del contenedor para que el código anterior no esté tan oculto. Solo estoy mostrando lo básico aquí, esto ciertamente no es un producto terminado con todo el cotillón posible. Además, a medida que observa los ejemplos, puede notar que hay propiedades MinValue y MaxValue que se han agregado a la subclase del Grid. Estas son para que no tengamos que graficar siempre en 0-100 o lo que sea (ver el código arriba). Los valores graficados pueden ser cualquier cosa, como 1000-10000, 250-350, etc.</p>
<p>Finalmente, para ver los cursores con los que se ejecutan los gráficos de barra del Grid, mire en los métodos Load de los formularios de ejemplo donde encontrará algo como el siguiente código ...</p>
<pre>
LOCAL lnCounter
CREATE CURSOR crsValues (Descript c(25), Value I)
=RAND(-1)
FOR lnCounter = 1 TO 100
INSERT INTO crsValues (Descript, Value) VALUES ("Item " + TRANSFORM(lnCounter), INT(100 * RAND( ) + 1))
ENDFOR
GO TOP IN crsValues</pre>
<p>Como puede ver, el cursor que se necesita para esto es extremadamente simple, de hecho solo dos campos, uno para la descripción y el otro para los valores. En cualquier caso, esto solo muestra una vez más el valor de la clase Grid de Visual FoxPro cuando se acopla con el método BackStyle_Access. Con muy pocas líneas de código podemos producir una clase bastante útil para mostrar datos visualmente a nuestros usuarios. Gracias por la sugerencia enviada por correo electrónico Sue!</p>
<p><b>Código fuente y captura de pantalla</b></p>
<p>Cuando descargue el archivo zip, extraiga el contenido. Abra el proyecto en Visual FoxPro y ejecute los formularios Example y Example2.</p>
<p>Descargue el código fuente de Grid Graph con los ejemplos desde el siguiente enlace: <a href="https://drive.google.com/file/d/1OAZuGf8ic5zmbYu5OVHpGPNQay6-9Rpg/">gridgraph.zip</a> (23 KB aprox.)</p>
<p><img alt="" border="0" data-original-height="458" data-original-width="576" src="https://1.bp.blogspot.com/-0b4a8qWJvrc/YHsF50N1_3I/AAAAAAAADaM/I3nJYfbNUf4xmyYFeMOd5pHCKXYWtHqzwCLcBGAsYHQ/s0/gridgraph.gif"/></p>
<hr>Luis Mariahttp://www.blogger.com/profile/01349796512778225709noreply@blogger.com3