29 de agosto de 2015

Texto justificado en informe de VFP

Hace unas semanas tuve la necesidad de imprimir texto justificado en un informe de VFP, el cual venia desde un campo memo, al no recibir una solución, decidi hacerlo yo mismo y ya he terminado. Coloco aqui la clase que realice y como la utilizo en mi aplicación.

Espero que le puedan dar uso y si es posible que la mejoraran.

Uso de la Clase:
LOCAL loJustify
loJustify = CREATEOBJECT("justify")
loJustify.cSalidaTexto = "salida.txt"
loJustify.set_textmerge_on
loJustify.dojustify(cTexto,nLong)
loJustify.set_textmerge_off
Donde:

cTexto = Texto que se quiere justificar
nLong = Longitud en caracteres a justificar

Despues Revisar el archivo salida.txt

**************************************************
*-- Class:        justify
*-- ParentClass:  custom
*-- BaseClass:    custom
*-- Author: Oscar Gonzalez Hernandez
*
DEFINE CLASS justify AS CUSTOM
  HEIGHT = 15
  WIDTH = 16
  *-- Contiene el texto ya justificado y se va acumulando
  *-- hasta contener todo el texto justificado.
  ctextoacumulado = ""
  NAME = "justify"
  *-- Contiene el nombre del archivo que se
  *-- creará con el texto justificado.
  csalidatexto = .F.
  *-- Separa el texto pasado como parámetro en diferentes
  *-- bloques si ses que se encuentran retornos de carro,
  *-- sino regresa el texto completo.
  PROCEDURE SEPARA
    LPARAMETERS tcParrafo,taBloques,tbBloques
    lnDimension = 0
    FOR i = 1 TO LEN(tcParrafo)
      IF ASC(SUBSTR(tcParrafo,i,1)) = 13
        lnDimension = lnDimension + 1
        DIMENSION laRetornos(lnDimension)
        laRetornos(lnDimension) = i
      ENDIF
    ENDFOR
    IF VARTYPE(laRetornos) = "U"
      tbBloques = .F.
      taBloques = tcParrafo
      RETURN @taBloques
    ENDIF
    lnInicio = 1
    FOR k = 1 TO ALEN(laRetornos)
      DIMENSION taBloques(k)
      taBloques(k) = SUBSTR(tcParrafo,lnInicio,laRetornos(k)-lnInicio)
      lnInicio = laRetornos(k)+1
    ENDFOR
    DIMENSION taBloques(k)
    taBloques(k) = SUBSTR(tcParrafo,lnInicio,LEN(tcParrafo)+1-lnInicio)
    tbBloques = .T.
    RETURN @taBloques
  ENDPROC
  *-- Ejecuta el proceso de justificado del texto separado
  *-- en bloques o texto único regresado por el método separa().
  PROCEDURE dojustify
    LPARAMETERS tcTextoJustificar,tnLongJustificado
    LOCAL lgBloques,lbBloques
    THIS.SEPARA(tcTextoJustificar,@lgBloques,@lbBloques)
    IF lbBloques && Se ha partido el exto en bloques
      FOR lnCont = 1 TO ALEN(lgBloques)
        THIS.ctextoacumulado = ""
        THIS.Justificar(lgBloques(lnCont),tnLongJustificado)
         TEXT
           <<This.ctextoacumulado>>
         ENDTEXT
      ENDFOR
    ELSE
      THIS.ctextoacumulado = ""
      THIS.Justificar(lgBloques,tnLongJustificado)
      TEXT
        <<This.ctextoacumulado>>
      ENDTEXT
    ENDIF
    RETURN
  ENDPROC
  *-- Método recursivo que justifica párrafo por párrafo
  *-- según la longitud de caracteres que se le indique.
  PROCEDURE justificar
    LPARAMETERS tcTexto,tnTamaño
    IF EMPTY(SUBSTR(tcTexto,tnTamaño,1))
      lcTextoJ = ALLTRIM(SUBSTR(tcTexto,1,tnTamaño-1))
      IF EMPTY(lcTextoJ)
        RETURN
      ENDIF
      tcTextoAlterno = SUBSTR(tcTexto,tnTamaño+1,LEN(tcTexto))
      IF !EMPTY(tcTextoAlterno)
        lcTextoAcumular = THIS.rellena(lcTextoJ,tnTamaño)
        THIS.ctextoacumulado = THIS.ctextoacumulado + lcTextoAcumular + CHR(13)
        tcTexto = SUBSTR(tcTexto,tnTamaño+1,LEN(tcTexto))
        THIS.justificar(tcTexto,tnTamaño)
      ELSE
        lcTextoAcumular = lcTextoJ
        THIS.ctextoacumulado = THIS.ctextoacumulado + lcTextoAcumular + CHR(13)
        tcTexto = SUBSTR(tcTexto,tnTamaño+1,LEN(tcTexto))
        THIS.justificar(tcTexto,tnTamaño)
      ENDIF
    ELSE
      lcChar = SUBSTR(tcTexto,tnTamaño,1)
      lnContador = tnTamaño
      DO WHILE !EMPTY(lcChar)
        lnContador = lnContador - 1
        lcChar = SUBSTR(tcTexto,lnContador,1)
      ENDDO
      lcTextoJ = ALLTRIM(SUBSTR(tcTexto,1,lnContador))
      tcTextoAlterno = SUBSTR(tcTexto,tnTamaño+1,LEN(tcTexto))
      IF !EMPTY(tcTextoAlterno)
        lcTextoAcumular = THIS.rellena(lcTextoJ,tnTamaño)
        THIS.ctextoacumulado = THIS.ctextoacumulado  + lcTextoAcumular + CHR(13)
        tcTexto = SUBSTR(tcTexto,lnContador+1,LEN(tcTexto))
        THIS.justificar(tcTexto,tnTamaño)
      ELSE
        lcTextoAcumular = lcTextoJ
        THIS.ctextoacumulado = THIS.ctextoacumulado + lcTextoAcumular + CHR(13)
        tcTexto = SUBSTR(tcTexto,lnContador+1,LEN(tcTexto))
        THIS.justificar(tcTexto,tnTamaño)
      ENDIF
    ENDIF
  ENDPROC
  *-- Rellena el párrafo cortado por el método justificar()
  *-- con el número de espacios correspondientes para crear
  *-- un parrafo de la longitud deseada.
  PROCEDURE rellena
    LPARAMETERS tcParrafo,tnLong
    IF LEN(tcParrafo) = tnLong
      RETURN tcParrafo
    ENDIF
    lnDimension = 0
    lcPalabra = ""
    FOR i = 1 TO LEN(tcParrafo)
      IF ASC(SUBSTR(tcParrafo,i,1)) = 32
        lnDimension = lnDimension + 1
        DIMENSION laEspacios(lnDimension)
        laEspacios(lnDimension) = i
      ENDIF
    ENDFOR
    IF VARTYPE(laEspacios) = "U"
      RETURN tcParrafo
    ENDIF
    lnInicio = 1
    FOR k = 1 TO ALEN(laEspacios)
      DIMENSION laPalabras(k)
      laPalabras(k) = SUBSTR(tcParrafo,lnInicio,laEspacios(k)-lnInicio)
      lnInicio = laEspacios(k)+1
    ENDFOR
    DIMENSION laPalabras(k)
    laPalabras(k) = SUBSTR(tcParrafo,lnInicio,LEN(tcParrafo)+1-lnInicio)
    lnTotalEspacios = tnLong - LEN(tcParrafo)
    lnEspaciosContados = 0
    DO WHILE !EMPTY(lnTotalEspacios)
      FOR l = 1 TO ALEN(laPalabras)-1
        laPalabras(l) = laPalabras(l) + " "
        lnTotalEspacios = lnTotalEspacios - 1
        IF EMPTY(lnTotalEspacios)
          EXIT
        ENDIF
      ENDFOR
    ENDDO
    lcParrafoFormateado = ""
    lcParrafoFormateado = laPalabras(1)
    FOR j = 2 TO ALEN(laPalabras)
      lcParrafoFormateado = lcParrafoFormateado + " " + laPalabras(j)
    ENDFOR
    RETURN lcParrafoFormateado
  ENDPROC
  *-- Activa la configuración de salida del texto justificado.
  PROCEDURE set_textmerge_on
    SET TEXTMERGE TO (THIS.csalidatexto) NOSHOW
    SET TEXTMERGE ON
  ENDPROC
  *-- Desactiva la configuración de salida del texto justificado.
  PROCEDURE set_textmerge_off
    SET TEXTMERGE TO
    SET TEXTMERGE OFF
  ENDPROC
ENDDEFINE
Saludos...

Oscar Gonzalez Hernandez
Mexico D.F

23 de agosto de 2015

Informes con imágenes variables

Introducción

Muchas veces necesitamos realizar un informe que contengan imágenes variables cuya ruta y nombre están contenidas en un campo de una tabla. En este breve ejemplo demostraremos lo fácil que es hacerlo con Visual FoxPro.

Manos a la obra

Lo primero que vamos a realizar es creación de una tabla llamada "Alumnos" con los campos necesarios para guardar la ruta y el nombre completo del archivo de la imagen (Figura 1).

 
Figura 1: Tabla de Alumnos

Las imágenes las tomaremos de la carpeta Samples\Data\Graphics ubicada en el directorio raíz de la instalación de Visual FoxPro en las versiones 7, 8 y 9 (Figura 2).


Figura 2: Imagenes de la carpeta "Samples\Data\Graphics\"

Para la crear la tabla y completar los campos utilizamos el siguiente código. Los nombres de cada alumno será el mismo nombre de la imagen sin la extensión.

LOCAL lcRuta, lcFoto, ln
*-- Creo una tabla de Alumnos
CREATE TABLE Alumnos (Numero i, Nombre c(10), Foto c(128))
*-- Recorro las imágenes de SAMPLES\DATA\GRAPHICS
lcRuta = HOME(2)+"DATA\GRAPHICS\"
lcFoto = SYS(2000,lcRuta + "*.GIF")
ln = 1
DO WHILE NOT EMPTY(lcFoto)
  INSERT INTO Alumnos (Numero, Nombre, Foto) ;
    VALUES (ln, JUSTSTEM(lcFoto), lcRuta + lcFoto)
  lcFoto = SYS(2000, lcRuta + "*.GIF", 1)
  ln = ln + 1
ENDDO

Diseñando el informe

El ejemplo de informe que vamos a elaborar es de un carnet (credencial, tarjeta de identificación) con el nombre y la imagen de cada alumno. Incluimos en el informe un control Imagen que escogemos del Menú -> Informe  -> Insertar control -> Imagen/ActiveX dependiente (Figura 3).


Figura 3: Control Imagen

En las propiedades del control Imagen seleccionamos el origen de la imagen como "Archivo" y escribimos el nombre de la tabla y campo que contiene la ruta y nombre de la imagen (Figura 4).


Figura 4: Propiedades del control Imagen

Una vez completados todos los campos que deseamos mostrar en el carnet, la vista previa del informe nos mostrará la imagen correspondiente de cada alumno (Figura 5).

REPORT FORM Carnet.frx NOCONSOLE PREVIEW


Figura 5: Vista previa del informe

El código de este pequeño ejemplo lo pueden descargar desde el siguiente enlace: Carnet.zip (3,41 KB)

Con esta simple técnica podemos insertar en nuestros informes logotipos de distintas empresas, firmas digitalizadas, fotografías, catálogos de productos, etc., cuyas rutas y nombres de archivo se encuentran almacenados en un campo de una tabla.

Hasta la próxima.

Luis María Guayán

22 de agosto de 2015

Día del año

Una función que retorna el correlativo del día del año de una fecha pasada como parámetro DiaDelAnio() y su inversa InvDiaDelAnio()
? DiaDelAnio(DATE())
? DiaDelAnio(DATE(2015,12,31))

? InvDiaDelAnio(1)
? InvDiaDelAnio(365)
? InvDiaDelAnio(60, 2012)


FUNCTION DiaDelAnio(tdFecha)
  *---
  * Retorna el día del año de la
  * fecha pasada como parámetro
  *---
  IF NOT VARTYPE(tdFecha)$"DT"
    tdFecha = DATE()
  ENDIF
  RETURN tdFecha - DATE(YEAR(tdFecha),1,1) + 1
ENDFUNC

FUNCTION InvDiaDelAnio(tnDia, tnAnio)
  *---
  * Retorna la fecha del día del
  * año pasado como parámetro
  * Inversa de la función DiaDelAnio()
  *---
  IF NOT VARTYPE(tnDia)$"N"
    RETURN NULL
  ENDIF
  IF EMPTY(tnAnio)
    tnAnio = YEAR(DATE())
  ENDIF
  RETURN DATE(tnAnio,1,1) + tnDia - 1
ENDFUNC
Luis María Guayán

11 de agosto de 2015

Marcas de agua en tus informes con GdiPlusX

Artículo original: Watermark your reports with GdiPlusX
http://weblogs.foxite.com/vfpimaging/2007/07/17/watermark-your-reports-with-gdiplusx
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Aquí está un ReportListener que genera imágenes como marcas de agua en informes.

Muy simple para configurar, solo establezca algunas propiedades, como la imagen del logo, el ancho, la altura y el porcentaje de transparencia.

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))

LOCAL loListener as ReportListener 
loListener = CREATEOBJECT("WatermarkListener")
loListener.LISTENERTYPE = 1
loListener.WaterMarkImage = ADDBS(HOME()) + "Graphics\Gifs\morphfox.gif"
loListener.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
loListener.WaterMarkTransparency = 0.25 && 0 = Transparente ; 1 = Opaco
loListener.WaterMarkWidthRatio = 0.75 && 0 - 1
loListener.WaterMarkHeightRatio = 0.75 && 0 - 1

* Ejecute el informe usando el nuevo motor de informes (la salida objeto-asistida)
REPORT FORM (ADDBS(HOME()) + "samples/solution/europa/employeesmd.frx") OBJECT loListener
RETURN

Abajo están algunos ejemplos generados con la clase WaterMarkListener. (El código fuente está al final de este artículo)

IMPORTANTE

Requiere VFP9 y GdiPlusX para funcionar.

¡Por favor asegúrese que tiene la última versión!

http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

1.

WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 1 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.90 && 0 - 1
WaterMarkHeightRatio = 0.90 && 0 - 1

2.

WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.50 && 0 - 1
WaterMarkHeightRatio = 0.50 && 0 - 1

3.

WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.10 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 1 && 0 - 1
WaterMarkHeightRatio = 1 && 0 - 1

4.

WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
WaterMarkWidthRatio = 0.75 && 0 - 1
WaterMarkHeightRatio = 0.75 && 0 - 1

_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) 
LOCAL loListener as ReportListener 
loListener = CREATEOBJECT("WatermarkListener")
loListener.LISTENERTYPE = 1
loListener.WaterMarkImage = ADDBS(HOME()) + "Graphics\Gifs\morphfox.gif"
loListener.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
loListener.WaterMarkTransparency = 0.25 && 0 = Transparent ; 1 = Opaco
loListener.WaterMarkWidthRatio = 0.75 && 0 - 1
loListener.WaterMarkHeightRatio = 0.75 && 0 - 1
* Ejecute el informe usando el nuevo motor de informes (la salida objeto-asistida)
REPORT FORM (ADDBS(HOME()) + "samples/solution/europa/employeesmd.frx") OBJECT loListener
RETURN
 
 
DEFINE CLASS WatermarkListener AS _ReportListener OF ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX"
  NewPage = .T.
  oGDIGraphics = NULL
  WaterMarkImage = ""
  WaterMarkType = 1 && 1 = Color ; 2 = Escala de grises
  WaterMarkTransparency = 0.50 && 0 = Transparente ; 1 = Opaco
  WaterMarkWidthRatio = 0.50
  WaterMarkHeightRatio = 0.50

  FUNCTION BEFOREREPORT
    DODEFAULT()
    This.oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New() && CREATEOBJECT('GPGraphics')
  ENDFUNC
  FUNCTION BEFOREBAND(nBandObjCode, nFRXRecNo)
    #DEFINE FRX_OBJCOD_PAGEHEADER 1
    IF nBandObjCode==FRX_OBJCOD_PAGEHEADER
      This.NewPage = .T.
      IF NOT This.IsSuccessor
        This.SharedGDIPlusGraphics = This.GDIPLUSGRAPHICS
      ENDIF
      This.oGDIGraphics.Handle = This.SharedGDIPlusGraphics
    ENDIF
    DODEFAULT(nBandObjCode, nFRXRecNo)
  ENDFUNC
  PROCEDURE RENDER(nFRXRecNo,;
      nLeft,nTop,nWidth,nHeight,;
      nObjectContinuationType, ;
      cContentsToBeRendered, GDIPlusImage)
    WITH _SCREEN.SYSTEM.Drawing
      IF This.NewPage
        LOCAL lnX, lnY, lnWidth, lnHeight
        lnX = (1 - This.WaterMarkWidthRatio) / 2
        lnY = (1 - This.WaterMarkHeightRatio) / 2
        lnWidth = This.WaterMarkWidthRatio
        lnHeight = This.WaterMarkHeightRatio
        * Creo un Rectangulo de tamaño 60% de la página del informe
        LOCAL loRect AS xfcRectangle
        loRect = .Rectangle.New(lnX * This.sharedPageWidth, ;
          lnY * This.sharedPageHeight, ;
          This.sharedPageWidth * lnWidth, ;
          This.sharedPageHeight * lnHeight)
        * Cargo el archivo de imagen a GDI+
        LOCAL loBmp as xfcBitmap
        loBmp = .Bitmap.New(This.WaterMarkImage)
  
        LOCAL loClrMatrix AS xfcColorMatrix

        IF This.WaterMarkType = 2 && 1 = Color ; 2 = Escala de grises
          loClrMatrix = .Imaging.ColorMatrix.New( ; 
            .33, .33, .33, 0 , 0, ; 
            .33, .33, .33, 0 , 0, ; 
            .33, .33, .33, 0 , 0, ;
            0, 0, 0, This.WaterMarkTransparency, 0, ; 
            0, 0, 0, 0, 0)
        ELSE
          loClrMatrix = .Imaging.ColorMatrix.New()
          loClrMatrix.Matrix33 = This.WaterMarkTransparency
        ENDIF 
        LOCAL loAttr AS xfcImageAttributes
        loAttr = .Imaging.ImageAttributes.New() 
        loAttr.SetColorMatrix(loClrMatrix)
        This.oGdiGraphics.DrawImage(loBmp, loRect, loBmp.GetBounds(), 2, loAttr)
        This.NewPage = .F.
      ENDIF
    ENDWITH
    DODEFAULT(nFRXRecNo,;
      nLeft,nTop,nWidth,nHeight,;
      nObjectContinuationType, ;
      cContentsToBeRendered, GDIPlusImage)
  ENDPROC
ENDDEFINE

5 de agosto de 2015

Formulario transparente mas no los controles contenidos

En la entrada Formularios transparentes vimos como darle distintos porcentajes de opacidad a los formularios, hasta convertirlos en transparentes.

En este caso hacemos 100% transparente el formulario, pero no así los objetos contenidos en él.


PUBLIC goMyForm
goMyForm = CREATEOBJECT("MyFormTopLevel")
goMyForm.SHOW(1)
RETURN

DEFINE CLASS MyFormTopLevel AS FORM
  HEIGHT = 266
  WIDTH = 386
  SHOWWINDOW = 2
  AUTOCENTER = .T.
  CAPTION = "Formulario Transparente"
  WINDOWTYPE = 0
  WINDOWSTATE = 0
  BACKCOLOR = RGB(255,0,255)
  NAME = "MyForm"

  ADD OBJECT text1 AS TEXTBOX WITH ;
    HEIGHT = 24, LEFT = 264, TOP = 24, WIDTH = 96, ;
    VALUE = "Hola!", ;
    NAME = "Text1"

  ADD OBJECT command1 AS COMMANDBUTTON WITH ;
    TOP = 60, LEFT = 264, HEIGHT = 48, WIDTH = 96, ;
    CAPTION = "Hola!", NAME = "Command1"

  ADD OBJECT pageframe1 AS PAGEFRAME WITH ;
    PAGECOUNT = 3, ;
    TOP = 24, LEFT = 24, WIDTH = 216, HEIGHT = 204, ;
    NAME = "Pageframe1", ;
    Page1.CAPTION = "Page1", ;
    Page1.NAME = "Page1", ;
    Page2.CAPTION = "Page2", ;
    Page2.NAME = "Page2"

  ADD OBJECT combo1 AS COMBOBOX WITH ;
    COLUMNCOUNT = 0, COLUMNWIDTHS = "", ROWSOURCETYPE = 1, ;
    ROWSOURCE = "Uno,Dos,Tres,Cuatro,Cinco", ;
    TOP = 120, WIDTH = 96, HEIGHT = 24, LEFT = 264, ;
    NUMBEROFELEMENTS = 0, STYLE = 2, ;
    FIRSTELEMENT = 3, NAME = "Combo1"

  PROCEDURE LOAD
    IF VAL(OS(3)) >= 5  && Windows 2000 o superior

      DECLARE GetWindowLong ;
        IN WIN32API ;
        AS __GetWindowLong ;
        INTEGER, ;
        INTEGER

      DECLARE SetWindowLong ;
        IN WIN32API ;
        AS __SetWindowLong ;
        INTEGER, ;
        INTEGER, ;
        INTEGER

      DECLARE SetLayeredWindowAttributes ;
        IN WIN32API ;
        AS __SetLayeredWindowAttributes ;
        INTEGER, ;
        INTEGER, ;
        INTEGER, ;
        INTEGER

      __SetWindowLong(THISFORM.HWND, -20, BITOR(IIF(__GetWindowLong(THISFORM.HWND, -20), 1, 0), 0x80000))
      __SetLayeredWindowAttributes(THISFORM.HWND, THISFORM.BACKCOLOR, 0, 1)
    ENDIF
  ENDPROC
ENDDEFINE

Luis María Guyán

4 de agosto de 2015

Una clase DataRepeater en Visual FoxPro

Articulo original: A Visual Foxpro DataRepeater Class
http://weblogs.foxite.com/bernardbout/archive/2008/08/26/6622.aspx
Autor: Bernard Bout
Traducido por: Luis María Guayán


Se trata de un control que va a mostrar sus datos en una secuencia repetida. A diferencia de una cuadrícula normal, este control puede mostrar varias líneas de un mismo registro, por lo tanto, tiene la capacidad de mostrar los datos en un contenedor desplazable sin restricción alguna sobre el número de campos a mostrar. Hay varios ejemplos de un DataRepeater que se pueden encontrar, pero este es mejor verlo que describirlo, así que aquí está mi control DataRepeater en acción.

Este control es muy fácil de instalar y utilizar como se verá. Solo algunos pasos y algunas propiedades a configurar. Así que aquí va.
Debido a la forma en que la clases está implementada en VFP, no se puede añadir un objeto a partir de un formulario, sólo de una clase, por lo tanto hay un paso adicional necesario para la creación de esta clase.
Abrir un nuevo formulario, modificar su tamaño y añadir un objeto DataRepeater como se muestra a continuación.


El formulario anterior tiene el control DataRepeater añadido. Esto es sólo un contenedor en el que desea colocar sus campos. Este control es DataBound para que todos los controles dentro de él, también puedan ser DataBound.
La forma más sencilla de añadir campos, es abrir una tabla en el Entorno de Datos del fomulario, y arrastrar y soltar los campos desde allí, al contenedor. La tabla puede ser eliminada del Entorno de Datos después de que se haya terminado de añadir los campos.
En la imagen que aparece a continuación he agregado los campos de la tabla CUSTOMER que se encuentra en el directorio \SAMPLES\DATA de VFP. Vea los campos añadidos para mostrar Memos en acción.

Ahora vuelva a ordenar y cambiar el tamaño de los campos, así también el tamaño del contenedor, según sea necesario. Una vez que esté satisfecho con el resultado, seleccione el contenedor y guardarlo como una clase - desde el menú Archivo | Guardar como una clase - y guardarlo en la biblioteca de clases bbDataRepeater. Yo he guardado mi clase como CustomerData.

El ejemplo de clase junto con los campos, ahora puede ser eliminado del formulario una vez que se haya guardado como una clase.
Luego inserte un objeto DataRepeaterGrid en el formulario. Ya que planeo tener 2 paneles para repetir los datos, necesito hacer mi formulario lo suficientemente grande como para que entre la cuadrícula que se ampliará en tiempo de ejecucuión para dar cabida a los 2 paneles. Esto depende del número de campos, ya que se usará el DataRepeater mas grande con los campos, es decir, la cuadrícula será más grande.

Una vez que se haya hecho, es cuestión de establecer algunas propiedades del objeto DataRepeaterGrid. Las propiedades que deben ser configuradas se muestran aquí como las hice.


Eso es todo. Ahora el formulario está listopara ejecutarlo ...

Al hacer clic en los botones se disparará el método ButtonClicked y puede ver que la clase conoce en que registro que se encuentra.

Descargue la clase y un formulario de ejemplo desde el siguiente enlace: bbDataRepeater.zip
A disfrutar.

1 de agosto de 2015

Cuadrículas, imágenes y "cuelgues"

Artículo original: Grids, images and a crash
http://www.foxpert.com/knowlbits_200807_1.htm
Autor: Christof Wollenhaupt
Traducido por: Luis María Guayán


En mi sesion "Crashing VFP and preventing crashes" mostré que exibiendo una imagen corrupta en una cuadrícula, Visual FoxPro se "cuelga". Realmente hay mas, ya que estos "cuelgues" no se limitan solo a las imágenes corruptas.

Ahora mismo estoy trabajando en un control que muestra imágenes como miniaturas. Eche un vistazo a la galería de fotos de Windows Vista para tener una impresión de como es el control. Hubieron algunas lecciones que aprendí escribiendo este control.

El uso de BackStyle_Access es una "buena" tecnología. Desafortunadamente, "bueno" no siempre se traduce muy bien como "rápido". Esto podría estar relacionado con el hecho de que muestro muchas imágenes en cada celda de la cuadrícula. Por ello, BackStyle_Access es llamado varias veces por cada celda. La cosa más extraña que sucede, es cuando el formulario con la cuadrícula no tiene el enfoque. En este caso, el método Access se dispara permanentemente. Moverse por la cuadrícula es lento. Luego la cuadrícula muestra nuevamente todas las imágenes cada vez que se cambia de columna o fila.

Por lo tanto, cambié el enfoque del tradicional DynamicCurrentControl por un contenedor para cada fila. La tarea de mostrar las imágenes es mucho mas rápido ahora. Por desgracia, Visual FoxPro se "culga" también fácil.

El control cuadrícula es muy sensible para mostrarse mientras tiene el foco. Esto es un problema conocido. Por ejemplo, cuando tengo que llamar al método Refresh, por lo general muevo el foco a algún control no visible, ejecuto Refresh y luego vuelvo el foco hacia la cuadrícula. No hacerlo puede causar que la cuadrícula no muestre los registros. Estos sólo aparecerán si hace Click en la cuadrícula y pulsa la tecla de flecha arriba.

Estos problemas no solo lo hace el Refresh, sino las operaciones de dibujo en general. En mi control imagen, yo uso el evento MouseWheel para cambiar el tamaño de la imagen. Haciendo cambios en RowHeight, en el ancho de la columna, potencialmente en la propiedad ColumnCount y también tener que volver a dibujar todas las imágenes de la cuadrícula. Tarde o temprano el formulario se "cuelga". Hasta el momento, una norma parece ser que la cuadrícula se "cuelga" más rápido mientras más optimizado es mi código. Yo opino que esto está relacionado con la cantidad de operaciones de dibujo, o quizás algunas acciones superpuestas.

De manera extraña, los "cuelgues" sólo ocurren cuando la cuadricula comienza la serie de eventos. El uso de la rueda del ratón en la cuadrícula y moverse en la cuadrícula muy rápidamente, ambos "cuelgan" a VFP. Pero si hago la misma acción desde un botón de comando fuera de la cuadrícula, hasta el momento no tengo ningún "cuelgue".