S P A C E F O L D  
data... any distance... minimal time  
 
Visual FoxPro 9.0 SP2
ReportListener Utility and File-handling Foundation Class
This is an addendum to an existing FFC class topic, covering changes in SP1 and SP2.
GeneralAbout this class
The UtilityReportListener class adds run-time configuration options and filehandling to the core FFC reportlistener set.  It is primarily designed to provide an ancestor class for reportlisteners  that need the capability of writing physical files to disk as part of providing their output target types.

Starting in SP2, UtilityReportListener descends from FXListener rather than directly from _ReportListener.   Its FRXCursor-providing features have all been moved to FXListener.  FRXCursor functionality is not specific to output target types that require file handling. 

Similarly, the getPathForExternals Method originally implemented in UtilityReportListener has been promoted up to FXListener, since the requirement for external files or object definitions in files which may be used in processing are needed by some output targets that do not generate file output. 

Starting in SP2, UtilityReportListener's file-handling features have also been significantly expanded to include the provision of output page images as a secondary output while the engine prepares any of its primary outputs (as defined by the reportlistener's ListenerType property).  This feature is available with any ListenerType value except -1.  When you use it, UtilityReportListener prepares a single page image for each rendered page, using the base class OutputPage method.  The feature works differently for ListenerType values 0 and 2, in which OutputPage is invoked as an event, than it does for ListenerTypes 1 and 3, in which OutputPage must be called explicitly after the base class reportlistener has cached all page images.

A subclass of UtilityReportListener such as HTMLListener may decide to provide page images as a secondary output along with its primary file output. When you use the Advanced Property HTML.PrintablePageLink, this is exactly what HTMLListener does. 

A subclass of UtilityReportListener may also use this feature as its primary output; the creation of page images provides the opportunity to archive and replay an exact report run at any time, and using any output device that can handle the page image type you used. The class providing the dsplay or print of the report contents, at that time, does not need to be a reportlistener, so there are no multi-threading restrictions.  It can be a simple VFP form, as in the example below, or it can be an application written in a different environment with the ability to display your specified image type.

When generating page images, UtilityReportListener uses a filenaming convention designed to make it possible to retrieve the pages in correct order later.  

The class provides options to specify the page image file type.  If you do not specify the page image file type, a subclass will generally have its own option for a default page image file type suitable to its output target.  For example, if you run a report that includes a request for an HTML.PrintablePageLink, by setting the Advanced Property, and if you have not already set HTMLListener's pageImageType property yourself, HTMLListener temporarily sets that property for the current report run, using a #DEFINEd value OUTPUTHTML_DEFAULT_PAGEIMAGE_TYPE.

ExternalFileLocation, a property previously provided in the derived class XMLDisplayListener to provide a location into which copies of images embedded within a page might be placed, has been promoted to UtilityReportListener in SP2.  This provides a way for all reportlisteners, whether XML-output-generating or not, with file output types to specify where their associated full-page images are placed.

PEMsProperties and Methods

The following table lists public properties and methods added by this class to its parent class, FXListener, and changed in SP1 or SP2.

Properties and methods Description

currentPageImageFilename Property

Provides the filename for the generated page image file for the current page during a report run, including the externalFileLocation path, which may be relative.

Default ""

Remarks:

This member property is exposed to allow helper objects to see/share the generated name for additional processing. See example below.

externalFileLocation Property

Optionally assigns a UNC or file system path, either relative or absolute, that this class will assign to any external files, such as images, referenced in the main output file. These images may be copies of file-based images, or saved images exported by the Report Engine from General fields or Image controls referenced in reports.

Also provides the directory into which page images are placed.

Remarks:

Previous to SP2, this property belonged to the XMLDisplayListener derived class.

Starting in SP2, the class attempts to create the directory if it does not exist. If it is unable to create the directory, the value of the property is set to "." (current directory relative to the primary target file).

frxCursor Property

This property and related features, including the loadFrxCursor Property,  are now provided by the parent class FXListener. Please see important notes on expanded functionality available  in frxCursor for SP2 .

getPathForExternals Method

This method is now provided by the parent class FXListener.

targetFileName Property

Provides the filename to which output will be written. A unique name is generated for the class instance, which will be overwritten for successive report runs if not adjusted by the user.

Default FORCEPATH(SYS(2015),SYS(2023))

Remarks:

Starting in SP1, you can specify a directory-only value for this property.  (This was not reliable before SP1, although in some situations it would work.) You must supply a final backslash for the class to recognize that you mean to supply a directory-only target; in this case, and if the directory is valid, it generates the filename in this location.

 

pageImageType

Indicates a type of image file you want generated for each output page in a report run. 

Default 0

Remarks:

The allowable values match the file types available from the OutputPage method, including multi-page TIFF. If you choose multi-page TIFF only one output page is created; subsequent pages are appended into the first page's file.

If the reportlistener's ListenerType is 1 or 3, and if NOPAGEEJECT is used,  this activity takes place at the conclusion of a chained report set,  a single  pageImageType value will apply to the full chained set of reports. However, if ListenerType is 0 or 2, the page images are created during the report run(s) .  If you change pageImageType between REPORT FORM statements there is no guarantee that multiple reports in a chain will have generated the same type of page images.

ExampleExamples

The following code example shows how a form displaying information about a customer can display an embedded report preview for that customer's invoices. The Preview method shown here is called when the form's navigation controls move to a different customer record.

The Load event of the form has added a reference to a reportlistener to the form, so it can create these page images on demand. It has also prepared the form to print from the embedded preview; an appropriate Print button has been added to the form's navigation controls.

PROCEDURE Load
   WITH THIS.rl
    .ListenerType = 2
    .QuietMode = .T.
    .pageImageType = 100 && EMF
    .targetFilename = FORCEPATH("CUSTPREVIEW",SET("DIRECTORY")) && provides the base
    .externalFileLocation = SET("DIRECTORY") && provides the location for all 
                                             && additional output, including page images
   ENDWITH 
   * illustrates NET4COM doing the printing, if you have it available...
   THIS.AddProperty("printer",NULL)
   TRY
     THIS.printer = CREATEOBJECT("NET4COM.Printer")
   CATCH
   ENDTRY
RETURN

PROCEDURE Preview
LOCAL lcID
lcID = Customer.Cust_ID
IF NOT EMPTY(m.lcID) && could be an empty file
   REPORT FORM (THIS.rptFile) OBJECT THIS.rl FOR Cust_ID = m.lcID
   * make sure we're back to the right record, this is not the right way of course:
   LOCATE FOR Cust_ID = m.lcID
   * synch up the image:
   THIS.imgPreview.Picture = THIS.rl.currentPageImageFilename + ".EMF"
ENDIF   
RETURN              

PROCEDURE PrintPreview
   IF VARTYPE(THIS.Printer) ="O"  && NET4COM is available
     THIS.Printer.printImage(  THIS.rl.currentPageImageFilename + ".EMF")
   ELSE
     MESSAGEBOX("You can run the report using listenertype 0 with " + ;
"the same criteria used in the Preview method, or" + CHR(13) + ; "use NET4COM.Printer here, with THIS.rl.currentPageImageFilename + " + ; "'.EMF', or " + CHR(13) + ; "use several other methods to print the EMF.") ENDIF RETURN

The following example shows you how a reportlistener proxy object  (a custom object implementing the reportlistener interface) can partner with the Report Preview application to display a previously cached set of image files.  It works just as if Report Preview had been invoked by a reportlistener in a report run -- except that the preview appears instantly. 

While it was easy to do this with the pre-SP2 version of Report Preview application, as a commented method at the end of the listing shows, the SP2 Report Preview has been enhanced to work with non-reportlistener classes for this type of scenario.

LOCAL lox
lox = CREATEOBJECT("ReportListenerCacheProxy")
lox.SetReportCacheSource(TestDir,"EMF")
loX.PreviewCachedReport()
RETURN

DEFINE Class ReportListenerCacheProxy AS Custom

   PROTECTED CurrentPage, Pages[1], FileExt
   CommandClauses = NULL
   FileExt = "EMF"
   FRXDataSession = 1
   CurrentPage = -1
   OutputPageCount = 0
   PreviewContainer = NULL
   PrintJobName = ""
   QuietMode = .F.
   CacheDir = ""
     
   PROC SetReportCacheSource(tvSource, tvExt)
      IF NOT EMPTY(tvExt)
         THIS.FileExt = tvExt
      ENDIF
      THIS.CacheDir = tvSource
      THIS.PrintJobName = THIS.CacheDir
      THIS.OutputPageCount = ADIR(THIS.Pages,FORCEPATH("*."+tvExt,THIS.CacheDir))      
   ENDPROC
   
   PROC PreviewCachedReport(tlNowait)
       IF THIS.OutputPageCount > 0
          THIS.GetReportPreview()
          IF NOT ISNULL(THIS.PreviewContainer)
             THIS.PreviewContainer.SetReport(THIS)
             IF (NOT EMPTY(tlNoWait)) OR ;
                THIS.CommandClauses.NoWait
                THIS.PreviewContainer.Show()
             ELSE
                THIS.PreviewContainer.Show(1)
             ENDIF
          ENDIF   
       ENDIF   
   ENDPROC
   
   PROC PrintCachedReport
      THIS.PrintCachedPages()
   ENDPROC
   
   PROC OnPreviewClose(tlPrint)
      IF NOT EMPTY(tlPrint)
         THIS.PrintCachedPages()
      ENDIF
   ENDPROC
   
   PROC PrintCachedPages() 
      IF THIS.OutputPageCount > 0
        * please see the PrintPreview method
        * in the other example in this topic
        * for notes and code
      ENDIF
   ENDPROC
   
   PROC GetPageHeight()
      * TBD, for now:
      RETURN 10560
   ENDPROC
   
   PROC GetPageWidth()
      * TBD, for now:
      RETURN 8160    
   ENDPROC
   
   PROC OutputPage(nPageNo, ;
                  eDevice, ;
                  nDeviceType, ;
                  nleft, nTop, nWidth, nHeight, ;
                  nClipLeft,nClipTop, nClipWidth, nClipHeight)
      LOCAL lHandled            
      IF BETWEEN(nPageNo,1,THIS.OutputPageCount)
         DO CASE
         CASE nDeviceType = 2 AND ;
             TYPE("eDevice.BaseClass") = "C" AND ;
             UPPER(eDevice.BaseClass) == "IMAGE"
             * this is all we're handling in this class 
             IF eDevice.Picture # FORCEPATH(THIS.Pages[nPageNo,1], THIS.CacheDir)
                eDevice.Picture = FORCEPATH(THIS.Pages[nPageNo,1], THIS.CacheDir)
             ENDIF   
             lHandled = .T.
         ENDCASE    
      ENDIF            
      IF lHandled
         THIS.CurrentPage = nPageNo
      ENDIF
  
   ENDPROC
   
   PROTECTED PROC GetReportPreview()
      IF ISNULL(THIS.PreviewContainer)
         LOCAL lox
         lox = NULL
         DO (_REPORTPREVIEW) WITH lox
         IF NOT PEMSTATUS(lox,"Memberclass",5)
            lcComponent = "ReportPreview"
            MESSAGEBOX(VERSION_PROBLEM_LOC,16)
            loX = NULL
         ENDIF
         IF NOT ISNULL(loX)
*&*         if Preview didn't provide such a class...         
*&*            lox.MemberClass = "Image"
*&*            lox.MemberClass = "PageImage"
*&*            lox.MemberClassLibrary = "PageImage.prg"

*&*         but now it does...
            lox.MemberClass = "ImageCanvas"
            lox.MemberClassLibrary = "frxpreview.vcx"
            THIS.PreviewContainer = lox
         ENDIF   
      ENDIF      
   ENDPROC
         
   PROTECTED PROC InitCommandClauses()
      THIS.CommandClausesPropRequired("NoWait",.F.)
      THIS.CommandClausesPropRequired("IsDesignerLoaded",.F.)
      THIS.CommandClausesPropRequired("IsDesignerProtected",.F.)
      THIS.CommandClausesPropRequired("Window","")
      THIS.CommandClausesPropRequired("InWindow","")
      THIS.CommandClausesPropRequired("InScreen",.F.)
      THIS.CommandClausesPropRequired("File","")     
      THIS.CommandClausesPropRequired("PrintRangeFrom",1)
      THIS.CommandClausesPropRequired("PrintRangeTo",-1)
      THIS.CommandClausesPropRequired("PrintPageCurrent",.F.)
      THIS.CommandClausesPropRequired("Prompt",.F.)
      THIS.CommandClausesPropRequired("Preview",.F.)
      THIS.CommandClausesPropRequired("OutputTo",1)
      THIS.CommandClausesPropRequired("ToFile","")
      THIS.CommandClausesPropRequired("ToFileAdditive",.F.)      
      THIS.CommandClausesPropRequired("NoDialog",.F.)
      THIS.CommandClausesPropRequired("StartDataSession",1)
      
  
      * for completeness in case of use like a "real" rl's 
      * commandclauses, although not useful to this class,
      * since some could be set by the caller and 
      * useful in user feedback:
      
      THIS.CommandClausesPropRequired("Off",.F.)
      THIS.CommandClausesPropRequired("NoConsole",.F.)
      THIS.CommandClausesPropRequired("NoEject",.F.)
      THIS.CommandClausesPropRequired("NoPageEject",.F.)
      THIS.CommandClausesPropRequired("NoReset",.F.)
      THIS.CommandClausesPropRequired("PDSetup",.F.)
      THIS.CommandClausesPropRequired("RecordTotal",RECCOUNT())
      THIS.CommandClausesPropRequired("RangeFrom",1)
      THIS.CommandClausesPropRequired("RangeTo",-1)
      THIS.CommandClausesPropRequired("Plain",.F.)
      THIS.CommandClausesPropRequired("ASCII",.F.)
      THIS.CommandClausesPropRequired("Summary",.F.)
      THIS.CommandClausesPropRequired("Heading","")
      THIS.CommandClausesPropRequired("Sample",.F.)          
      THIS.CommandClausesPropRequired("Environment",.F.)
      THIS.CommandClausesPropRequired("IsReport",.T.)      
      THIS.CommandClausesPropRequired("DE_Name","ReportDE")      
  
   ENDPROC
   
   PROTECTED PROC CommandClausesPropRequired(tcProp, tvVal)
      LOCAL lcType
      lcType = VARTYPE(tvVal)
      IF TYPE("THIS.CommandClauses." + tcProp) # lcType
         ADDPROPERTY(THIS.CommandClauses,tcProp, tvVal)
      ENDIF   
   ENDPROC
   
   PROTECTED PROC Init()
      THIS.CommandClauses = CREATEOBJECT("Empty")
      THIS.InitCommandClauses()
   ENDPROC
      
   
   PROC CommandClauses_Assign(tvVal)
      IF ISNULL(tvVal) OR VARTYPE(tvVal) = "O"
         THIS.CommandClauses = tvVal
      ELSE
         THIS.CommandClauses = CREATEOBJECT("Empty")
         THIS.InitCommandClauses()
      ENDIF
   ENDPROC
   
   PROC PreviewContainer_Assign(tvVal)
       IF VARTYPE(tvVal) = "O" AND ;
          PEMSTATUS(tvVal,"Show",5) AND ;
          PEMSTATUS(tvVal,"SetReport",5)
          THIS.PreviewContainer = tvVal
       ENDIF   
   ENDPROC
   
ENDDEFINE

*&*    This is really all we'd need if the 
*&*    preview container didn't already have one:
*&*   DEFINE Class PageImage AS Image
*&*      Stretch = 1
*&*      PROC RightClick()
*&*         THISFORM.RightClick()
*&*      ENDPROC
*&*   ENDDEFINE

See AlsoSee Also