In
Chapter 13, "Introduction to Object-Oriented Programming," you briefly learned some of the issues related to creating classes in Visual FoxPro. All the work was done in code, which is a good way to look at creating classes because it provides a clear view of what you can do when you create classes.
The following sections provide a definitive look at the syntax of creating and using classes in Visual FoxPro.
You define classes using the
DEFINE CLASS/
ENDDEFINE construct. Here is an example:
DEFINE CLASS <classname> AS <baseclass>
*-- Declaration Code Here
PROTECTED <list of member variables>
PROCEDURE <methodproc> (param1, param2 ....)
LOCAL <list of local variables>
*-- Procedure Code Here
ENDPROC
FUNCTION <methodfunc> (param1, param2 ....)
LOCAL <list of local variables>
*-- Function code here
RETURN <returnval>
ENDFUNC
ENDDEFINE
In the following sections you read about each portion of the construct separately.
This line of code tells Visual FoxPro that you are creating a class. All code between
DEFINE and
ENDDEFINE relates to this class.
<classname> is the name of the class and
<superclass> is the name of the class upon which the class is based. This can be a built-in class provided with Visual FoxPro 6 or one that you create or purchase.
The term
superclass used here is in line with terminology used in most texts that discuss object orientation. Unfortunately, Microsoft uses the term
parentclass to mean the same thing. Don't let the terminology throw you.
By definition, every class created in Visual FoxPro is a subclass of another class. At the highest level, classes created in Visual FoxPro are subclasses of what Microsoft calls
base classes, the classes that ship with Visual FoxPro. Visual FoxPro 6 comes with the base classes shown in Table 14.1.
Table 14.1 Visual FoxPro 6 Base Classes
Class Name |
Description |
Visual |
Form Control Toolbar |
SubclassOnly |
ActiveDoc | An active document object that can be hosted in a host browser such as Internet Explorer. | | | |
CheckBox | A standard check box control similar to the check box created in FoxPro 2.x. | | | |
Column | A column on a grid control. | | | |
ComboBox | A combo box similar to the pop-up control in FoxPro 2.x. | | | |
CommandButton | Equivalent to a pushbutton in FoxPro 2.x. | | | |
CommandGroup | A group of command buttons that operate together. Equivalent to a group of pushbuttons in FoxPro 2.x controlled by one variable. | | | |
Container | A generic object designed to hold other objects. This is useful when you are creating a class that has more than one object on it. | | | |
Control | The same as the container class with one major difference: When the object in a container class is instantiated from the class, you can address all objects within the container. The Control class hides all internal objects and only allows communication with the control class. | | | |
Cursor | A cursor definition in a data environment. | | | |
Custom | Primarily used for objects that are not visual but might contain visual objects as members. | | | |
Data | A collection of cursors Environment and relations to open or close as a unit. | | | |
EditBox | The equivalent of a FoxPro 2.6 edit region. | | | |
Form | A single "screen." This is a container object in that it can (and usually does) contain other objects. The equivalent of a FoxPro 2.x screen. | | | |
FormSet | A container-type object that has one or more forms as members. This is the equivalent of a FoxPro 2.x screen set. | | | |
Grid | A container-type object that allows display and editing of information in browse-type format. | | | |
Header | The header of a grid column. | | | |
Hyperlink Object | Provides button, image, or label object that when clicked, launches a Web browser and navigates to a hyperlink. | | | |
Image | A picture. | | | |
Label | The equivalent of placing text on a screen in FoxPro 2.x. | | | |
Line | A drawn line. | | | |
ListBox | The equivalent of the FoxPro 2.x scrolling list control. | | | |
OleControl | A control based on an OLE 2 object. | | | |
OptionButton | A single radio button-type object. | | | |
OptionGroup | Multiple radio buttons that operate as a single control. This is the equivalent of a FoxPro 2.x radio button object. | | | |
Page | A single page within a page frame. | | | |
PageFrame | A tabbed control. Each tab within a tab control is a separate page. The page frame control is a container-type control because it can (and usually does) contain many objects. | | | |
ProjectHook | Creates instance of opened project that enables programmatic access to project events. | | | |
Relation | A definition of a relation between two cursors in a data environment. | | | |
Separator | Object that puts blank spaces between controls on a toolbar. | | | |
Shape | A shape (such as a circle or a box). | | | |
Spinner | The equivalent of the FoxPro 2.x spinner control. | | | |
TextBox | The equivalent of a FoxPro 2.x "plain" GET control. | | | |
Timer | A visual object that does not display on a form. This control is designed to allow for actions at certain timed intervals. | | | |
ToolBar | A toolbar, which is a group of objects that can be docked at the top, bottom, or sides of the desktop. When not docked, a toolbar looks something like a form. | | | |
As Table 14.1 indicates, classes can be categorized in three ways: "Visual," "Form Control Toolbar," and "Visual Class Designer Only." Classes can be visual or nonvisual. A visual class "displays," whereas a nonvisual class does not have a display component attached to it. In addition, some classes are not available from the Form Control toolbar. Finally, some classes are available only in the Visual Class Designer for subclassing, not for use as controls.
The Visual column specifies whether a base class is visual or nonvisual. Form Controls Toolbar specifies whether the base class is available on that toolbar. Subclass Only specifies those base classes that are intended for subclassing and provide little functionality on their own (for example, the
Container class).
Most classes are available in the Form Designer from the Form Controls toolbar, but others are not. Some of those unavailable classes (such as
Page,
Header, and
OptionButton) are unavailable because they are members of other objects. For example,
Page is a member of
PageFrame,
Header is a member of
Grid, and
OptionButton is a member of
OptionGroup.
FormSet is not a control per se but a container of forms; it is created by combining multiple forms.
Finally, some classes are specifically designed for subclassing and are only available either in code or through the Visual Class Designer. You learn about the Visual Class Designer in the section "The Visual Class Designer" later in this chapter.
The classes that are controls available within the Form Designer are discussed in
Chapter 9 "Creating Forms." In addition to the base classes included with Visual FoxPro 6, you can base classes on your own classes. Finally, the
DEFINE CLASS/
ENDCLASS structure must live on its own and cannot be nested within a loop or a decision structure (such as
IF/
ENDIF). Think of each class definition construct as its own "procedure" and you'll be fine.
*--Declaration Code Here/PROTECTED <list of member variables> Declaration code declares your class member variables. Only the member variables listed here are properties of objects instantiated from this class (with the exception of member objects, which are discussed later in this chapter in the section "Creating Composite Classes"). If a member variable is an array, you would declare the array in this section of code.
Another important piece in this section is the declaration of protected members. A
protected member is a member variable that is not visible outside the class. In other words, methods within the class can access and modify that variable, but the variable does not exist as far as the outside world (anything that is not a method of the class) is concerned.
You declare member variables protected by using the keyword
PROTECTED and then listing the member variables that you want protected. The following example creates a protected member variable called
cProtected:
PROTECTED cProtected
You must declare a property protected within the declaration section of the
DEFINE CLASS construct.
An example of a member variable that would be declared
PROTECTED? is a member that saves the state of the environment when the object is instantiated. The variable can be used to reset the environment when the object is released, but it serves no purpose to programs instantiating the object and interacting with it. As a matter of fact, you would not want this member variable to be changed by the outside world. Hence, you would protect it in the declaration section of code.
PROCEDURE <methodproc> (param1, param2....)/ENDPROC FUNCTION <methodfunc> (param1, param2....)/ENDFUNC This line of code defines a method.
<methodproc> and
<methodfunc> refer to the name of the method. Note that you can call a method a FUNCTION or a
FUNCTION-both syntaxes are equivalent. I like to use the
FUNCTION syntax if the method is intended to return a value; otherwise I use
PROCEDURE.
Parameters sent to a method can be accepted with a
PARAMETERS statement (more typically
LPARAMETERS), or the parameters can be accepted in parentheses after the name of the method. For example, if a method called
ShowVals were to accept two parameters (
Parm1 and
Parm2), the code to accept these parameters would look like this:
PROCEDURE ShowVals
LPARAMETERS Parm1, Parm2
it might also look like this:
PROCEDURE ShowVals(Parm1, Parm2)
Of the two, I prefer the second syntax because I think it reads better. You can choose either one.
Be aware that parameters sent through to methods, whether the parameters are called as a procedure (such as
loObject.Method(Parm1)) or a function (such as
lcVar = loObject.Method(Parm1)), are treated like parameters sent through to a user-defined function: They are sent through by
VALUE unless either
SET UDFPARMS has been set to
REFERENCE (I don't recommend changing the setting of
SET UDFPARMS) or the name of the parameter is sent through with the
@ sign.
For example, note the TSTPROC.PRG test procedure presented in Listing 14.1. The return values quoted assume the default setting of
SET UDFPARMS.
Listing 14.1 14CODE01.PRG-Test Procedure That Illustrates the SET UDFPARMS Command Settings
lcText = "Menachem"
loX = CREATEOBJECT("test")
*-- Call testfunc first as a procedure and then as a method
*-- without specificying by reference.
loX.testfunc(lcText) && "Proc" Syntax
? lcText && Shows "Menachem"
=loX.testfunc(lcText) && Func Syntax
? lcText && Shows "Menachem"
loX.testfunc(@lcText) && "Proc" Syntax
? lcText && Shows 10
lcText = "Menachem" && Reset for next test
=loX.testfunc(@lcText) && Func Syntax
? lcText && Shows 10
lcText = "Menachem" && Reset for next test
loX.testproc(lcText) && "Proc" Syntax
? lcText && Shows "Menachem"
=loX.testproc(lcText) && Func Syntax
? lcText && Shows "Menachem"
loX.testproc(@lcText) && "Proc" Syntax
? lcText && Shows 10
lcText = "Menachem" && Reset for next test
=loX.testproc(@lcText) && Func Syntax
? lcText && Shows 10
lcText = "Menachem" && Reset for next test
DEFINE CLASS test AS custom
FUNCTION testfunc (Parm1)
Parm1 = 10
ENDFUNC
PROCEDURE testproc (Parm1)
Parm1 = 10
ENDPROC
ENDDEFINE
Methods can be protected like member variables-that is, they can only be called from other methods in the class-by adding the keyword
PROTECTED before
PROCEDURE or
FUNCTION (
PROTECTED PROCEDURE <methodproc>). Methods that are protected do not exist outside the class, and an error is generated if an attempt is made to call them.
As a general rule, methods should be protected if they are not intended for the "outside world." This saves you a lot of trouble down the road. For example, a method that is intended only to be called by other methods in the class would be protected.
If a method has to return a value, a
RETURN statement precedes the
ENDPROC/
ENDFUNC statement as shown in the following example:
PROCEDURE ShowDate
RETURN date()
ENDPROC
FUNCTION FuncShowDate
RETURN date()
ENDFUNC
Methods are closed with the
ENDPROC or
ENDFUNC command; the command you use depends on the command used to start the method definition.
Objects are instantiated from their classes with the
CREATEOBJECT function. Here's the syntax:
loObject = CREATEOBJECT(<classname> [, <Parameter list>])
The
CREATEOBJECT function returns an object reference that is stored in
loObject.
<classname> is a string indicating the class to be used for instantiation. Parameters follow in the
CREATEOBJECT function; they appear one at a time and are separated by commas. Parameters are accepted in the object's
Init method. (You learn the
Init method and sending parameters later in this chapter in the section "A Technical Tip-Sending Parameters to an Object.")
In order to instantiate an object with
CREATEOBJECT, the class definition has to be available when the
CREATEOBJECT function is used. If you have manually coded your classes as opposed to using the Visual Class Designer (as shown in
Chapter 15, "Creating Classes with Visual FoxPro"), the program that has the class definitions must be available. The program is made available with
SET PROCEDURE or by placing the class definitions in a program that is higher in the calling chain.
You can release the procedure file once the object is instantiated if you use
SET PROCEDURE. Visual FoxPro loads all the methods into memory when the object is instantiated.
Always remember that an instance is just a memory variable and follows almost all of the same rules as regular memory variables. Objects can be made local, public, or private and will lose or keep scope like any other memory variable.
There is one significant difference between an object (known as an
instance variable) and other Visual FoxPro variables: Variables can be thought of as holding
values, whereas instance variables do not hold values-they hold references to an object, which in turn holds the values.
This has three implications. First, an instance variable is always passed to procedures and functions by reference. Second, if an instance variable is copied into another variable, all changes in the second variable affect the same object. Finally, an object is not released until all references to it have been released. Here is an example:
loInstance = CREATEOBJECT("Form")
loInstance.Show() && Show the form
loVar = loInstance && loVar points to the form
too, now.
loInstance.Caption = "Hello" && Caption changes
loVar.Caption = "There" && Caption changes again
RELEASE loInstance && Form does not disappear
RELEASE loVar && Now it disappears
You always call methods by specifying the name of the instance variable, then a period, and then the name of the method. Here is an example:
loClass.MyMethod
Parameters are sent through to a method by listing them in parentheses after the method name. Here is an example:
loClass.MyMethod(Parm1, "StringParm2")
There is no
DO syntax for a method. To call a method and get a return value, the syntax is almost identical. You specify a variable to accept the value:
lcRetVal = loClass.MyMethod(Parm1, "StringParm2")
If no parameters are sent through to the method, you can still use parentheses after the method name. Here is an example:
loClass.MyMethod
I use this syntax exclusively because it is much clearer that the member you are accessing is a method, not a property.
As you know from reading
Chapter 9 different controls have different events, methods, and properties. For example, the Label control has a
Caption property, whereas the TextBox control has a
Value property.
Each control shown in the default Form Controls toolbar is a base class in Visual FoxPro and can be used as the basis for your own classes.
In addition to the classes shown in the Form Controls toolbar, there are four classes specifically designed to be used as the basis for user-defined classes. They do not show up on the Form Controls toolbar nor are they part of other classes (such as an
OptionButton, which is part of an OptionGroup control). These classes are
Container,
Control,
Custom, and
ToolBar.
Although each base class supports its own set of events, properties, and methods, there is a common set of events, methods, and properties that apply to all base classes in Visual FoxPro.
The following list shows the properties that are common to all of Visual FoxPro's base classes.
Property | Description |
Class | The name of the object's class |
BaseClass | The name of the object's base class |
ClassLibrary | The full path of the class library where this class is defined |
ParentClass | The name of the class upon which this class is based |
The following list shows the events and methods that are common to all of Visual FoxPro's base classes.
Event | Description |
Init | Invoked when the object is created. Accepts parameters sent through to the object. Returning .F. aborts object instantiation. |
Destroy | Invoked when the object is released. |
Error | Invoked when an error occurs inside one of the object's methods. |
In the next chapter you learn the properties and methods of the four special base classes just mentioned.
The
Error method is important and worthy of special note. The
Error method is called in the event an
ON ERROR-type error occurs in a class. The
Error method takes precedence over the setting of
ON ERROR, which is important because this enables you to encapsulate error handling where it belongs-within the class itself. You see examples of the
Error method and its uses in the next chapter.
A
composite class is a class that has members that are themselves instances of other classes. A perfect example of this is a class based on a container-type class, such as a form. When you think of it, a form itself is a class, yet the objects in it are classes, too. Therefore, you have one object that has other objects contained in it (hence the name container class).
When you work with code you can add object members in one of two ways. The first way uses the
ADD OBJECT command and is called within the declaration section of code. Here is the syntax:
ADD OBJECT <ObjectName> AS <ClassName> ;
[ WITH <membervar> = <value>, <membervar> = <value> ... ]
<ObjectName> is the name you want to give to the instance variable being added to the class.
<ClassName> is the class upon which
<ObjectName> is based. You can specify special settings for member variables of the added object by setting them after a
WITH clause. Here is an example:
DEFINE CLASS Foo AS FORM
ADD OBJECT myCommandButton AS CommandButton ;
WITH caption = "Hello", ;
height = 50
ENDDEFINE
When class
Foo is instantiated, a member variable called
myCommandButton is added to the form with a height of 50 pixels and a caption of "Hello." When the form is shown, the command button will be happily waiting for you to click it.
The second syntax is the
AddObject method. This method can be called from either inside the class or outside the class. This means that you can add objects to container-type objects on-the-fly. Here is the
AddObject method's syntax:
<object>.AddObject(<Member Name>,<Class Name>[, Parameters])
To mimic the prior example (note that I do not even define a class for this one), I could do this:
loFoo = CREATEOBJECT("Form")
loFoo.AddObject("myCommandButton", "CommandButton")
loFoo.MyCommandButton.Caption = "Hello"
loFoo.MyCommandButton.Height = 50
loFoo.MyCommandButton.Visible = .T.
loFoo.Show
As far as Visual FoxPro is concerned, using the object names from the previous example,
loFoo is a parent object and
MyCommandButton is a child object. As you just saw, the properties of the child object,
MyCommandButton, can only be accessed by going through its parent.
TIP |
Composite objects can have members that are themselves composite objects. This can lead to a very long "path" to get to a member variable. If you want to cut through the keystrokes, copy a reference-to another memory variable-to the object you're trying to get to and work with it that way. For example, assume you have an object with the following hierarchy and you want to work with the text box for a little while: MyForm.MyPageFrame.myContainer.myTextBox
Just use this code:
loMyObj = MyForm.MyPageFrame.myContainer.myTextBox
From here on you can access all the properties and methods of MyTextBox through loMyObj. This can save you a lot of typing. Remember, though, that you will not be able to get rid of any parent objects of myTextBox without first releasing loMyObj. |
A review of the code examples for
ADD OBJECT and
AddObject show some differences. First of all,
ADD OBJECT enables you to set properties on the calling line, whereas
AddObject does not. You have to access the member variables individually. Secondly, when a visual object is added to a container with
AddObject, the object is hidden by default (its
Visible property is set to
.F.), which enables you to set the display characteristics before showing the control.
AddObject enables you to send parameters to the object's
Init method, whereas
ADD OBJECT does not. Finally,
ADD OBJECT enables you to turn off the
Init method when you instantiate the member object with the
NOINIT clause;
AddObject does not have this capability.
In the previous chapter you learned a special keyword called
THIS. Its purpose is to enable a class to refer to itself. There are three additional keywords along this line that are applicable for composite classes only. Here is a list of these keywords:
THISFORM | This keyword is special for members of Form-based classes. It refers to the form on which an object lies. |
THISFORMSET | This keyword is special for members of a form that is part of FormSet. It refers to the FormSet object of which the current object is a member. |
PARENT | This keyword refers to the parent of the current object. |
Note that you can move up the hierarchy with
this.parent.parent.parent (you get the idea).
Can you add an object to another object with
CreateObject? In effect, can you do this:
DEFINE CLASS test AS custom
oForm = .NULL.
PROCEDURE INIT
this.oForm = CREATEOBJECT("form")
ENDPROC
ENDDEFINE
The short answer is yes and no. Sound confusing? Let me explain. The code snippet shown here does indeed create an object with a member called
oForm that is an object. However,
oForm is not a child of the object.
oForm is a member variable that is an object. This might sound as if I'm splitting hairs, but there are some very important differences.
First, the
PARENT keyword will not work with
oForm. Second, because the member object is not a child object, you can do some interesting things. Take a look at this bit of code:
DEFINE CLASS foo AS form
ADD OBJECT myForm AS Form ;
WITH caption = "Hello", ;
height = 50
ENDDEFINE
DEFINE CLASS bar AS form
PROCEDURE init
this.addobject("myForm", "Form")
ENDPROC
ENDDEFINE
DEFINE CLASS foobar AS Form
oForm = .NULL.
PROCEDURE init
this.oForm = CREATEOBJECT("form")
ENDPROC
ENDDEFINE
Neither class
Foo nor
Bar works. If you try to instantiate them, you get an error because you cannot add an object based on any descendant of the
Form class to a form-based object. The last iteration, class
FooBar, works just fine. You'll see a more practical example of this capability in
Chapter 17, "Advanced Object-Oriented Programming."
So far, all the examples you have seen for creating classes in Visual FoxPro deal with code. In fact, as shown in Table 14.1 and further detailed in
Chapter 15, there are some classes that can only be created via a coded program.
For the most part, however, creating classes is much more efficient using the Visual Class Designer-the tool provided with Visual FoxPro to make the job of creating classes easier.
Why do you need a Visual Class Designer when you can easily create classes with code? There are three reasons the Visual Class Designer is integral to class development. The first reason is that it insulates you from the intricacies of code. Although you have learned the syntax for creating classes, you should not have to remember and type in the constructs and keywords related to the structure of a defined class. The Visual Class Designer handles this for you.
The second reason is that some classes can get complex rather quickly. This is especially true of some visual classes, such as forms (where the placement of the objects within the container are critical). Creating complex classes is best done visually.
Finally, only classes created with the Visual Class Designer can be managed with the Class Browser, a wonderful tool provided with the product. The Class Browser is discussed in
Chapter 16, "Managing Classes with Visual FoxPro."
All three reasons are valid for using the Visual Class Designer. That's why I recommend that you use it whenever you can for developing classes.
\source :
http://www.webbasedprogramming.com/Special-Edition-Using-Visual-FoxPro-6/ch14/ch14.htm