1001 Things You Wanted To Know About Visual FoxPro phần 8 potx

47 472 1
1001 Things You Wanted To Know About Visual FoxPro phần 8 potx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Chapter 10: Non-Visual Classes 321 Reading 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 ZAP IN curIniFile .RefreshForm() RETURN ENDIF ELSE *** Source file check failed! RETURN ENDIF *** 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 WITH ThisForm *** Must have a destination IF EMPTY( .txtDestFName.Value ) MESSAGEBOX( 'An output file must be specified!', 16, 'Unable to Continue') .txtDestFName.SetFocus() RETURN ENDIF *** We have a destination lcDestFile = ALLTRIM(ADDBS(.txtDestDir.Value)) + ALLTRIM(.txtDestFName.Value) *** Delete the File if it already exists IF ! FILE( lcDestFile ) DELETE FILE (lcDestFile) ENDIF *** 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 ) 322 1001 Things You Always Wanted to Know About Visual FoxPro IF 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 ) ENDWITH *** Clear Cursor ZAP IN curIniFile .RefreshForm() ENDWITH How to select a different work area, OOP style! ( Example: ChgArea.prg ) One of the most frequently written snippets of code, in almost any application, looks something like this: *** Save Current work area lnSelect = SELECT() *** Select Required Area IF ! USED( <Alias> ) USE <table> IN 0 AGAIN ALIAS <Alias> ENDIF SELECT <New Work Area> *** Do Something There … <commands> … *** Return to original work area SELECT (lnSelect) Now, admittedly, this is not really very difficult, but it or some variant is repeated many times in an application. We really should be able to do better than this now that we have all the power of Object Orientation behind us and indeed we can. 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 it will 'remember' that it opened the table and will, by default, close it when it is destroyed. The class provides support for an additional parameter which can be used to specify an alias name when it is necessary to open a table with an alias other than the real name of the table. Chapter 10: Non-Visual Classes 323 The 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 need never 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 to load and retain procedure files in memory and is useful when you need to create an object 'on the 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 or method to open tables, either ensure that all objects are created from the same work area or that they are released in the reverse order to that in which they were instantiated. If you do not do this 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 table name, it assumes that the table is named the same as the alias. If passed, the table name may include an extension and may also include a path. (Notice the use of ASSERT in this part of the method. The objective here is to warn developers of errors that may arise in the calling syntax without impacting the run time code.) 324 1001 Things You Always Wanted to Know About Visual FoxPro PROCEDURE INIT( tcAlias, tcTable ) LOCAL llRetVal *** No Alias Passed - Bail Out IF ! VARTYPE( tcAlias ) = "C" ASSERT .F. MESSAGE "Must Pass an Alias Name to Work Area Selector" RETURN .F. ENDIF tcAlias = UPPER( ALLTRIM( tcAlias )) IF VARTYPE( tcTable ) # "C" OR EMPTY( tcTable ) tcTable = tcAlias ELSE tcTable = UPPER( ALLTRIM( tcTable )) ENDIF Next, it checks the currently selected alias. If this is already the required alias, it simply returns a value of .F. and the object is not instantiated. The reason is simply that if the table is already open and selected, there is nothing for the object to do anyway: *** 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 allows the same table to be opened more than once under different aliases. If the table cannot be opened, 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 valid Visual FoxPro table, before attempting to open it with a USE command. Such code has already been 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 IF llRetVal This.lWasOpen = .F. ENDIF ELSE llRetVal = .T. ENDIF 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 Chapter 10: Non-Visual Classes 325 IF llRetVal This.nOldArea = SELECT() SELECT (tcAlias) This.cAlias = tcAlias ENDIF *** Return Status RETURN llRetVal ENDPROC The 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 is then selected and the object released: PROCEDURE DESTROY WITH This *** If table opened by this object, close it IF ! .lWasOpen USE IN (This.cAlias) ENDIF *** Restore Previous work area IF ! EMPTY( .nOldArea ) SELECT ( .nOldArea ) ENDIF ENDWITH ENDPROC 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' )" 326 1001 Things You Always Wanted to Know About Visual FoxPro ? "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() ? *** Now close the Invoices table by releasing the object RELEASE loSelInv ? "RELEASE loSelInv" ? "USED( 'SqlInv' ) => "+IIF( USED( 'SqlInv' ), "Still In Use", "Not Open" ) ? *** Now releaswe the Client table object RELEASE loSelCli ? "RELEASE loSelCli" ? "USED( 'SqlCli' ) => "+IIF( USED( 'SqlCli' ), "Still In Use", "Not Open" ) ? ? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS() ? ? "Press a key to clear the screen and continue " INKEY(0, 'hm' ) CLEAR ? 'Using Selector to create an Alias' ? '=================================' ? ? "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', 'SqlInv')" ? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS() ? *** Now close the Invoices table by releasing the object RELEASE loSelInv ? "RELEASE loSelInv" ? "USED( 'Invoices' ) => "+IIF( USED( 'Invoices' ), "Still In Use", "Not Open" ) ? *** Now release the Client table object RELEASE loSelCli ? "RELEASE loSelCli" ? "USED( 'Clients' ) => "+IIF( USED( 'Clients' ), "Still In Use", "Not Open" ) ? ? "Area:"+PADL(SELECT(),2)+" Using Table "+JUSTSTEM(DBF())+" as Alias "+ALIAS() ? ? "Press a key to clear the screen and finish " INKEY(0, 'hm' ) CLEAR Chapter 10: Non-Visual Classes 327 How can I manage paths in a form's dataenvironment? The form's dataenvironment provides many benefits including the facility to auto-open and close tables, to set the buffering of individual tables and, at design time, to use drag and drop to create data bound controls on a form. However, there is one perennial problem with using the form'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 database container. 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 drive Relative Path and File Name of DBC The name of the table in the DBC Bound Table, DBC on different drive Absolute Path and File Name of DBC The name of the table in the DBC Free Table on current drive Empty Relative Path and File Name of DBF Free Table on different drive Empty Absolute Path and File Name of DBF 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 directory: [1] Free Table on a different drive Alias = "messageb" Database = "" CursorSource = e:\vfp50\common\libs\messageb.dbf [2] Free table in subdirectory of current working directory (G:\VFP60\) Alias = "customer" Database = "" CursorSource = data\customer.dbf [3] Table from a DBC on a different drive Alias = "demone" Database = c:\vfp60\ch08\ch08.dbc CursorSource = "demone" [4] Table from a DBC on the same drive but NOT a subdirectory of working directory (G:\VFP60\) Alias = "clients" 328 1001 Things You Always Wanted to Know About Visual FoxPro Database = \samples\data\testdata.dbc CursorSource = "clients" [5] Table from a DBC in subdirectory of current working directory (G:\VFP60\) Alias = "clients" Database = data\testdata.dbc CursorSource = "clients" At run time Visual FoxPro will always try and use the information saved with the cursor first, but if the file cannot be found at the specified location, it will continue to search all available paths. The 'no code' solution! The easy answer to this issue is, therefore, to keep all tables (free or bound) and database containers in the same directory and to make sure it is defined as a sub-directory of your development directory. This ensures that Visual FoxPro only ever stores the relative path for tables. (See examples 2 and 5 above.) When you distribute your application, ensure that a subdirectory (named the same as the one used during development) is created under the application's home directory and that all data files are installed there. However, there are many times when this solution is just not possible, most obviously when the application is being run on client machines but using shared data 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 our own dataenvironment classes in code). This means that there is no way of writing code into a form 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 to handle 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 we can also greatly simplify the task of maintaining the application. Such a solution is presented in the 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 Chapter 10: Non-Visual Classes 329 the member objects of the dataenvironment and, for each cursor object that it finds, perform a look 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 a single class and uses a single table of pre-defined structure. Maintenance is, therefore, a minor matter when you adopt this strategy. The path management table The first component that we need in order to implement the strategy outlined above is the lookup table that will hold the information we wish Visual FoxPro to use at run time. This table has been (imaginatively) named 'datapath.dbf' and although we have included it in the project's database container we would normally recommend that it be used as a free table. The structure is as follows: Structure For: C:\VFP60\CH10\DATAPATH.DBF ========================================= DBC : CH10.DBC CDX : DATAPATH.CDX Associated Indexes ================== *** PRIMARY KEY: CTABLE: UPPER(CTABLE) ISDEL: DELETED() Field Details ============= CTABLE C ( 20,0 ) NOT NULL && Table Name - either DBC Name or DBF File name 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 330 1001 Things You Always Wanted to Know About Visual FoxPro The 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. The class defines two protected properties for its internal use, an array to hold object references to the 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 and actually relates to an object whose base class is 'dataenvironment.' The calling DE is then parsed to get a reference to each cursor object, which is stored to the internal array. Having opened 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 (if any) 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 PROTECTED aCursors[1] aCursors[1] = NULL *** Object Reference to the DE PROTECTED oDe oDe = NULL The Init method is used to control the processing and first checks the parameter passed to ensure that it is a reference to a dataenvironment object. Next it calls the GetTables method and, if any tables are found, calls OpenRefTable to open the lookup table. Finally it calls the SetPaths method to actually check each cursor and see if a new path has been defined for it: PROCEDURE Init( toDe ) LOCAL lnCursors *** Check the parameter IF VARTYPE( toDe ) = "O" *** Have a valid Object reference [...]... WindowState > 0 WindowState = 0 ENDIF *** Force to top AlwaysOnTop = T *** Activate the form Activate() *** Cancel Force To Top AlwaysOnTop = F *** Sort out Toolbars, pass the toolbar name (if any) SetToolBar( aFmList[.nFmIndex, 4]) *** And Exit right now RETURN llRetVal ENDIF ENDWITH ENDIF 341 342 1001 Things You Always Wanted to Know About Visual FoxPro If the form is not already instantiated, or if it... instances of the form Call manager's FormAction method Returns an object reference to the form manager 336 1001 Things You Always Wanted to Know About Visual FoxPro The 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 name and the assigned instance name in its internal collection... this, as we discussed in Chapter 2, is that it allows you to use 'named' parameters which simplifies the code needed to read the passed in values 3 38 1001 Things You Always Wanted to Know About Visual FoxPro Form 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... activation will re-set it anyway) nFmIndex = nFmCount ENDWITH ENDFUNC 3 48 1001 Things You Always Wanted to Know About Visual FoxPro The form manager FmIdx method This protected custom method is called with either an object reference to a form or the instance name of a form and searches the form collection to find and return the correct index into the collection for that form: ****************************************************************... forms on screen so as to present the appearance illustrated in Figure 10.5 below: Figure 10.5 Running multiple forms under the Form Manager As different forms get focus they are brought to the top and their associated toolbar is activated, as shown by the following illustrations: 352 1001 Things You Always Wanted to Know About Visual FoxPro Figure 10.6 SCX form with associated toolbar Figure 10.7 VCX... references to cursor objects are stored to the array property The method returns the number of cursors that it found: 332 1001 Things You Always Wanted to Know About Visual FoxPro PROTECTED 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... different drive and directory 334 1001 Things You Always Wanted to Know About Visual FoxPro Figure 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 Chapter 10: Non -Visual Classes 335 How can I manage forms and toolbars in my application? There are probably as many answers to this question as there... 340 1001 Things You Always Wanted to Know About Visual FoxPro Table 10.4 Custom properties and methods for the form manager form class Name PEM Purpose aFmList aTbList nFmCount nTbCount nFmIndex nTbIndex Array Property Array Property Property Property Property Property The Forms Collection The Toolbars Collection Number of forms contained in the Forms Collection Number of toolbars contained in the toolbars... 354 1001 Things You Always Wanted to Know About Visual FoxPro • User Errors: Only 11 out of the 700 standard errors could really be ascribed to this category These are errors that we would normally expect to occur only as the result of some user action Most should be capable of resolution without needing to close the application down, although it is unlikely that a generic solution could be devised to. .. about, therefore, is 'error recording' rather than 'error handling!' Of course, we still have to rely on Visual FoxPro' s error handler to tell us when something has gone wrong Not every error that Visual FoxPro detects will necessitate shutting down the application so there is still some handling element about it all though perhaps not as much as you might think Classifying Visual FoxPro' s errors Visual . Returns an object reference to the form manager 336 1001 Things You Always Wanted to Know About Visual FoxPro The custom properties • The cInsName property is used to store the instance name assigned. is that it allows you to use 'named' parameters which simplifies the code needed to read the passed in values. 3 38 1001 Things You Always Wanted to Know About Visual FoxPro Form class. ready for writing to *** We need to do this to ensure that deletions get made properly lnHnd = FCREATE( lcDestFile ) 322 1001 Things You Always Wanted to Know About Visual FoxPro IF lnHnd <

Ngày đăng: 05/08/2014, 10:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan