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.
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. |
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