The original design for this class was proposed in the RTM CHM Report XML MemberData Extensions topic, as the example class FXProcessMemberDataScript. In the original design, FXProcessMemberDataScript derives from FXMemberData, which extracts MemberData XML into a cursor. This code is still in the SP2 CHM topic but, in the shipping SP2 design, FRXCursor has that responsibility, and also is responsible to translate Dynamics-based MemberData into script at both designtime and runtime.
The Dynamics feature is exposed by the Report Builder Application user interface as a Properties dialog tab. It provides an easy way for end users to access the benefits of the EvaluateContents event for Field Expression layout controls and of the AdjustObjectSize event for Image, and Shape layout controls, without writing code. Each Dynamic event's instruction set is stored in FRX Memberdata; details of this storage are in Appendix A of the whitepaper Data Visualization in Reports with VFP 9.0 SP2.
At runtime, FXMemberDataScript uses the FXListener's FRXCursor reference to generate scripts for the Dynamics feature, and then executes the script during the two Dynamic events.
|As a class that potentially invokes user code in one or both Dynamic events, FXMemberDataScript may adjust the values of the reportlistener CallEvaluateContents and CallAdjustObjectSize properties during a report run. It adjusts either or both of these properties during the BeforeReport event, if it can identify an invocation of code in Dynamics-based Memberdata or developer-script Memberdata that has been specified to run during a Dynamics method.|
FXMemberDataScript also executes free-form, or developer scripts. These scripts are stored separately in FRX Memberdata according to requirements specified in the next section of this topic. The developer script feature is exposed through the Report Builder Application Properties dialogs as the Run-time extensions panel (on the Other tab). It is available for any reportlistener event triggered by any layout elements, including bands, and also for report-global behavior.
|The Report Builder's exposure of runtime extension attributes for bands and report-global behavior is new in SP2.|
|MemberData column||Contents||ReportBuilder user interface exposure|
|Must be empty to be recognized as a developer script record. Because developer script is free-form, and also because it has been exposed on the Other tab since RTM, developer script is specified as not belonging to a specific namespace, unlike other Reporting MemberData.||None.|
|Must be R (the #DEFINE FRX_BLDR_MEMBERDATATYPE) to be recognized as a developer script record.||None.|
|The conditions under which developer script should execute.||Execute When textbox|
|The developer script to execute.||Run-time Extensions editbox and Code Zoom edit window.|
When you write developer scripts, the Execute When textbox in the Report Builder dialog is your opportunity to specify when FXMemberDataScript will execute it. Note that not all layout elements trigger the same set of events; for example, the BeforeReport and AfterReport events are invoked only on a report-global level, not for individual controls and bands. With that restriction in mind, you can provide appropriate instructions in this textbox using any one of three methods:
- a single (case-insensitive) method token, such as Render
- an expression that will evaluate to a logical value, such as MyApplication.IncludeScript or MyTable.MyLogicalField or .T.
- a "|"-delimited set of method tokens (case-insensitive), such as |BeforeReport|AfterReport|
In addition to the instructions you can provide in the Execute When textbox, you can use a CASE statement within your script to provide different behavior for different events.
|The Execute When instructions are evaluated in the ReportListener's CurrentDataSession, (where your data for the report resides), and script is executed within the CurrentDataSession unless you change the session within your script.|
FXMemberDataScript sends your script a set of parameters when it executes the script. The parameters, as shown below, are very similar to the to the standard FX subsystem method interface, with a leading parameter added to provide a reference to the FXMemberDataScript instance itself:
LPARAMETERS toFX, toListener, tcMethodToken,; tP1, tP2, tP3, tP4, tP5, tP6,; tP7, tP8, tP9, tP10, tP11, tP12
Just as with the ApplyFX method of the FX subsystem interface, tcMethodToken is an uppercase version of the reporting event that triggered the call, and the tP1... through tP12 parameters are the parameters sent by the base class reportlistener for each reporting event. As with ApplyFX, these parameters are passed by reference so that your script has an opportunity to change the values.
FXMemberDataScript checks your script for a leading PARAMETERS or LPARAMETERS line when it gathers your script from MemberData at the beginning of a report run. If it does not find one, it prepends the LPARAMETERS line of code you see above.
|If you prefer to put in the LPARAMETERS or PARAMETERS line yourself, perhaps to use different variable names, be sure to include the entire tP1.. TP12 set, even if the events you choose to handle with script have fewer parameters.|
When you edit developer script through the Report Builder dialog interface, if you haven't written any script previously for this layout element, the dialog provides some helpful notes to get you started. These notes include the default LPARAMETERS statement. You can find the text of the notes that the Report Builder puts in this dialog in XSOURCE.ZIP, in the REPORTBUILDER\memberscript_default_loc.txt file.
|If you do not want users to be able to add any Dynamics for a Field Expression, Shape, or Image layout control for which you have designated developer script, you can mark a control Protected so that its Properties dialog is not available to users. Of course, you can write code similar to Dynamics formatting code, for the EvaluateContents and AdjustObjectSize methods, as part of your developer scripts.|
During a non-PROTECTED report design session, the Report Builder Application invokes FRXCursor to generate a preview of the Dynamic script that will be executed by FXMemberDataScript. This preview is readonly because the script is not stored in MemberData; only the individual attributes chosen by the user in the Report Builder dialogs are stored. You can, however, copy the previewed Dynamic script and paste it into the Run-time extensions dialog to serve as the basis for a developer script.
The RTM CHM Report XML MemberData Extensions topic included another example class that might use the Memberdata cursor, FXMemberDataAware, with a hook method, AlterMemberDataInfo, providing a path for classes derived from FXMemberDataAware to change the values in the shared Memberdata cursor directly.
practice is still feasible in the finished design, in most cases it is more practical
for FX and GFX objects to maintain their own "private" cursors in the FRXDataSession,
which they can alter and optimize as needed.
The SP2 shipping class FXMemberDataScript is an example of a class that maintains such a private cursor, which it fills during the BeforeReport event. For each layout element with discoverable script of either of its two types, FXMemberDataScript creates a row in this "script cursor". For each row in the "script cursor", FXMemberDataScript repeats the original contents of the columns in the MemberData cursor with significance for its own work: FRXRECNO (the layout element on which action takes place), EXECUTE (the free-form script to executed), and EXECWHEN (conditions under which free-form developer script will execute). It adds a USERSCRIPT column, which holds the contents of the generated Dynamics script.
|You can see another example of an FX subsystem object with a private cursor in EXAMPLE3.PRG and forward, in Data Visualization in Reports with VFP 9.0 SP2.|
Because the script cursor is readwrite, it is possible to alter the contents of EXECUTE and USERSCRIPT on the fly, as many times as needed during a report run.
FXMemberDataScript acts to change the script unilaterally during the report run if its exposed property removeScriptOnFailure is .T. and a scripting error occurs, thus providing an example of this behavior. The other script cursor columns could also conceivably be altered by script; for example, you could dynamically alter EXECWHEN throughout the lifetime of a report. However it is probably more practical to use an EXECWHEN value of .T. and a CASE statement in the script in most scenarios.
Note, however, that scriptAlias is a PROTECTED property of the class. It is not accessible using toFX.scriptAlias within the executed script. Because FXMemberDataScript uses the EXECSCRIPT command to execute script, it is not accessible using the syntax THIS.scriptAlias within script, either. This design aspect is chosen to avoid multiple objects attempting to change the script cursor in the shipping version. You can derive a class from FXMemberDataScript that provides a method such as AlterMemberDataInfo with whatever method signature makes sense to you. If you are confident of the behavior of all cooperating objects in your environment, you can expose that method as PUBLIC. In your AlterMemberData method, you have an opportunity to validate the requested changes and error trap as necessary.
FXMemberDataScript adds only one public member to the required FX interface:
|Properties and methods||Description|
Indicates whether any script failure should remove the failed Dynamics or developer
script for the balance of the report run. If .F., this script continues to be executed
for additional report events and errors are handled silently.
In this scenario, a report has many Shape and Image layout controls, representing checkboxes on a survey response form. Only one such control, in the Page Header band, an Image control filled with a custom logo, requires Dynamics behavior.
For best performance, you should turn off AdjustObjectSize invocation except when it is needed. You can add developer script to the Page Header band to accomplish this.
You could set an explicit Execute When condition of |BeforeBand|AfterBand|, as part of the Page Header's Run-time extension settings. However, bands don't trigger any other events, and you need to use both of them, so it is easier just to use an Execute When condition of .T. in this scenario. You then add the following code as developer script:
DO CASE CASE tcMethodToken = "BEFOREBAND" toListener.CallAdjustObjectSize = 2 CASE tcMethodToken = "AFTERBAND" toListener.CallAdjustObjectSize = 1 ENDCASE
At runtime, FXMemberDataScript "notices" the AdjustObjectSize dynamic behavior during the BeforeReport event, and it will instruct the reportlistener to use the value 2 (always call) for its initial CallAdjustObjectSize value. However, the reportlistener respects the changed value as the report run continues after BeforeReport. Your developer code for this band ensures that the AdjustObjectSize event is only triggered for the shape(s) and image(s) in the Page Header band.