Special Edition Using Visual FoxPro 6


Chapter 14

OOP with Visual FoxPro


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.

The Visual Class Designer

The Visual Class Designer is a superset of the Form Designer. In fact, the Visual Class Designer is so much like a Designer that the metafile it uses to store the classes you create is a copy of the .SCX/.SCT file structure. The only difference here is that the extension given a class file is .VCX instead of .SCX, and a .VCX can hold many classes in one file as opposed to just one form (in an .SCX).

When you create classes visually, Visual FoxPro 6 gives you access to the Controls toolbar, the Properties window, and the visual canvas.

Figure 14.1 shows the Visual Class Designer when you are working on a command button-based class.

Figure 14.1 : The Visual Class Designer.

All the options on the menu operate in an almost identical manner to their counterparts on the Form Designer. Rather than cover the Visual Class Designer in detail, I only cover instances where it differs from the Form Designer.

Design Surface Differences

The first difference is the most apparent, but technically is not really a difference at all. The object being modified in the Form Designer is a form. A form is a container object and therefore can have many objects on it. Figure 14.1 only shows a CommandButton. That's because the class being designed is a subclass of CommandButton, which does not support adding other objects to it. Therefore, all you see is the single object on which you are working. If you are working on a Form type class in the Visual Class Designer, the canvas looks the same as it does in the Form Designer.

Menu Differences

Menu options that are specific to forms, such as Create Form Set and Remove Form Set are not on the Visual Class Designer's class menu. The Visual Class Designer does add one menu option, Class Info, and the operation of New Methods and New Properties is slightly changed. These changes are discussed first.

Adding Properties and Methods

The New Property and New Method menu options work as they do in the Form Designer, with one important change: There is a new check box control on the dialog box that enables you to protect new methods and properties. Checking this box means that the property or method is protected. To "unprotect" a method or property, you need to use the Edit Property or Edit Method option. Note that a property or method, once protected, cannot be unprotected in a subclass. It must be unprotected on the level at which it was added to the class hierarchy.

A Technical Tip-Adding Member Arrays to a VCX Class

Suppose you want to add a property to a class that is an array. This is not a problem in code because you have access to the declaration section. However, you do not have access to the declaration code with visually designed classes. The trick is to specify the name of the array with its default length in the New Properties dialog box. For example, to add a property called aProp to a class, the name of the property you type in would be this:

aProp[1]

The array subscript tells Visual FoxPro that the property you are adding is an array. If you look at the new property in the Properties window, you'll notice that the new array is read-only. Don't worry about this; it just means that you have to work with the array in code. There is no provision for setting values in arrays in the Properties window.

Accessing Class Information

The Class Info menu option gives the developer access to information about the class being modified with the dialog box shown in Figure 14.2.

Figure 14.2 : The Class Info dialog box.

The Class tab on this page frame shows some basic information about the class. Here is a list of the options:

Toolbar Icon This modifiable option specifies which icon will show in the Form Controls toolbar when the .VCX file is loaded. Clicking the command button to the right of the text box displays a GETPICT dialog box that enables you to select a graphics file.
Container Icon This is the same idea as Toolbar Icon except that it deals with the icon shown for this class in the Class Browser. The Class Browser is discussed in Chapter 16.
OLE Public If this option is set, whenever a program containing this class is built using the Project Manager, a custom automation server and a GUID (Globally Unique Identifier) are generated.
Scale Units This option determines whether the grid is measured in terms of pixels or foxels.
Description This is an EditBox in which the developer can enter a description of the class.
Class Name This is the name of the class being modified.
Parent Class This is the name of the class upon which this class is based (that is, the immediate superclass).
Class Library This is the name of the .VCX file in which the class is stored.

The next tab is Members, which it shows the members of the current class. Members of the class can be properties, methods, events, or objects. Object members occur if the class is a composite (container-type) class.

This tab, which is shown in Figure 14.3, has a list of all the members of the class and enables the developer to protect any or all of them by checking the box in the Protected column. The No Init column is only applicable for object members and tells Visual FoxPro whether to run the Init event for this object when it is added at runtime. Checking the box means that the Init event is skipped.

Figure 14.3 : The Members tab for the Class Info dialog box.

Now that you have seen how to use the Visual Class Designer, the next step is to look at the syntax involved in getting into the Visual Class Designer. Obviously, there are two modes: CREATE and MODIFY.

Creating a Class

A new class can be created with the CREATE CLASS command. Issuing CREATE CLASS with no additional information presents the dialog box shown in Figure 14.4.

Figure 14.4 : The New Class dialog box.

Classes can be based on Visual FoxPro base classes (listed in the drop-down list box) or on any class you create. In order to base your new class on a class you have created (as opposed to one Visual FoxPro supplies for you), click the ellipses (…) CommandButton. A dialog box will enable you to select the class you want to use (shown in Figure 14.5). The left portion of the dialog box is a GETFILE type display that enables you to select a .VCX file. Once you select a .VCX file, the classes it contains shows in the list to the right. You can then select a class to open and click the Open CommandButton; the Visual Class Designer comes up.

Figure 14.5 : The dialog box in which you can select a class.

Note that you can only use a class stored in a .VCX file as the base class for another VCX-stored class. Figure 14.6 shows the New Class dialog box once the parent class has been selected.

Figure 14.6 : The New Class dialog box for the VCX subclass.

Another way to create a class and bypass the dialog box is to provide all the information needed to create the class on the command line. Here's the syntax for this:

CREATE CLASS <ClassName> OF <ClassLibraryName1>    ;
        AS cBaseClassName [FROM ClassLibraryName2]

ClassName is the name of the class to create. ClassLibraryName1 is the name of the .VCX file in which to store the class. If the .VCX file does not exist, Visual FoxPro creates it for you (note that you can create a class library, too, with the CREATE CLASSLIB command). cBaseClassName is the name of the class on which to base the new class. This can be a Visual FoxPro base class or a class you create. If it's a class you have created, you must specify the .VCX file it is stored in with ClassLibraryName2. You need to specify the class library name from where the superclass is coming even if it comes from the same .VCX file where the new class is stored.

Modifying Classes in a .VCX File

Classes are loaded for modification into the Visual Class Designer with the MODIFY CLASS command. Here's the basic syntax:

MODIFY CLASS <ClassName> OF <ClassLibraryName>

ClassName is the name of the class to modify, and ClassLibraryName is the name of the .VCX file containing the class to be modified. If you prefer a more visual method of selecting a class to edit, you can issue this without any additional information:

MODIFY CLASS

Visual FoxPro displays the same dialog box shown in Figure 14.5.

Using Classes in a .VCX File

Classes created with the Visual Class Designer are stored in a .VCX file (also called a class library). The structure of the table is the same as the structure of the .SCX file.

In order to instantiate an object from a class stored in a .VCX file, the class library must be loaded with the SET CLASSLIB command. Here is the syntax:

SET CLASSLIB TO <vcxFileName> [IN APPFileName | EXEFilename]
   [ADDITIVE] [ALIAS AliasName]

After the class library is loaded, any classes in the class library can be used for instantiating objects.

Be careful of one thing. As you saw previously in this chapter, a class in a .VCX file can be a subclass of a class stored in another class library. Make sure that all the .VCX files you need are loaded with SET CLASSLIB before you instantiate objects. A good strategy is to load all the .VCX files used in an application at the beginning. This way you are sure not to miss a .VCX file when you need it. If the visual class library is in a Visual FoxPro application or Visual FoxPro executable file, you can include the IN clause to designate the filename. In addition, be sure to use the ADDITIVE keyword when adding class libraries, or else those already loaded will be released.

You can designate an ALIAS name for your visual class library. You can then use the alias name to reference the specified visual class library. This is especially useful if your visual class library's filename is long, such as "My Very Own Excellent Visual Class Library." Here is an example:

SET CLASSLIB TO "My Very Own Excellent Visual Class Library" ;
   ALIAS Mine
oMyForm = CREATEOBJ(Mine.SpecialForm)

Use the RELEASE CLASSLIB command to release a single class library. To release all the class libraries, issue a SET CLASSLIB TO command without specifying a class library file. By the way, if all of this syntax looks familiar, it should. This is one thing Microsoft did really well-it kept the syntax for similar commands, such as SET LIBRARY and SET PROCEDURE, virtually identical.

A Technical Tip: Sending Parameters to an Object

When you instantiate an object it may be necessary to send parameters through to the object. For example, if you were creating an object that displays a message (I know there is a MessageBox function, but please bear with me), you might want to send parameters through that indicate the message text, the form caption, and so on.

The syntax for sending parameters through when instantiating an object is simple-all you do is add the parameters (separated by commas) after the name of the class from which you are instantiating. Here is an example:

loForm = CREATEOBJECT("MyMsgForm", "Hello. This is a message.")

This line of code instantiates an object called loForm from the "MyMsgForm" class and passes through the string "Hello. This is a message." as a parameter.

The next question is where do you accept the parameters? The answer is in the Init method. Beware, however, because unlike sending parameters through in FoxPro 2.6, parameters in Visual FoxPro that are accepted in the Init method are released when the Init method completes. Don't forget that Init is a procedure and thus causes its parameters to lose scope when it returns from where it was called.

How do you keep the parameters around? If the parameters are intended to be sent through by value (which is usually the case) you can move the parameters' values into the object's custom properties. You can't pass a parameter and have it stick around to be modified.

Managing Instances with AInstance

Visual FoxPro can give you a list of instances created for a particular class with the AInstance function. Here is this function's syntax:

lnNumInstances = AInstance(<ArrayName>, <ClassName>)

The AInstance function returns a numeric value: the number of instances found.

For example, you would issue the following command in order to determine how many instances have been created for class FORM:

lnNumInstances = AInstance(laInstances, "Form")

The variable lnNumInstances has the number of instances found, and the array laInstances has a list of the instance variables instantiated from class Form.

Note that this function only returns member variables created with CREATEOBJECT that are not themselves members of other objects. For example, if you have an instance of class Form that is a member of another object, this instance does not show up with AInstance.

AInstance can have many uses. A more common one is to use it to manage multiple instances of a class (a form, for example). The following example illustrates such a use. In this form class' Init method, the AInstance function checks for the number of instances of this class. Each instance of the class has a custom property called nInstanceNumber, which holds the instance number of the form. The Init method determines the instance number for this instance and then adds the instance number to the caption of the form. (Note that if this is the first instance of the form, the caption remains unmodified.) The Init method could do more. For instance, many developers adjust the Top and Left properties to cascade the form on the desktop. The code is presented in Listing 14.2.


Listing 14.2  14CODE02-The Multform Class That Illustrates How to Pass Arguments to a Class's Init Method
*  Class.............: Multform
*  Author............: Menachem Bazian, CPA
*  Notes.............: Exported code from Class Browser.

**************************************************
*-- Class:        multform (d:\data\docs\books\vfu\code\oop2.vcx)
*-- ParentClass:  form
*-- BaseClass:    form
*
DEFINE CLASS multform AS form

    DoCreate = .T.
    Caption = "Form"
    Name = "multform"

    *-- The Instance Number of this form
    ninstancenumber = .F.

    PROCEDURE Init
        *-- This code will determine what instance number
        *-- this form is and set the header accordingly.

        LOCAL lnNumInstances, lnThisInstance, lcInstance

        lnNumInstances = AInstance(laInstances, this.class)
        lnThisInstance = 1
        FOR lnCounter = 1 TO lnNumInstances
            lcInstance = laInstances[lnCounter]
            lnThisInstance = MAX(lnThisInstance,&lcInstance..nInstanceNumber + 1)
        ENDFOR

        this.nInstanceNumber = lnThisInstance

        IF lnThisInstance > 1
           this.caption = ALLTRIM(this.caption) + ": " + ALLT(STR(lnThisInstance))
        ENDIF
    ENDPROC

ENDDEFINE
*
*-- EndDefine: multform
**************************************************

ACLASS

If you have an involved class hierarchy, it can be difficult to remember what classes precede the instantiated class in the hierarchy. Take, for example, the following class hierarchy:

DEFINE CLASS myBaseForm AS FORM
ENDDEFINE

DEFINE CLASS myModalBaseForm AS MyBaseForm
ENDDEFINE

DEFINE CLASS ModalDialog AS myModalBaseForm
ENDDEFINE

When an object is instantiated from ModalDialog, you might wonder what the class hierarchy looks like. ACLASS answers that question. Assume this:

loModalDialog = CREATEOBJECT("ModalDialog")
lnNumClasses = ACLASS(laClasses, loModalDialog)

If you assume as much, laClasses will have this:

LACLASSES Pub                 A
   ( 1) C       "MODALDIALOG"
   ( 2) C       "MYMODALBASEFORM"
   ( 3) C       "MYBASEFORM"
   ( 4) C       "FORM"

AMembers

AMembers is another very useful function. This function populates an array with the members of an instance. It comes in two flavors:

lnNumMembers = AMembers(<ArrayName>, <InstanceVar>)
lnNumMembers = AMembers(<ArrayName>, <InstanceVar>, 1)

The first version of this function creates an array with the properties of an instance. The array is unidimensional. For example, you would state the following to see the properties of the _Screen object:

lnNumMembers = AMembers(laMembers, _screen)

This function returns an array with the following information. For brevity, only the first few elements are shown here:

LAMEMBERS  Pub                 A
          (   1) C       "ACTIVECONTROL"
          (   2) C       "ACTIVEFORM"
          (   3) C       "ALWAYSONTOP"
          (   4) C       "AUTOCENTER"
          (   5) C       "BACKCOLOR"
          (   6) C       "BASECLASS"
          (   7) C       "BORDERSTYLE"
          (   8) C       "BUFFERMODE"
          (   9) C       "CAPTION"
          (  10) C       "CLASS"

The second version creates a two-dimensional array. The first column is the name of the member and the second column is the type of the member (event, method, object, or property). The following example inspects _Screen:

lnNumMembers = AMembers(laMembers, _screen, 1)

The laMember array element values are shown in Listing 14.3.


Listing 14.3  14CODE03-Array Elements That Contain the Members of Screen Object
LAMEMBERS  Pub      A
   (  1, 1) C       "ACTIVATE"
   (  1, 2) C       "Event"
   (  2, 1) C       "ACTIVECONTROL"
   (  2, 2) C       "Property"
   (  3, 1) C       "ACTIVEFORM"
   (  3, 2) C       "Property"
   (  4, 1) C       "ADDOBJECT"
   (  4, 2) C       "Method"
   (  5, 1) C       "ALWAYSONTOP"
   (  5, 2) C       "Property"
   (  6, 1) C       "AUTOCENTER"
   (  6, 2) C       "Property"
   (  7, 1) C       "BACKCOLOR"
   (  7, 2) C       "Property"
   (  8, 1) C       "BASECLASS"
   (  8, 2) C       "Property"
   (  9, 1) C       "BORDERSTYLE"
   (  9, 2) C       "Property"
   ( 10, 1) C       "BOX"
   ( 10, 2) C       "Method"

Note that the second version of AMembers returns a two-dimensional array and shows more members of the object than the first version. Without the additional ,1 parameter, only properties of the object are returned. The ,1 parameter adds methods, objects, and events to the list. Each version of this function has its uses. To look at an object when you are concerned only with its properties, you would use the first version of this function. To get more information, you would use the second version.

Inspecting Objects with AMembers

One of the more difficult aspects of working with object-oriented code is that a single memory variable can have a great deal of data and code attached to it. Take _Screen, for example. _Screen is a system memory variable that is an object based on the class Form. Changes made to _Screen are reflected in the main Visual FoxPro window. Thus, you could reset the main Visual FoxPro window's title with this line of code:

_Screen.Caption = "Visual FoxPro UNLEASHED With Object Orientation!"

In order to properly use this nifty little object, it is necessary to learn what is attached to the memory variable. AMembers is a useful way to get this information, as you saw in the previous example. The basic problem with this approach, though, is that there is so much information returned by AMembers that it is very difficult to make sense of it all. Clearly it would be beneficial to have some manner of presenting the information in a more useful format.

There are several ways to accomplish this goal. One simple method is to create the array with AMembers and to read the resultant array into a cursor. Listing 14.4 illustrates the use of the AMembers() function to create a cursor of the members of an object.


Listing 14.4  14CODE04
*  Program...........: CURSMEMB.PRG
*  Author............: Menachem Bazian, CPA
*) Description.......: Creates a cursor of the members of an object
*  Calling Samples...: =CURSMEMB(oObject)
*  Parameter List....: toObject - The Object to View
*  Major change list.:

LPARAMETERS toObject

IF TYPE("toObject") # "O"
    =MessageBox("Parameter must be an object!", 16)
    RETURN
ENDIF

*-- If we get this far, we can read the object...

LOCAL laMembers[1]
=AMEMBERS(laMembers, toObject, 1)

CREATE CURSOR _members (cMembName C(25), ;
                        cMembtype C(25))

INSERT INTO _members FROM ARRAY laMembers

*-- And that's it
RETURN

This procedure is called with one parameter: the object to inspect. To see the members of the object, simply browse the _members cursor.

Taking the Inspector one Step Further

The Cursmemb.prg program, presented in Listing 14.4, provides a simple view of an object, but it still does not go far enough. Cursmemb.prg replaces an array view with the capability to browse and index a cursor. Although this is certainly an advance in how you can look at an object, it would be useful to see property values where possible and maybe even to get a prettier interface behind the information.

ObjectInspectorForm is a class that accomplishes this. This form-based class gives you a clean and easily readable form that you can use to view the contents of an object. The next section gives you a look at the interface; I show you how to call the object as well as explain the code behind it.

The User Interface

The interface has three controls. The first is the Order option group object, which enables you to switch between two display orders in the list: alphabetical and grouped. The Alphabetical option button shows all the members of the object in alphabetical order without regard to the members type. The Grouped option button shows all the members of a particular type together in alphabetical order. Events show first, then methods, objects, and properties. Figures 14.7 and 14.8 show an example of each display order.

Figure 14.7 : The Object Inspector showing members in alphabetical order.

Figure 14.8 : Object Inspector showing members in grouped order.

The second control is a ListBox, which lists all the members of the object.

Finally, the third control is the OK CommandButton, which releases the form.

All the properties in the list are shown with their values, as best as they can be determined, within the screen. Here is how this is accomplished-first, the class's code is presented in Listing 14.5.


Listing 14.5  14CODE05-Object Inspector Form Class Code Was Exported from the Class Browser
* Program: ObjInspector.prg
* Description: Test Program that creates ObjectInspectorForm objects
*              object to display the Form showing the__Screen object
*              properties.
oObjInspect = CREATE("Objectinspector",_Screen)
oObjInspect.Show()
RETURN
*  Class.............: Objectinspectorform
*  Author............: Menachem Bazian, CPA
*  Project...........: Visual FoxPro Unleashed!
*  Copyright.........: (c) Flash Creative Management, Inc. 1998
*) Description.......:
*  Notes.............: Exported code from Class Browser.

**************************************************
*-- Class:        objectinspectorform (d:\data\docs\books\vfu\code\oop2.vcx)
*-- ParentClass:  form
*-- BaseClass:    form
*
DEFINE CLASS objectinspectorform AS form

    DataSession = 2
    Height = 309
    Width = 466
    DoCreate = .T.
    AutoCenter = .T.
    BackColor = RGB(192,192,192)
    BorderStyle = 3
    Caption = ""
    MaxButton = .F.
    MinButton = .F.
    ClipControls = .F.
    WindowType = 1
    PROTECTED cobjectbmpfile
    cobjectbmpfile = (home()+"SAMPLES\GRAPHICS\BMPS\FOX\APPS.BMP")
    PROTECTED cmethodbmpfile
    cmethodbmpfile = (home()+"SAMPLES\GRAPHICS\BMPS\FOX\CLASSES.BMP")
    PROTECTED cpropertybmpfile
    cpropertybmpfile = (home()+"SAMPLES\GRAPHICS\BMPS\FOX\INDEXES.BMP")
    PROTECTED ceventbmpfile
    ceventbmpfile = (home()+"SAMPLES\GRAPHICS\BMPS\FOX\AUTOFORM.BMP")
    Name = "objectinspectorform"

    ADD OBJECT lstmembers AS listbox WITH ;
        FontName = "Courier New", ;
        Height = 205, ;
        Left = 12, ;
        Top = 48, ;
        Width = 438, ;
        ItemBackColor = RGB(192,192,192), ;
        Name = "lstMembers"

    ADD OBJECT cmdok AS commandbutton WITH ;
        Top = 264, ;
        Left = 180, ;
        Height = 37, ;
        Width = 109, ;
        Cancel = .T., ;
        Caption = "OK", ;
        Default = .T., ;
        Name = "cmdOK"

    ADD OBJECT label1 AS label WITH ;
        AutoSize = .F., ;
        BackStyle = 0, ;
        Caption = "Order:", ;
        Height = 18, ;
        Left = 24, ;
        Top = 15, ;
        Width = 40, ;
        Name = "Label1"

    ADD OBJECT opgorder AS optiongroup WITH ;
        ButtonCount = 2, ;
        BackStyle = 0, ;
        Value = 1, ;
        Height = 25, ;
        Left = 96, ;
        Top = 12, ;
        Width = 217, ;
        Name = "opgOrder", ;
        Option1.BackStyle = 0, ;
        Option1.Caption = "Alphabetical", ;
        Option1.Value = 1, ;
        Option1.Height = 18, ;
        Option1.Left = 0, ;
        Option1.Top = 4, ;
        Option1.Width = 109, ;
        Option1.Name = "optAlphabetical", ;
        Option2.BackStyle = 0, ;
        Option2.Caption = "Grouped", ;
        Option2.Value = 0, ;
        Option2.Height = 18, ;
        Option2.Left = 127, ;
        Option2.Top = 4, ;
        Option2.Width = 73, ;
        Option2.Name = "optGrouped"

    PROCEDURE buildlist
        LPARAMETERS toObject

        #DEFINE COLUMNLENGTH 25

        WAIT WINDOW NOWAIT "Building members list. Please stand by..."

        this.lockscreen = .t.
        this.lstMembers.clear()

        SELECT _members

        IF this.opgOrder.value = 1
            SET ORDER TO TAG Alpha
        ELSE
            SET ORDER TO TAG Grouped
        ENDIF

        GO TOP

        lnCounter = 0

        SCAN
            lnCounter = lnCounter + 1
            lcText = PADR(_members.cMembName, COLUMNLENGTH)

            IF _members.cMembType = "Prop"
                *-- Now we need to get the value of the property
                lcText = lcText + ALLTRIM(_members.cMembVal)
            ENDIF

            thisform.lstMembers.additem(" " + lcText)

            lcBmpVar = "this.c" + alltrim(_members.cMembType)+"bmpfile"
            thisform.lstMembers.picture(lnCounter) = EVAL(lcBmpVar)
        ENDSCAN

        this.lockscreen = .f.
        thisform.refresh()

        WAIT CLEAR
    ENDPROC

    PROCEDURE Resize
        this.lockscreen = .t.

        this.lstMembers.width = this.width - 24
        this.cmdOk.left = (this.width-this.cmdOk.width)/2
        this.cmdOK.top = (this.height - 8 - this.cmdOK.height)
        this.lstMembers.height = this.cmdOK.top - this.lstMembers.top - 11

        this.lockscreen = .F.
    ENDPROC

    PROCEDURE Init
        *  Class.............: OBJECTINSPECTORFORM
        *  Author............: Menachem Bazian, CPA
        *  Project...........: Visual FoxPro Unleashed
        *  Created...........: May 16, 1998 - 07:44:03
        *  Copyright.........: (c) Flash Creative Management, Inc., 1998
        *) Description.......: When passed an object, it builds a list of the
        *)                   : members and displays them nicely.
        *  Calling Samples...: oForm = CREATEOBJECT("OBJECTINSPECTORFORM",
            oObject)
        *  Parameter List....: toObject - Object to inspect
        *  Major change list.:

        LPARAMETERS toObject

        this.caption = "Inspecting object: " + ALLT(toObject.Name)

        IF TYPE("toObject") # 'O'
            =MessagebOx("You can only pass OBJECT type parameters!", 16)
            RETURN .F.
        ENDIF

        *-- If we get this far, we can inspect the object
        *-- Let's define some memory variables and do the AMembers()

        LOCAL laMembers[1], lcText, lcName
        =AMembers(laMembers,toObject,1)

        *-- In order to create the list in proper order, it is useful to have
        *-- a table to work off of (so we can INDEX). Hence:

        CREATE CURSOR _members (cMembName C(25), cMembtype C(25), cMembVal 
           C(40))
        INSERT INTO _members FROM ARRAY laMembers

        INDEX ON cMembName TAG Alpha
        INDEX ON cMembType + cMembName TAG Grouped

        SCAN FOR _members.cMembType = "Prop"

            *-- Determine the value of the property and place it in the
            *-- cMembVal field

            lcName = "toObject."+ALLTRIM(_members.cMembName)
            DO CASE
                CASE TYPE(lcName) = 'U'
                    lcText = ".UNDEFINED."
                CASE isnull(EVAL(lcName))
                    lcText = ".NULL."
                CASE TYPE(lcName) = 'L'
                    lcText = IIF(EVAL(lcName), ".T.", ".F.")
                OTHERWISE
                    lcText = ALLTRIM(PADR(EVALUATE(lcName),50))
            ENDCASE
            REPLACE _members.cMembVal WITH lcText
        ENDSCAN

        this.buildlist()
    ENDPROC

    PROCEDURE cmdok.Click
        release thisform
    ENDPROC

    PROCEDURE opgorder.Click
        thisform.buildlist()
    ENDPROC

ENDDEFINE
*
*-- EndDefine: objectinspectorform
**************************************************

You can call the form as follows. (The example shows how you can call the InspectorForm to inspect the _Screen object.)

DO ObjInspector.prg

The key work in this class takes place in the form's Init method and in a custom method called BuildList.

The Init Method

The Init method has several responsibilities. It accepts the parameter toObject and makes sure that its data type is Object. If the parameter sent through is not an object, an error message is returned. Note that an error condition causes Init to return .F.. When .F. is returned from Init, the object is not instantiated.

The next step is to create a cursor, much like the one in the Cursmemb.prg program shown earlier in this chapter. The differences here are threefold. First of all, an additional field called cMembValue is added to the cursor. Second, cMembValue is populated with the actual value of the property. Third, the cursor is indexed. I use the indexes in BuildList later in this chapter.

You fill in the cMembValue field by creating a text string beginning with "toObject." (remember that toObject is the object passed through on the parameter line) and then adding the name of the object as recorded in the cMembName field. This gives you the name of the property you are trying to evaluate in a format you can use to query the value.

The next step is a little tricky. Sometimes an object might have a property that does not have a value. For example, if _Screen.ActiveForm is queried with no form open, Visual FoxPro returns an error. "U" for undefined is returned when you check the type of the property with the TYPE function (? TYPE("_Screen.ActiveForm"), for example)"". Therefore, a check has to be made of the data type of the property to avoid an error.

A property can also be NULL; you trap for that with the ISNULL function. Finally, a logical value is checked for and converted to a string. All other value types can be converted to a string with the PADR function, as done in the OTHERWISE clause of the CASE statement.

The BuildList Method

BuildList is responsible for building the contents of the list box based on the cursor. The method is very simple. It begins by clearing the list with the Clear method; then the value of the Order option group is queried and the index order is set accordingly. The next step is to loop through the cursor records and add the items from the cursor to the list. This is accomplished by using the AddItem method. Note that the value is added to the string passed through to AddItem for all the PROPERTY-type members. Finally, the picture property of each row is set based on the type of member stored in the row. The four picture filenames and locations are stored in the custom form properties cEventBMPFile, cMethodBMPFile, cObjectBMPFile, and cPropertyBMPFile.

One interesting tip that comes out of the BuildList method is the use of the LockScreen property of the form. When LockScreen is set to .T., it prevents the form's display from changing. This is very important in this case because a list box automatically refreshes itself every time AddItem is issued. This slows the process considerably. By setting LockScreen to .T. for the duration of the list building, you ensure maximum performance from Visual FoxPro during this process. A final call to the Refresh method after LockScreen is reset redraws everything nicely.

The Resize Method

The form's default size might not be large enough to display a property and its value if the value is a lengthy string. It would be nice to be able to resize the form and have the controls on the form automatically adjust to the new size of the form.

The Resize method responds to an event (when the user changes the size of the form, for example). The method is called when the user is done resizing the form. The Resize method in this case simply recalculates the height and width of the list box and makes sure that the command button is still situated at the bottom of the form. Setting LockScreen to .T. while the recalculation takes place prevents the controls from adjusting one at a time. It looks cleaner this way (and is more efficient, too).


© Copyright, Sams Publishing. All rights reserved.