S P A C E F O L D  
data... any distance... minimal time  
 
Visual FoxPro 9.0 SP2
CommandClauses Property
This is an update to an existing Property topic, covering a change in SP2.

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

ExampleExamples

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