30 de mayo de 2016

Desplazarse verticalmente en un Grid con la tecla Enter

Algunos usuarios de mis aplicaciones me plantean la posibilidad de desplazarse verticalmente en un Grid con la tecla Enter y que sea solo en la(s) columna(s) a editar.

Si existe esa posibilidad en VFP6 o en VFP8 no la he encontrado e hice lo siguiente:


Figura 1

Suponiendo que el control Grid se nombre Grid1.

  •  En el Evento Gotfocus de Grid1.Column1.Text1 teclear:
Thisform.Grid1.Column3.Text1.SetFocus
  •  En el Evento GotFocus de Grid1.Column2.Text1 teclear:
Thisform.Grid1.Column3.Text1.Setfocus 
  • En el Evento KeyPress de Grid1.Column3.Text1 teclear:
IF NkeyCode = 13
  SKIP
  Thisform.Grid1.Refresh
ENDIF

Y con esos 3 pasos nos moveremos hacia abajo en el control Grid con la tecla Enter.

Si las columnas a editar fueran mas de una, entonces el paso 1 se repite en las columnas informativas, pero con la diferencia que el SetFocus iría dirigido a la primera columna a editar.


Figura 2.

  • En el Evento Gotfocus de Grid1.Column1.Text1 teclear:
Thisform.Grid1.Column3.Text1.SetFocus 
  • En el Evento GotFocus de Grid1.Column2.Text1 teclear:
Thisform.Grid1.Column3.Text1.Setfocus 
  • En el Evento KeyPress de Grid1.Column4.Text1 teclear:
IF NkeyCode = 13
  SKIP
  Thisform.Grid1.Refresh
ENDIF

Ojo : Para evitar que las columnas se desplacen fuera de foco a la izquierda cuando se presiona la tecla Enter, el ancho del Grid debe ser un poco mayor que el ancho de todas las columnas.

Disculpen si me extendí en la explicación, la programación es mucho mas pequeña de lo que parece. Tengo otros truquitos más, posteriormente se los entregaré.

Saludos.

José David González Cardonell

24 de mayo de 2016

Cuadros de textos con indicios o "hints"

Un ejemplo de como hacer nuestros cuadros de textos con indicios.

* TextBoxes con Hints o "Indicios"

WITH CREATEOBJECT("xForm")
  .SHOW(1)
ENDWITH
RETURN


DEFINE CLASS xForm AS FORM
  HEIGHT = 170
  WIDTH  = 250

  ADD OBJECT txt1 AS HintTextBox WITH LEFT = 10, TOP = 10 , HintText = "Nombre de usuario", VALUE = "Usuario"
  ADD OBJECT txt2 AS HintTextBox WITH LEFT = 10, TOP = 40 , HintText = "Contraseña anterior"
  ADD OBJECT txt3 AS HintTextBox WITH LEFT = 10, TOP = 70 , HintText = "Nueva contraseña"
  ADD OBJECT txt4 AS HintTextBox WITH LEFT = 10, TOP = 100, HintText = "Confirmar la contraseña"

  ADD OBJECT cmdExit AS COMMANDBUTTON WITH LEFT = 70, TOP = 130, HEIGHT = 30, CANCEL = .T., CAPTION = "Salir"

  PROCEDURE cmdExit.CLICK
    THISFORM.HIDE()
  ENDPROC
ENDDEFINE


DEFINE CLASS HintTextBox AS TEXTBOX
  LabelName = ""
  HintText  = ""
  KeyPressed = .F.
  WIDTH = 200
  SELECTONENTRY = .T.

  PROCEDURE INIT
    LOCAL sName
    WITH THISFORM
      sName = SYS(2015)
      .ADDOBJECT(sName, "HintLabel", THIS.HintText)
      THIS.LabelName = sName
      WITH .&sName
        .TextBoxName = THIS.NAME
        .BACKCOLOR = THIS.BACKCOLOR
        .MOVE(THIS.LEFT + THIS.MARGIN + 1, THIS.TOP + THIS.MARGIN + 1)
        .WIDTH = THIS.WIDTH - THIS.MARGIN * 2
      ENDWITH
    ENDWITH
  ENDPROC

  PROCEDURE REFRESH
    WITH THIS
      .UpdateHint()
    ENDWITH
  ENDPROC

  PROCEDURE GOTFOCUS
    WITH THIS
      .UpdateHint()
    ENDWITH
  ENDPROC

  PROCEDURE LOSTFOCUS
    WITH THIS
      .KeyPressed = .F.
      .UpdateHint()
    ENDWITH
  ENDPROC

  PROCEDURE UpdateHint
    WITH EVALUATE([ThisForm.] + THIS.LabelName)
      .VISIBLE = EMPTY(THIS.VALUE) AND ! THIS.KeyPressed
    ENDWITH
    RETURN

  PROCEDURE INTERACTIVECHANGE
    WITH THIS
      .KeyPressed = .T.
      .UpdateHint()
    ENDWITH
  ENDPROC

ENDDEFINE

DEFINE CLASS HintLabel AS LABEL
  FORECOLOR = RGB(100, 100, 100)
  BACKSTYLE = 1
  TextBoxName = ""

  PROCEDURE INIT (sCaption)
    IF PCOUNT() > 0
      THIS.CAPTION = sCaption
    ENDIF
  ENDPROC

  * Hago al label "transparente" a los clicks
  PROCEDURE CLICK
    WITH EVALUATE([ThisForm.] + THIS.TextBoxName)
      .KeyPressed = .T.
      .UpdateHint()
      .SETFOCUS()
    ENDWITH
  ENDPROC

ENDDEFINE

*---

Mario López

22 de mayo de 2016

Alinear el texto verticalmente en los controles Labels en VFP 9

Si hay algo que siempre me ha molestado es que el texto de los controles LABEL no se puede alinear verticalmente.

Eso produce situaciones como la siguiente:

He añadido un borde a los controles LABEL para que pueda verse su tamaño. La solución que se me ocurre en estos casos es cambiar la altura del control, y alinear los centros horizontalmente con los controles TEXTBOX adyacentes:

Como esto me parece demasiado trabajo, siempre estuve pensando en encontrar una solución a este problema, y leo en algún lado que en VFP9 está la propiedad ROTATION del control LABEL, y que si se fija en 360 grados, el texto se centra verticalmente en la etiqueta! El problema es que el texto se centra también horizontalmente. Me pregunto ¿Por qué no respetará el valor de la propiedad ALIGNMENT?

Como la idea es que el texto quede alineado horizontalmente a la derecha, me pongo a pensar un poco, y decido añadir el siguiente código en el evento INIT de mi clase base LABEL:

If Not Pemstatus(_Screen,[lblVertAligned],5) Then 
_Screen.AddObject([lblVertAligned],[Label])
Endif
With _Screen.lblVertAligned
.Top = -100
.Visible = .T.
.AutoSize = .T.
.FontBold = This.FontBold
.FontItalic = This.FontItalic
.FontName = This.FontName
.FontSize = This.FontSize
.FontStrikethru = This.FontStrikethru
.FontUnderline = This.FontUnderline
.Caption = This.Caption
.Anchor = This.Anchor
This.Anchor = 0
This.Rotation = 360
This.Width = .Width
This.Anchor = .Anchor
Endwith

El objetivo de este código es fijar la propiedad ROTATION en 360, con lo que se logra el deseado centrado vertical y fijar el ancho del control LABEL para simular la propiedad AUTOSIZE, pero solamente en el sentido horizontal. De esa forma se logra que el texto parezca "alineado" a la izquierda.

La idea es en tiempo de diseño crear los objetos LABEL como se mostraba en la primera figura, obviamente sin los bordes:

Mi clase base TEXTBOX tiene una altura de 23 píxeles, para poder usar cómodamente la grilla estándard. Experimentando, comprobé que la alineación óptima se logra dando a los controles LABEL una altura de 24 píxeles. En tiempo de ejecución, el resultado es este:

He trazado una delgada línea roja en la imagen, para que se pueda comprobar que la alineación es perfecta.

Espero les sirva.

Carlos Alloatti


15 de mayo de 2016

DLLs de .Net en VFP

Hoy voy a tratar un tema ampliamente tratado ya, pero que no está muy unificado en la forma de plantearlo: La generación de DLLs en .Net y el consumo desde Visual FoxPro como objeto COM. Y no significa que este artículo plantee una unificación de todas las metodologías que se encuentran. Todo lo contrario, es otro caso particular, basado en mi experiencia personal sobre el tema. Simplemente ocurre que tengo planeado escribir algunos artículos sobre la interoperabilidad entre .Net y VFP, y estas especificaciones van a repetirse en todos ellos. Igualmente, este es un tema que se presta a experimentos de prueba y error, así que este artículo también puede servir como una alternativa más en la búsqueda de este objetivo que, al principio, genera sus dolores de cabeza, pero luego muestra sus beneficios y permite la escalabilidad de los sistemas unificando tecnologías que aporten, cada una, su mejor parte.

Antes que nada, les dejo algunos links que tratan del tema:

Otro paso importante, antes de poner manos a la obra, es describir el entorno sobre el que fue desarrollado este caso de prueba:

  • Microsoft Visual Studio 2010 Express: para desarrollar la librería de clases. Esto es necesario solamente en la máquina donde se compile la DLL. Se puede hacer con versiones anteriores. Los pasos que se explican en el resto del documento son idénticos para la versión 2005 y posteriores.
  • Framework de .Net 4.0: Esto se necesita tanto en la máquina donde se compila como en la del cliente que vaya a consumir la DLL. Se puede hacer con versiones anteriores (al menos, con la versión 2.0 puedo afirmar que funciona).
  • Visual FoxPro 9.0

Del lado desde el que se compila la DLL


Empezamos por crear una librería de clases con alguno de los lenguajes de .Net (C# para este ejemplo). Es importante que exista al menos una clase pública en la librería, para poder consumirla como objeto COM. A continuación se muestra un ejemplo básico de una clase con un atributo privado llamado propiedad el cual se manipula mediante otro miembro, pero de alcance público, llamado Propiedad (C# .Net diferencia mayúsculas y minúsculas):

namespace LibreriaNET
{
    public class ClaseNet
    {
        private string propiedad;

        public string Propiedad
        {
            get { return this.propiedad; }
            set { this.propiedad = value; }
        }
    }
}

Luego, es necesario realizar ciertos pasos de configuración sobre las propiedades del ensamblado que se va a generar. Para esto, ingresamos en la sección de propiedades de la librería, ubicada en el explorador de soluciones.

Todo cambio que se realice en la pantalla que va a aparecer se verá reflejado en el archivo AssemblyInfo.cs. Por lo tanto es indistinto trabajar sobre este archivo o sobre la interfaz gráfica.

La primer sección de las propiedades es Aplicación. Una de las características importantes de esta sección es la propiedad Nombre del ensamblado que es el nombre de la DLL, y se va a utilizar a la hora de instanciar las clases. Puede o no coincidir con otras propiedades como espacio de nombres predeterminado, título del ensamblado, etc.

En la misma sección, si se presiona sobre el botón Información del Ensamblado, se verá una pantalla con opciones como Título, Descripción, etc. La opción que nos interesa es Crear ensamblado visible a través de COM, la cual debe quedar seleccionada.

Esta acción es idéntica a modificar el archivo AssemblyInfo.cs para que incluya la siguiente línea:

[assembly: ComVisible(true)]

Con esto ya se logra que la librería pueda utilizarse como objeto COM. Sin embargo, es posible que las aplicaciones que quieran consumir la DLL, no encuentren la referencia a dicha librería. Una solución es registrarla en una especie de repositorio de librerías que maneja el framework de .Net, llamado GAC (Global Assembly Cache). Para esto, es necesario que la librería cuente con una firma que la identifique dentro del repositorio. Esta firma se genera como un archivo con extensión SNK. En la mayoría de los artículos que consulté, la opción utilizada es por línea de comandos, mediante sn -k firma.snk y luego se asocia al proyecto de .Net. En mi caso, suelo usar la interfaz gráfica directamente (no encontré diferencias).

Desde la sección Firma dentro de las propiedades del ensamblado se debe seleccionar la opción Firmar el ensamblado y seleccionar un archivo SNK del combo (o elegir crear una nueva firma).

Con esto se genera el archivo SNK y se asocia al proyecto. Solamente queda generar la DLL y el trabajo en la máquina en la que se genera la librería ya está terminado. Desde ahora, cada vez que se necesite modificar algo, no será necesario rehacer todos los pasos. Simplemente se cambia el código fuente y se vuelve a generar la DLL. Las propiedades y la firma se mantienen.

Del lado desde el que se consume la DLL


Ahora queda explicar la distribución de la DLL a cualquier máquina que no necesita contar con Visual Studio, pero sí con el framework de .Net, más precisamente para utilizar el comando RegAsm, usado para registrar la librería. Desde la línea de comandos y ubicados en la ruta donde se encuentra el archivo DLL, se ejecuta el siguiente comando:

c:\rutaDLL\>c:\WINDOWS\Microsoft.Net\Framework\V4.0.30319\RegAsm libreriaNet.dll /register /codebase /tlb

La ruta del comando RegAsm puede variar según la instalación del framework. El parámetro /codebase es el que indica que la DLL se va a registrar en la GAC, y esto es posible porque la librería fue firmada con un archivo SNK.

Cada vez que se quiera implementar una nueva versión, se deberá ejecutar nuevamente el mismo comando. Por otro lado, si se necesita, por alguna razón, remover la DLL del registro, se utiliza el comando regasm libreriaNet.dll /u.

Y ya solamente queda probar a consumir la clase creada. Desde Visual FoxPro se puede hacer con las siguientes líneas:

objNet = CREATEOBJECT("LibreriaNet.ClaseNet")
objNet.Propiedad = "Hola Mundo"
?objNet.Propiedad

Si no hay error, la ejecución debería ser exitosa, creando el objeto, seteando el valor de su atributo "propiedad" y mostrándolo por pantalla.

PabloLissa


Unas aclaraciones importantes:

  • Visual FoxPro no permite instanciar objetos de .Net utilizando un constructor con parámetros. Es decir, solamente permite utilizar el constructor por defecto de la clase.

    Lo extraño es que una línea como la siguiente:

     obj = CREATEOBJECT("LibreriaNet.ClaseNet", 1, .F., .T., "Parámetro")
    

    no arroja ningún error, lo que daría a pensar que si la clase tiene definido un constructor con 4 parámetros, el objeto estaría correctamente instanciado, pero no es así. En este caso, se omiten los parámetros y se ejecuta el constructor por defecto.

  • No se puede utilizar la sobrecarga de métodos. Si existen dos métodos con distinto número de parámetros, solamente se puede invocar al que se encuentre primero en el código fuente. Por ejemplo, si en la definición de la clase se hubiese incluido:
     public void funcion1(string s, string s2) { }
     public void funcion1(string s) { }
    

    solamente se podría usar la primera, con dos parámetros. La otra fallaría, mostrando el error "Código de estado COM desconocido"

    Si, en cambio, se hubiese definido:

     public void funcion1(string s) { }
     public void funcion1(string s, string s2) { }
    

    la función con un parámetro sería la que responda correctamente.

14 de mayo de 2016

Registrar un control ActiveX .NET

Artículo original: Registering the .NET ActiveX Control
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,df3f1be8-62bc-491c-935e-570ebfe53164.aspx
Autor: Craig Boyd
Traducido por: Ana María Bisbé York


Problemas con el registro

He recibido algunos comentarios y un par de mensajes referidos a cómo registrar adecuadamente el ActiveX Menustrip/Toolstrip (http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,3dd24f92-a52c-4bb0-8121-c2e6e2cc4f93.aspx). El problema es que incluso después de registrar el control con RegAsm, el desarrollador obtiene un error sobre que el control no se ha registrado, o un error de que un archivo especificado no se ha encontrado. El segundo de estos mensajes es más cercano a la verdad, ya que el problema es que incluso, aunque se ha registrado el control el sistema, no puede encontrado el ensamblado. Existen varias formas en las que se puede arreglar esto, algunas mejores que otras.

Primera forma (definitivamente una mala idea)

La primera forma  para hacerlo es colocarlo en la Global Assembly Cache (GAC) (http://www.codeproject.com/dotnet/DemystifyGAC.asp) que tiene dos problemas: es una mala idea estar mezclando su GAC o la de sus usuarios con un ensamblado casero y como el original que he liberado para descarga no está firmado con un nombre estricto, no funcionaría.

Segunda forma (probablemente una mala idea)

La segunda forma de hacerlo es utilizar el parámetro /codebase con regasm.exe. Para encontrar qué es el parámetro /codebase debe leer sobre el en Assembly Registration Tool (http://msdn.microsoft.com/library/en-us/cptools/html/cpgrfassemblyregistrationtoolregasmexe.asp).

El comando básico que realiza esto en la línea de comandos es:

regasm /codebase dotnetcontrol.dll

Justo en este caso de que usted no sepa, debe incluir toda la ruta tanto para regasm y dotnetcontrol.dll en el comando arriba. Entonces, para usted debe ser algo como esto:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe /codebase C:\DotNetControl\DotNetControl.dll

El problema con este procedimiento es que debido a que el ensamblado (dotnetcontrol.dll) no está firmado con nombre estricto, va a recibir el siguiente mensaje, que da miedo, cuando ejecuta el ensamblador con el parámetro /codebase

RegAsm : warning RA0000 : Registering an unsigned assembly with /codebase can ca
use your assembly to interfere with other applications that may be installed on
the same computer. The /codebase switch is intended to be used only with signed
assemblies. Please give your assembly a strong name and re-register it.
Types registered successfully

Ahora, yo personalmente pienso que el mensaje de aviso es un poco exagerado; pero si ellos dicen que el parámetro /codebase puede ser utilizado sólo para ensambladores con nombre estricto, entonces permítanme moverme hacia la tercera forma de hacerlo.

Tercera forma

Descargar el ensamblador que he regenerado. (http://www.sweetpotatosoftware.com/files/dotnetcontrol_strongname.zip) Está firmado con un nombre estricto así que puede utilizar el parámetro /codebase sin recibir ese mensaje aterrorizante. Si ya ha registrado alguno de mis entradas anteriores, entonces deshaga el registro. Entonces, registre este nuevo ensamblador utilizando los mismos pasos que di en la segunda forma, antes mostrada.

Cómo firmar un ensamblado con nombre estricto

Para aquellos que están trabajando en un proyecto C# y quieran saber cómo firmar su ensamblado con un nombre estricto, puede leer sobre esto en Signing an Assembly with a Strong Name (http://msdn.microsoft.com/library/en-us/cpguide/html/cpconassigningassemblystrongname.asp) Básicamente, la forma más sencilla es utilizar sn.exe (viene dentro del SDK) para crear un archivo clave de nombre estricto (.SNK). Entonces, coloque este archivo clave de nombre estricto en cualquier lugar de su proyecto. Finalmente, abra el archivo AssemblyInfo.cs en su proyecto y coloque la siguiente línea en el final (va a necesitar, o toda la ruta, o la ruta relativa del archivo clave dentro de las comillas):

[assembly:AssemblyKeyFileAttribute(@"sgKey.snk")] 

Puede regenerar su ensamblado y firmarlo con nombre estricto.

Compartiendo ideas

Al crear controles o ejemplos con .NET, ya sea un FLL, un control ActiveX, o cualquiera, vale la pena asignar los pasos para la instalación adecuada de runtimes o registros de control. Haber empleado unos minutos extras de mi parte, podrían haber ahorrado parte de su valioso tiempo. Pido disculpas y pienso que esta vez he aprendido mi lección. Muchas gracias a Bernard Bout y Gary Williams por ser los dos primeros desarrolladores en llamar mi atención sobre estos aspectos.

13 de mayo de 2016

Matar sus índices con REINDEX

Artículo original: Kill your index with REINDEX
http://www.foxpert.com/knowlbits_200708_2.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


Cuando usted lee sobre cómo reparar índices en foros de FoxPro, frecuentemente recibe el consejo de evitar REINDEX. La razón más frecuente es que REINDEX depende del encabezado, que podría estar corrupto. Esto es cierto; pero como la mayoría de los desarrolladores, no he visto una cabecera de índice corrupta en años. La cabecera se actualiza solamente cuando agrega una etiqueta. Como esto requiere un acceso exclusivo a la tabla, hay poca oportunidad de introducir errores debido a empleo de caché, problemas con acceso multiusuario, etc.

Sin duda alguna, REINDEX es una mala idea. Para crear un nuevo archivo CDX, Visual FoxPro tiene que eliminar el CDX antiguo. Durante un corto tiempo la tabla existe sin archivo CDX. Este momento es suficientemente corto, para causar un mal funcionamiento. Cuando se cancela REINDEX debido a un error, no va a restablecer el archivo CDX anterior. Como demuestra el siguiente ejemplo, al final tendrá una tabla sin índices.

Create Cursor curDemo (cID C(1))
Insert into curDemo Values ("A")
Insert into curDemo Values ("B")
Index on GetID(cID) Tag cID CANDIDATE
? ">", Key(1)
Reindex
? ">", Key(1)
plKill = .T.
Reindex
? ">", Key(1)

Procedure GetID(tcID)
  If Vartype(m.plKill) == "L"
    Return "A"
  Else
    Return m.tcID
  EndIf
EndProc

Ejecute este programa e ignore el mensaje de error. Puede ver que KEY(1) devuelve una expresión válida las dos primeras veces; pero nada la última vez. Si ha utilizado una tabla, notará que el archivo CDX no está. Este es el único problema cuando encuentra un error durante una operación de índices. Además de los problemas de memoria y conexiones de red, puede encontrar errores en índices candidatos y primarios cuando:

  • añadió un índice sin permitir a VFP verificar los datos existentes
  • ejecutó algún tipo de índice corrupto que permitió a VFP agregar múltiples registros con la misma llave o
  • tuvo un índice en una función que está provocando un error

En cualquier caso, es probablemente mejor evitar estos problemas y no emplear REINDEX en una aplicación en producción.

9 de mayo de 2016

Un gran selector de Color

Artículo original: A great Color Picker
http://weblogs.foxite.com/vfpimaging/archive/2008/01/17/5556.aspx
Autor: VFPIMAGING
Traducido por: Luis María Guayán


Recientemente el MVP Cetin Basoz envio un gran script que genera un excelente selector de color. Es tan bueno y útil, que lo añadí a mi menú de "Herramientas" de desarrollo.

¡Gracias Cetin!

Si está interesado, puede descargarlo de aquí: mousetracker.zip 1,16 KB


7 de mayo de 2016

Como imprimir campos BLOB de manera sencilla

Recientemente tuve que desarrollar un sistema del tipo medico, y en los requerimentos habia la necesidad de incorporar hasta 20 fotografias del paciente. Para el caso, use una tabla con campos BLOB que me ha parecido bastante practico de incorporar las imagenes. Anteriormente en otros proyectos los he usado y hasta la fecha he comprobado que son mas estables que los campos general.

Sin embargo, estas imagenes tambien tenian que ser impresas por lo que me di a la tarea de buscar como.

El problema era que el Report Writer no acepta campos BLOB, acepta campos GENERAL y FILE (imagenes en la ruta especifica). De manera que no habia forma de imprimirlas.

Como siempre, mi primer referencia es portalfox.com, ya que habia leido unos articulos de Cesar Ch. y de Calvin Hsia, asi como una referencia en MSDN. Sus ejemplos son excelentes, y su codigo muy bueno, sin embargo a la 1:30 de la madrugrada no me daba para mas, asi que en un arranque de inspiracion, hice algo mas simple...

  1. Abri la tabla con los campos blob
  2. Use la fucion STRTOFILE y converti el valor de los campos de nuevo en imagenes reales.
  3. Una vez hecho esto las tome al reporte y funciono.

El siguiente codigo muestra como se procesa. Este codigo va dentro de un boton de comando puesto dentro de la pestana que se muestran las fotos.

* Procedimiento para imprimir el las fotos como miniaturas
* Como estamos posicionados en el registro del paciente no necesitamos movernos a ninguna parte.
* Debemos convertir cada foto en una jpg independiente y grabarla al disco para despues 
* tomarla desde el reporte. En caso de que no exita foto, debemos poner una imagen generica...
*
* Eliminar las fotos anteriores creadas
ERASE photo1.jpg
ERASE photo2.jpg
ERASE photo3.jpg
ERASE photo4.jpg
ERASE photo5.jpg
ERASE photo6.jpg
ERASE photo7.jpg
ERASE photo8.jpg
ERASE photo9.jpg

backImage = 'noid.jpg'

* Convertir fotos a jpg de nuevo ya que estan en campos blob (caracteres o binarios)
lbFoto1 = IIF(EMPTY(photo1), 'noid.jpg', STRTOFILE(pacfotos.photo1, 'photo1.jpg'))
lbFoto2 = IIF(EMPTY(photo2), 'noid.jpg', STRTOFILE(pacfotos.photo2, 'photo2.jpg'))
lbFoto3 = IIF(EMPTY(photo3), 'noid.jpg', STRTOFILE(pacfotos.photo3, 'photo3.jpg'))
lbFoto4 = IIF(EMPTY(photo4), 'noid.jpg', STRTOFILE(pacfotos.photo4, 'photo4.jpg'))
lbFoto5 = IIF(EMPTY(photo5), 'noid.jpg', STRTOFILE(pacfotos.photo5, 'photo5.jpg'))
lbFoto6 = IIF(EMPTY(photo6), 'noid.jpg', STRTOFILE(pacfotos.photo6, 'photo6.jpg'))
lbFoto7 = IIF(EMPTY(photo7), 'noid.jpg', STRTOFILE(pacfotos.photo7, 'photo7.jpg'))
lbFoto8 = IIF(EMPTY(photo8), 'noid.jpg', STRTOFILE(pacfotos.photo8, 'photo8.jpg'))
lbFoto9 = IIF(EMPTY(photo9), 'noid.jpg', STRTOFILE(pacfotos.photo9, 'photo9.jpg'))

* Sin embargo dentro del reporte se vuelven a poner estas variable detectando si se 
* creo el archivo de manera correcta, para poder visualizarlo
REPORT FORM pacrepfotos NEXT 1 TO PRINTER PROMPT PREVIEW

Ahora, habria que hacer el reporte. Para ello, primero se declararon unas variables dentro de la seccion variables del reporte:

Basicamente en el valor a almacenar se puso la siguiente instruccion:

IIF( FILE('photo1.jpg'),'photo1.jpg','noid.jpg')

Luego, en el reporte, se hicieron las inscrustaciones del objeto OLE (imagen) para verlas asi:

Y en las propiedades:

Usamos la variable declarada anteriormente.

De esta forma es muy facil imprimir cualquier foto, ya que algunas veces no se toman todas las fotos (en el reporte se imprimen 9 a la vez), y si solamente fueron 7 u 8 las que se tienen almacenadas el resto quedan vacias.

Hacer esto, me tomo un poco de menos de media hora y pude irme a dormir tranquilo.

Para finalizar, he aqui las pantallas de como se ve ejecutandose...

Sergio Hugo Sanchez

3 de mayo de 2016

Problema con el alcance de la aplicación

Artículo original: Application scope gotcha
http://www.foxpert.com/knowlbits_200707_4.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


Visual FoxPro nos brinda flexibilidad para incrustar archivos en archivos APP / EXE. Otros lenguajes tienen esta capacidad; pero requieren que el desarrollador utilice funciones de recursos para acceder a estos archivos. En Visual FoxPro, por otra parte, el acceso a archivos incrustados ocurre completamente transparente. Cuando usted abre un archivo que tiene el mismo nombre que uno incrustado, Visual FoxPro utiliza el interno.

Solamente con una aplicación difícilmente pensará sobre este mecanismo. Sin embargo, con múltiples aplicaciones, el concepto de aplicación se torna cada vez más importante. Como Visual FoxPro ejecuta el código, está al tanto de qué código pertenece a qué aplicación. El código puede solamente acceder a archivos que estén incrustados en esa aplicación.

Por ejemplo, puede incluir un archivo DBF en una aplicación. Esta aplicación puede abrir el DBF sin ningún problema. Sin embargo, una segunda aplicación podría intentar abrir la misma tabla utilizando código como este:

USE DBF("alias") AGAIN IN 0

Esta línea de código podría fallar de repente, porque la segunda aplicación no tiene acceso al archivo DBF incrustado. ¿Por qué estoy hablando sobre esto? Por que Visual FoxPro 9 introdujo problemas en cuanto alcance de la aplicación, a aplicaciones que no antes no tenían problemas. Estoy hablando del nuevo motor de informe, que confía en tres aplicaciones externas para realizar su tarea.

En las versiones anteriores a la 9, frecuentemente incrustábamos los archivos FOXUSER para controlar la barra de herramientas de Presentación preliminar, por ejemplo, quitar el botón imprimir de esta barra de herramientas. La rutina de impresión primero cambiaba el archivo de recursos, imprimía el informe y restauraba el archivo de recursos. Si usted no cambia este código y pone solamente REPORT FORM ... PREVIEW, puede recibir un error en el método OpenResourceFile. El código que da problemas es aparentemente inocente:

select 0
use (set("RESOURCE",1)) again shared
set order to 1

Cuando VFP carga un archivo FOXUSER, siempre crea un índice temporal. El código funciona bien si se va a ejecutar en el mismo ámbito que su aplicación. Esto es parte de REPORTPREVIEW.APP. Lo que ocurre es lo siguiente:

Su aplicación carga el recurso incrustado y VFP crea un índice temporal. La aplicación ReportPreview intenta guardar y restaurar la posición de la ventana preliminar. La instrucción USE debe abrir el archivo de recursos actual. Una vez que el código preliminar se ejecute en un ámbito de aplicación diferente a su aplicación inicial, Visual FoxPro busca un archivo externo con el mismo nombre. Ahora pueden ocurrir dos cosas:

Si no existe un archivo externo con este nombre, Visual FoxPro lanza un mensaje de error, porque el archivo no ha podido ser encontrado. Si el archivo existe externamente, Visual FoxPro lo abre. Para Visual FoxPro, este archivo ahora es diferente al que abrió antes como archivo de recursos.

Visual FoxPro siempre abre las tablas una sola vez. Cuando usted abre una tabla en múltiples áreas o sesiones de datos, todas estas instancias se refieren en realidad a la entrada actual de la tabla. Esta es la razón, por la que hace falta USE AGAIN cuando abre una tabla en más de un área. La entrada de la tabla mantiene un registro de los archivos índices abiertos. Esta es la razón por la cual, al abrir nuevamente una tabla, hereda todos los archivos abiertos.

Cuando usted abre el archivo de recursos nuevamente, utilizando el código como el que fue mostrado antes, Visual FoxPro reutiliza la entrada existente de la tabla con el índice que VFP creó internamente. En nuestro escenario de un archivo de recursos incrustado, sin embargo, la sentencia USE crea una nueva tabla que no tiene un índice asociado. Consecuentemente la línea SET ORDER TO provoca un error. Este problema en particular se puede evitar desconectando el archivo de recursos mientras se accede al formulario de presentación preliminar.

Sin embargo, no se trata solamente de un error en la aplicación ReportPreview. Se trata de un error que puede ser aun más y más importante si incorporamos características a nuestra aplicación que se extiende a través de múltiples archivos APP. Los problemas de alcance de la aplicación vienen de muchas formas, incluyendo la restauración de parámetros como SET CLASSLIB, SET PROCEDURE, y así con todos los que pueden referirse a archivos incrustados.