Microsoft Corporation
October 1997
Many products that support object-oriented programming only do so for the creation of graphical user interface (GUI) objects. In Microsoft® Visual FoxPro™, an entire universe of classes lies beneath the surface of the user interface, just waiting for our imagination and skill to bring them to light. These classes are the non-visual classes.
A non-visual class is any class that is not designed primarily for display. For example, a command button is a class that is specifically designed to display on a form. A timer class, on the other hand, does not show on a form at all.
Non-visual classes in Visual FoxPro are typically descendants of the Custom or Timer classes and often will have no display component attached to them at all. However, as we will see later, non-visual classes may have a visual component attached to them.
There are many reasons why a non-visual class may be created. In fact, there are as many reasons to create non-visual classes as there are to create visual ones. Principally, the reasons are:
So what's the difference between non-visual and visual classes? The basic difference lies in the kind of classes we will create. While visual classes typically center on the user interface, non-visual classes play key roles in management functions. Non-visual classes also incorporate the type of classes that are most often neglected in discussions of object orientation in systems development: business classes.
When we talk about classes written for management roles, there are many different aspects of management we will want to create a class for. One type of management would be to manage the interface between one program and another. A good example of this would be the management of the interface between Visual FoxPro code and DLLs, FLLs, or other function libraries. These classes are created to make it easier to use these sources' functions, to enhance their capabilities, and to encapsulate the definition, loading, and error trapping required when working with the function library.
This process is known as wrapping a class around some existing functionality. Appropriately, we call these classes wrapper classes.
Another typical non-visual class would be a class that manages other classes, for example a class that handles multiple instances of forms. Such a class would allow us to create "tile all windows" functions and the like. These classes are known as manager classes.
A business class is a class that is designed to model an entity in a business environment. A good example would be a customer class. These classes are a combination of information and actions designed to do what a business entity needs to do within the context of the problem domain (that is, the environment being modeled and automated).
The responsibilities of a business class are often determined after careful analysis and design. Business class responsibilities can be very abstract in nature and require careful modeling before implementation. Some common responsibilities might be to:
Business classes are a little more difficult to classify as visual or non-visual. Business classes may, and often do, have visual components. Or it might be based on a visual class (for example, a form class) with the appropriate properties and methods added to the form. Which category does it belong in? It depends on how the object is created.
Let's look at a sample wrapper class first.
The purpose of a wrapper class, as we discussed earlier, is to create a class that manages and perhaps even enhances the functionality of some other code. Any code can be wrapped into a class. If you have an old procedure library written in prior versions of FoxPro®, you could wrap a class around it if you like. The tough part is deciding when it is appropriate to wrap a class around something.
The best reason to wrap a class around something is to make it easier and better to use. A perfect example of a candidate for a wrapper class would be a .DLL or .FLL. These function libraries can be obscure, their parameters difficult to determine, and their error-handling requirements rather extensive. For example, if you are using an .FLL library, such as FOXTOOLS.FLL, what do you do if someone else's code unloads it accidentally with SET LIBRARY TO? Can you rely on the fact that the library is there all the time? Take the example of calling some of the Windows® application programming interface (API) functions, such as the functions to write and read from .INI files. These can be difficult to learn to use.
When a class is wrapped around some other piece of code, the class developer has the ability to control which portions of the .DLL or .FLL are available to the outside world, how they are called, and even what values are returned.
Wrapper classes carry a myriad of benefits with them. First of all, if a .DLL or .FLL is used with a wrapper class, the developers who use that class do not have to know anything about the .DLL or .FLL that serves as the basis for the class. They also do not have to be concerned with issues of loading the .DLL or .FLL or registering the functions contained therein.
In effect, the result is a much reduced learning curve and coding time for all concerned.
Let's look at the following example of a wrapper class. This class is a wrapper around a library of functions that ships with Visual FoxPro called FOXTOOLS.FLL.
* Program...........: FTOOLS.PRG
* Author............: Menachem Bazian, CPA
* Copyright.........: (c) Flash Creative Management, Inc. 1994, 95
* 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
*-- JustPath
*-- JustDrive
*-- AddBs
*-- CleanDir
*--
*-- A couple of quick notes here:
*--
*-- JustPath has been modified to add a backslash where necessary to the return
*-- value.
*--
*-- 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 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 justpath(tcString)
LOCAL lcRetVal
lcRetVal = (this.addbs(justpath(tcString)))
RETURN lcRetVal
ENDFUNC
FUNCTION addbs(tcString)
LOCAL lcRetVal
lcRetVal = (addbs(tcString))
RETURN lcRetVal
ENDFUNC
FUNCTION cleandir(tcString)
RETURN(UPPER(sys(2027, tcString)))
ENDFUNC
PROCEDURE error (tnError, tcMethod, tnLine)
LOCAL lcMessage
tcMethod = UPPER(tcMethod)
DO CASE
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
This class shows the various purposes of a wrapper. Let's look at each one individually:
When a library is loaded within Visual FoxPro, it can be unloaded by other objects by issuing one command. The FTOOLS class automatically loads FOXTOOLS.FLL (if it is not already loaded) when the object is instantiated. If the library is released by another module or object and a FoxTools function is called, Visual FoxPro will generate an error #1 (File Not Found). In that case, the error method calls the LoadLib method to reload the library. This provides developers with a simple way to use FoxTools without having to worry whether someone else's code unloaded the library.
The JustPath function in FoxTools calculates what portion of a filename string is the path designation and returns that path as a string. The string may or may not have a backslash at the end. In order to promote consistency, the method that calls JustPath also calls the AddBs method to add a backslash at the end of the string if one doesn't already exist. This is an example of enhanced functionality that provides developers with a simple and consistent return value.
The CleanDir method is designed to adjust a path string for "backsteps." For example, a path string of "C:\WINAPPS\VFP\SAMPLES\DATA\...\GRAPHICS\" would adjust to "C:\WINAPPS\VFP\SAMPLES\ GRAPHICS\". This function does not call FoxTools at all; however, its functionality is related to the other functions included in this class. By adding a method for this function, we are giving developers access to related functionality in one place, without requiring them to load multiple classes.
The ability to create and use wrapper classes is a major benefit to software development. Since the complexity of working with something can be hidden within a class without compromising the functionality, developers who use the wrapper will immediately notice an increase in their productivity by having it in their arsenals, without the cost of learning its intricacies.
A second type of non-visual class that is often created is a manager class. This class will typically manage instances of another class. A good example is the management of multiple instances of a form to ensure that subsequent instances are properly placed on the screen with a header that is identifiable (for example, Document1, Document2, and so on).
This next example deals with that issue and shows a manager class along with a simple form class to manage.
* Program...........: MANAGER.PRG
* Author............: Menachem Bazian, CPA
* Copyright.........: (c) Flash Creative Management, Inc. 95
* Created...........: 05/03/95
*) Description.......: Sample Manager Class with Managed Form Class
* Major change list.:
*-- This class is designed to manage a particular form class and make
*-- sure that when the forms are run they are "tiled" properly.
DEFINE CLASS FormManager AS Custom
DECLARE aForms[1]
nInstance = 0
PROCEDURE RunForm
*-- This method runs the form. The instance of the form class
*-- is created in the aForms[] member array.
LOCAL lnFormLeft, llnFormTop, lcFormCaption
nInstance = ALEN(THIS.aForms)
*-- Set the Top and Left Properties to Cascade the new Form.
IF nInstance > 1 AND TYPE('THIS.aForms[nInstance -1]') = 'O' ;
AND NOT ISNULL(THIS.aForms[nInstance -1])
lnFormTop = THIS.aForms[nInstance -1].Top + 20
lnFormLeft = THIS.aForms[nInstance -1].Left + 10
ELSE
lnFormTop = 1
lnFormLeft = 1
ENDIF
*-- Set the caption to reflect the instance number.
lcFormCaption = "Instance " + ALLTRIM(STR(nInstance))
*-- Instantiate the form and assign the object variable
*-- to the array element.
THIS.aForms[nInstance] = CREATEOBJECT("TestForm")
THIS.aForms[nInstance].top = lnFormTop
THIS.aForms[nInstance].left = lnFormLeft
THIS.aForms[nInstance].caption = lcFormCaption
THIS.aForms[nInstance].show()
*-- Redimension the array so that more instances of
*-- the form can be launched.
DIMENSION THIS.aforms[nInstance + 1]
ENDPROC
ENDDEFINE
*-- This class is a form class that is designed to work with
*-- the manager class.
DEFINE CLASS TestForm AS form
Top = 0
Left = 0
Height = 87
Width = 294
DoCreate = .T.
BackColor = RGB(192,192,192)
BorderStyle = 2
Caption = "Form1"
Name = "Form1"
ADD OBJECT label1 AS label WITH ;
FontName = "Courier New", ;
FontSize = 30, ;
BackStyle = 0, ;
Caption = (time()), ;
Height = 61, ;
Left = 48, ;
Top = 12, ;
Width = 205, ;
Name = "Label1"
ENDDEFINE
Note that forms are instantiated through the RUNFORM method rather than directly with a CREATEOBJECT function. This allows the manager class to maintain control over the objects it instantiates.
Manager classes are very useful. They allow a simple way to encapsulate code that would normally have to be duplicated every time an object is instantiated into one single place.
Business classes are object oriented representations of business entities (a Customer, for example). The responsibilities of these classes will vary depending on the behavior of a particular object within the problem domain.
The purpose of a business class is multifold. At an abstract level, it is possible to determine what the basic functionality of a business object would typically be and then to create a class around it. For example, the basic responsibilities of a business object might be to:
These functions could be abstracted in a class as follows:
DEFINE CLASS BaseBusiness AS custom
cAlias = ""
oData = .NULL.
PROCEDURE INIT
IF !EMPTY(this.cAlias) AND !USED(this.cAlias)
=MessageBox("Alias is not open!", 16)
ENDIF
ENDPROC
PROCEDURE next
SELECT (this.cAlias)
SKIP 1
IF EOF()
GO BOTTOM
ELSE
this.readrecord()
ENDIF
ENDPROC
*-- Additional methods here for movement would mimic
*-- procedure NEXT.
PROCEDURE readrecord
*-- This procedure is initially empty.
SCATTER NAME this.oData
ENDPROC
*-- Additional methods for saving would follow mimicing
*-- procedure readrecord.
ENDDEFINE
In order to create a customer object, for example, all we would need to do is subclass it as follows:
DEFINE CLASS customer AS basebusiness
cAlias = "Customer"
ENDDEFINE
The fields in the customer alias would be automatically added as members of oData. Thus, if an object called oCust were instantiated from the customer class, the cName field would be held in oCust.oData.cName.
Of course, the beauty of this method of development is that there is little coding to do from one business class to another. In effect, all you do is code by exception.
Object-oriented programming (OOP) goes well beyond the world of the graphical user interface in Visual FoxPro. Non-visual classes represent a powerful weapon in our OOP arsenal for rapid, bug-free systems development.
We acknowledge the help of Flash Creative Management, Inc., Hackensack, NJ, in providing portions of this material.