The data path manager class The data path manager class is designed to be instantiated as a transient object in the BeforeOpenTables method of a Form dataenvironment.. However, by doing
Trang 1Reading the data from the specified source into the form's cursor is handled by the custom
ReadFile() method as follows:
LOCAL ARRAY laTfer[1,2]
LOCAL lcSceFile, lcOldFile
WITH ThisForm
*** Check the source file, clear cursor if a new file is being created
IF chkSource() > 0
IF EMPTY( ALLTRIM( txtFName.Value ) )
*** Creating a new file - just return
*** Specified source is OK, so gather full path and file name
lcSceFile = ALLTRIM( ADDBS( txtDir.Value )) + ALLTRIM( txtFName.Value )
IF JUSTEXT( lcSceFile ) = "DBF"
*** It's a table, so just read it into an array
SELECT heading, item FROM (lcSceFile) ORDER BY sortorder INTO ARRAY laTfer ELSE
*** It's an INI File (maybe) So read it
goIniMgr.ReadIniFile( @laTfer, lcSceFile )
ENDIF
*** Clear Cursor and Copy results in
ZAP IN curIniFile
INSERT INTO curIniFile FROM ARRAY laTfer
*** Strip off heading "[]" - they will be re-written anyway
REPLACE ALL heading WITH CHRTRAN( heading, '[]','') IN curIniFile
RefreshForm()
ENDWITH
Writing the data out from the cursor is handled in the custom WriteFile() method as
follows:
LOCAL ARRAY laTfer[1,2]
LOCAL lcOldFile, lcDestFile
*** Now create a new, empty file ready for writing to
*** We need to do this to ensure that deletions get made properly
lnHnd = FCREATE( lcDestFile )
Trang 2IF lnHnd < 0
MESSAGEBOX( 'Unable to create new file ' + CHR(13) ;
+ lcDestFile, 16, 'Cannot Contuinue')
RETURN
ELSE
FCLOSE(lnHnd)
ENDIF
*** Now write the new file - ignore empty "heading" fields
SELECT * FROM curinifile WHERE ! EMPTY(heading) INTO ARRAY laTfer
WITH goIniMgr
*** Write file contents
.WriteIniFile( @laTfer, lcDestFile )
One of the most frequently written snippets of code, in almost any application, looks
something like this:
*** Save Current work area
Overview
The SelAlias class is designed to accept the alias name of a table as a parameter and switch to
that table's work area If the table is not open it will open the table for us More importantly itwill 'remember' that it opened the table and will, by default, close it when it is destroyed Theclass provides support for an additional parameter which can be used to specify an alias namewhen it is necessary to open a table with an alias other than the real name of the table
Trang 3The class has no exposed properties or methods and does all of its work in its Init and Destroy methods By creating an object based on this class, and scoping it as LOCAL, we neednever write code like that shown above again.
A word on creating the selector object
A selector object may be created in the usual way by first loading the procedure file into
memory and then using the CreateObject() function whenever an instance is needed However, Version 6.0 of Visual FoxPro introduced an alternative method, using the NewObject()
function, which allows you to specify the class library from which a class should be
instantiated as a parameter While it is marginally slower, it does mean that you do not need toload and retain procedure files in memory and is useful when you need to create an object 'onthe fly', like this one The syntax for both methods is given below (Note that with
NewObject(), if the class is not a visual class library, Visual FoxPro expects both a 'module or
program' name as the second parameter and either an application name or an empty string or a NULL value as the third.)
*** Using CreateObject()
SET PROCEDURE TO selalias ADDITIVE
loSel = CREATEOBJECT( 'xSelAlias', <Alias>, [|<Table Name>])
*** Using NewObject()
loSel = NEWOBJECT( 'xSelAlias', 'selalias.prg', NULL, <Alias>, [|<Table Name>])
One word of caution – if you use multiple instances of this class in the same procedure ormethod to open tables, either ensure that all objects are created from the same work area or thatthey are released in the reverse order to that in which they were instantiated If you do not dothis you could end up in a work area that was selected as a result of opening a table but which
is now empty
How the selector class is constructed
As mentioned in the overview this class has no exposed properties or methods and does all of
its work in its Init or Destroy methods Internally it uses three protected properties to record:
• the work area in which it was instantiated
• the alias of the table it is managing
• whether the table was already open on instantiation
The selector class Init method
The Init method does four things First it checks the parameters An alias name is the
minimum that must be passed, and in the absence of the optional second parameter – the tablename, it assumes that the table is named the same as the alias If passed, the table name mayinclude an extension and may also include a path (Notice the use of ASSERT in this part of themethod The objective here is to warn developers of errors that may arise in the calling syntaxwithout impacting the run time code.)
Trang 4PROCEDURE INIT( tcAlias, tcTable )
tcAlias = UPPER( ALLTRIM( tcAlias ))
IF VARTYPE( tcTable ) # "C" OR EMPTY( tcTable )
*** If already in correct work area - do nothing
IF UPPER(ALLTRIM( ALIAS() )) == tcAlias
RETURN F.
ENDIF
Then it determines whether the required alias is already in use and, if not, tries to open the
table under the specified alias If it succeeds it sets its 'lWasOpen' property to .F. This allowsthe same table to be opened more than once under different aliases If the table cannot beopened, a value of .F will be returned and the object will not be instantiated (NOTE: A
"production" version of this class should also check that the file exists, and that it is a validVisual FoxPro table, before attempting to open it with a USE command Such code has alreadybeen covered elsewhere and has been deliberately omitted from this class to keep it as simple
as possible See the ISDBF() function in Chapter 7, "How to compare the structures of two
tables" for one solution.)
*** If Specified Alias not open - Open it
IF ! USED( tcAlias )
USE (tcTable) AGAIN IN 0 ALIAS (tcAlias) SHARED
*** And Check!
llRetVal = USED( tcAlias )
*** If Forced Open, Note the fact
Finally it stores the currently selected work area number and the alias name to its
'nOldarea' and 'cAlias' properties and switches to the required work area The object is,
therefore, only instantiated when everything has worked as expected:
*** IF OK, save current work area and
*** Now Move to the specified Work Area
Trang 5The selector class Destroy method
The Destroy method handles the tidying up of the environment If the selector opened the table,
it is closed – otherwise it is left open The work area in which the object was instantiated isthen selected and the object released:
Using the selector class
The class is intended to be used to instantiate a local object in a procedure or method whenever
it is necessary to change work areas The example program (ChgArea.prg) shows how it may
be used:
**********************************************************************
* Program : ChgArea.prg
* Compiler : Visual FoxPro 06.00.8492.00 for Windows
* Abstract : Illustrate the use of the SELALIAS class for controlling
* : and changing Work Areas Output results to screen
**********************************************************************
*** Make sure we are all closed up
CLEAR
CLOSE TABLES ALL
*** Open Clients table
USE sqlcli ORDER 1 IN 0
? 'Using Selector with Just an Alias'
? '================================='
?
? "USE sqlcli ORDER 1 IN 0"
? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS()
?
*** Create a Client Selection Object
loSelCli = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'SqlCli' )
? "loSelCli = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'SqlCli' )"
Trang 6? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS()
?
*** Open Invoices Table (temporarily)
loSelInv = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'SqlInv' )
? "loSelInv = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'SqlInv' )"
? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS()
*** Open Clients Again under new Alias
loSelCli = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'Clients', 'SqlCli' )
? "loSelCli = NEWOBJECT('xSelAlias', 'SelAlias.prg', NULL, 'Clients',
'SqlCli')"
? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS()
?
*** Open Invoices Table (temporarily)
loSelInv = NEWOBJECT( 'xSelAlias', 'SelAlias.prg', NULL, 'Invoices', 'SqlInv' )
? "loSelInv = NEWOBJECT('xSelAlias','SelAlias.prg', NULL, 'Invoices',
Trang 7How can I manage paths in a form's dataenvironment?
The form's dataenvironment provides many benefits including the facility to auto-open andclose tables, to set the buffering of individual tables and, at design time, to use drag and drop tocreate data bound controls on a form However, there is one perennial problem with using theform's dataenvironment - the way in which it handles the issue of paths for the tables it
contains is, to say the least, convoluted
Every cursor created in the dataenvironment has two properties that are involved with the
table name and path information, namely 'Database' and 'CursorSource' However, they are
used differently depending on whether the table in question is free or bound to a databasecontainer The actual way in which information gets stored depends upon the location of the
tables at design time according to the following rules:
Table 10.2 Cursor properties that determine the location of source data
Table Type and Location Database CursorSource
Bound Table, DBC on current
The following examples show the results of adding a table to the DE of a form while
running a VFP session with drive "G:"set as the default drive and "\VFP60\" as the current
Trang 8The 'no code' solution!
The easy answer to this issue is, therefore, to keep all tables (free or bound) and databasecontainers in the same directory and to make sure it is defined as a sub-directory of yourdevelopment directory This ensures that Visual FoxPro only ever stores the relative path fortables (See examples 2 and 5 above.)
When you distribute your application, ensure that a subdirectory (named the same as theone used during development) is created under the application's home directory and that alldata files are installed there However, there are many times when this solution is just notpossible, most obviously when the application is being run on client machines but using shareddata stored on a server So what can we do about it?
The hard-coded solution!
A form's native dataenvironment cannot be sub-classed (although we can, of course, create ourown dataenvironment classes in code) This means that there is no way of writing code into aform class at design time to handle the resolution of paths, because such code would have to be
placed into the dataenvironment BeforeOpenTables method (Why BeforeOpenTables?
Because the OpenTables method creates the cursor objects and then calls BeforeOpenTables
after the objects are created but before the information in them is used to actually open the
tables.) So one approach is to add some code to the BeforeOpenTables method of every form to set the paths for the contained tables as necessary This will work, but seems rather an 'old- fashioned' way of doing it Apart from anything else it would make maintaining an application
with a lot of forms a major undertaking There must be a better way!
The data-driven object solution!
If we cannot sub-class the native dataenvironment, perhaps we could create our own class tohandle the work, and simply limit the code that has to be added to each instance of a form class
to a single line? Indeed we can do just that, and if we use a table to hold path information wecan also greatly simplify the task of maintaining the application Such a solution is presented inthe next section of this chapter
The data path manager class
The data path manager class is designed to be instantiated as a transient object in the
BeforeOpenTables method of a Form dataenvironment It's function is to scan through all of
Trang 9the member objects of the dataenvironment and, for each cursor object that it finds, perform alook up in a separate 'system' table which defines the paths to be used for its tables at run time.
While we still need to add code to the BeforeOpenTables method of the dataenvironment in
every form that we create, we only need to add one line The code executed is contained in asingle class and uses a single table of pre-defined structure Maintenance is, therefore, a minormatter when you adopt this strategy
The path management table
The first component that we need in order to implement the strategy outlined above is thelookup table that will hold the information we wish Visual FoxPro to use at run time Thistable has been (imaginatively) named 'datapath.dbf' and although we have included it in theproject's database container we would normally recommend that it be used as a free table Thestructure is as follows:
Structure For: C:\VFP60\CH10\DATAPATH.DBF
SET_PATH C ( 60,0 ) NOT NULL && Drive and Path
SET_DBC C ( 20,0 ) NOT NULL && DBC Name (Bound Tables only)
SET_TABLE C ( 20,0 ) NOT NULL && Name of table in DBC (Bound Tables) && File name and extension (Free Tables)
To speed searches the table is indexed on the table name field and has an index on
DELETED() Since this table would probably be set up locally on a client machine (to handle individual's drive mappings), the issue of bringing down large indexes on DELETED() over the
network is not likely to arise We have our example table populated as illustrated in Figure
10.2 below:
Figure 10.2 Data path mapping table
Trang 10The path management class (Example: chgpaths.scx)
The actual class, like the work area selector, does its work directly in the Init method, or methods called from Init, and has no exposed methods or properties This means that, when
instantiated, the object automatically carries out its function and can then be released Theclass defines two protected properties for its internal use, an array to hold object references tothe dataenvironment cursors and a property to store the reference to the calling
dataenvironment object itself
The principle behind its operation is that it receives a reference to the calling
dataenvironment (as a parameter) and validates that the reference is both a valid object andactually relates to an object whose base class is 'dataenvironment.' The calling DE is thenparsed to get a reference to each cursor object, which is stored to the internal array Havingopened the lookup table the final step is to retrieve each cursor's reference in turn and
determine the name of the table on which it is based (uses JUSTSTEM() to return the name from the CursorSource property).
The table name is then looked up in the mapping table and depending on the data found (ifany) the Database and CursorSource properties are updated The actual code used is:
**********************************************************************
* Program : DPathMgr.prg
* Compiler : Visual FoxPro 06.00.8492.00 for Windows
* Abstract : Uses lookup table to get correct paths for tables
* : at run time, set the paths in DE Cursor Object
* : Call from BeforeOpenTables Method of a Form DE
* : Expects a reference to the DE to be passed - can use
NEWOBJECT():
* : loPathSet = NEWOBJECT( 'dPathMgr', 'dpathmgr.prg', NULL, THIS )
**********************************************************************
DEFINE CLASS DPathMgr AS relation
*** Define Protected Properties ***
*** Array for list of Cursors
PROCEDURE Init( toDe )
LOCAL lnCursors
*** Check the parameter
IF VARTYPE( toDe ) = "O"
*** Have a valid Object reference
Trang 11This.oDe = toDe
IF LOWER( This.oDE.BaseClass ) # "dataenvironment"
*** But it's not a DE!
ASSERT F MESSAGE "DPathMgr Class Requires a reference to the " ; + CHR(13) + "DataEnvironment Object which calls it." RETURN F.
The OpenRefTable explicitly tries to open the DataPath table This could, if necessary, be
parameterized but we cannot see any great immediate benefit for doing so (rather the opposite
in fact) There may conceivably be situations in which multiple mapping tables would berequired by an application and it would then be entirely appropriate to pass a parameter for thetable to use However, by doing so you lose one of the main benefits of this approach, which is
that the code to be inserted into the BeforeOpenTables of forms would no longer be the same
for every form:
PROTECTED PROCEDURE openreftable
*** Open up the Reference table
The GetTables method uses the stored object reference to the calling dataenvironment to
populate an array with all member objects This is then scanned and references to cursorobjects are stored to the array property The method returns the number of cursors that itfound:
Trang 12PROTECTED PROCEDURE GetTables
LOCAL ARRAY laObj[1]
LOCAL lnObjCnt, lnCnt, loObj, lnRows, lcObjName
*** Get a list of all objects in the DE
lnObjCnt = AMEMBERS( laObj, This.oDe, 2)
*** Scan the list
lnRows = 0
FOR lnCnt = 1 TO lnObjCnt
*** Check if this object is actually a Cursor
loObj = EVAL( "This.oDe." + laObj[lnCnt] )
IF loObj.BaseClass = "Cursor"
*** It is, so save its reference to the internal array
*** Add a new row to the cursors array
no entry in the lookup table, the cursor's properties are not changed in any way:
PROTECTED PROCEDURE SetPaths( tnCursors )
LOCAL lnCnt, loObj, lcTable
*** Scan the list
FOR lnCnt = 1 TO tnCursors
*** Retrieve the Object Reference from the array
loObj = This.aCursors[lnCnt]
*** Find the Table Name
lcTable = UPPER( JUSTSTEM( loObj.CursorSource ))
*** Look up the name in the reference table which lists
*** where the data should be taken from
IF SEEK( lcTable, "datapath", "ctable")
*** We have a reference for this table!
IF ! EMPTY( set_dbc )
*** We have a bound table
loObj.Database = ALLTRIM( datapath.set_path ) ;
Trang 13Using the data path manager
The sample form ChgPaths.SCX uses the data path manager to change the paths of the tables
that have been added to its dataenvironment To experiment with this, simply copy the
CH10.DBC (and all the tables) to an alternate location and change the SET_PATH field in the
copy of the DataPath table that remains in the original location The only code that has been added to the form's dataenvironment is the single line in the BeforeOpenTables method, as
follows:
NEWOBJECT( 'dpathmgr', 'dpathmgr.prg', NULL, THIS )
which instantiates the data path manager object and passes it a reference to the
Trang 14Figure 10.3 Setup for the SQLCLI tables in the example form DE
Figure 10.4 The example form running – note that tables are now drawn from a
different source
Trang 15How can I manage forms and toolbars in my application?
There are probably as many answers to this question as there are developers writing
applications using Visual FoxPro A key part of any application framework is the mechanismfor managing forms and all frameworks include a "Form Manager" of some sort The
mechanism for implementing it will depend on your framework but there are certain basic
tasks that any such manager object must perform:
• Instantiation of forms (whether SCX or VCX based)
• Tracking which form (and which instance of a form) is currently active
• Ensuring that the appropriate toolbar is available
• Adding and removing forms to its own list of active forms as they are initialized orreleased
Of course there are many other functions that could be performed by the form manager
(for example, adding/removing items to the Window list or 'cascading' forms as they areinitialized) but the four listed above constitute the basic functionality which the class mustprovide
In order to implement a form manager, it is necessary to create a 'managed' subclass, for
both Forms and Toolbars so that the additional code to interact with the manager can beisolated The following sections present the code for these classes and for a form manager classthat will handle all the basic tasks described above This class has been designed to be
instantiated as a 'global' object, which isn't the only way to do it, but is the simplest to
illustrate We could also have implemented the necessary methods as part of a broader
'application manager' class or even handled the instantiation and referencing of the formmanager indirectly through an application object
The managed form class
Forms intended to work with the form manager belong to a special class ('xFrmStdManaged' in GenForms.vcx) In addition to some necessary code in the Init, Activate and Destroy methods,
three custom properties and two methods are required for interaction with the Form Manager
as follows:
Table 10.3 Custom properties and methods for the managed form class
cInsName Property Instance name, assigned by the form manager when form
initialized cTbrName Property Name of the toolbar used by the form (if any)
lOneInstance Property When T prevents Form Mgr from creating multiple instances of
the form ReportAction Method Call manager's FormAction method
CheckFrmMgr Method Returns an object reference to the form manager
Trang 16The custom properties
• The cInsName property is used to store the instance name assigned by the form
manager, to a form when it is initialized The form manager stores both the form nameand the assigned instance name in its internal collection This caters for multipleinstances of a form by providing the form manager a means for uniquely identifyingeach instance of a particular form
• The cTbrName is populated at design time with the name of the toolbar class
associated with the form This property will be read by the form manager at run time
to determine which, if any, toolbars are needed and to ensure that when a particularform is activated, the correct toolbar is displayed
• The lOneInstance property may be set to indicate to the form manager that the form is
single instance only When the form manager is instructed to instantiate a form whichalready exists in its collection, it will simply restore and activate the existing form ifthis property is set
Form class ReportAction method
This method provides the "single point of contact" between the form and the form manager Itcan be called by any form method that passes a parameter indicating the type of action
required from the form manager (in our example, this would be either 'ACTIVATE' or 'DESTROY'):
IF VARTYPE( loFrmMgr ) = "O"
*** Tell Form Manager to make this the active form
loFrmMgr.FormAction( tcAction, cInsName )
ELSE
*** No form Manager, so nothing special required
ENDIF
ENDWITH
The responsibility for checking for the existence of the form manager is passed to the
CheckFrmMgr method, which returns either the appropriate object reference or a NULL value If
a valid reference is returned, the method then calls the manager's FormAction method and passes both the required action and the form's instance name to provide an unambiguous
reference for the form manager
Trang 17Form class Init method
The form class Init method expects to receive either a parameter object containing a property named cInsName or a character string which is the name to be stored to its cInsName property:
LPARAMETERS tuParam
*** Class method expects the Instance Name to be passed
*** either as 'cInsName' in a parameter object or as a string.
*** Could actually test here but what the heck! Live dangerously!
*** (In fact the test should be done in either the instance or the subclass)
IF VARTYPE( tuParam ) = "O"
*** Store cInsName property to form property
LPARAMETERS toParams
*** Extract Instance name from the parameter object
IF VARTYPE( toParams ) = 'O' AND PEMSTATUS( toParams, 'cInsName', 5 )
*** Pass Instance Name up to parent class method
*** No Instance Name specified
*** Take whatever action is appropriate at the time
ENDIF
*** Do whatever else is needed here
One major benefit of using a parameter object like this, as we discussed in Chapter 2, isthat it allows you to use 'named' parameters which simplifies the code needed to read thepassed in values
Trang 18Form class Activate, Release and QueryUnload methods
In addition, the class includes two lines of code in both the Activate and Release methods to initiate communication with the form manager The code, in each case, calls the ReportAction
method and passes the name of the method which is executing:
ThisForm.ReportAction( JUSTEXT( PROGRAM() ))
DODEFAULT()
Finally, the QueryUnLoad method of this class includes an explicit call to the Release
method to ensure that however the user exits from the form, the Form Manager is notified
(This is because QueryUnLoad normally bypasses the Release method The next event
common to both Release and QueryUnload is Destroy, and this is too late for the Form
Manager.)
The managed toolbar class
The use of toolbars in an application is difficult to address generically Whether you usedifferent toolbars for different forms, or use a single toolbar and enable/disable options asnecessary will affect the design details However, whichever approach you take, the toolbarwill need to interact with your forms Since the Form Manager controls the forms, it seemsentirely reasonable that it should also look after the toolbars, which must, therefore, be
designed accordingly Our managed abstract toolbar class ("xTbrStdManaged" in
GenClass.vcx) has been set up as follows
First, the toolbar's ControlBox property has been set to .F. thereby ensuring that a usercannot inadvertently close a toolbar (now the responsibility of the form manger) and thetoolbar has been given a Private DataSession Three custom methods have been added and
some code added to the native Activate method of the class as follows.
The toolbar class Activate method
The issue addressed here is to ensure that whenever a toolbar is activated, it will synchronize
itself with whatever form is currently active on the screen A toolbar's Activate method is called
whenever the toolbar is shown, and since the form manager will handle toolbars by calling
their Show and Hide methods we can use this to call the method that will synchronize the
toolbar's settings with the current form:
*** Synchronize Toolbar to currently active form
*** Activate is called from Show() so will always fire
*** When Form Manager calls the Toolbar.Show()
This.SetDataSession()
The toolbar class SetDataSession method
When called, this method will either set the toolbar to the same datasession as the currently
active form and then call the toolbar's custom SynchWithForm method If no form is active, it simply calls the custom SetDisabled method:
Trang 19LOCAL loForm
*** Get reference to active form
IF TYPE( "_Screen.ActiveForm" ) = "O" AND ! ISNULL( _Screen.ActiveForm )
*** Get Reference to Active Form
*** No form, so disable the toolbar!
*** Note: This should never happen because the form manager should
*** always be handling the visibility of the toolbar
This.SetDisAbled()
ENDIF
The toolbar class SynchWithForm method
This is simply a template method to be completed in a concrete class It is called by the custom
SetDataSession method when an active form is found and is where you would handle any
synchronization details (enabling/disabling buttons and so on) It receives, as a parameter, areference to the currently active form
The toolbar class SetDisabled method
This method provides default behavior to disable all controls on the toolbar when no active
form is found This should never happen when running under form manager control, but the
behavior is provided for anyway
The form manager class
The form manager class illustrated here has a very simple public interface There are only three
custom methods ('DoForm', 'FormAction' and 'ReleaseAll') The DoForm method is intended to
be called explicitly in code and is responsible for creating forms and their associated toolbars
The FormAction method is called automatically from the Activate and Destroy methods in the
managed form class but could easily be extended to handle other actions if needed The
ReleaseAll method is designed to be called from the shutdown process but could also be called
from a 'Close All Forms' menu item This class is designed to work together with the ManagedForm and Managed Toolbar classes described in the preceding sections The actual code isdiscussed in the following sections
Form manager definition and Init method
The class defines two arrays and four properties, all of which are protected as follows:
Trang 20Table 10.4 Custom properties and methods for the form manager form class
Name PEM Purpose
aFmList Array Property The Forms Collection
aTbList Array Property The Toolbars Collection
nFmCount Property Number of forms contained in the Forms Collection
nTbCount Property Number of toolbars contained in the toolbars Collection
nFmIndex Property Index to the currently active form in the Forms Collection
nTbIndex Property Index to the currently active toolbar in the Toolbar Collection
The Init method simply initializes these properties:
DEFINE CLASS xFrmMgr AS RELATION
PROTECTED ARRAY aFmList[1,4], aTbList[1,3]
PROTECTED nFmIndex, nFmCount, nTbCount, nTbIndex
FUNCTION Init
WITH This
*** Initialise Properties
aFmList = "" && Form Collection
nFmCount = 0 && Managed Form Count
nFmIndex = 0 && Index into the Collection for current form
aTbList = "" && Toolbar Collection
nTbCount = 0 && Toolbar Count
nTBIndex = 0 && Index into the Collection for current toolbar
ENDWITH
ENDFUNC
The form manager DoForm method
This custom method is where the form manager creates forms and any associated toolbars It isthe largest single method in the class but does not really lend itself to further decomposition.The method allows for up to three parameters to be passed in, but we would normally expect topass a single parameter object The only reason for this structure is to simplify calling themethod directly from a menu item
The first two parameters are used for the name and method to be used for instantiating the
form When calling an SCX only the form name need be passed, unless there are additional
parameters, because the default value for the second will be .F. - which will invoke the DO FORM mechanism If the form is to be instantiated from a class, the second parameter mustalways be passed explicitly as .T.
The first thing this method does is to check the parameters and generate (using
SYS(2015)) a valid character string which will be used for both the object reference to the formand its "instance" name:
****************************************************************
*** xFrmMgr::DoForm( tcFmName, tlIsClass, tuParm1, tuParm2, tuParm3 )
*** Exposed Method to Run a Form
*** Provision for 3 params, but normally would expect only 1 (as
*** A parameter object)
****************************************************************
Trang 21FUNCTION DoForm ( tcFmName, tlIsClass, tuParm1, tuParm2, tuParm3 )
LOCAL lnFormParams, lcFmName, loFmRef, lnFmIdx, llRetVal, lnCnt
"Name of a Form, or a Form Class," + CHR(13) ;
+ "Must be passed to Form Manager DoForm()"
*** Check to see if we have this Form already?
.nFmIndex = FmIdx(tcFmName)
*** If we have it, is it single instance
IF nFmIndex > 0
*** Get a reference to the form and see if we can
*** have multiple instances of it.
Trang 22If the form is not already instantiated, or if it is but is not single instance, then a new form
is required First we generate the object reference and instance name, and then construct aparameter object for the form:
*** Either first run of the form, or a new instance is required
*** Create the parameter object
*** Generate an Instance Name and Object Reference
STORE SYS(2015) TO lcFmName, loFmRef
*** Create the Parameter Object
oParams = NEWOBJECT( "xParam", "genclass.vcx" )
WITH oParams
*** First the Instance Name
AddProperty( 'cInsName', lcFmName )
*** Add a property count
AddProperty( 'nParamCount', lnFormParams )
*** Add any additional parameters to be passed to the form
IF lnFormParams > 0
FOR lnCnt = 1 TO lnFormParams
lcPName = "tuParm" + ALLTRIM(STR(lnCnt))
AddProperty( lcPName, &lcPName )
*** Run as a Form using NAME and LINKED clauses
DO FORM (tcFmName) NAME loFmRef WITH oParams LINKED
ENDIF
*** Update the Collection with the new form details
IF VARTYPE( loFmRef ) = "O"
*** YEP - got a form, so increment form count and populate the collection nFmCount = nFmCount + 1
DIMENSION aFmList[.nFmCount, 4]
.aFmList[.nFmCount, 1] = loFmRef && Object Reference
.aFmList[.nFmCount, 2] = lcFmName && Instance Name
.aFmList[.nFmCount, 3] = tcFmName && Form Name
.aFmList[.nFmCount, 4] = UPPER( ALLTRIM ( loFmRef.cTbrName )) && Toolbar
to use
*** Make this the Active Form
Trang 23After creating the form we check to ensure that the form really did get created, and then
we populate the Forms collection with the relevant information The last thing to do is to
handle display of the toolbars, which is done by calling the DoToolBar method:
*** Finally sort out the toolbar requirement
The form manager DoToolbar method
This method is called only when a new form, or a new instance of a form, is created Itsfunction is to update the Toolbar collection if the new form requires a toolbar This mayinvolve incrementing the count of an existing toolbar or creating a new toolbar The samemethod handles both contingencies:
****************************************************************
*** xFrmMgr::DoToolBar( tcTbName )
*** Protected method to create or set the named toolbar active
*** Called when creating a form
*** Check to see if we have the toolbar already
lnTbIdx = TbIdx( tcTbName )
IF lnTbIdx > 0
*** We already have this one, so activate it
*** And increment its counter by one
aTbList[ lnTbIdx, 2] = aTbList[ lnTbIdx, 2] + 1
ELSE
*** We need to create it and add it to the collection