Now that we have looked at the structure of a coded class, and how objects are created and their components used, let's take a look at an example of a coded class.
Earlier, we discussed wrapper classes. We saw that wrapper classes are designed to encapsulate functionality and make it easier to use.
A good candidate for a wrapper class is FOXTOOLS.FLL. This is a function library that is chock full of useful functions. There are several problems, however, when working with FoxTools. Here's what they are:
The functions exist in an outside library that must be loaded prior to use. This means that whenever you use the functions, you must make sure that the library is loaded.
Many of the functions are cryptic and are difficult to use and learn.
Some of the functions don't do exactly what I want them to.
By wrapping a class around the FoxTools library, we can alleviate all these issues. Let's take a look at the code first and then we'll discuss some of the more interesting issues the code engenders:
* Program...........: FTOOLS.PRG * Author............: Menachem Bazian, CPA * Project...........: COMMON * Created...........: 11/29/1994 *) Description.......: Wrapper class for FoxTools * Major change list.: *-- This is a wrapper class for FoxTools. The following *-- functions have been added as *-- methods to this class: *-- *-- DriveType *-- JustFname *-- JustStem *-- JustPath *-- JustDrive *-- JustPathNoDrive *-- AddBs *-- IsDir *-- CleanDir *-- *-- A couple of quick notes here: *-- *-- JustPath has been modified to add a backslash where necessary *-- to the return *-- value. *-- *-- JustPathNoDrive is the same as JustPath except that it strips *-- out the drive designator. *-- *-- IsDir accepts a string and tests to see if it is a *-- directory. Returns a logical. Although IsDir is not based on *-- FoxTools, it fits well with other FoxTools *-- functions and, therefore, is part of this class. *-- *-- CleanDir deals with the issue of directories specified with *-- ..\. It returns an "adjusted" directory name. *-- *-- In all cases when running a FoxTools function, the class will *-- check to make sure that FoxTools is still loaded (the user *-- may have released it on their own). If this class loads *-- FoxTools, it released it when the object is released. DEFINE CLASS ftools AS custom PROTECTED lLoaded PROCEDURE init this.lLoaded = .F. this.loadlib() ENDPROC PROCEDURE destroy IF this.lLoaded RELEASE LIBRARY (SYS(2004)+"foxtools.fll") ENDIF ENDPROC PROCEDURE loadlib IF !"FOXTOOLS" $ SET("library") SET LIBRARY TO (SYS(2004)+"FOXTOOLS") this.lLoaded = .T. ENDIF ENDPROC FUNCTION drivetype(tcDrive) LOCAL lnRetVal lnRetVal = (drivetype(tcDrive)) RETURN lnRetVal ENDFUNC FUNCTION justfname(tcString) LOCAL lcRetVal lcRetVal = (justfname(tcString)) RETURN lcRetVal ENDFUNC FUNCTION juststem(tcString) LOCAL lcRetVal lcRetVal = (juststem(tcString)) RETURN lcRetVal ENDFUNC FUNCTION justpath(tcString) LOCAL lcRetVal lcRetVal = (this.addbs(justpath(tcString))) RETURN lcRetVal ENDFUNC FUNCTION justdrive(tcString) LOCAL lcRetVal lcRetVal = (justdrive(tcString)) RETURN lcRetVal ENDFUNC FUNCTION justpathnodrive(tcString) LOCAL lcRetval, ; lnAtPos lcRetVal = this.justpath(tcString) lnAtPos = AT(':', lcRetVal) IF lnAtPos > 0 IF lnAtPos < LEN(lcRetVal) lcRetVal = this.addbs(SUBST(lcRetVal,lnAtPos+1)) ELSE lcRetVal = "" ENDIF ENDIF RETURN (lcRetVal) ENDFUNC FUNCTION addbs(tcString) LOCAL lcRetVal lcRetVal = (addbs(tcString)) RETURN lcRetVal ENDFUNC FUNCTION isdir(tcString) LOCAL lcOldError, ; lcOldDir PRIVATE plRetval && Need this in the error method *-- Some quick notes about this function. FoxPro does not *-- have a function (neither does FoxTools for that *-- matter) that will test to see if a string represents *-- a directory or not. For example, if the string is *-- A:TEST, is that a FILE or a DIRECTORY? *-- *-- The idea here is to SET DEFA to the parameter. If it *-- generates an error, the parameter is not a directory. *-- *-- Causing an error in a method will call the ERROR() *-- method for the class. So, the only way to accomplish *-- this is to set a PRIVATE variable (which would be *-- available to the ERROR() method) and let *-- the error method handle setting the return value. plRetVal = .T. lcOldDir = SET("DEFA")-CurDir() SET DEFA TO (tcString) && Test the Parameter SET DEFA TO (lcOldDir) && Back to where we were RETURN plRetVal ENDFUNC FUNCTION cleandir(tcString) RETURN(UPPER(sys(2027, tcString))) ENDFUNC PROCEDURE error (tnError, tcMethod, tnLine) LOCAL lcMessage tcMethod = UPPER(tcMethod) DO CASE CASE tcMethod = "ISDIR" plRetval = .F. CASE tnError = 1 && File not found -- Cause by the library not loaded this.loadlib() RETRY OTHERWISE ?? CHR(7) lcMessage = "An error has occurred:" + ; CHR(13) + ; "Error Number: " + PADL(tnError,5)+; CHR(13) + ; " Method: " + tcMethod + CHR(13)+; " Line Number: " + PADL(tnLine,5) =MESSAGEBOX(lcMessage, 48, "Ftools Error") ENDCASE ENDPROC ENDDEFINE
For the most part, all the methods in the class represent direct calls to FoxTools functions. For example, ADDBS(), just calls the ADDBS() function in FoxTools. In effect, calling the method ADDBS() is the same as calling the ADDBS() function in FoxTools.
The class does more than just emulate the functionality of FoxTools, however. First of all, it encapsulates the process of loading the .FLL and checking for the existence of the .FLL when running a function.
The INIT method calls the LoadLib() method that loads the library (if it is not already loaded). When running a FoxTools function, the ERROR method automatically kicks in (the ERROR method of an object takes precedence over the setting of ON ERROR). If the error was caused by running a function without the .FLL loaded, the error function calls LOADLIB() and retries the function.
The function also modifies some of the behavior of FoxTools. For example, the JustPath() function is modified to make sure that a backslash is always present at the end of the path. A new function called JustPathNoDrive has been added, which is the Justpath() function without the drive designator.
New functionality has been added to the list of functions as well. For example, IsDir() checks whether the string that was passed through is a directory. CleanDir uses the SYS(2027) function to clean up a directory string. "Cleaning up" means that indirect directory movement (e.g. ..\) is removed from the string and the path is adjusted. For example:
? oFtools("c:\apps\tapcis\files\..\) && returns c:\apps\tapcis\
ISDIR uses an old trick with a twist. The idea here is to test whether the string passed through is a valid directory.
Back in Microsoft FoxPro 2.x, we did that by setting ON ERROR to set a variable to false and then SET DEFAULT to the contents of the string. If the string is not a valid directory, an error would be generated thus setting the variable to .F.
The rules are a little different with Microsoft Visual FoxPro 3.0. The ERROR method of a class takes precedence over an ON ERROR routine. The idea, then, is to trap the error within the ERROR method and set the variable to .F. there.