Foxite.COM Community Weblog

Foxite.COM Community Weblog - free weblog service for the Visual FoxPro Community.
Welcome to Foxite.COM Community Weblog Sign in | Join | Help
in
Home Blogs Forum Photos Forum Archives

VFP IMAGING



Full Justified Texts in your reports with GdiPlus X

This is a continuation of my previous post, Full-Justified Text with GdiPlusX . Now I'll show how we can use the new method DRAWSTRINGJUSTIFIED from GdiPlusX library to have full Justified texts in our reports.

 

There are 2 ways of using this feature in reports.

The most obvious is to use the picture saved in the samples from the previous post, and use it directly in the report.

But VFP9 brings so many other cool possibilities, that I couldn't leave them without a try, so I decided to create a subclass of the Report Listener that will transform some of the texts from my reports to Full-Justified. My idea was that the user would just need to add a "<FJ>" tag in the beginning of any string from a textbox in a report, to tell my ReportListener that it will draw the text using the DRAWSTRINGJUSTIFIED method from GdiPlusX.

Please note that I'm NO expert in the new Object Assisted Reports from VFP9. My experience using Report Listeners is really very limited. I'm sure that the report listener below can be improved, so, if you have any suggestion that can improve the performance or ease the process, please tell me !

Hey Report Gurus ! Colin, Lisa, Doug, Cathy, Dorin, Garret, Craig, Bo, and others... I'd be very grateful if you could have a look at the code below, and send some feedback... and suggestions !

Anyway, it's working nice.

My "FullJustifyListener" performs the following actions:

- Initializes GdiPlusX

- Creates a GDI+ Graphics object that will be used to draw in the report

- Stores in an array the required information needed to draw the string(Font, Size, Style and Color)

- Before Rendering the string, checks if the "<FJ>" tag is at the beginning of text - if yes, draws the string using the new method.

VERY IMPORTANT - READ THIS !

All samples below require that you have the latest PLANNED RELEASE, 0.08A. It's a little bit hidden at codeplex, so I'm putting below the link for direct downloading:

http://www.codeplex.com/VFPX/Release/ProjectReleases.aspx?ReleaseId=1711

The main GDI+ part of the custom report listener is located at the procedure "Render" of the subclass.

Please Copy and Paste the code below, and save it as FJLISTENER.PRG in the samples folder of GdiPlusX 

DEFINE CLASS FullJustifyListener AS _ReportListener OF ;
      ADDBS(HOME(1)) + "FFC\_ReportListener.VCX"
   oGDIGraphics =
NULL
   DIMENSION
aRecords[1]

* Before we run the report, we need to Make sure that GdiPlusX
* was initialized, and create a new Graphics object

FUNCTION BEFOREREPORT
   DODEFAULT
()
   WITH This
      * Check if we already have the "System" object in "_Screen"
      IF NOT PEMSTATUS(_Screen,"System",5)
         DO LOCFILE("System.App") 
      ENDIF
      .oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New()
      .SetFRXDataSession()
      DIMENSION .aRecords[reccount(), 12]
      .ResetDataSession()
   ENDWITH
ENDFUNC
 
FUNCTION BEFOREBAND
(nBandObjCode, nFRXRecNo)
   This.SharedGDIPlusGraphics = This.GDIPLUSGRAPHICS
   This
.oGDIGraphics.Handle = This.SharedGDIPlusGraphics
   DODEFAULT(nBandObjCode, nFRXRecNo)
ENDFUNC
 
PROCEDURE RENDER
(tnFRXRecNo,;
      tnLeft,tnTop,tnWidth,tnHeight,;
      nObjectContinuationType, ;
      cContentsToBeRendered, GDIPlusImage)
   LOCAL lcText
   lcText =
This.aRecords(tnFRXRecNo,1)

   IF VARTYPE(lcText) = "C" AND lcText = "<FJ>"
      lcText =
SUBSTR(lcText,5) && Remove the <FJ> tag from string
      This.oGDIGraphics.Handle = This.GDIPlusGraphics

      WITH _SCREEN.SYSTEM.Drawing

      * Create a GDI+ Rectangle which specifies where on the
      * surface we're drawing the text.
      LOCAL loRectF as xfcRectangleF
      loRectF = .RectangleF.New(tnLeft, tnTop, tnWidth, tnHeight)

      * Create a Font Object based on the report original settings
      LOCAL loFont as xfcFont
      loFont = .
Font.New(This.aRecords(tnFRXRecNo,2) ;
         ,
This.aRecords(tnFRXRecNo,4), This.aRecords(tnFRXRecNo,3) ;
         , .GraphicsUnit.
Point)

      * If we have an opaque background set for the text, then draw a rectangle
      * using the background chosen background color
      IF This.aRecords[tnFRXRecno,8] <> 0 && Alpha

         * Retrieve colors for the background
         LOCAL lnRed, lnGreen, lnBlue, lnAlpha
         lnRed =
This.aRecords[tnFRXRecno,5]
         lnGreen =
This.aRecords[tnFRXRecno,6]
         lnBlue =
This.aRecords[tnFRXRecno,7]
         lnAlpha =
This.aRecords[tnFRXRecno,8]

         * Create a Solid Brush that will be used to draw the Background
         LOCAL loBackBrush as xfcSolidBrush
         loBackBrush = .SolidBrush.New(;
            .
Color.FromArgb(lnAlpha,lnRed, lnGreen, lnBlue))

         * Draw the background rectangle
         This.oGDIGraphics.FillRectangle(loBackBrush, tnLeft, tnTop, tnWidth, tnHeight)
      ENDIF

      * Retieve colors for the Text
      lnRed = This.aRecords[tnFRXRecno,9]
      lnGreen =
This.aRecords[tnFRXRecno,10]
      lnBlue =
This.aRecords[tnFRXRecno,11]
      lnAlpha =
This.aRecords[tnFRXRecno,12]

      * Create a Solid Brush that will be used to draw the text
      LOCAL loTextBrush as xfcSolidBrush
      loTextBrush = .SolidBrush.New(;
         .
Color.FromArgb(lnAlpha,lnRed, lnGreen, lnBlue))

      * Finally, draw the text in FullJustified mode.
      This.oGDIGraphics.DrawStringJustified(lcText, loFont, loTextBrush, loRectF)
      ENDWITH
   ELSE

      * If we're not drawing a full justified string,
      * let VFP draw the text as usual.
      DODEFAULT(tnFRXRecNo, tnLeft, tnTop, tnWidth, tnHeight, ;
      nObjectContinuationType, cContentsToBeRendered, GDIPlusImage)
   ENDIF

   * Since we already drew the text, we don't want the default
   * behavior to occur.
   NODEFAULT
ENDPROC
 
FUNCTION EvaluateContents
(tnFRXRecNo, toObjProperties)
   * Get the FRX data
   This.aRecords[tnFRXRecno,1] = toObjProperties.Text
   This
.aRecords[tnFRXRecno,2] = toObjProperties.FontName
   This
.aRecords[tnFRXRecno,3] = toObjProperties.FontStyle
   This.aRecords[tnFRXRecno,4] = toObjProperties.FontSize
   This
.aRecords[tnFRXRecno,5] = toObjProperties.FillRed
   This.aRecords[tnFRXRecno,6] = toObjProperties.FillGreen
   This.aRecords[tnFRXRecno,7] = toObjProperties.FillBlue
   This.aRecords[tnFRXRecno,8] = toObjProperties.FillAlpha
   This.aRecords[tnFRXRecno,9] = toObjProperties.PenRed
   This.aRecords[tnFRXRecno,10] = toObjProperties.PenGreen
   This.aRecords[tnFRXRecno,11] = toObjProperties.PenBlue
   This.aRecords[tnFRXRecno,12] = toObjProperties.PenAlpha
ENDFUNC
ENDDEFINE

To use it is very simple:

Open any report, and select a textbox that has a memo field associated to it. In the field properties, tab General, on expression, just add a "<FJ>" string before the original field. Supposing you had the expression "MyTable.MyMemoField", to have it justified, you'll change it to: "<FJ>" + MyTable.MyMemoField.

The next step is to make sure that VFP will use our new listener to render the report.

* Tell VFP that we'll be using the new report features
SET REPORTBEHAVIOR 90
LOCAL loReportListener
loReportListener =
NEWOBJECT("FullJustifyListener",LOCFILE("FJListener.prg"))
* loReportListener = CREATEOBJECT("FullJustifyListener")
loReportListener.LISTENERTYPE = 1
REPORT FORM MyReport OBJECT loReportListener

  

 

 

 

UPDATE 04/07/2007

After some good suggestions from Victor Espinoza, below is a modified version of the "FJListener" class, that will receivethe <FJ> tag at the user data field for the textbox (under the "Other" tab), this way, the "<FJ>" tag wouldn't affect the reports if it was latter decided to use an alternative report listener.

* Program : FJLISTENER.PRG
* Purpose : Provides a Report Listener that allows rendering text in
*           Full Justify alignment.
* Author : Cesar Chalom and Victor Espinoza
* Class based on article "Listening to a report" by Doug Hennig
*
http://msdn2.microsoft.com/en-us/library/ms947682.aspx

DEFINE CLASS FullJustifyListener AS _ReportListener OF ;
      ADDBS(HOME(1)) + "FFC\_ReportListener.VCX"
   oGDIGraphics =
NULL
   DIMENSION
aRecords[1]

* Doug Hennig - If ListenerType hasn't already been set, set it based on whether the report
* is being printed or previewed.
FUNCTION LoadReport
   WITH This
   DO CASE
   CASE
.ListenerType <> -1
   CASE .CommandClauses.Preview
      .ListenerType = 1
   CASE .CommandClauses.OutputTo = 1
      .
ListenerType = 0
   ENDCASE
   ENDWITH
   DODEFAULT
()
ENDFUNC

* Before we run the report, we need to Make sure that GdiPlusX
* was initialized, and create a new Graphics object
FUNCTION BEFOREREPORT
   DODEFAULT
()
   WITH This
   * Check if we already have the "System" object in "_Screen"
   IF NOT PEMSTATUS(_Screen,"System",5)
      DO LOCFILE("System.App") 
   ENDIF

   .oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New()
   .SetFRXDataSession()
&& switches to the FRX cursor's datasession
   DIMENSION .aRecords(RECCOUNT(), 13)
   SCAN FOR "<FJ>" $ UPPER(User)
      .aRecords(
RECNO(), 13) = "FJ"
   ENDSCAN
   .ResetDataSession() && restores the datasession ID to the one the listener is in
   ENDWITH
ENDFUNC

FUNCTION BEFOREBAND(nBandObjCode, nFRXRecNo)
   This.SharedGDIPlusGraphics = This.GDIPLUSGRAPHICS
   This
.oGDIGraphics.Handle = This.SharedGDIPlusGraphics
   DODEFAULT(nBandObjCode, nFRXRecNo)
ENDFUNC

PROCEDURE RENDER(tnFRXRecNo,;
      tnLeft,tnTop,tnWidth,tnHeight,;
      nObjectContinuationType, ;
      cContentsToBeRendered, GDIPlusImage)
   IF VARTYPE(This.aRecords(tnFRXRecNo,13)) = "C" AND ;
      This.aRecords(tnFRXRecNo,13) == "FJ"
      LOCAL lcText
      lcText =
This.aRecords(tnFRXRecNo,1)
      This.oGDIGraphics.Handle = This.GDIPlusGraphics

      WITH _SCREEN.SYSTEM.Drawing
         * Create a GDI+ Rectangle which specifies where on the
         * surface we're drawing the text.
         LOCAL loRectF as xfcRectangleF
         loRectF = .RectangleF.New(tnLeft, tnTop, tnWidth, tnHeight)

         * Create a Font Object based on the report original settings
         LOCAL loFont as xfcFont
         loFont = .
Font.New(This.aRecords(tnFRXRecNo,2) ;
            ,
This.aRecords(tnFRXRecNo,4), This.aRecords(tnFRXRecNo,3) ;
            , .GraphicsUnit.
Point)

         * If we have an opaque background set for the text, then draw a rectangle
         * using the background chosen background color
         IF This.aRecords[tnFRXRecno,8] <> 0 && Alpha
            * Retrieve colors for the background
            LOCAL lnRed, lnGreen, lnBlue, lnAlpha
            lnRed =
This.aRecords[tnFRXRecno,5]
            lnGreen =
This.aRecords[tnFRXRecno,6]
            lnBlue =
This.aRecords[tnFRXRecno,7]
            lnAlpha =
This.aRecords[tnFRXRecno,8]

            * Create a Solid Brush that will be used to draw the Background
            LOCAL loBackBrush as xfcSolidBrush
            loBackBrush = .SolidBrush.New(;
               .
Color.FromArgb(lnAlpha,lnRed, lnGreen, lnBlue))

            * Draw the background rectangle
            This.oGDIGraphics.FillRectangle(loBackBrush, tnLeft, tnTop, tnWidth, tnHeight)
         ENDIF

         * Retieve colors for the Text
         lnRed = This.aRecords[tnFRXRecno,9]
         lnGreen =
This.aRecords[tnFRXRecno,10]
         lnBlue =
This.aRecords[tnFRXRecno,11]
         lnAlpha =
This.aRecords[tnFRXRecno,12]

         * Create a Solid Brush that will be used to draw the text
         LOCAL loTextBrush as xfcSolidBrush
         loTextBrush = .SolidBrush.New(;
            .
Color.FromArgb(lnAlpha,lnRed, lnGreen, lnBlue))

         * Finally, draw the text in FullJustified mode.
         This.oGDIGraphics.DrawStringJustified(lcText, loFont, loTextBrush, loRectF)
      ENDWITH
   ELSE
      * If we're not drawing a full justified string,
      * let VFP draw the text as usual.
      DODEFAULT(tnFRXRecNo, tnLeft, tnTop, tnWidth, tnHeight, ;
         nObjectContinuationType, cContentsToBeRendered, GDIPlusImage)
   ENDIF

   * Since we already drew the text, we don't want the default
   * behavior to occur.
   NODEFAULT
ENDPROC

FUNCTION EvaluateContents(tnFRXRecNo, toObjProperties)
   * Get the FRX data
   This.aRecords[tnFRXRecno,1] = toObjProperties.Text
   This
.aRecords[tnFRXRecno,2] = toObjProperties.FontName
   This
.aRecords[tnFRXRecno,3] = toObjProperties.FontStyle
   This.aRecords[tnFRXRecno,4] = toObjProperties.FontSize
   This
.aRecords[tnFRXRecno,5] = toObjProperties.FillRed
   This.aRecords[tnFRXRecno,6] = toObjProperties.FillGreen
   This.aRecords[tnFRXRecno,7] = toObjProperties.FillBlue
   This.aRecords[tnFRXRecno,8] = toObjProperties.FillAlpha
   This.aRecords[tnFRXRecno,9] = toObjProperties.PenRed
   This.aRecords[tnFRXRecno,10] = toObjProperties.PenGreen
   This.aRecords[tnFRXRecno,11] = toObjProperties.PenBlue
   This.aRecords[tnFRXRecno,12] = toObjProperties.PenAlpha
ENDFUNC

ENDDEFINE

For now this seems good to me. But there's still some more to improve here. For the next version this can become a "Directive" to be used together with other custom report listeners, as Doug Hennig did in his great article "Listening to a Report"

Related links that helped me to understand how the Report Listener works:

Doug Hennig - Listening to a Report
Doug Hennig - Hyperlink your reports
Doug Hennig - Extending the Visual FoxPro 9 Reporting System
Cathy Pountney - VFP 9.0 Report Writer In Action
Cathy Pountney - What's New in the VFP 9 Report Writer
Garret Fitzgerald - Using the GDI+ FFCs to work with output

Published Thursday, April 05, 2007 1:36 PM by cesarchalom
Filed Under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Victor Espinoza said:

Hi Cesar, as always, excellent code, Calvin's and yours are my all time favorite blogs.

Just an idea, wouldn't it be better to use the user data field for the textbox (under the "Other" tab), this way, the "<FJ>" tag wouldn't affect the reports if it was latter decided to use an alternative report listener. All you would need is:
- Move tag to user data field (apparently designed for custom user tags)
- Increase "arecords" array to 13
- In the "EvaluateContents" function add:
      This.aRecords[tnFRXRecno,13] = toObjProperties.User
- In the Render method, validate against aRecords[tnFRXRecno,13] for "<FJ>"

I'm no expert either, it's just a thought.

Regards

Victor

Hi Victor,

This sounds very nice, and I agree to move the tag to the "USER" tab. I'll just wait some more time, to see if some more suggestions arise.

Thanks very much !

Saludos

Cesar

April 5, 2007 3:23 PM
 

Olivier HAMOU said:

Thank's Cesar, for your sample,

I was waited this sample since long time,

Thank's also to victor for your answer.
(but the link UT report preview Custom is broken)

I will making on in action your sample !

Thank's
Olivier

Hi Olivier,

The links seems to be working fine here... You can also enter UT directly: www.universalthread.com , go to the downloads section, and search for "Dorin Vasilescu".

HTH[

Cesar

April 5, 2007 3:59 PM
 

Victor Espinoza said:

Hi Cesar, I found a problem in my approach, maybe you can help. Somehow, the property "toObjProperties.User" in the "EvaluateContents" function does not pass along the values in the User field of the Report file. Thus, the Render procedure does not activate the Justification routine. I'm still working on a solution, your original code works perfect.

Regards

Victor
April 6, 2007 6:48 PM
 

Victor Espinoza said:

Hi Cesar, found a solution for the EvaluateContents problem, please disregard previous post and this one, I will post a corrected suggestion in a couple of minutes... for all to see  :)

It is a pleasure to contribute to your Blog ...

Thank you

Victor

April 6, 2007 6:56 PM
 

Victor Espinoza said:

Hi Cesar, here is my updated suggestion. My original idea was sound, but flawed, in the sense that the "toObjProperties" object in the "EvaluateContents" function does not contain a property with the value of the "user" field in the report file. Thus, the validation in the Render method would never find the <FJ> tag.
So here is my updated suggestion; I have tried it and is working properly now.

- move the <FJ> tag to the "User" field (under "Other" tab of textbox properties)
 (this avoids affecting output when using alternate listener)

- modify the BeforeReport method like so:
FUNCTION BEFOREREPORT
  DODEFAULT()
  WITH This
     * Check if we already have the "System" object in "_Screen"
     IF NOT PEMSTATUS(_Screen,"System",5)
        _SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", locfile("system.vcx")))
     ENDIF
     .oGDIGraphics = _SCREEN.SYSTEM.Drawing.Graphics.New()
     .SetFRXDataSession()
     DIMENSION .aRecords[reccount(), 13]
     SCAN FOR "<FJ>" $ UPPER(User)
      .aRecords[recno(), 13] = "FJ"
     ENDSCAN      
     .ResetDataSession()
  ENDWITH
ENDFUNC

- In the Render method validate like so :
IF VARTYPE(This.aRecords(tnFRXRecNo,13)) = "C" AND This.aRecords(tnFRXRecNo,13) == "FJ"

Note: there is no need anymore to trim <FJ> tag from lcText variable

Thank you for the chance to contribute to your excellent Blog....

Regards

Victor Espinoza
Miami, FL

Hi Victor,

Thanks very much for the suggestions. Unfortunately, I'm out of town, at this moment, and without my working machine, so I did not test any of the suggestions. But this last makes a lot of sense. Have you tested the performance of both aproaches ?

Regards

Cesar 

April 6, 2007 7:34 PM
 

Cesar Chalom said:

Hi Victor,
Thanks for the suggestions, it's working like a charm.
I've just updated the post, with the full code.
Thanks again
Cesar
April 8, 2007 6:36 AM
 

Barbara S said:

Cool !
April 9, 2007 12:54 AM
 

ravisobhan said:

the FJ is functioning well. thank you.
the problem of splitting text between pages is not sorted out.
please look into and post at the arliest for community.

Hi, sorry for the late answer...

I don`t unsderstand what you mean, can you explain it better ? Maybe sending me some screenshots ?

Thanks in advance

Cesar

May 8, 2007 9:52 AM
 

flprogrammer said:

I have a program printing a daily report with about 20 lines that need to be full justified, the report is printing for 500 clients in one shot, vfp9 sp1. How much of a slowdown do you think it would be to include this listener to full justify the lettering.

Hi, sorry for the late answer... :-(

For sure this report will run slower using a listener. The full justify function is really extremely fast. You can try it using the full justify samples from the GdiPlusX library.

I really don't know about how important the delay will be in a big report. I suggest you to make some tests, and tell us how it works for you. At this moment, for my own needs, it's working nice, and I didn`t notice a significant delay.

Hope to hear from your tests soon!

May 9, 2007 10:06 PM
 

ravisobhan said:

sir I saw yor reply
sorry I could not explain properly.
The problem is the memo field having more lines of data running in to pages, while drawing the same on report from one page to next page. The data already rendered in the previous page is  repeating in the next page and truncating/ limiting the balance to be rendered.

actually it has to render balance lines only on the subsequent pages till the data is complete.
it needs additional handling which has been explained by BO Durban in his clear method of rendering RTF in reports without full justification.
shortly i will send screen shots / movie for better understanding
kindly pardon me i fail to explain my views.
thanking you sir

--ravisobhan
May 30, 2007 10:00 AM
 

mailponus said:

Thank's Cesar, for your sample, Great Cesar
but ...
I found bug in gdiplusx when i try use, SET ANSI ON, SET EXACT ON, SET NEAR ON the report cannot fulljustify. Then When I try Use Clear dlls, Error in xfcbrush (vcx error)
June 22, 2007 6:20 AM
 

fernando mesquita said:

O exemplo de "justificar texto" corresponde ao que preciso. No entanto, tenho alguma dificuldade de traduzir todas as indicações de Inglês/Português.
Será que era possível disponibilizá-lo já traduzido para Português e enviadi para o seguinte Email : fernando.p.mesquita@iol.pt
March 16, 2008 12:26 AM
 

VFP IMAGING said:


&amp;nbsp;
The GdiPlusX library is intended to be compatible with the .NET Framework’s System.Drawing...
May 26, 2008 6:59 PM
 

Alejandro Fernández said:

Cómo podría integrarlo con xfrx para convertir mi report a pdf pero con texto justificado de un campo memo del fichero pdf ?.

Espero su ayuda. Gracias

Hola,

Desafortunadamentem nunca utilize XFRX. Pero convertir la grafica para una imagen es super simples ! Puedes salvar en un archiivo de imagen o entonces imprimir directamiente. Mire el codigo en el boton "Print" del formulario de ejemplo.

Saludos !

May 28, 2008 12:00 AM
 

VFP IMAGING said:

As I've shown in a previous post from last year, this is totally possible for us in VFP, using the GdiPlusX...
June 15, 2008 7:42 AM
 

arwebz said:

Thanks for the sample
September 11, 2008 3:10 PM

What do you think?

(required) 
(optional)
(required)