7 de junio de 2007

FontFace de Reportes

Me paso que todos mi reports estaban armados con la fuente 'Tahoma' y queria pasarlos a 'Arial'. Cuando termine el primero me di cuenta que necesitaba casi todo un día para terminar todos los .FRX, asi que, a facilitar la cosa.

En 7 lineas en la ventana de comando pude cambiar todas la fuentes de todos los títulos y textboxes de los informes de mi proyecto. Pueden ver en el codigo que pueden hacer varias otras cosas (ej. Cambiar el tamaño de las letras que reunan alguna(s) condicion(es), etc etc).

EL CODIGO:

SELECT NAME FROM project.pjx WHERE TYPE='R' INTO CURSOR tmp
SCAN ALL
 SELECT 0
 USE (tmp.name)
 REPLACE fontface WITH 'Arial' FOR !EMPTY(FONTFACE)
 USE IN (tmp.name)
ENDSCAN

Guillermo Gastón Giménez

4 de junio de 2007

Cliente-Servidor: Parametrización de Variables

Cuando manejamos la conexión a un DBMS (DataBase Management System) podemos mandar cualquier tipo de datos, no solo caracteres de texto. Aplicable a cualquier servidor de Base de Datos (Microsoft SQL Server, MySQL, PostgreSQL, Firebird, etc).

Es muy común que cuando empezamos a manejar datos de una fuente externa a VFP, es decir, a servidores de base de datos, nos encontremos con la limitación de que únicamente podemos mandar cadenas de texto en las consultas SQL.

Para apoyarnos un poco, tomaremos una típica consulta SQL...
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <NumeroInferior > ) 
        AND (iID < < NumeroSuperior > )
     FROM Cliente

Donde, obviamente, el "Numero Inferior" y "Numero Superior" serían datos variables a consultar, tomados de algún formulario de VFP.

Nuestro primer intento puede ser intentar formatear la cadena, de manera a que lo que se envía sea igual que la consulta deseada:
   lcConsulta = "  SELECT iID, cClave, cCliente, cRegistro "+
                      "      WHERE (iID > " + STR(thisform.txtNumerInferior.Value) + ")"+
                      "           AND (iID < " + STR(Thisform.txtNumeroSuperior.Value) + ")"+
                      "    FROM Cliente "

Lo anterior funcionará, ya que igual, tenemos una cadena de texto y los valores, que ya han sido convertidos a cadena serán enviados tal cual a nuestro servidor.

Pero eso, al parecer, no es eficiente... Quizás lo podríamos hacer mejor, no? Pues si, como vimos en un artículo pasado, podríamos usar bloques TEXT ... ENDTEXT, para mejorar nuestra presentación:
WITH ThisForm
   lnNumeroInferior = .txtNumeroSuperior.Value
   lnNumeroSuperior = .txtNumerSuperior.Value
ENDWITH

TEXT TO lcConsulta NOSHOW TEXTMERGE
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <<NumeroInferior>> ) 
        AND (iID < <<NumeroSuperior >> )
     FROM Cliente
ENDTEXT

Funciona, si, también hace lo que deseamos, enviará los datos con los valores que queremos sin ningún problema. Pero, hagamos esto un poco más complicado: ¿Qué pasará cuando querramos hacer una búsqueda en base a un dato de tipo caracter? Cambiando un poco el ejemplo, buscaremos el conjunto de datos que cumpla con la condición de encontrar los datos de un cliente con base a la clave (string) del mismo:
   SELECT iID, cCliente, cDireccion, dFechaAlta, yAdeudo
        FROM Cliente
        WHERE cClave = 'ALFKI'

Como vemos en la consulta, y en el caso específico de SQL Server, cuando se hace una query en base a datos de caracteres debemos encerrar dicho valor entre comillas sencillas, ¿sencillo? , ¿o no?
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW TEXTMERGE
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = '<<lcCliente>>'      
   ENDTEXT

Una vez más, vemos que el ejemplo, también funciona, pero tiene un detalle: debemos formatear cada consulta de acuerdo a los requerimentos de nuestro servidor, y según sea el caso, podría que fuera diferente de un servidor a otro, además de que por lo que a mí respecta, pienso que es bastante engorroso y podría evitarse.

La Parametrización al rescate

La forma sencilla de no tener complicaciones con los distintos tipos de datos de los servidores, y los distintos tipos de datos de VFP, resulta en usar la Parametrización, ¿y que es esto?, consiste en anteponer un símbolo de interrogación justo antes de una variable:
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW 
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = ?lcCliente
   ENDTEXT
   IF SQLExec(lnHandle, lcConsulta, lcCursor) > 0
     ***  ¿Manipular, mostrar los datos?
     *** SELECT (lcCursor)
   ELSE
      IF AERROR(laError) > 0
         Messagebox("Error en la consulta: "+laError[2])
      ELSE
         Messagebox("Error inesperado en la aplicación")
      ENDIF
   ENDIF   

Como podrás notar, en el bloque TEXT...ENDTEXT hemos prescindido de la función de TEXTMERGE, además de las comillas sencillas, y en su lugar, sólo hemos puesto un símbolo de cierre de interrogación.

¿Qué estamos haciendo?, cuando usamos la Parametrización, delegamos la función de formateo de variables al driver ODBC, VFP se encargará de comunicarle qué tipo de datos es, y casi por arte de magia, hará la conversión pertinente. ¿Sencillo?, asi es, y funcionará con todos los tipo de datos de VFP (no será el caso de objetos, no confundir). Haz el intento ;-).

Nota al margen

En los ejemplos arriba escritos hemos hecho solo consultas de datos, pero aplicará a cualquiera de los comandos de tipo DML (Data Manipulation Language) de SQL, tales como INSERT, UPDATE, DELETE, etc.:
  ** Insertar datos 
  TEXT TO lcInsert NOSHOW
    INSERT INTO Detalles (cClave, iProductoID, nCantidad, yPrecioVenta, dFechaVenta)
       VALUES (?lcClave, ?lnProductoID, ?lnCantidad, ?lyPreciodeVenta, ?ldFechaVenta)
   ENDTEXT
   IF SQLExec(lnHandle,lcInsert) > 0
      *** Datos insertados con éxito
      *** Messagebox("Datos insertados con éxito")
   ELSE
       IF AERROR(laError) > 0
          Messagebox("Error al insertar datos:"+laError[2])
       ELSE
          Messagebox("Error inesperado!!")
       ENDIF
   ENDIF

   ** Actualizar un registro 
  TEXT TO lcUpdate NOSHOW
     UPDATE Detalles SET nCantidad = ?lnCantidad 
       WHERE ( iMovimientoID = ?lnMovimiento )
  ENDTEXT
  IF SQLExec(lnHandle, lcUpdate) > 0
     *** Actualizados con exito
     *** Messagebox("Datos actualizados con éxito")
  ELSE
     IF AERROR(laError) > 0
        Messagebox("Error al actualizar el registro:"+laError[2])
     ELSE
        Messagebox("Error inesperado!!")
     ENDIF
  ENDIF

   ** Borrar registro(s) 
   TEXT TO lcDelete NOSHOW
     DELETE FROM Detalles WHERE (iFacturaID = ?lnFactura)
   ENDTEXT
   llSucess = .F.
   IF VERSION(2) >= 9
      *** Usar las nuevas características de VFP9 para conocer los registros afectados
      llSucess = SQLExec(lnHandle,lcDelete,NULL,aRowsAffected) > 0
      IF (llSucess)
         lcMessage = "Se han borrado "+STR(aRowsAffected[1,2] ) + ;
                           " artículos de la Factura "+STR(lnFactura)
      ENDIF
   ELSE
     llSucess = SQLExec(lnHandle,lcDelete) > 0
     lcMessage = "Se han borrado todos los articulos de la Factura "+str(lnFactura)
   ENDIF
   IF llSucess
      *** Registros borrados con exito
      *** Messagebox(lcMessage)
   ELSE
      IF AERROR(laError) > 0
        Messagebox("Error al intentar borrar los datos:"+laError[2])
      ELSE
        Messagebox("Error inesperado!!")
      ENDIF
   ENDIF

Espero que este artículo les sea de utilidad.

Espartaco Palma Martínez

8 de mayo de 2007

Diversión con Vista Aero y la función DwmExtend Frame IntoClientArea

Usted puede tener un poco de diversión con Windows Vista Aero utilizando la función API DwmExtendFrameIntoClientArea.

Calvin Hsia nos comenta en su Blog que con la función API DwmExtendFrameIntoClientArea (solo para Vista) podemos hacer que los bordes de VFP se pinten con el efecto de cristal alrededor del borde del zorro.

Podemos intentar lo siguiente en nuestros formularios:
Public loForm
loForm = CreateObject("MyForm")
loForm.Show
Define Class MyForm  As Form
  ShowWindow = 2 && formulario de nivel superior
  Procedure Init
    Declare Integer DwmExtendFrameIntoClientArea  In dwmapi ;
      Integer HWnd, String  @ daMargins
    cStr = Replicate(BinToC(100,"4rs"),4)
    This.BackColor = 0
    This.ForeColor = -1
    DwmExtendFrameIntoClientArea (This.HWnd, @cStr)
  Endproc
Enddefine

El enlace al el artículo original de Calvin Hsia es: Fun with Vista Aero using the DwmExtendFrameIntoClientArea

4 de mayo de 2007

Funciones API para la barra de tareas

Existen algunas funciones API que nos permiten ajustar algunas configuraciones de la Barra de Tareas de Windows.

En este artículo veremos la manera de mostrar, ocultar, habilitar, deshabilitar y hacer porcentualmente transparente la barra de tareas.

También podremos controlar los objetos contenidos en la barra de tareas, como el área de notificación (SysTray) y sus elementos, como los íconos y el reloj.

Guarde el siguiente código como TaskBar.PRG:

Define Class TaskBar As Custom

  Procedure Init
    Declare Integer FindWindow In "user32" ;
      String lpClassName, String lpWindowName

    Declare Integer FindWindowEx In "user32" ;
      Integer HWnd, Integer hWnd2, ;
      String lpsz1, String lpsz2

    Declare Integer GetWindowLong In "user32" ;
      Integer HWnd, Integer nIndex

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

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

    Declare Integer ShowWindow In "user32" ;
      Integer HWnd, Integer nCmdShow

    Declare Integer EnableWindow In "user32" ;
      Integer  HWnd, Integer fEnable

  Endproc

  Proc BarraDeTareaVisible(tlVisible)
    Local lnHWndShell_TrayWnd
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    If lnHWndShell_TrayWnd <> 0
      ShowWindow(lnHWndShell_TrayWnd, Iif(tlVisible, 1, 0))
    Endif
  Endproc

  Proc BarraDeTareaHabilitada(tlEnabled)
    Local lnHWndShell_TrayWnd
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    If lnHWndShell_TrayWnd <> 0
      EnableWindow(lnHWndShell_TrayWnd, tlEnabled)
    Endif
  Endproc

  Procedure RelojVisible(tlVisible)
    Local lnHWndShell_TrayWnd, lnHWndTrayNotifyWnd, lnHWndTrayClockWClass
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    lnHWndTrayNotifyWnd = FindWindowEx(lnHWndShell_TrayWnd, 0, "TrayNotifyWnd", 0)
    lnHWndTrayClockWClass = FindWindowEx(lnHWndTrayNotifyWnd, 0, "TrayClockWClass", 0)
    If lnHWndTrayClockWClass <> 0
      ShowWindow(lnHWndTrayClockWClass, Iif(tlVisible,1,0))
    Endif
  Endproc

  Procedure SystrayVisible(tlVisible)
    Local lnHWndShell_TrayWnd, lnHWndTrayNotifyWnd
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    lnHWndTrayNotifyWnd = FindWindowEx(lnHWndShell_TrayWnd, 0, "TrayNotifyWnd", 0)
    If lnHWndTrayNotifyWnd <> 0
      ShowWindow(lnHWndTrayNotifyWnd, Iif(tlVisible,1,0))
    Endif
  Endproc

  Procedure IconosVisible(tlVisible)
    Local lnHWndShell_TrayWnd, lnHWndTrayNotifyWnd, lnHWndSyspager
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    lnHWndTrayNotifyWnd = FindWindowEx(lnHWndShell_TrayWnd, 0, "TrayNotifyWnd", 0)
    lnHWndSyspager = FindWindowEx(lnHWndTrayNotifyWnd, 0, "Syspager", 0)
    If lnHWndSyspager <> 0
      ShowWindow(lnHWndSyspager, Iif(tlVisible,1,0))
    Endif
  Endproc

  Procedure InicioRapidoVisible(tlVisible)
    Local lnHWndShell_TrayWnd, lnHWndReBarWindow32, lnHWndToolbarWindow32
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    lnHWndReBarWindow32 = FindWindowEx(lnHWndShell_TrayWnd, 0, "ReBarWindow32", 0)
    lnHWndToolbarWindow32 = FindWindowEx(lnHWndReBarWindow32, 0, "ToolbarWindow32", 0)
    If lnHWndToolbarWindow32 <> 0
      ShowWindow(lnHWndToolbarWindow32, Iif(tlVisible,1,0))
    Endif
  Endproc

  Procedure BarraDeTareaTransparente(tnLevel)
    Local lnLevel, lnOldStyle, lnWndShell_TrayWnd
    lnLevel = 0xFF - Max(0,Min(Int(tnLevel*255/100),255))
    lnHWndShell_TrayWnd = FindWindow("Shell_TrayWnd", 0)
    If lnHWndShell_TrayWnd <> 0
      lnOldStyle = GetWindowLong(lnHWndShell_TrayWnd, -20)
      SetWindowLong(lnHWndShell_TrayWnd, -20, Bitor(lnOldStyle, 0x80000))
      SetLayeredWindowAttributes(lnHWndShell_TrayWnd, 0, lnLevel, 0x2)
    Endif
  Endproc

Enddefine
A continuación la instancia de la clase y algunos ejemplos de sus métodos.
*-- Instanciamos la clase TaskBar en TaskBar.prg
loTB = NEWOBJECT("TaskBar", "TaskBar.prg")

*-- Barra de tarea no visible
loTB.BarraDeTareaVisible(.F.)

*-- Barra de tarea visible
loTB.BarraDeTareaVisible(.T.)

*-- Area de notificación no visible
loTB.SysTrayVisible(.F.)

*-- Area de notificación visible
loTB.SysTrayVisible(.T.)

*-- Barra de tarea 75% transparente
loTB.BarraDeTareaTransparente(75)

*-- Barra de tarea 25% transparente
loTB.BarraDeTareaTransparente(25)

*-- Barra de tarea 0% transparente
loTB.BarraDeTareaTransparente(0)

Hasta la próxima,

Luis María

3 de mayo de 2007

Arreglar los bordes de los formularios en Windows Vista (Aero)

Calvin Hsia en su Blog nos explica que cuando se ejecuta un formulario de VFP, éste solicita a Windows crear una ventana, entonces configura el estilo del borde (BorderStyle) de la ventana. Al parecer, en Windows Vista con Aero (excepto como administrador), el estilo del borde no puede ser configurado después que la ventana se ha creado.

Para reproducir el problema, Calvin nos indica los siguientes pasos:

  1. Iniciar Visual FoxPro (Calvin ejecutó VFP7 lanzado a mediados de 2001)
  2. Crear un nuevo formulario
  3. En la ventana de propiedades, cambiar la propiedad BorderStyle a 1 ó 2
    • 0 = ningún borde (ningún problema, porque no hay bordes para dibujar)
    • 1 = fijo simple
    • 2 = fijo doble
    • 3 = ajustable (por omisión) (ningún problema, porque es por omisión)
  4. Guardar y ejecutar el formulario
  5. Mover el formulario por la pantalla de modo que los bordes y la barra de título deban ser repintadas

Resultado: La ventana se comporta como si no tuviera bordes, y no puede repintarlo.

Calvin continua diciendo que es bastante asombroso que funcionando bajo modo administrador, el problema desaparece. Quizás una aplicación ejecutada como no administrador, que cambia el estilo del borde puede ser un riesgo en la seguridad.

Cuando VFP ejecuta un formulario, lee las propiedades y las configura en el orden que se encuentran. Para un formulario, la pseudo propiedad DoCreate significa crear la ventana. La propiedad BorderStyle suele estar escrita después de DoCreate, así que no hace caso del ajuste de BorderStyle.

Calvin nos brinda las siguientes opciones para solucionar el problema:

  • Abrir el formulario como una tabla y manualmente colocar la propiedad BorderStyle antes de DoCreate.
  • Ejecutar el programa (ver artículo original) que corrige los archivos SCX colocando la propiedad BorderStyle antes de DoCreate.
  • Convertir los formularios de SCX a VCX (clases visuales), donde el BorderStyle se configura al momento de la creación de la ventana.
  • Esperar el lanzamiento del próximo SP de VFP.
  • Ejecutar Windows Vista sin la interface Aero.
  • Ejecutar la aplicación como Administrador.

El enlace al artículo original en el Blog de Calvin Hsia es: Fix your forms to paint borders correctly under Vista Aero. Allí se encuentra el procedimiento FixDir() que corrige todos los formularios SCX del directorio y subdirectorios pasado como parámetro.

NOTA: Esto también sucede en Visual Studio 2005 según comenta Calvin en la siguente entrada de su blog: Windows Vista Aero BorderStyle Paint problem as non Administrator