Regarding CommandClauses.File in SP2
ReportListener's CommandClauses member exists to provide user code with knowledge of the conditions and clauses used on the current REPORT FORM command. By design, native code fills, but does not read or use, CommandClauses member properties for a report run, except in specified circumstances.
As an example of a specified circumstance, user code can make a change to ReportListener.CommandClauses.Prompt and this change is respected when ReportListener's PrintCachedPages method or OnPreviewClose(.T.) is called.
The read-write special cases are documented in the CommandClauses help file topic. In VFP 9 RTM and SP1, ReportListener.CommandClauses.File was not one of the specified exceptions.
In SP2, the Report Engine reads the ReportListener.CommandClauses.File value after the LoadReport event, at the point at which native code reads the FRX and begins to process it. The value in this property, not the original value in the REPORT FORM command, determines what FRX the native code reads and processes.
This change enables user code to preprocess the FRX in a multi-user-safe way, by making a private copy of the FRX and instructing the engine to use this copy for the current run. Xbase code can create a temporary FRX copy and (for example) remove objects not needed for the current OutputType value or other conditions, such as permissions levels for various kinds of content.
Although it is possible to do similar things using events during the report run, this change provides better performance for changes that affect every instance of a layout control in a report run (or remove it altogether), or is a global change of any other type, such as a change to DataEnvironment items. It is also possible to do the same thing (preprocess the FRX table, creating a temporary copy) before issuing the REPORT FORM command, but this approach does not "bind" the preprocessing behavior into the ReportListener system.
If the new value is unusable (named file cannot be found, is not a valid FRX, is open in another Designer session or locked by another user for some other reason, etc), the same or similar errors are thrown as if the REPORT FORM command was issued with an unusable filename originally.
From the ReportListener's Xbase code point of view, along with being able to "see" this error in a surrounding TRY-CATCH, the end result is similar to what happens if the Xbase code issues a RETURN .F. from the LoadReport method: no report run occurs. Since this result is already a possibility, it should be handled in the surrounding user code.
For example, because a LoadReport can RETURN .F. and abend the report run, if the report run is supposed to create a file, it is already a valid possibility that the output target file will not exist after the run.
As usual, user code that makes such a change accepts responsibility to make it safely, not remove items critical to report calculations, clean up any temporary files afterwards, etc.
To use this feature safely, user code should restore CommandClauses.File to contain the same value it would have contained normally in the UnloadReport event. This user action does not require any response or adjustment in native code; it simply allows any followup user code reading the value after the report run to see the correct value.
Caution |
---|
Pre-processing the FRX under design during a Report Designer run is not possible
in exactly the same way. Because the Report Designer has a lock on the CommandClauses.File
FRX or LBX, user code can't make a copy of it easily. By design, the ReportListener
is passed the actual FRX or LBX value, not the temporary FRX or LBX name, during
a Design session. This doesn't change in SP2.
Xbase code should check ReportDesigner.CommandClauses.IsDesignerLoaded to determine what is possible. In some circumstances, it is possible for user code to compensate for this scenario transparently. In others, user code may have to indicate the unavailability of a certain feature during the Designer session. |
The SP2 version of FFC ReportListener Base Foundation Class, _ReportListener, handles the creation and removal of "swap copy" for a report run, if one is requested by any of the object participants in a report run. The GFX Render-Suppression Implementation, GFXNoRender, is an example of a class that makes good use of this facility. Please see the TMM docoids for these classes for more information.
The following GFXNoRender code illustrates a check for a possible preview during a Design session before attempting to read, and possibly swap and edit, the FRX during the LoadReport event.
* in a DO CASE construct, we handle the LoadReport event: CASE m.tcMethodToken == "LOADREPORT" AND ; VERSION(4) > "09.00.0000.3504" AND ; (NOT m.toListener.CommandClauses.IsDesignerLoaded) AND ; FILE(m.toListener.CommandClauses.File) * We can't do this in a design session because * of the Designer's lock on the original file. * We also can't do this in SP1 or below, * or when the report is built in to a different app. * (Note the use of FILE() rather than SYS(2000) here, * it's okay if it's not on disk.) * We will still handle this at render time if we can't make the swap; * (we just won't get the performance benefits of preprocess-deletion) TRY m.liSession = SET("DATASESSION") SET DATASESSION TO m.toListener.FRXDataSession m.liSelect = SELECT(0) * nb: this is before memberdata is normally available. SELECT 0 USE (m.toListener.CommandClausesFile) ; AGAIN NOUPDATE SHARED ALIAS FRX m.toListener.FRXCursor.UnpackFrxMemberData("FRX",; m.toListener.MemberDataAlias, m.toListener.FRXDataSession) USE IN FRX IF USED(m.toListener.MemberDataAlias) SELECT (m.toListener.MemberDataAlias) LOCATE FOR Type = FRX_BLDR_MEMBERDATATYPE AND ; Name == FRX_BLDR_NAMESPACE_ADVANCEDPROPS AND ; ExecWhen == FRX_BLDR_ADVPROP_PREPROCESS_NORENDER AND ; NOT EMPTY(Execute) IF FOUND() * check to see if we are already working on * a temporary report. IF (NOT m.toListener.isFRXSwapCopyPresent()) * must perform the swap, storing * CommandClauses.File for later use m.llSwap = .T. m.toListener.prepareFRXSwapCopy(,,.T.) ENDIF * After creating swap report or ascertaining that * we are already working with a temp report, * delete the items in that report as required. * BUT FIRST make sure the swap went through as planned. IF m.llSwap AND ; (EMPTY(m.toListener.CommandClauses.File) OR ; EMPTY(SYS(2000,m.toListener.CommandClauses.File))) m.llSwap = .F. m.toListener.CommandClauses.File = ; m.toListener.commandClausesFile ENDIF * now if we're positive we're in the right place * and that we have possible memberdata content * we can start deleting records out of the swap copy: IF USED(m.toListener.MemberDataAlias) AND ; NOT (m.toListener.CommandClauses.File == ; m.toListener.commandClausesFile) USE (m.toListener.CommandClauses.File) IN 0 ; AGAIN EXCLU ALIAS FRX * can't do a NOUP to do the delete. * since we're in a private copy, * we switch this to an * EXCLU and PACK although it doesn't appear * to be necessary for the engine's POV. * This means users of this copy of the * FRX don't have to pay attention to * whether some records are potentially-deleted. SELECT (m.toListener.MemberDataAlias) SCAN FOR Type = FRX_BLDR_MEMBERDATATYPE AND ; Name == FRX_BLDR_NAMESPACE_ADVANCEDPROPS AND ; ExecWhen == FRX_BLDR_ADVPROP_PREPROCESS_NORENDER AND ; NOT EMPTY(Execute) m.liRecno = FRXRecno m.llNoRender = THIS.omitRendering(m.toListener,ALLTRIM(Execute)) IF m.llNoRender SELECT FRX DELETE ALL FOR RECNO() = m.liRecno SELECT (m.toListener.MemberDataAlias) ENDIF ENDSCAN SELECT FRX PACK ENDIF ENDIF ENDIF CATCH TO m.err * revert the swap if we did one * being careful not to remove the original report #IF OUTPUTCLASS_DEBUGGING m.toListener.DoMessage(OUTPUTFX_CONDITIONALRENDERING_UNAVAILABLE_LOC + ; CHR(13) + m.err.Message ) #ENDIF IF m.llSwap m.toListener.removeFRXSwapCopy(,OUTPUTCLASS_DEBUGGING ) ENDIF FINALLY IF USED("FRX") USE IN FRX ENDIF * we must close this early version of the * memberdata cursor -- later its frxrecno values * will be re-created correctly IF USED(m.toListener.MemberDataAlias) USE IN (m.toListener.MemberDataAlias) ENDIF SELECT (m.liSelect) SET DATASESSION TO (m.liSession) ENDTRY * CASE statement continues here...
The following test code provides an opportunity to see error handling behavior if CommandClauses.File is incorrectly set:
LPARAMETERS WhichTest #DEFINE OtherReport "c:\temp\some.frx" #DEFINE ThisReport "c:\temp\other.frx" * the two reports above should exist CLEAR TRY ox = CREATEOBJECT("r") DO CASE CASE EMPTY(WhichTest) USE (OtherReport) EXCLU CASE WhichTest = 1 MODI REPO (OtherReport) NOWAIT OTHERWISE * we will be setting to a non-existant report name ox.fileNotExist = .T. ENDCASE REPORT FORM (ThisReport) object ox CATCH TO err LIST OBJECTS LIKE err MESSAGEBOX(MESSAGE() + " " + MESSAGE(1)) ENDTRY CLEAR ALL CLOSE ALL RETURN DEFINE CLASS r as ReportListener listenertype = 1 fileNotExist = .F. PROCEDURE loadreport IF THIS.fileNotExist THIS.CommandClauses.File = "somethingsomething.frx" ELSE THIS.CommandClauses.File = OtherReport ENDIF ENDPROC ENDDEFINE
See Also