21 de julio de 2017

Imprimir un gráfico creado con SimpleChart

SimpleChart es una clase creada por Mike Lewis (http://www.ml-consult.co.uk) quien logra, mediante un grupo de propiedades, facilitar el trabajo con MSChart y posibilitar que se obtengan gráficos con mucha rapidez y sin complejidades.

Para ver todos los detalles relativos a esta clase ver:

SimpleChart revisado (Mike Lewis) Traducción
https://comunidadvfp.blogspot.com/2003/10/simplechart-revisitado.html

Nos había quedado pendiente aclarar cómo se pueden imprimir estos gráficos. Pues bien, la pregunta es ¿Se puede copiar un gráfico creado con SimpleChart en un informe de VFP?

¡¡Sí se puede!! Tal y como se indica en el segundo artículo, hay que invocar el método EditCopy del gráfico.

Aquí dejo un ejemplo concreto

* Copiar al Clipboard
THISFORM.Grafico.EditCopy
* Copiar a un bmp
THISFORM.cRutaGrafico = GETENV("Temp")+SYS(2015)+".bmp"
DO Graficos WITH (THISFORM.cRutaGrafico),""

Pego directamente el código del programa gráficos escrito por el compañero J. Enrique Ramos Menchaca, quien lo publicó en PortalFox y nos autorizó a modificarlo dadas las nuevas necesidades que yo tenía.

Cambios que hicimos:

  1. Incluir 2 parámetros: tcArchivo, tcreporte
    • tcArchivo - nombre del .bmp que se va a cargar en el objeto imagen del informe
    • tcreporte - nombre del reporte a llamar
  2. No emplear campo general y sí campo de caracteres con la ruta de la imagen a mostrar, mira donde dice código nuevo.

Lo único que necesitamos es copiar y pegar el programa gráfico en nuestra aplicación e invocarlo con los parámetros adecuados.

***********************************************
* Programa Graficos
* Autor: J. Enrique Ramos Menchaca
* Modificado por Jorge Mota y Ana María Bisbé York
***********************************************
LPARAMETERS tcArchivo, tcreporte
#DEFINE CF_BITMAP 2
#DEFINE OBJ_BITMAP 7
DO decl
LOCAL hClipBmp, lcTargetFile
*lcTargetFile = "C:\clipboard.bmp"
lcTargetFile = tcArchivo && "grafime.bmp"
= OpenClipboard (0)
hClipBmp = GetClipboardData (CF_BITMAP)
= CloseClipboard()
IF hClipBmp = 0 Or GetObjectType(hClipBmp) <> OBJ_BITMAP
  = MessageBox("No se encontro imagen bitmap en el Portapapeles.",;
    64, "Clipboard to BMP")
  RETURN .F.
ENDIF
= bitmap2file (hClipBmp, lcTargetFile)
= DeleteObject (hClipBmp)
***
* crear las referencias para los arreglos
* external array laAspecto, lavalor, laporci,;
* lavalor1, lavalor2, lavalor3, lavalor4
* Código original
*!* CREATE CURSOR GRAFICO (GRAFICA G)
*!* APPEND BLANK
*!* APPEND GENERAL GRAFICO.GRAFICA FROM "c:\clipboard.bmp"
*!* REPORT FORM GRAFICA preview
*!* USE IN grafico
*!* RETURN
* Código modificado
CREATE CURSOR GRAFICO (cRuta c(250))
APPEND BLANK
REPLACE GRAFICO.cRuta WITH tcArchivo
IF !empty(tcreporte)
  REPORT FORM (tcreporte) to print prompt noconso
ENDIF
USE IN GRAFICO
RETURN
***********************************************
* InitBitsArray()
***********************************************
PROCEDURE InitBitsArray()
  #DEFINE GMEM_FIXED 0
  LOCAL lnPtr
  pnBitsSize = pnHeight * pnBytesPerScan
  lnPtr = GlobalAlloc (GMEM_FIXED, pnBitsSize)
  = ZeroMemory (lnPtr, pnBitsSize)
  RETURN lnPtr
ENDPROC

***********************************************
* String2file
***********************************************
PROCEDURE String2File (hFile, lcBuffer)
  DECLARE INTEGER WriteFile IN kernel32;
    INTEGER hFile, STRING @lpBuffer, INTEGER nBt2Write,;
    INTEGER @lpBtWritten, INTEGER lpOverlapped
  = WriteFile (hFile, @lcBuffer, Len(lcBuffer), 0, 0)
  RETURN
ENDPROC

***********************************************
* Bitmap2file
***********************************************
PROCEDURE Bitmap2file (hBitmap, lcTargetFile)
  #DEFINE DIB_RGB_COLORS 0
  PRIVATE pnWidth, pnHeight, pnBitsSize, pnRgbQuadSize, pnBytesPerScan
  STORE 0 TO pnWidth, pnHeight, pnBytesPerScan, pnBitsSize, pnRgbQuadSize
  = GetBitmapDimensions(hBitmap, @pnWidth, @pnHeight)
  LOCAL lpBitsArray, lcBInfo
  lcBInfo = InitBitmapInfo()
  lpBitsArray = InitBitsArray()
  LOCAL hwnd, hdc, hMemDC
  hwnd = GetActiveWindow()
  hdc = GetWindowDC(hwnd)
  hMemDC = CreateCompatibleDC (hdc)
  = ReleaseDC (hwnd, hdc)
  = GetDIBits (hMemDC, hBitmap, 0, pnHeight, lpBitsArray,;
    @lcBInfo, DIB_RGB_COLORS)
  #DEFINE BFHDR_SIZE 14 && BITMAPFILEHEADER
  #DEFINE BHDR_SIZE 40 && BITMAPINFOHEADER
  LOCAL hFile, lnFileSize, lnOffBits, lcBFileHdr
  lnFileSize = BFHDR_SIZE + BHDR_SIZE + pnRgbQuadSize + pnBitsSize
  lnOffBits = BFHDR_SIZE + BHDR_SIZE + pnRgbQuadSize
  lcBFileHdr = "BM" + num2dword(lnFileSize) +;
    num2dword(0) + num2dword(lnOffBits)
  #DEFINE GENERIC_WRITE 1073741824 && 0x40000000
  #DEFINE FILE_SHARE_WRITE 2
  #DEFINE CREATE_ALWAYS 2
  #DEFINE FILE_ATTRIBUTE_NORMAL 128
  #DEFINE INVALID_HANDLE_VALUE -1
  hFile = CreateFile (lcTargetFile,;
    GENERIC_WRITE,;
    FILE_SHARE_WRITE, 0,;
    CREATE_ALWAYS,;
    FILE_ATTRIBUTE_NORMAL, 0)
  IF hFile <> INVALID_HANDLE_VALUE
    WAIT WINDOW "Storing to file..." NOWAIT
    = String2File (hFile, @lcBFileHdr)
    = String2File (hFile, @lcBInfo)
    = Ptr2File (hFile, lpBitsArray, pnBitsSize)
    = CloseHandle (hFile)
  ELSE
    = MessageBox("Unable to create file: " + lcTargetFile)
  ENDIF
  = GlobalFree(lpBitsArray)
  = DeleteDC (hMemDC)
  RETURN
ENDPROC

***********************************************
* Ptr2File
***********************************************
PROCEDURE Ptr2File (hFile, lnPointer, lnBt2Write)
  DECLARE INTEGER WriteFile IN kernel32;
    INTEGER hFile, INTEGER lpBuffer, INTEGER nBt2Write,;
    INTEGER @lpBtWritten, INTEGER lpOverlapped
  = WriteFile (hFile, lnPointer, lnBt2Write, 0, 0)
  RETURN
ENDPROC

***********************************************
* InitBitmapInfo
***********************************************
PROCEDURE InitBitmapInfo(lcBIHdr)
  #DEFINE BI_RGB 0
  #DEFINE RGBQUAD_SIZE 4
  #DEFINE BHDR_SIZE 40
  LOCAL lnBitsPerPixel, lcBIHdr, lcRgbQuad
  lnBitsPerPixel = 24
  pnBytesPerScan = Int((pnWidth * lnBitsPerPixel)/8)
  IF Mod(pnBytesPerScan, 4) <> 0
    pnBytesPerScan = pnBytesPerScan + 4 - Mod(pnBytesPerScan, 4)
  ENDIF
  lcBIHdr = num2dword(BHDR_SIZE) + num2dword(pnWidth) +;
  num2dword(pnHeight) + num2word(1) + num2word(lnBitsPerPixel) +;
  num2dword(BI_RGB) + Repli(Chr(0), 20)
  IF lnBitsPerPixel <= 8
    pnRgbQuadSize = (2^lnBitsPerPixel) * RGBQUAD_SIZE
    lcRgbQuad = Repli(Chr(0), pnRgbQuadSize)
  ELSE
    lcRgbQuad = ""
  ENDIF
  RETURN lcBIHdr + lcRgbQuad
ENDPROC

***********************************************
* num2dword
***********************************************
FUNCTION num2dword (lnValue)
  #DEFINE m0 256
  #DEFINE m1 65536
  #DEFINE m2 16777216
  LOCAL b0, b1, b2, b3
  b3 = Int(lnValue/m2)
  b2 = Int((lnValue - b3*m2)/m1)
  b1 = Int((lnValue - b3*m2 - b2*m1)/m0)
  b0 = Mod(lnValue, m0)
  RETURN Chr(b0)+Chr(b1)+Chr(b2)+Chr(b3)
ENDFUNC

***********************************************
* GetBitmapDimensions
***********************************************
PROCEDURE GetBitmapDimensions(hBitmap, lnWidth, lnHeight)
  #DEFINE BITMAP_STRU_SIZE 24
  LOCAL lcBuffer
  lcBuffer = Repli(Chr(0), BITMAP_STRU_SIZE)
  IF GetObjectA (hBitmap, BITMAP_STRU_SIZE, @lcBuffer) <> 0
    lnWidth = buf2dword (SUBSTR(lcBuffer, 5,4))
    lnHeight = buf2dword (SUBSTR(lcBuffer, 9,4))
  ENDIF
  RETURN
ENDPROC

***********************************************
* buf2dword
***********************************************
FUNCTION buf2dword (lcBuffer)
  RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ;
    Asc(SUBSTR(lcBuffer, 2,1)) * 256 +;
    Asc(SUBSTR(lcBuffer, 3,1)) * 65536 +;
    Asc(SUBSTR(lcBuffer, 4,1)) * 16777216
ENDFUNC

***********************************************
* num2word
***********************************************
FUNCTION num2word (lnValue)
  RETURN Chr(MOD(m.lnValue,256)) + CHR(INT(m.lnValue/256))
ENDFUNC

***********************************************
* decl
***********************************************
PROCEDURE decl
  DECLARE INTEGER GetActiveWindow IN user32
  DECLARE INTEGER GetClipboardData IN user32 INTEGER uFormat
  DECLARE INTEGER OpenClipboard IN user32 INTEGER hwnd
  DECLARE INTEGER CloseClipboard IN user32
  DECLARE INTEGER DeleteObject IN gdi32 INTEGER hObject
  DECLARE INTEGER GetWindowDC IN user32 INTEGER hwnd
  DECLARE INTEGER ReleaseDC IN user32 INTEGER hwnd, INTEGER hdc
  DECLARE INTEGER CreateCompatibleDC IN gdi32 INTEGER hdc
  DECLARE INTEGER DeleteDC IN gdi32 INTEGER hdc
  DECLARE INTEGER GlobalAlloc IN kernel32 INTEGER wFlags, INTEGER dwBytes
  DECLARE INTEGER GlobalFree IN kernel32 INTEGER hMem
  DECLARE INTEGER GetObject IN gdi32 AS GetObjectA;
    INTEGER hgdiobj, INTEGER cbBuffer, STRING @lpvObject
  DECLARE INTEGER GetObjectType IN gdi32 INTEGER h
  DECLARE RtlZeroMemory IN kernel32 As ZeroMemory;
    INTEGER dest, INTEGER numBytes
  DECLARE INTEGER GetDIBits IN gdi32;
    INTEGER hdc, INTEGER hbmp, INTEGER uStartScan,;
    INTEGER cScanLines, INTEGER lpvBits, STRING @lpbi,;
    INTEGER uUsage
  DECLARE INTEGER CreateFile IN kernel32;
    STRING lpFileName, INTEGER dwDesiredAccess,;
    INTEGER dwShareMode, INTEGER lpSecurityAttr,;
    INTEGER dwCreationDisp, INTEGER dwFlagsAndAttrs,;
    INTEGER hTemplateFile
  DECLARE INTEGER CloseHandle IN kernel32 INTEGER hObject
ENDPROC

***********************************************

Espero que sirva como complemento a la información que ya teníamos para trabajar con esta clase SimpleChart

Saludos,

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


17 de julio de 2017

Técnicas para una interfaz alternativa de Report Preview

Artículo original: Techniques for an alternative Report Preview UI
http://www.spacefold.com/colin/archive/articles/reportpreview/techniques.htm
Autor: Colin Nicholls
Traducido por: Ana María Bisbé York


Antes de comenzar

Un grupo importante de los debates sobre informes en VFP de estos últimos tiempos en Universal Thread (http://www.universalthread.com) se han referido al tema de la nueva pantalla de presentación preliminar (Report Preview) y cómo controlarla. Un aspecto particularmente debatido es el comportamiento de la barra de herramientas cuando empleamos REPORT FORM ... PREVIEW en una aplicación de nivel superior. Existe un bug en VFP 9.0 (vea el código para reproducirlo en http://www.spacefold.com/colin/archive/articles/reportpreview/repro.prg) que provoca que la barra de herramientas asociada sea visible, aunque inhabilitada a los clics del ratón. La funcionalidad existe a través del menú contextual de la ventana preliminar, pero esto se convierte en un problema de educación de los usuarios o hay que utilizar NOWAIT para forzar que la ventana preliminar se muestre como una forma no modal. ( Esto no es un gran problema, ahora que tenemos la cláusula NOPAGEEJECT en el comando report form. Muchas de las razones por la que la gente piensa que necesitan una ventana modal no se aplican.

No obstante de los bugs del producto, en mis sesiones me esfuerzo por mostrar que la presentación preliminar predeterminada es sólo eso - predeterminada - y que ahora, no sólo son posible formularios con soluciones alternativas, sino que son fáciles de implementar. Entonces, voy a mostrar hoy una interfaz de usuario alternativa para la ventana de presentación preliminar que puede utilizar y personalizar acorde a sus contenidos.

He aquí una presentación preliminar.

Esto es lo que estamos construyendo:

Es un formulario sencillo con algunos botones y un contenedor en el que reside un control forma (shape) en el que se generará una única página del informe. El formulario permite hacer acercamientos y alejamientos (zoom) y arrastrar la página, empleando el ratón, por dentro del contenedor.

Realmente, aquí no hay mucho que hacer.

Crear el marco preliminar

*------------------------------------------------
* Marco preliminar...
*------------------------------------------------
define class myFrame as Container
  Width = 296
  Height = 372
  Left = 12
  Top = 12
  SpecialEffect = 1
  BackColor = rgb(192,192,192)

  add Object canvas as myCanvas && vea debajo:
enddefine

*------------------------------------------------
* ...y su forma hija:
*------------------------------------------------
define class myCanvas as Shape
  Height = 330
  Width = 255
  Left = 20
  Top = 20
  BackColor = rgb(255,255,255)

Vamos a permitir que la forma se mueva dentro del marco añadiendo código al evento .MouseDown del objeto Shape.

  procedure MouseDown
    lparameters nButton, nShift, nXCoord, nYCoord

    offsetX = m.nXCoord - THIS.Left
    offsetY = m.nYCoord - THIS.Top
  
    THISFORM.MousePointer = 5
    do while mdown()
      THIS.Left = mcol(0,3) - m.offsetX
      THIS.Top = mrow(0,3) - m.offsetY
    enddo
    THISFORM.MousePointer = 0 
  endproc
enddefine

El .MousePointer de 5 es el puntero del ratón que muestra ambas flechas NSEO en lugar de un cursor normal. Puede verlo en la figura mostrada antes.

Crear la clase form

Ahora que ya tenemos el marco, podemos crear la clase form myPreview y colocarlo en el marco:

*------------------------------------------------
* La clase form:
*------------------------------------------------
define class myPreview as Form
  add object frame as myFrame && vea arriba

Junto con los botones de comandos:

  add object cmdPrev as myButton ;
    with Top=12, Caption = "Previous"
  add object cmdNext as myButton ;
    with Top=44, Caption = "Next"
  add object cmdZoomIn as myButton ;
    with Top=96, Caption = "Zoom In"
  add object cmdZoomOut as myButton ;
    with Top=128, Caption = "Zoom Out"
  add object cmdReset as myButton ;
    with Top=176, Caption = "Whole Page"
  add object cmdClose as myButton ;
    with Top=220, Caption = "Close"

La clase derivada myButton que he utilizado aquí es precisamente una plantilla para .FontName, .FontStyle y la posición .Left. En breve, vamos a implementar algunos .Click. Sea paciente.

Viendo cómo necesitamos interactuar con el motor de informes, vamos a darle al formulario un par de propiedades de usuarios: Un marcador de posición para una referencia a un objeto ReportListener, y una propiedad numérica para el número de página actual:

Listener = .null.
PageNo = 1

La llave para crear un formulario de presentación preliminar en VFP 9.0 es utilizar el método OutPutPage de ReportListener, pasándole tres parámetros:

  • El número de página a generar
  • El dispositivo u objeto para generar la presentación preliminar
  • Un número que indica el tipo de dispositivo u objeto que se está generando.

Ya que estamos generando un objeto Visual FoxPro - THIS.Frame.Canvas - el parámetro - tipo de dispositivo es 2. Vamos a tener el número de página en THIS.PageNo, y la referencia al ReportListener es THIS.Listener, así que la sintaxis completa será:

THIS.Listener.OutputPage(THIS.PageNo, THIS.Frame.Canvas, 2)

Vamos a colocar todo el código - con las condiciones de verificación de límites - en un método de usuario del formulario, OutPage():

procedure Outputpage()
  with THIS
    if not isnull(.Listener) and .Listener.PageTotal > 0
      .Listener.OutputPage(.PageNo, .Frame.Canvas, 2)
      .Caption = ;
        justfname(.Listener.commandClauses.file) ;
        + " - page " + trans(.PageNo)
    endif
  endwith
endproc

Como puede ver, vamos a utilizar la propiedad Caption del formulario para mostrar el nombre de archivo y el número de página actual, solo por dejar las cosas más sencillas. Para asegurar que la página de informe se generará cuando se dibuje el formulario, añadimos una llamada a nuestro método de usuario en el evento Paint del formulario:

procedure Paint()
  THIS.OutputPage()
endproc

Hacer que estos botones reaccionen al Clic

¿Recuerda aquellos botones de comando que agregamos al formulario? Tenemos un par para la navegación alrededor del informe. Es importante tener en cuenta las condiciones de límites entre datos. El máximo número de páginas que se puede generar está determinado por: ReportListener.PageTotal:

procedure cmdPrev.Click
  with THISFORM
    .PageNo = max(1,.PageNo-1)
    .OutputPage()
  endwith
endproc

procedure cmdNext.Click
  with THISFORM
    .PageNo = min(.Listener.PageTotal,.PageNo+1)
    .OutputPage()
  endwith
endproc

Tenemos además un par de comandos para ajustar el tamaño de la presentación preliminar. Es fácil: solamente cambiar el tamaño del Shape y llamar a OutPutPage para re-pintar:

#define ZOOM_SCALE 1.3

procedure cmdZoomIn.Click
  with THISFORM.Frame.Canvas
    .Width = int(.Width * ZOOM_SCALE)
    .Height = int(.Height * ZOOM_SCALE)
  endwith
  THISFORM.OutputPage()
endproc

procedure cmdZoomOut.Click
  with THISFORM.Frame.Canvas
    .Width = max(17,int(.Width / ZOOM_SCALE))
    .Height = max(22,int(.Height / ZOOM_SCALE))
  endwith
  THISFORM.OutputPage()
endproc

Como puede ver, estamos utilizando algunas condiciones de límite en el tamaño más pequeño de la Forma, explícitamente escrito en el código 8.5 x 11, sobre esto podíamos pensar en algo más.

En caso de que desee perder el acercamiento y el movimiento, es conveniente un botón para re-inicializar el tamaño y la posición de la forma (shape).

procedure cmdReset.Click
  with THISFORM.Frame.Canvas
    .Top = 20
    .Left = 20
    .Width = 255
    .Height = 330
  endwith
endproc

El botón Cerrar no es realmente necesario - el formulario tiene su cuadro Cerrar en el título que funciona perfectamente. Pero, permítanme agregarlo de todas formas:

procedure cmdClose.Click
  THISFORM.Release()
endproc

Probamos hasta ahora

Bien, ¡Lo hemos hecho!, ahora vamos a probarlo. Lo primero que necesitamos es crear un ReportListener

rl = newobject("ReportListener")
rl.ListenerType = 3

Vea que estamos utilizando un ReportListener igual a 3. Un ListenerType igual a 1 provocaría que se invocara el contenedor preliminar predeterminado que no es lo deseado en este caso: Deseamos solamente generar la salida y guardarla en memoria temporal (caché) para nuestro propio empleo.

Ahora, necesitamos el motor de informes para que envíe un informe a nuestro listener:

report form ? object m.rl

Escoja cualquier informe que desee. (Yo escogí _SAMPLES+"\Solution\Reports\wrapping.frx" porque es bonito.) Ahora debemos instanciar nuestra Interfaz de usuario y asignarle una referencia a nuestra instancia ReportListener, y pedirle que lo muestre:

x = newobject("myPreview")
x.Listener = m.rl
x.Show(1)

Esperemos que en este punto tengamos algo como lo que tenemos en la figura mostrada antes.

  • Intente navegar por el informe.
  • Intente hacer Zoom.
  • Trate de arrastrar la página.

Un pequeño problema

Bien, confieso que he omitido ante un aspecto importante. (Los escritores de artículos hacen así todo el tiempo, porque piensan que es un buen recurso didáctico.)

Como probablemente ha notado, el método ReportListener.OutputPage() utiliza la posición y las dimensiones del objeto Shape para generar la página del informe sobre la superficie del formulario. En realidad no dibuja el objeto como tal.

Como resultado, la página sobre-escribe los otros controles en el formulario, produciendo un efecto visual desconcertante. Vea la imagen.

Existe una solución: tenemos que utilizar parámetros adicionales a OutPutPage() para especificar los límites dentro de los que se va a dibujar la imagen.

Especificar un área fija para OutputPage()

La documentación para el método OutPutPage (ver ayuda VFP) indica que los parámetros adicionales no se aplican para un tipo de dispositivo igual a 2 (2 = objeto FoxPro) Esto no es estrictamente correcto. En realidad, los parámetros 4to, 5to, 6to y 7mo se ignoran; pero los parámetros nClipLeft, nClipTop, nClipWidth, y nClipHeight se respetan por el método ReportListener.OutputPage() para objetos FoxPro

Estos cuatro parámetros finales definen una "ventana" - en pixels - en el área del formulario a través de la cual la salida regenerada va a aparecer. Cualquier cosa que quede fuera de esta área no se mostrará en el formulario.

Afortunadamente para nosotros, tenemos la forma conveniente para determinar la ubicación y dimensiones de área fija: el objeto frame como tal nos lo dirá.

Substituya la línea de código resaltada en negrita en el método OutPutPage del formulario para que sean especificados los parámetros adicionales:

* .Listener.OutputPage( .PageNo, .Frame.Canvas, 2 )
.Listener.OutputPage( ;
.PageNo, .Frame.Canvas, 2, ;
  0, 0 , 0 , 0 , ;
  .Frame.Left +2, ;
  .Frame.Top +2, ;
  .Frame.Width -4, ;
  .Frame.Height -4 )

Ahora el formulario preliminar debe trabajar como esperamos (vea la siguiente figura)

Ejercicios para estudiantes:

Considero que he mostrado propiedades excitantes de las posibilidades existentes. Pero no hemos hecho todo: existen algunas cosas adicionales que va a necesitar hacer antes de emplear este código en una aplicación en producción.

Debe:

  • Ajustar el código en el evento MouseDown del objeto Shape para evitar que el ratón se arrastre fuera de los límites del borde del marco.
  • Ajustar la proporción del objeto Shape para que se corresponda con las dimensiones del informe, utilizando los métodos .GetPageWidth() y .GetPageHeight() del ReportListener.
  • Permitir al formulario mostrar una escala apropiada para el nivel DPI de la pantalla, incluyendo una opción "Zoom al 100%"
  • Ajustar el código de alejamiento-acercamiento de tal forma que el centro del área visible del objeto Forma permanezca centrado en lugar de moverse basado en la posición de la esquina superior izquierda como ocurre actualmente
  • Posiblemente establecer el nivel máximo y mínimo para el zoom.

Puede además:

  • Hacer que el formulario sea redimensionable

Y quizás, lo que puede ser aun más útil, podría agregar además código adicional a la clase, para que conecte en el sistema de informes de Visual FoxPro, proporcionando un Contenedor preliminar (Preview container) alternativo cuando SET REPORTBEHAVIOR 90 está activado.

... oh, está bien. He aquí cómo se hace:

Empaquetado como Contenedor preliminar

Necesitará agregar estos métodos en la clase myPreview

*-------------------------------------------
* Métodos requeridos para que opere como 
* un contenedor preliminar :
*-------------------------------------------
procedure Release()
  if not isnull(THIS.Listener)
    THIS.Listener.OnPreviewClose(.F.)
    THIS.Listener = .null.
  endif
  THIS.Hide()
endproc

procedure Destroy()
  THIS.Listener = null
  dodefault()
endproc

procedure QueryUnload()
  nodefault
  THIS.Release()
endproc

procedure SetReport( oRef )
  if not isnull(oRef) and vartype(oRef) = "O"
    THIS.Listener = m.oRef
  else
    THIS.Listener = .null.
    THIS.Hide()
  endif
endproc

Deseará además colocar el código al inicio del archivo, de tal forma que solamente apunte a _REPORTPREVIEW directamente al programa.

*------------------------------------------
* Utilizado como contenedor preliminar:
* _REPORTPREVIEW = "<this program>"
*
* Comprobación fuera del sistema de informes:
* DO <este programa>
*------------------------------------------
lparameters oRef
if pcount()=0
  rl = newobject("ReportListener")
  rl.ListenerType = 3
  report form ? object m.rl
  x = newobject("myPreview")
  x.Listener = m.rl
  x.Show(1)
  return
else
  oRef = newobject("myPreview")
endif
return

¡Que lo disfrute!

He aquí el código fuente para este artículo: http://www.spacefold.com/colin/archive/articles/reportpreview/tech_src.prg

Copyright (c) 2005 Colin Nicholls

11 de julio de 2017

Observaciones sobre etiquetas alineadas a la derecha en informes de VFP 9.0

Artículo original: Observations on Rightaligned labels in VFP9 Reports
http://www.spacefold.com/colin/archive/articles/vfp9reporting/rightalign/monofonts.html
Autor: Colin Nicholls
Traducido por: Ana María Bisbé York


Introducción

Este artículo se basa en un trabajo que realicé tratando de contestar a una pregunta realizada por Alejandro Sosa en Universal Thread. Alex estaba teniendo problemas al alinear correctamente a la derecha títulos con campos/expresiones alineados a la derecha. Su informe era complejo y migraba desde una versión antigua de FoxPro, por lo que he creado una prueba sencilla para mostrar lo que ocurría.

Revisando: REPORTBEHAVIOR

En Visual FoxPro 9.0, existe un nuevo motor de informes que emplea GDI+ para generar la salida. Sin embargo, está soportado el motor viejo, con compatibilidad hacia atrás. Puede alternar entre los dos motores al vuelo empleando el comando SET REPORTBEHAVIOR.

Revisando: Fuentes con un único espacio contra fuentes proporcionales

Las fuentes que emplean igual espaciado tienen un ancho constante para todos los caracteres en dependencia de una altura dada. Las fuentes proporcionales utilizan anchos variables apropiado para hacer la fuente más legible. En este artículo verá la diferencia de forma muy simple: Los textos son proporcionales y los trozos de código están todos en formato de espacio único.

Resumen

En REPORTBEHAVIOR=90, todas las etiquetas de texto se van a generar con longitudes mayores que las que puede esperar al ver el Diseñador de informes. Esto se debe a que la generación de texto con GDI+ emplea más espacio. Las etiquetas sufren por esto, debido a que los diseños almacenan su posición inicial exacta, lo contrario a los campos / expresiones que permiten calcular la alineación a realizar por el motor, utilizando GDI+.

Lea más para detalles.

Observaciones

Datos de prueba

Quise comparar la alineación a la derecha para ambos tipos de valores: cadena y números, así que creé dos columnas en mi cursor de pruebas:

CREATE CURSOR x ( ftitle C(10), fnumb N(8,2) )
INSERT INTO x VALUES ( 'Bagels', 32.45 )
INSERT INTO x VALUES ( 'Cucumber', 5.23)
El informe de prueba

He aquí una imagen del informe de prueba, abierto en el diseñador, con los controles etiqueta seleccionados para mostrar sus posiciones exactas. He empleado líneas rojas para mostrar los ejes de alineación:

El control campo/expresión para los campos FTITLE y FNUMB tienen que ser configurados con Justificación: Derecha, el campo tiene que ser configurado manualmente: el campo numérico está alineado a la izquierda predeterminado. Puede ver que tengo visualmente alineadas las etiquetas con el final de cada control de campo/expresión. He duplicado el diseño de estos controles para cada una de las fuentes diferentes. Tahoma y Candara son fuentes proporcionales, y Courier New, Bitstream Vera Sans Mono y Consolas son mono espaciadas.[1]

Con REPORTBEHAVIOR 80

Así se ve la Presentación preliminar con REPORTBEHAVIOR = 80:

Y el resultado impreso

Asumiendo que las líneas rojas son una referencia exacta, se puede ver que los mejores resultados se ven con Courier New y Vera Sans Mono. Consolas y las fuentes proporcionales al parecer exceden sus posiciones esperadas ligeramente. Por tanto, debe estar muy ajustada la tolerancia para una salida aceptable, al utilizar estos tipos de fuente, o será un gran problema.

Con REPORTBEHAVIOR 90

He aquí cómo se ve la Presentación preliminar con REPORTBEHAVIOR = 90:

y el resultado impreso

Conclusiones

Es fácil ver desde la comparación que con REPORTBEHAVIOR = 90, todas las etiquetas de texto exceden su longitudes esperada, no sólo en las fuentes no espaciadas. Sin embargo, el efecto es definitivamente más pronunciado en fuentes mono espaciadas.

¿Por qué ocurre esto para etiquetas y no para para campos/expresiones?

Con REPORTBEHAVIOR = 90, el nuevo motor de informe utiliza GDI+ para generar la salida, y la generación de las cadenas de textos necesitan más espacio que la forma plana del viejo GDI. Escribí sobre por qué GDI+ necesita espacio adicional antes de las cadenas de texto: http://www.spacefold.com/colin/posts/2005/08-18GDIplusinreports.html

El Diseñador de informe utiliza GDI - no GDI + para generar los componentes del diseño del informe, incluyendo todas cadenas de texto que ve. Entonces, si visualmente ha justificado a la derecha un elemento de informe, el diseñador de informe guarda la coordenada más izquierda del elemento (la posición inicial del texto) en el diseño. La longitud de la cadena generada por GDI+ será mucho más grande que lo que cree, basada en que es lo que ve en el Diseñador.

No hay dudas del hecho de que tiene que tener en cuenta este efecto al diseñar sus controles etiqueta en los informes.

Ahora, verá que este problema no ocurre con los controles campo/expresiones. Ellos están todos alineados correctamente, debido a que el Diseñador de Informe, no especifica una posición exacta para la cadena de texto, pero sí especifica la caja donde el texto va a ser generado. Entonces, el cálculo de la alineación tiene lugar en el motor de informe, mientras GDI+ tiene en cuenta el tamaño del texto.

Notas al pie:

[1] Candara y Consolas son fuentes nuevas de Microsoft, distribuidas con Windows Vista.

Agradecimientos:

Gracias a Alex Sosa por su pregunta tan interesante.

7 de julio de 2017

VFP9 - Mejoras realizadas al Diseñador de Informes

Tanto el Motor de Informes, como su Diseñador en VFP 9.0, son los aspectos sobre los que más cambios se han incorporado. Como respuesta a la retroalimentación hecha por los usuarios, Microsoft ha mejorado significativamente el Generador de informes en VFP 9.0, ha velado por proteger todo el trabajo invertido, por lo que no modificó la estructura actual del archivo FRX.

Nuevo comando SET REPORTBEHAVIOR

Es como un interruptor que enciende o apaga la salida asistida por objetos.

Admite como parámetros, los valores 80 ó 90

Dado que antes se empleaba GDI y ahora GDI+ hay diferencias en el interlineado, alineación y espaciado. Eso puede afectar a informes existentes. Por tanto el valor predeterminado es 80. La asignación desde Herramientas - Opciones - Informes es Global; pero aun así se puede cambiar para cada informe que haga falta de forma específica.

El nuevo generador de informes se llama ReportBuilder.APP y viene con VFP 9.0.

Algunos de los logros han sido:

1. Mejorar el interfaz de usuario:

La interfaz de usuario ha sido mejorada en varios aspectos, que al sumarlos representan un importante ahorro de tiempo, veamos en detalles las diferencias entre VFP 8.0 y VFP 9.0.

- Menú contextual mejorado

El Menú contextual del Generador de informes en VFP 9.0 agrega las opciones: Bandas opcionales, Variables y Propiedades.

- Nueva ventana propiedades de Informes

Por su parte la nueva ventana Propiedades de Informe utiliza un marco de página y páginas para todos los aspectos de propiedades de informe. Esto agiliza el trabajo, ya que no hay que estar abriendo y cerrando cuadros de diálogo. Muchas de estas páginas agrupan a ventanas que ya existían en versiones anteriores como ventanas independientes. Tal es el caso de Cálculos, Condición. Pero hay otras: Protección, Entorno de datos, y Otros que son nuevas en este generador.

- Rediseñadas las ventanas Propiedades de campo y banda

En VFP 8.0 la ventana propiedades de un campo se corresponde con la ventana expresión de informe mostrada más adelante en este mismo escrito. Por su parte, VFP 9.0 ofrece una nueva ventana de Propiedades de campo, cuyo formato es también un marco de páginas con páginas que determinan los valores para los diferentes aspectos relativos al campo. El diseño actual es muy práctico e intuitivo.

Las bandas en VFP 9.0 adquieren una nueva dimensión y en el Diseñador se ha incluido una nueva ventana Propiedades para cada banda. El nuevo diseño comprende no sólo las bandas de detalle, sino todas las bandas del informe. Veamos ambos casos.

- Cambios en el Cuadro de diálogo generador de expresiones.

El primer cambio que salta a la vista es la mayor amplitud para la expresión, tanto en la ficha General como en el generador de expresiones.

- Nuevo Cuadro de diálogo selección múltiple

En VFP 9.0 existe el cuadro de diálogo Selección múltiple que permite establecer las propiedades Protección e Imprimir cuando para más de un objeto de diseño a la vez. Permite además cambiar cualquier otra propiedad de Protección a un objeto individual. Para utilizar esta nueva característica, seleccione más de un objeto, y luego haga doble clic en cualquiera de ellos para llamar al cuadro de diálogo Selección múltiple. Resulta muy productivo sobre todo el haber incluido la posibilidad de dar valor a la condición de impresión en el cuadro de texto Imprimir cuando.

- Nueva opción en el Menú Archivo

Este menú contiene una nueva opción Guardar como clase. El objetivo es guardar el Entorno de datos como clase, lo que constituye, a su vez, una de las novedades aportadas al Motor de informes en VFP 9.0. Sobre el tratamiento del Entorno de datos, ver: Visual FoxPro 9.0 - Novedades - Manipulación de entorno de datos en el diseñador de Informes

- Nueva opción en el Menú Ver

En el menú Ver hay una nueva opción - Barra de Diseñador de informes

- Nuevos botones la barra de Herramientas del Diseñador de informes

Incluye dos opciones nuevas: Ajustar página y Propiedades del tipo de letra, las que vemos ahora en la barra de Herramientas.

- Mejoras en el Menú Informes, y el menú contextual del Generador de informes

En el menú Informes hay opciones nuevas: Vista Preliminar, Carga entorno de datos, Bandas opcionales y Propiedades. El resto de las opciones están mejor agrupadas.

- Cambios en la ficha Informes de Herramientas - Opciones.

Las nuevas opciones son:

  • Generador de Expresiones - regula si el nombre del alias va a preceder al nombre del campo: las posibilidades son: siempre, solo para alias no seleccionadas, nunca.
  • Comportamiento Motor de Informes - Determina el comportamiento en tiempo de ejecución: las posibilidades son 80 que mantiene el comportamiento anterior y 90 que es la generación de informes asistida por objetos (comando SET REPORTBEHAVIOR). La vamos a configurar para que trabaje sobre la 90, asistida por objetos.
  • Escala - Agrega nuevos valores: centímetros y pulgadas ()
  • Usar alfabeto para fuentes - indica el conjunto de caracteres de lenguaje que va a estar habilitado en el cuadro de diálogo Fuente. Si marcamos esta opción, en la Ventana Fuentes se activará el combobox Alfabeto. En caso contrario, permanece desactivado.
  • Se agregaron los elementos del contenedor Grid, Forzar al Grid y Mostrar líneas del Grid

Además, el cursor del ratón cambia de forma para dar una clave visual de los objetos que pueden ser redimensionables.

2. Proporcionar nuevas posibilidades:

- Tooltips

Se pueden agregar Tooltips a los controles del informe. Para ello vamos a la ficha Otros de la ventana propiedades. El comando Modificar Tooltip nos permite acceder a un editbox en el que podemos escribir el texto deseado y este se verá reflejado al pasar el ratón por el control. Solamente se aplica a controles.

- Comentarios

De igual forma, se pueden agregar Comentarios a los controles del informe. Desde la ficha Otros de la ventana propiedades y comando Modificar Comentarios nos permite acceder a un editbox en el que podemos escribir el texto deseado y este se verá reflejado al pasar el ratón por el control. Se aplica a controles, bandas y el Informe.

- Modo de recorte para expresiones de caracteres

Se puede definir desde la ficha Formato de la Ventana propiedades, los valores se definen desde el cuadro combinado Modo para truncar expresiones de caracteres.

Si se escoge el Recorte predeterminado lo que ocurre es que se agregan 3 puntos suspensivos indicando que no cabe la expresión en el espacio actual. El resto de las opciones para aplicar recorte de caracteres, se pueden intuir por la descripción, que aparece en idioma español, si tiene instalado el IDE VFP 9.0 en español.

- Protección

En VFP 9.0, puede crear protección para uno o más objetos al utilizar el Diseñador de Informes o Diseñador de etiquetas. Esto ofrece la posibilidad de que el usuario pueda modificar un informe, sin permitirle aún hacer determinados cambios. Para configurar las banderas de protección vemos los cuadros de diálogo correspondientes a Propiedades tanto para campos, como para bandas y también para informes. En el Diseñador de informes esto se ve reflejado en la ficha Protección en la Ventana Propiedades, tanto de controles, bandas, como del informe entero.

Sobre este tema, ver más elementos en: Visual FoxPro 9.0 - Novedades - Protección de Informes

- Posicionamiento absoluto

Desde la ventana propiedades - Ficha general se puede indicar el posicionamiento absoluto con propiedades tales como Top, Left, Height y Width. Es muy útil para controles que no dependen de una banda.

- Tratamiento de imágenes

Se han ampliado las posibilidades para definición de objetos tipo Imagen. Si tenemos definida una clase con valor en la propiedad PictureVal se puede emplear como origen de una imagen en un informe.

- Múltiples bandas de detalle

Contar con la posibilidad de crear múltiples bandas de detalle es una de las mejoras más importantes y más solicitadas. A partir de VFP 9.0 es posible procesar múltiples tablas hijas para cada registro de la tabla padre. Existen posibilidades ilimitadas de lo que se puede hacer con esta nueva característica. En el Diseñador de informes esto se ve reflejado en nuevas opciones de menú, la ficha Bandas Opcionales en la Ventana Propiedades.

Hemos visto algunos elementos, digamos, los más importantes. Seguramente quedan aspectos por explorar.

Espero que haya resultado de utilidad.

Saludos,

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