Friday, January 14, 2011

Creating and Using Classes with Visual FoxPro

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.

Defining Classes

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.

DEFINE CLASS <classname> AS <superclass>

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
ActiveDocAn active document object that can be hosted in a host browser such as Internet Explorer.  
CheckBoxA standard check box control similar to the check box created in FoxPro 2.x.  
ColumnA column on a grid control.  
ComboBoxA combo box similar to the pop-up control in FoxPro 2.x.  
CommandButtonEquivalent to a pushbutton in FoxPro 2.x.  
CommandGroupA group of command buttons that operate together. Equivalent to a group of pushbuttons in FoxPro 2.x controlled by one variable.  
ContainerA generic object designed to hold other objects. This is useful when you are creating a class that has more than one object on it.  
ControlThe 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.  
CursorA cursor definition in a data environment.  
CustomPrimarily used for objects that are not visual but might contain visual objects as members.  
DataA collection of cursors Environment and relations to open or close as a unit.  
EditBoxThe equivalent of a FoxPro 2.6 edit region.  
FormA 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.  
FormSetA container-type object that has one or more forms as members. This is the equivalent of a FoxPro 2.x screen set.  
GridA container-type object that allows display and editing of information in browse-type format.  
HeaderThe header of a grid column.  
Hyperlink ObjectProvides button, image, or label object that when clicked, launches a Web browser and navigates to a hyperlink.  
ImageA picture. 
LabelThe equivalent of placing text on a screen in FoxPro 2.x.  
LineA drawn line.  
ListBoxThe equivalent of the FoxPro 2.x scrolling list control.  
OleControlA control based on an OLE 2 object.  
OptionButtonA single radio button-type object.  
OptionGroupMultiple radio buttons that operate as a single control. This is the equivalent of a FoxPro 2.x radio button object.  
PageA single page within a page frame.  
PageFrameA 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.  
ProjectHookCreates instance of opened project that enables programmatic access to project events.  
RelationA definition of a relation between two cursors in a data environment.  
SeparatorObject that puts blank spaces between controls on a toolbar.  
ShapeA shape (such as a circle or a box).  
SpinnerThe equivalent of the FoxPro 2.x spinner control.  
TextBoxThe equivalent of a FoxPro 2.x "plain" GET control.  
TimerA visual object that does not display on a form. This control is designed to allow for actions at certain timed intervals.  
ToolBarA 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.

Instantiating Objects

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

Calling Methods

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.

Base Events, Methods, and Properties

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.

Base Properties

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

Base Events and Methods

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

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.

Creating Composite Classes

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

Accessing Child Member Variables and Methods

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.

Differences Between ADD OBJECT and AddObject

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.

THIS Revisited

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).

Adding Objects with CreateObject

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."

How Classes are Created in Visual FoxPro

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 a Visual Class Designer?

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

No comments:

Post a Comment