The help file includes some general recommendations for data-session handling during a REPORT FORM command in the Data Sessions Associated with Object-Assisted Report Runs section of the Understanding Visual FoxPro Object-Assisted Reporting topic, because every such command potentially involves three or more datasessions:
- the datasession containing data for the report (ReportListener.CurrentDataSession)
- the datasession in which the REPORT FORM command executed (ReportListener.CommandClauses.StartDataSession), which is not the same as the datasession containing data for the report when the FRX has a private data session
- the ReportListener's private work datasession (ReportListener.FRXDataSession)
- the datasession in which the ReportListener was instantiated (_ReportListener.listenerDataSession)
It is not a good practice to create FX subsystem objects randomly in datasession that may belong to forms invoking a report. Once they are added to the FXs or GFXs collection, they may "live" far longer than the first form that needed them, resulting in dangling references and what is commonly known as "stuck" datasessions in your application environment. Additionally, when a VFP object's methods are called, the object ordinarily pulls the action back to the datasession in which it was "born" -- which may not be the session containing the data on which the object needs to act.
The FFC classes generally try to create helper objects in the default datasession (datasession ID 0), because this practice eliminates the "stuck" sessions that result from objects created in transient datasessions. This practice allows the collection members to exist safely after the forms are destroyed. But the approach does not resolve all possible datasession combinations and issues, if a helper object switches datasessions during the report run to handle different types of data.
GFXExample's ApplyFX method shows a technique the object can use to remove itself from FXListener's collection at the conclusion of a report run. This technique is critical to safe use of FX and GFX objects that do not closely monitor their use of datasessions. Alternatively, a class can carefully save and restore datasessions during every action it takes; GFXExample also includes commented-out code that illustrates this approach.
|There is no real significance to this class having been marked a GFX rather than an FX by name; its behavior would be the same in either collection. In the sample code below, we add it to the FXs collection.|
FXMemberDataScript adds only one public member to the required FX interface:
|Properties and methods||Description|
Toggles demonstration of proper datasession handling in this example GFX class.
The following code is the full text of the ApplyFX method for this class.
Notice that, when GFXExample releases itself from the FXListener's collection using the RemoveCollectionMember method of the FXListener class, it uses THIS.Name. By default, the AddCollectionMember method has generated a unique name for every collection member you have added. The default behavior ensures that you can always safely address collection members uniquely even if they are not Singletons. Singleton collection members can usually be safely addressed by their .Name member property, even if it is not generated, and they can also be addressed by their .Class property when you use the FXListener.RemoveCollectionMember method.
LPARAMETERS m.toListener, m.tcMethodToken,; m.tP1, m.tP2, m.tP3, m.tP4, m.tP5, m.tP6,; m.tP7, m.tP8, m.tP9, m.tP10, m.tP11, m.tP12 IF m.tcMethodToken == "BEFOREREPORT" AND ; THIS.showDataSessionIssue AND ; (m.toListener.CurrentDataSession = m.toListener.CommandClauses.StartDataSession) MESSAGEBOX("This report does not use a private data session," + CHR(13) + ; "so you won't see the problem.") ENDIF IF m.tcMethodToken == "BEFOREBAND" SET DATASESSION TO (m.toListener.CurrentDataSession) m.toListener.doStatus("working here... ") IF THIS.showDataSessionIssue * no switch back here. *!* ELSE *!* SET DATASESSION TO (m.toListener.ListenerDataSession) ENDIF ENDIF IF (NOT THIS.showDataSessionIssue) AND ; m.tcMethodToken == "AFTERREPORT" * if the following is not included, * a "stuck" datasession results unless * some additional object later in * the collection did the switch back m.toListener.removeCollectionMember(THIS.Name,.T.) RELEASE THIS ENDIF
The following code shows how you might add this class to an FXListener collection to see correct and incorrect datasession behavior.
LOCAL ox DO (_REPORTOUTPUT) WITH 1, ox ? ox.addCollectionMember("gfxexample","_reportlistener") REPORT FORM c:\temp\customers OBJECT ox SET && no stuck session, because CUSTOMERS.FRX does not have a private datasession CLEAR ALL MODIFY REPORT c:\temp\customers.FRX && add private datasession to the report LOCAL ox DO (_REPORTOUTPUT) WITH 1, ox ? ox.addCollectionMember("gfxexample","_reportlistener") REPORT FORM customers OBJECT ox SET && you see an "Unknown" session in the Data Session window CLEAR ALL LOCAL ox DO (_REPORTOUTPUT) WITH 1, ox ? ox.addCollectionMember("gfxexample","_reportlistener") oy = ox.FXs oy.showDataSessionIssue = .F. REPORT FORM customers OBJECT ox SET && no stuck session, because gfxExample releases itself