29 de abril de 2005

Directivas no documentadas para la herramienta Beautify en Visual FoxPro 9.0

Visual FoxPro incluye una herramienta conocida como Presentación (Beautify) que está disponible en las ventanas de edición y nos permite especificar el uso de mayúsculas, minúsculas y el tipo de sangrias en códigos de programas con el fin de una mejor presentación y legibilidad.

Estas opciones se configuran en el cuadro de diálogo Opciones de Presentación (Beautify options) y se utilizan cada vez que usamos ésta herramienta en algún bloque de código.

A veces necesitamos que en ciertos bloques de código, ésta herramienta no utilice las opciones configuradas. Para ello Visual FoxPro 9.0 incluye dos nuevas directivas no documentadas (en la ayuda del producto) para Beautify (Presentación) que nos ayudarán a resolver este problema. Las directivas son las siguientes:

*#beautify keyword_nochange 
*#beautify

Como se observa, estas directivas tienen la forma de comentarios, ya que están precedidas por el caracter asterisco "*". Esto es una ventaja, ya que el código puede ser compilado en versiones anteriores a Visual FoxPro 9.0 sin ningún tipo de inconvenientes.

Un ejemplo de su utilidad es cuando una palabra reservada de VFP, es igual que el nombre de una función de la API de Windows, como por ejemplo ShowWindow, que debe ser llamada respetando el uso de mayúsculas y minúsculas (case sensitive) para que no nos retorne un error.

El siguiente código es un ejemplo de lo expuesto anteriormente:

*-- Bloque 1
*#beautify keyword_nochange
#DEFINE SW_MINIMIZE 6
DECLARE INTEGER ShowWindow IN WIN32API ;
  INTEGER nHWND, ;
  INTEGER nCmdShow
ShowWindow(_VFP.HWND, SW_MINIMIZE)
*#beautify
*--- Fin Bloque 1 
*
*--- Bloque 2
#DEFINE SW_MINIMIZE 6
DECLARE INTEGER ShowWindow IN WIN32API ;
  INTEGER nHWND, ;
  INTEGER nCmdShow
ShowWindow(_VFP.HWND, SW_MINIMIZE)
*--- Fin Bloque 2

Si ejecutamos la herramienta Beautify en el código anterior, y configuramos para que las palabras claves de VFP se escriban en MAYÚSCULAS, observaremos la diferencia de utilizar estas directivas.

Si luego ejecutamos el código, el Bloque 1 se ejecutara sin problemas, mientras en el Bloque 2 obtendremos el error "No se puede encontrar el punto de entrada SHOWWINDOW en la DLL".

Este artículo está basado en el artículo #894818 de la Base de Conocimientos de Microsoft:

-- Nuevas directivas para la herramienta Beautify disponibles en Visual FoxPro 9.0 --
http://support.microsoft.com/kb/894818

26 de abril de 2005

Accediendo a datos de VFP 9.0 desde la Web con el Proveedor OLE DB de VFP 9.0

Introducción

A partir de Visual FoxPro 9.0 tenemos una nueva opción para publicar los datos de nuestras tablas en la Web. Esta opción la logramos sin adquirir ni descargar ninguna herramienta de terceras partes, ni desarrollar objetos COM con Visual FoxPro.

En este artículo veremos con un ejemplo como desarrollar esta opción, con el uso de procedimientos almacenados en nuestra base de datos, el uso de una nueva función incluida en Visual FoxPro 9.0 y el nuevo Proveedor OLE DB de Visual FoxPro 9.0.

¿Qué necesitamos?

Suponemos en este artículo que uno tiene conocimientos sobre Microsoft Internet Information Server (IIS) y como crear aplicaciones con Active Server Pages (ASP).

Lo que necesitamos para desarrollar este breve y funcional ejemplo es:

1. Una PC servidora con Internet Information Server (IIS) instalado en donde montaremos nuestro sitio Web. Si tienen poca experiencia en esto, pueden leer el siguiente artículo publicado en PortalFox:

Configurando IIS
Por Antonio Muñoz de Burgos y Caravaca
http://www.portalfox.com/article.php?sid=802

2. Visual FoxPro 9.0 instalado, ó solamente el Proveedor OLE DB de Visual FoxPro 9.0 que lo pueden descargar de:

Microsoft OLE DB Provider for Visual FoxPro 9.0
http://www.microsoft.com/downloads/details.aspx?FamilyID=e1a87d8f-2d58-491f-a0fa-95a3289c5fd4&DisplayLang=en

Como parte de lo nuevo que trae el Proveedor OLE DB de Microsoft para Visual FoxPro 9.0, y que utilizaremos en este ejemplo es:

  • Los procedimientos almacenados pueden ahora retornar RecordSets creados a partir de cursores de VFP usando las nuevas funciones de la versión 9: SETRESULTSET(), GETRESULTSET() y CLEARRESULTSET()
  • Se actualizó la base de datos Northwind con 5 (cinco) procedimientos almacenados que incluyen la función SETRESULTSET() de modo que éstos retornan RecorSets cuando se ejecutan.

3. Microsoft Data Access Components (MDAC) versión 2.6 o superior. La última versión de MDAC la pueden descargar de:

MDAC 2.8 (Español)
http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=6c050fe3-c795-4b7d-b037-185d0506396c

4. Disponer de un editor de páginas ASP como Visual InterDev, FrontPage o simplemente el conocido Block de Notas.

Algunos conceptos previos

¿Que es el Proveedor OLE DB de Visual FoxPro?

OLE DB (Object Linking and Embedding for DataBases) para Visual FoxPro es un componente COM que se utiliza para tener acceso a las bases de datos y tablas de VFP desde otros lenguajes de programación u otras aplicaciones.

OLE DB no se utiliza directamente, sino por medio de ADO (ActiveX Data Object). ADO permite conectarse a orígenes de datos compatibles con OLE DB u ODBC (Open DataBase Connectivity). ADO esta formado por varios objetos organizados de forma jerárquica y cada uno de estos objetos poseen sus métodos y propiedades.

Veamos a los 2 objetos de ADO que veremos en el desarrollo del ejemplo:

Objeto Connection: Nos proporciona una conexión a una base de datos. Esta conexión nos permitirá ejecutar los distintos comandos sobre la base de datos. Connection es el objeto primario de ADO, ninguno de los otros objetos puede existir si este no es declarado. La conexión terminará cuando ejecutamos el método Close o cuando termine la ejecución de la página ASP.

Objeto Recordset: Representa el resultado de una consulta ejecutada contra la base de datos. Este objeto es la interface natural contra la base de datos.

Nuestra primer página

Una vez instalado y corriendo el servicio de IIS, e instalado el Proveedor OLE DB de VFP en el servidor creamos una carpeta ("\vfp") bajo el sitio Web predeterminado (por defecto "C:\Inetpub\wwwroot\") y esta será nuestra carpeta o sitio de trabajo.

En esta nueva carpeta creamos nuestra primera página ASP con el nombre "vfpoledb.asp" con el siguiente contenido:

<%@ Language=VBScript%>
<%
Dim Ruta, oConn, oRs
'-- Si solo tenemos instalado el proveedor OLE DB de VFP 9.0
' Ruta = "C:\Archivos de programa\" & _
' "Microsoft Visual FoxPro OLE DB Provider\Samples\Northwind\Northwind.DBC"

'-- Si tenemos instalado Visual FoxPro 9.0
Ruta = "C:\Archivos de programa\" & _
"Microsoft Visual FoxPro 9\Samples\Northwind\Northwind.DBC"

Set oConn = CREATEOBJECT("ADODB.Connection")
Set oRs = CREATEOBJECT("ADODB.RecordSet")
oConn.Open("Provider=VFPOLEDB.1;Data Source=" & Ruta)
oRs.Open "SELECT CustomerId, CompanyName, Address, City, Country FROM Customers", oConn
%>
<html>
<body>
<h3>Listado de Clientes al: <% =Now() %></h3>
<table border=1 width=100% cellspacing="1" cellpadding="2">
  <tr>
    <% For i = 0 to oRS.Fields.Count - 1 %>

      <th><% = UCASE(oRS(i).Name) %></th>
    <% Next %>
  </tr>
  <% Do While Not oRS.EOF %>
    <tr bgcolor="#FFFFFF">
      <% For i = 0 to oRS.Fields.Count - 1 %>
        <td><% = oRS(i) %></td>
      <% Next %>
    </tr>
    <%
    oRs.MoveNext
  Loop
  oRs.Close
  oConn.Close 
  Set oRs = Nothing
  Set oConn = Nothing
  %>
</table>
</body>
</html>

Guardamos los cambios y desde el Explorador de Internet ejecutamos:

http://localhost/vfp/vfpoledb.asp

Si todo ha ido bien, debemos visualizar la siguiente página:

¿Fácil? Ahora ya sabemos como podemos hacer nuestras páginas consultando dinámicamente los datos de nuestras tablas de VFP y publicarlas en la Web.

Para no repetir el código de creación y apertura de nuestro objeto Connection en todas nuestras páginas, vamos a utilizar los Include que nos permiten las páginas ASP y en caso de cambiar la ruta o el nombre de nuestra base de datos solo deberemos cambiar en un solo lugar. Esto también nos dará mas seguridad a nuestro código.

Creamos una nueva página que llamaremos "conn.asp" y copiamos el siguiente código.

<%
Dim cRuta, oConn
'-- Si tenemos instalado Visual FoxPro 9.0
cRuta = "C:\Archivos de programa\" & _
"Microsoft Visual FoxPro 9\Samples\Northwind\Northwind.DBC"

Set oConn = CREATEOBJECT("ADODB.Connection")
oConn.Open("Provider=VFPOLEDB.1;Data Source=" & cRuta & ";Mode=ReadWrite")
%>

Cambiamos el código de la página "vfpoledb.asp"  y guardamos como "vfpoledb2.asp".

<%@ Language=VBScript%>
<!-- #include file="conn.asp" -->
<%
Dim oRs
Set oRs = CREATEOBJECT("ADODB.RecordSet")
oRs.Open "SELECT CustomerId, CompanyName, Address, City, Country FROM Customers", oConn
%>
<html>
<body bgcolor="#E0EAFC">
<h3>Listado de Clientes al: <% =Now() %></h3>
<table border=1 width=100% cellspacing="1" cellpadding="2">
  <tr>
    <% For i = 0 to oRS.Fields.Count - 1 %>
      <th><% = UCASE(oRS(i).Name) %></th>
    <% Next %>
  </tr>
  <% Do While Not oRS.EOF %>
    <tr bgcolor="#FFFFFF">
      <% For i = 0 to oRS.Fields.Count - 1 %>
        <td><% = oRS(i) %></td>
      <% Next %>
    </tr>
    <%
    oRs.MoveNext
  Loop
  oRs.Close
  oConn.Close 
  Set oRs = Nothing
  Set oConn = Nothing
  %>
</table>
</body>
</html>

Ejecutamos desde el Explorador de Internet:

http://localhost/vfp/vfpoledb2.asp

Y veremos los mismos resultados que el ejemplo anterior.

Utilizando procedimientos almacenados

Para nuestro ejemplo utilizaremos los procedimientos almacenados actualizados que trae Visual FoxPro 9.0 en la base de datos "Northwind" y crearemos algunos nuevos procedimientos almacenados.

Para crear nuestro procedimiento almacenado, abrimos la base de datos y editamos los procedimientos almacenados

OPEN DATABASE (HOME(2) + "Northwind\Northwind")
MODIFY PROCEDURE

Al final del archivo, escribimos nuestro procedimiento almacenado llamado "CustomersAll" que nos retornará todos los registros de la tabla "Customers"

PROCEDURE CustomersAll
  SELECT CustomerId, CompanyName, Address, City, Country ;
    FROM Customers ;
    INTO CURSOR CustomersAll
  RETURN SETRESULTSET("CustomersAll")
ENDPROC

El código para llamar un procedimiento almacenado de nuestra base de datos desde la página ASP es el siguiente:

<%
Dim cRuta, oConn
Ruta = "C:\Archivos de programa\" & _
  "Microsoft Visual FoxPro 9\Samples\Northwind\Northwind.DBC"
Set oConn = CREATEOBJECT("ADODB.Connection")
oConn.Open("Provider=VFPOLEDB.1;Data Source=" & cRuta)

Dim oRS, nReg
Set oRs = oConn.Execute("CustomersAll",nReg,4)
%>

Creamos la nueva página "customers.asp" para obtener todos los registros de la tabla "Customers", esta vez llamando al procedimiento almacenado creado anteriormente. En esta página agregamos código HTML para enlazar con las siguientes páginas que realizaremos para completar este ejemplo. El código de esta nueva página es:

<%@ Language=VBScript%>
<!-- #include file="conn.asp" -->
<%
Dim oRs, nReg
Set oRs = oConn.Execute("CustomersAll",nReg,4)
%>
<html>
<body bgcolor="#E0EAFC">
<h3>Listado de Clientes al: <% =Now() %></h3>
<table border=1 width=100% cellspacing="1" cellpadding="2">
  <tr>
    <th>Id</th>
    <th>Compañia</th>
    <th>Dirección</th>
    <th>Ciudad</th>
    <th>País</th>
  </tr>
  <% Do While Not oRS.EOF %>
    <tr bgcolor="#FFFFFF">
      <td><a href="/vfp/orders.asp?cId=<%=oRs(0)%>"><%=oRs(0)%></a></td>
      <td><%=oRs(1)%></td>
      <td><%=oRs(2)%></td>
      <td><%=oRs(3)%></td>
      <td><%=oRs(4)%></td>
    </tr>
    <%
    oRs.MoveNext
  Loop
  oRs.Close
  oConn.Close 
  Set oRs = Nothing
  Set oConn = Nothing
  %>
</table>
<p>Registros: <%=nReg%></p>
</body>
</html>

Al ejecutar la página "customers.asp" nos muestra la siguiente tabla:

Como se aprecia en la figura, hemos creado un enlace a la página "orders.asp" pasando como parámetro el Id del cliente. El código de esta página es el siguiente, el cual llama al procedimiento almacenado "CustOrdersOrders", que ya está incluido en la base de datos "Northwind", con el parámetro del Id del cliente:

<%@ Language=VBScript%>
<!-- #include file="conn.asp" -->
<%
Dim cId
cId = Request("cId")
Dim oRs, nReg
Set oRs = oConn.Execute("CustOrdersOrders('" + cId + "')",nReg,4)
%>
<html>
<body bgcolor="#E0EAFC">
<h3>Ordenes del cliente <%=cId%></h3>
<table border=1 width=100% cellspacing="1" cellpadding="2">
  <tr>
    <th>Orden Id</th>
    <th>Fecha Orden</th>
    <th>Fecha Pedido</th>
    <th>Fecha Envio</th>
  </tr>
  <% Do While Not oRS.EOF %>
    <tr bgcolor="#FFFFFF">
      <td><a href="/vfp/details.asp?oId=<%=oRs(0)%>"><%=oRs(0)%></a></td>
      <td><%=oRs(1)%></td>
      <td><%=oRs(2)%></td>
      <td><%=oRs(3)%></td>
    </tr>
    <%
    oRs.MoveNext
  Loop
  oRs.Close
  oConn.Close 
  Set oRs = Nothing
  Set oConn = Nothing
  %>
</table>
<p>Registros: <%=nReg%></p>
<p><a href="javascript:history.back()">Página&nbsp;anterior</a></p>
</body>
</html>

Los resultados de la ejecución de la página "orders.asp" se muestran en la siguiente figura:

Al igual que en la página anterior, incluimos un enlace a la página "details.asp" que llama al procedimiento almacenado "CustOrdersDetails" con el parámetro correspondiente. El código de la página es el siguiente:

<%@ Language=VBScript%>
<!-- #include file="conn.asp" -->
<%
Dim oId
oId = Request("oId")
Dim oRs, nReg
Set oRs = oConn.Execute("CustOrdersDetail(" + oId + ")",nReg,4)
%>
<html>
<body bgcolor="#E0EAFC">
<h3>Detalle de la orden <%=oId%></h3>
<table border=1 width=100% cellspacing="1" cellpadding="2">
  <tr>
    <th>Producto</th>
    <th>Precio unitario</th>
    <th>Cantidad</th>
    <th>Descuento</th>
    <th>Precio Total</th>
  </tr>
  <% Do While Not oRS.EOF %>
    <tr bgcolor="#FFFFFF">
      <td><%=oRs(0)%></td>
      <td align="right"><%=oRs(1)%></td>
      <td align="right"><%=oRs(2)%></td>
      <td align="right"><%=oRs(3)%>%</td>
      <td align="right"><%=oRs(4)%></td>
    </tr>
    <%
    oRs.MoveNext
  Loop
  oRs.Close
  oConn.Close 
  Set oRs = Nothing
  Set oConn = Nothing
  %>
</table>
<p>Registros: <%=nReg%></p>
<p><a href="javascript:history.back()">Página&nbsp;anterior</a></p>
</body>
</html>

Y el resultado de esta página lo vemos en la siguiente figura:

 

Hasta aquí se observa que con un poco de código ASP y código en nuestra base de datos, podemos realizar cualquier tipo de consultas y publicarlas en la Web fácilmente. Si la sentencias  SELECTs escritas en nuestros procedimientos almacenados son optimizables, los tiempos de respuestas son excelentes.

Modificando los datos de las tablas

Aparte de consultar los datos de nuestras tablas, también podemos modificar e insertar datos en tablas de VFP. Sobre este punto se debe tener mucha atención al realizar nuestras páginas ASP y nuestros procedimientos almacenados. Las páginas ASP deben ser seguras y debemos validar todos los datos ingresados, no solo por errores del usuario, sino también por datos ingresados con fines maliciosos. En los procedimientos almacenados también debemos validar los parámetros recibidos.

En este artículo solo veremos el caso de modificar los datos de los clientes, sin poner mucha atención en el punto anteriormente citado.

Para realizar la página de edición de datos, primeramente agregaremos una columna en la página "customers.asp" para enlazar la página "editcustomer.asp". Solo debemos agregar el siguiente código a continuación de la última columna de la tabla.

 <td><a href="/vfp/editcustomer.asp?cId=<%=oRs(0)%>">Editar</a></td>

Antes de crear las páginas para la edición y actualización de los datos, agregamos dos nuevos procedimientos almacenados a la base de datos "Northwind".

PROCEDURE GetCustomer(tcCID)
  SELECT CustomerId, CompanyName, ContactName, ContactTitle, ;
    Address, City, REGION, PostalCode, Country, Phone, Fax ;
    FROM Customers ;
    WHERE CustomerId = tcCid ;
    INTO CURSOR GetCustomer
  RETURN SETRESULTSET("GetCustomer")
ENDPROC
PROCEDURE UpdateCustomer

  LPARAMETERS tcCustomerid, tcCompanyName, tcContactName, ;   
    tcContactTitle, tcAddress, tcCity, tcRegion, ;
    tcPostalCode, tcCountry, tcPhone, tcFax
  LOCAL lnErr
  UPDATE Customers SET ;
    Companyname = tcCompanyname, ;
    Contactname = tcContactname, ;
    Contacttitle = tcContacttitle, ;
    Address = tcAddress, ;
    City = tcCity, ;
    Region = tcRegion, ;
    Postalcode = tcPostalcode, ;
    Country = tcCountry, ;
    Phone = tcPhone, ;
    Fax = tcFax ;
    WHERE Customerid = tcCustomerid
  lnErr = _TALLY
  RETURN lnErr
ENDPROC 

La página "editcustomer.asp" llama al procedimiento almacenado "GetCustomer" y contiene un formulario con todos los campos del registro y un botón "Grabar" que invoca a la página "updatecustomer.asp". El código de la página es el siguiente:

<%@ Language=VBScript %>
<!-- #include file="conn.asp" -->
<html>
<%
Dim oRs, nReg, cId
cId = Request("cId")
Set oRs = oConn.Execute("GetCustomer('" & cId & "')", nReg, 4)
%>
<body bgcolor="#E0EAFC">
<h3>Editar cliente <%=cId%> </h3>
<hr>
<form id="frmCust" name="frmCust" action="updatecustomer.asp" method="post">
<table width="100%" border="0" cellpadding="5" cellspacing="0">
  <tr>
    <td>Id Cliente</td>
    <td><input name="CustomerId" size="10" value="<%=Trim(oRs(0))%>" 
      readonly style="color: #FF0000"></td>
  </tr>
  <tr>
    <td>Nombre Compañia</td>
    <td><input name="CompanyName" size="40" value="<%=Trim(oRs(1))%>"></td>
  </tr>
  <tr>
    <td>Nombre Contacto</td>
    <td><input name="ContactName" size="40" value="<%=Trim(oRs(2))%>"></td>
  </tr>
  <tr>
    <td>Título Contacto</td>
    <td><input name="ContactTitle" size="40" value="<%=Trim(oRs(3))%>"></td>
  </tr>
  <tr>
    <td>Domicilio</td>
    <td><input name="Address" size="40" value="<%=Trim(oRs(4))%>"></td>
  </tr>
  <tr>
    <td>Ciudad</td>
    <td><input name="City" value=" <%=Trim(oRs(5))%>"></td>
  </tr>
  <tr>
    <td>Región</td>
    <td><input name="Region" value=" <%=Trim(oRs(6))%>"></td>
  </tr>
  <tr>
    <td>Código postal</td>
    <td><input name="PostalCode" size="10" value="<%=Trim(oRs(7))%>"></td>
  </tr>
  <tr>
    <td>País</td>
    <td><input name="Country" value="<%=Trim(oRs(8))%>"></td>
  </tr>
  <tr>
    <td>Teléfono</td>
    <td><input name="Phone" value="<%=Trim(oRs(9))%>"></td>
  </tr>
  <tr>
    <td>Fax</td>
    <td><input name="Fax" value="<%=Trim(oRs(10))%>"></td>
  </tr>
  <tr> 
    <td></td>
    <td>
      <input type="submit" value="Grabar" name="cmdG">&nbsp;
      <input type="button" value="Cancelar" name="cmdC" OnClick="history.back()"/>
    </td>
  </tr>
</table>
</form>
<script Language="VBScript">frmCust.CompanyName.Focus</script>
</body>
</html>

Al ejecutar la página nos retorna el registro a modificar como lo muestra la figura siguiente:

Una vez editados los datos, el botón "Grabar" llama a la página "updatecustomer.asp" que invoca al procedimiento almacenado "UpdateCustomer" con todos los campos pasados como parámetros. El código es el siguiente:

<%@ Language=VBScript %>
<!-- #include file="conn.asp" -->
<html>
<%
Dim cParams
cParams = "'" & Request("CustomerId") & "','" & Request("CompanyName") & "','" & _
  Request("ContactName") & "','" & Request("ContactTitle") & "','" & _
  Request("Address") & "','" & Request("City") & "','" & _
  Request("Region") & "','" & Request("PostalCode") & "','" & _
  Request("Country") & "','" & Request("Phone") & "','" & Request("Fax") & "'" 
Dim nReg, oRs
nReg = 0
oRs = oConn.Execute("UpdateCustomer(" & cParams & ")", nReg, 4)
%>
<body bgcolor="#E0EAFC">
<h3>Grabar cliente <%=Request("CustomerId")%></h3>
<hr>
<% If nReg > 0 Then %>
  <h1>Los datos se grabaron correctamente</h1>
<% Else %>
  <h1>Error al grabar los datos</h1>
<% End If %> 
<input type="button" value="Continuar" name="cmdC" OnClick="location.href='customers.asp'"/>
</body>
</html>

Si los datos se grabaron correctamente en la tabla, la página se visualizará como lo muestra la siguiente figura:

Con este último ejemplo observamos que la modificación de los datos, no es una tarea complicada de realizar. Solo se deberán crear las páginas ASP y los procedimientos almacenados para cada necesidad y tomar las precauciones descriptas anteriormente.

Para tener en cuenta

Cuando se instala Internet Information Server (IIS) en un servidor, éste crea el usuario "IUSR_<Nombre_Servidor>" para el acceso anónimo al sitio publicado. Para que todo funcione correctamente, éste usuario deberá tener los privilegios necesarios para consultar y modificar la carpeta donde se encuentra nuestra base de datos.

Por defecto IIS controla la contraseña de éste usuario, esto nos puede traer problemas, si la base de datos se encuentra en otro servidor distinto al servidor donde esta ubicado el sitio web. Para evitar esto debemos desmarcar la casilla de verificación "Permitir que IIS controle la contraseña"

Para llegar a la ventana de la figura anterior, en las "Propiedades" del sitio Web, seleccionamos la solapa "Seguridad de directorio" y oprimimos el botón "Modificar'"  en "Control de autentificación y acceso anónimo".

Este caso está documentado en la Base de Conocimientos de Microsoft:

Accessing FoxPro Table in ASP Returns Error 80040e14
http://support.microsoft.com/kb/175801

Descargas

Todas las páginas ASP desarrolladas en este ejemplo las podemos descargar y copiar en el sitio Web del servidor en la carpeta "\vfp".

Enlace a los ejemplos: Accediendo a datos de VFP 9.0 desde la Web.zip (6 KB)

Conclusiones

Como comentamos al inicio de este artículo, esta es una opción válida y muy fácil de implementar para publicar nuestros datos de Visual FoxPro en la Web. La complejidad y el alcance de la aplicación a desarrollar solo depende de nosotros y de nuestras necesidades.

Para finalizar deseo agradecer el apoyo y las ideas recibidas de Antonio Muñoz de Burgos (eMans) que mucho me ayudaron para elaborar el código de los ejemplos de este artículo.

Hasta la próxima.

Luis María

25 de abril de 2005

Evolución del tratamiento de cadenas con TEXTMERGE

Como siempre, VFP ha ido evolucionando, y desde hace tres versiones el manejo de cadenas y TEXTMERGE ha sido uno de los que más impacto y mejora de funcionalidad ha tenido.

A continuación una experiencia que me llevé al manejarlo para crear cadenas de conexión hacia cliente servidor.

Estaba desarrollando una clase para manejo de conexiones y usarios a bases de datos remotas (a servidores de Base de Datos para ser más específico), cuando me tope con un código que según mi vieja memoria, podría optimizarse (o reducirse) un poco.

El caso estaba en que teniendo una tabla con los usuarios y passwords, poder armar una cadena de conexión hacia SQLServer:
SET TEXTMERGE ON
SET TEXTMERGE TO MEMVAR lcStringConnection
Driver={SQL Server};Server=<<alltrim(cServer)>>
;Database=<<alltrim(cDataBase)>>;Uid=<<alltrim(cUser)>>
;Pwd=<<alltrim(cPassword)>>;
SET TEXTMERGE TO
SET TEXTMERGE OFF

Esto funciona correctamente, y hace su trabajo como debe ser, pero puede quedar aún mejor!!, por lo que revisé la ayuda de SET TEXTMERGE, encontrándome que ésta puede simplificarse...
TEXT TO lcString NOSHOW TEXTMERGE
Driver={SQL Server};Server=<<alltrim(cServer)>>
;Database=<<alltrim(cDataBase)>>;Uid=<<alltrim(cUser)>>
;Pwd=<<alltrim(cPassword)>>;
ENDTEXT

Hasta ahí todo funciona correcto, ya he quitado algunas líneas sin perder funcionalidad, pero se me ocurrió algo, que de éste modo estaría atado a escribir en el código cada cadena de conexion si es que quisiera tener más de un servidor de bases de datos, teniendo con esto, que modificar mi código fuente en el caso que dicha cadena de conexión llegara a cambiar en un futuro, o si deseara agregar algún otro tipo de servidor (quizás MySQL, FireBird, PostgreSQL)...

Mi primera idea vino en tener tambien en una tabla DBF, las correspondientes cadenas de conexión según el servidor a usar, y simplemente hacer un textmerge desde esa cadena escrita en mi tabla, sonaba bien en un inico, pero me topé con un problema, cómo macrosustituia mi campo de mi tabla dentro de un bloque TEXT ... ENDTEXT??

Los siguientes fueron mis intentos:
1.- Pondré el campo de mi tabla en el bloque...
TEXT TO lcStringConnection NOSHOW PRETEXT
   cServer.cStringConnection
ENDTEXT

2.- Poner el campo de mi tabla macrosustituyendo...
TEXT TO lcStringConnection NOSHOW PRETEXT
  &cServer.cStringConnection
ENDTEXT

3.- Poner el campo de mi tabla con expresión de nombres...
TEXT TO lcStringConnection NOSHOW PRETEXT
  (cServer.cStringConnection)
ENDTEXT

4.- Poner el campo de mi tabla evaluando...
TEXT TO lcStringConnection NOSHOW PRETEXT
  EVALUATE(cServer.cStringConnection)
ENDTEXT

No está demás decir que ninguna de las opciones funcionó :'(, el problema radica en que lo que se pone en un bloque TEXT... ENDTEXT, se evalua literalmente, por lo que tendría que hacer un TEXTMERGE de lo que contenia la variable...

TEXT TO lcStringConnection NOSHOW TEXTMERGE
  <<cServer.cStringConnection>>
ENDTEXT

Bingo!, ya se expandió la variable, pero no era precisamente lo que quería, ya que el contenido de esa variable era el texto de mi campo, pero aún le faltaba expandir los campos de mi tabla, tales como usuario, password, etc. Por lo que necesitaba algo así como un textmerge anidado :S. Se me ocurrieron varias cosas, como mandarlo a ejecutar con ExecScript primero, retomar el valor y hacerle un segundo TEXTMERGE, hmm, no, demasiado rollo para sólo eso, recorrer la cadena expandida y cambiar los valores... si, quizás, pero nada adecuado.

Una vuelta más a la ayuda de SET TEXTMERGE me dió la solución, la función TextMerge(), que aparentemente hace lo mismo que SET TEXTMERGE TO MEMVAR y los bloques TEXT .. ENDTEXT, probé y milagro!, justo lo que esperaba (segun lo leido), tienen la función... pero con esteroides, ya que también implementa la expansión de variables de manera recursiva, en este caso primero expande lo que hay en cServer.cServidor, y como ésta misma tiene expansión de variables, vuelve a hacer un textmerge de manera implícita, expandiendo igualmente el contenido de las variables de servidor, usuario, contraseña, contenidas en la tabla de perfiles de usuario, con lo que mi código se había reducido a lo siguiente:

IF SEEK(lcTipoServer,"cServer")
  IF SEEK(lcPerfilUsuario,"cPerfiles")
    lcStringConnection = TextMerge(cStringConnection)
  ELSE
     *** Perfil de usuario no encontrado
  ENDIF
ELSE
   *** Tipo de Servidor no encontrado 
ENDIF

Con esto, sólo tuve que mover la cadena a sustituir hacia mi tabla dónde ahora puedo guardar distintas cadenas de conexión y simplemente sustituirlas, de este modo no solo tengo cómo conectarme, sino también la flexibilidad de usar cualquier tipo de servidor que acepte una cadena de conexión (hasta ahora todos los que he usado).

Para colmo, cinco dias después de andar batallando con esto, me encuentro con el siguiente artículo en FoxAdvisor:

--- Understanding TEXTMERGE ---
http://advisorupdate.info/Articles.nsf/nl/16362

Moraleja de la historia, nunca hay que dejar de leer por completo, la ayuda del producto, más en específico, los What's New de cada versión.

Espero que la información les sea de utilidad...

Espartaco Palma Martínez

21 de abril de 2005

Leyendo Servidores SQL Server con SQLDMO & Visual FoxPro

En un ejemplo anterior vimos, como leer Servidores SQL por medio del API del ODBC.

En esta ocasión, veremos como leer los servidores SQL Server disponibles, por medio de SQL-DMO.

Breve introducción, que es SQL-DMO:

SQL Distributed Management Objects (SQL-DMO), es una colección de objetos que encapsulan la base de datos de MS SQL Server.
SQL-DMO es un interfaz dual COM, por lo tanto podemos utilizar Automatización OLE.

Para poder trabajar con SQL-DMO, es necesario tener los componentes SQL-DMO.


Código de ejemplo; Servidores disponibles:


LOCAL lnIndi, loServidoresSQL, loListaServidoresDisponibles,loSRV
*
STORE 0       TO lnIndi
STORE .NULL.TO loServidoresSQL, loListaServidoresDisponibles, loSRV
*
loServidoresSQL = CREATEOBJECT( "SQLDMO.SQLServer2" )
*
loListaServidoresDisponibles =loServidoresSQL.Application.ListAvailableSQLServers
*
? "Total de Servidores SQL Server disponibles : " + ALLTRIM( STR( loListaServidoresDisponibles.Count ) )
*
FOR lnIndi = 1 TO loListaServidoresDisponibles.Count
    *
    ? "Servidor: " + loListaServidoresDisponibles.Item( lnIndi )
    *ENDFOR*
*
********************* CÓDIGO ALTERNATIVO. *
*
********************
*

FOR EACH
loSRV IN loListaServidoresDisponibles
   
*
   
? "Servidor: " +loSRV
    *
ENDFOR
*RELEASE loServidoresSQL, loListaServidoresDisponibles,loSRV
*
 
MISCELÁNEA


De la ayuda de SQL Server: Instalando SQL-DMO.
Los ficheros que se corresponden con SQL-DMO:

sqldmo.dll : C:\Program Files\Microsoft SQL Server\80\Tools\Binn
sqldmo.rll : C:\Program Files\Microsoft SQL Server\80\Tools\Binn\Resources\xxxx
sqldmo.h : C:\Program Files\Microsoft SQL Server\80\Tools\Binn\Devtools\Include
sqldmoid.h : C:\Program Files\Microsoft SQL Server\80\Tools\Binn\Devtools\Include
sqldmo.sql : C:\Program Files\Microsoft SQL Server\MSSQL\Install
 
Registrando los componentes SQL-DMO en el puesto cliente.
  • From C:\Program Files\Microsoft SQL Server\80\Tools\Binn\Resources\<language> directory, execute: \Program Files\Microsoft SQL Server\80\Tools\Binn\REGSVR32 SQLDMO.DLL
     
  • From any directory, execute: C:\Program Files\Microsoft SQL Server\80\Tools\Binn\REGSVR32.EXE
    C:\Program Files\Microsoft SQL Server\80\Tools\Binn\resources\1033\SQLDMO.RLL
     



Antonio Muñoz de Burgos y Caravaca
www.emans.com  (Web realizada en vFoxPro)
eMans AC (Administrador Corporativo para SQL Server 2000&2005)
Sevilla - España
Manifestando el apoyo a la comunidad de desarrolladores de MS Visual FoxPro.
 


Todo cabe en lo breve.
Pequeño es el niño y encierra al hombre.
Alejandro Dumas (1802-1870), escritor Francés
 

19 de abril de 2005

VFP9 - Novedades - Propiedad Anchor

Autor:  Ana María Bisbé York

Antes de la llegada de VFP 9.0 necesitábamos programar el evento Resize de los contenedores para reconfigurar la posición y tamaño de los controles contenidos al redimensionar contenedores. La nueva propiedad ANCHOR que se agregó a los controles en VFP 9.0, define los bordes del contenedor a los que un objeto contenido va a estar anclado. ¡¡Bien!!

Es una propiedad de Lectura / Escritura en tiempo de diseño y tiempo de ejecución. En la Ayuda hay ejemplos de posibles valores, algunos valores No recomendados y poco más. La utilidad de esta propiedad me anima a comentar un poco sobre ella para sacarle el mayor provecho.

En este escrito veremos la base sobre las que se rige esta propiedad, ejemplos de valores adecuados en dependencia al tipo de control, posibles conflictos que se pueden presentar y para finalizar vamos a describir los elementos que integran el editor para esta propiedad - Anchor editor.

Diferencia entre mover y redimensionar

Para redimensionar el control, la propiedad Anchor ha de tener un valor que sea la combinación de ambos ejes opuestos, por ejemplo: superior - inferior o izquierda - derecha.

Ejemplo: Anchor = 10

El control se encuentra anclado de forma absoluta a la Izquierda (Left Absolute) (Anchor = 2) y a la Derecha (Right Absolute) (Anchor = 10)

Para mover el control sin que este se redimensione, sólo se indicará uno de estos ejes opuestos.

Ejemplo: Anchor = 8

El control se encuentra anclado de forma absoluta a la Derecha (Anchor = 10)

Conociendo esto nos planteamos ¿Cómo determinar el valor para la propiedad?

Determinar el valor a asignar

Para determinar el valor a asignar a la propiedad tenga en cuenta que cada borde del contenedor y cada tipo de anclaje que se desee obtener tiene valores diferentes, estos valores sumados dan como resultado el número a asignar a la propiedad.

Ejemplo:

Anchor = 4 Bottom Absolute - Los controles mantienen la distancia con el borde inferior del contenedor.

Anchor = 8 Right Absolute - Los controles mantienen la distancia con el borde derecho del contenedor

Anchor = 12 Bottom Absolute + Right Absolute - Los controles mantienen la distancia con los bordes inferior y derecho del contenedor.

Valores posibles

Existe una amplia gama de valores posibles (0-672) y resulta poco recomendable la utilización de varios de ellos, veamos algunos casos de conflicto.

Conflictos - VFP devuelve un mensaje de error

No es posible establecer valores para la propiedad Anchor que entren en conflicto, por ejemplo:

1. No se pueden establecer al mismo tiempo valores Absolutos (no cambia la distancia entre el control y el eje indicado) y Relativos (mantiene distancia relativa entre el control y el eje indicado) para un mismo eje.
Ejemplo: Top Absolute = 1 y Top Relative = 16. No es posible que el control esté anclado de forma relativa y absoluta) a un mismo eje.

Si intentamos asignar 17, a la propiedad Anchor desde la ventana Propiedades, en tiempo de diseño, la propiedad conserva su valor anterior y aparece, en tiempo de diseño, el siguiente mensaje de error: "La expresión ha sido evaluada en un valor ilegal"
Si, por el contrario, asignamos este tipo de valores por programación, (This.Anchor = 17), el error se muestra entonces en tiempo de ejecución.
2. No es posible establecer al mismo tiempo Fixed Size (Tamaño fijo - el control se centra en relación a los bordes pero mantiene su tamaño fijo) y anclaje para uno de los ejes.
Ejemplo: Left Absolute = 2 y Horizontal Fixed Size = 256, porque Horizontal Fixed Size asume que el objeto se centrará horizontalmente y no puede, a la vez, anclarse a la izquierda.
Nos preguntamos entonces, ¿cómo determinar qué valor utilizar? Veamos a continuación algunas sugerencias.

¿Qué valor utilizar?

Eso depende del tipo de control y del efecto que queremos obtener en nuestra aplicación. Depende también de si el control es único en el contenedor o está acompañado por otros. He dividido los controles en dos grupos, los que se deben redimensionar y los que no.


Controles que no deben redimensionarse del todo.


Para los controles OptionGroup, OptionButtom, Label, TextBox, Combobox, Spinner, y Checkbox el valor que parece ser el más adecuado es Anchor = 672, porque mantienen distancia relativa a los ejes derecho e izquierdo) (32+128) y No se redimensionarán verticalmente.

Este efecto lo vemos en las dos figuras siguientes.





Controles que deben redimensionarse totalmente

Para los controles ListBox, Image, Line, Shape, OLEControl y EditBox, el valor que parece ser el más adecuado es Anchor = 240, porque mantienen distancia relativa a los cuatro bordes (16+32+64+128). Este efecto lo vemos en las dos figuras siguientes.





Otros casos

Control Grid

Lo que sucede con el Grid es, que a pesar de ser un contenedor y en teoría poder regular el anclaje de los objetos contenidos (columnas), no es posible, porque las columnas no tienen propiedad Anchor. Entonces el valor que parece ser más recomendable es Anchor = 336 Anclaje relativo a los bordes superior (16) e inferior (64); pero no se redimensiona horizontalmente (256). En caso que tengamos un gris con muchas columnas y es previsible que no se va a crear un efecto desagradable, entonces se puede recomendar Anchor = 240.

PageFrame y Container

Para el PageFrame y asumiendo que es el contenedor único dentro del formulario, como todos los ejemplos que hemos visto, Anchor = 15, porque garantiza que se amplíe de alto y de largo tanto como lo hace el propio formulario.
En caso de existir más controles en el formulario, además del PageFrame, pues hay que tener en cuenta que se puede producir un solape no deseado entre los controles.

CommandButton y CommandGroup

El valor de la propiedad Anchor para estos controles, depende del diseño que queramos mantener en nuestra aplicación. Pueden valer, por ejemplo: Anchor = 12, que mantiene los comandos abajo a la izquierda, Anchor = 260 que los mantiene centrados horizontalmente y al final del formulario o contenedor.

El control Toolbar no tiene asociada esta propiedad, al igual que Column que ya mencionamos.
Si conocemos el valor a asignar lo podemos introducir directamente. En caso contrario, podemos utilizar el Anchor Editor – Ventana para configurar el valor a asignar a esta propiedad. Este editor no se encuentra descrito en la ayuda de VFP 9.0 por lo que me detendré a describir brevemente algunos de sus componentes y su funcionalidad.

Anchor editor

Para tener acceso a este editor, hay que ejecutar previamente AnchorEditor.APP que se encuentra en C:Archivos de programaMicrosoft Visual FoxPro 9Wizards. Emite un mensaje que dice que ha quedado registrado el editor para la propiedad Anchor ("The Anchor Property editor has been registered"). Luego, para acceder al Editor, desde un formulario, para un control concreto o selección múltiple de controles, se hace Clic en el botón de comandos para editor de propiedades (…) que se encuentra al lado del cuadro de texto donde asignamos valor a la propiedad.



Para trabajar con el editor sólo será necesario hacer Clic en las barras que separan al control del contenedor estableciendo de esta forma el valor.
  • Si la barra es de color gris claro indica que no se aplicará ninguna acción al redimensionar el contenedor.
  • Si la barra es de color gris oscuro indica valores absolutos (1+2+4+8)
  • Si la barra es de color negro indica valores relativos (16+32+64+128)
Las casillas de verificación determinan:
  • No redimensionar verticalmente (512)
  • No redimensionar horizontalmente (256)
El cuadro combinado Configuraciones comunes (Common settings) tiene como objetivo ahorrar tiempo, brinda configuraciones posibles, lógicas y sin conflictos para cada tipo de control. Al seleccionar la opción deseada se actualizan el resto de los controles del editor. Contiene valores que dependen del tipo de control que se esté evaluando y se encuentra inhabilitado para selección múltiple, incluso si son controles del mismo tipo, ejemplo, dos botones de comandos.

Cada cambio que hagamos se verá reflejando en el cuadro de edición (editbox) con encabezado Valores de los bordes (Border values) y el número que va resultando de la suma y que es el valor que tomará la propiedad lo vemos junto a la etiqueta Anchor value
El botón de comando Ejemplo (Sample) muestra un formulario que al redimensionarlo ejemplifica el efecto que ese valor hará sobre el control determinado.

Hasta aquí algunas consideraciones sobre esta nueva propiedad que nos permite ahorrar mucho código al redimensionar objetos y contenedores de forma nunca antes vista.

Saludos,

Ana María Bisbé York
www.amby.net