Define Class

This command is the code-based method for defining new classes based on existing ones (including the Visual FoxPro base classes). You define a class "as" an existing class, and then spell out the differences between the new class and the old one. With each new version of VFP, use of this command seems to increase. The Document View tool added in VFP 7 makes working with classes defined in code far easier than in earlier versions.

Usage

DEFINE CLASS ClassName AS ParentClass [ OF ParentClassLibrary ]
  [ OLEPUBLIC ]
        [ PROTECTED ProtectedPropertyList ]
        [ HIDDEN HiddenPropertyList ]
        [ Property = uExpr ]
        [ Property_COMATTRIB = nFlags |
          DIMENSION PEMName_COMATTRIB[ 5 ]
          [ Property_COMATTRIB[ 1 ] = nFlags ] 
          [ Property_COMATTRIB[ 2 ] = cHelpString ]
          [ Property_COMATTRIB[ 3 ] = cPropertyCase ]
          [ Property_COMATTRIB[ 4 ] = cPropertyType ]
          [ Property_COMATTRIB[ 5 ] = nOptionalParams ] ]
        [ IMPLEMENTS cInterface [ EXCLUDE ] IN
          cTypeLib | TypeLibGUID | cProgID ]
        [ ADD OBJECT [ PROTECTED ] ObjectName AS ObjectClass 
          [ NOINIT ]
          [ WITH PropertyList ] ]
        [ [ PROTECTED | HIDDEN ] PROCEDURE | FUNCTION MethodName
            [ ( ParamName [ AS ParamType ] [ @ ] [, � ] ) ]
            [ AS MethodReturnType ]
            [ HELPSTRING cHelpString ]
          Commands
          [ ENDPROC | ENDFUNC ] ]
ENDDEFINE

Parameter

Value

Meaning

ClassName

Name

The name to assign the new class.

ParentClass

Class Name

The existing class on which the new class is based.

ParentClassLibrary

Class Library Name

The full path and name of the class library containing ParentClass.

ProtectedPropertyList

List of Names

Names of properties that can be seen only within the class and its subclasses.

HiddenPropertyList

List of Names

Names of properties that can be seen only in this class.

Property

Name

A property to receive an initial value. It can be one listed in ProtectedPropertyList or HiddenPropertyList, a new property to add, or a property inherited from ParentClass.

uExpr

Expression

The value to assign to Property.

nFlags

Numeric

The set of attributes to give the property or method in the type library. See Help for the values.

Omitted

The property is read-write and visible, or the method is visible.

cHelpString

Character

The help string for the property or method, appearing in Object Browsers or IntelliSense, via the Type Library.

Omitted

The property or method has no help string.

cPropertyCase

Character

The name of the property with the desired capitalization. This version is placed in the Type Library. (Doesn't apply to methods; specify capitalization in the PROCEDURE line.)

Omitted

The property name is put in the Type Library in all caps.

cPropertyType

Character

The property type to be stored in the Type Library. Uses the same list of types as the AS clause. (Doesn't apply to methods; use the AS clause.)

Omitted

The property is listed as a Variant.

nOptionalParams

Positive number

The number of parameters that are optional. The remaining parameters (which begin with the first parameter) are required. This item applies to methods only and is ignored for properties.

Omitted or 0

All parameters are required.

cInterface

Character

The name of the interface to implement.

cTypeLib

Character

The full path to the Type Library containing the interface definition.

TypeLibGUID

GUID

The unique ID for the Type Library containing the interface definition, followed by a backslash and the major and minor version numbers of the Type Library.

cProgID

Character

The registered ProgID for the Type Library containing the interface definition.

ObjectName

Name

The name to give an object to be added to this class as a member. Only container classes can contain object members.

ObjectClass

Class Name

The class on which the object ObjectName is based. It must be derived from a base class that can be a member of this type of container class.

PropertyList

 

A list of initial assignments to properties of ObjectName in the form:
Property = uExpr

MethodName

Name

A valid procedure or function name. The method may be a new one or one inherited from ParentClass. It can also be a property name followed by _Access or _Assign or the name This_Access.

ParamName

Name

The name of a parameter for the method.

ParamType

Type Name

The type for this parameter, to be stored in the Type Library. If the AS clause is omitted, the parameter is a Variant.

MethodReturnType

Type Name

The return type of the method, to be stored in the Type Library. If the AS clause is omitted, the return type is Variant. Use a return type of VOID to indicate that the method doesn't return anything.

Commands

 

The code to execute for method MethodName. May include the special NoDefault designator that indicates that the base system behavior for this method should not be executed, and the DoDefault() function that calls up the class hierarchy.


The property assignment, ADD OBJECT and method definition sections can be repeated. A single class definition can contain many of each.Let's look at each part of a class definition. Start with the DEFINE CLASS line. You can't create classes out of nothing. Every new class is derived from an existing class. Ultimately, all classes can trace their heritage back to one of the Visual FoxPro base classes. (See "Base Clef" in "OOP is Not an Accident" for a list.)When creating a class, you base it on the class that's closest in appearance and behavior to what you want to end up with. That way, you minimize the amount of work you have to do to get what you want. Basing one class on another means that the new class starts out with all the properties, events and methods of the ParentClass, and that changes to the ParentClass will usually be passed along to the new class. (That's inheritance.) This is a Good Thing, since it lowers your maintenance burden. In fact, it's one of the main points of OOP.Beginning in VFP 7, you can also specify the class library for the parent class, right in the DEFINE CLASS. This means that when you instantiate classes based on the new class, you don't have to be sure to SET PROCEDURE TO the right library first.Adding the OLEPUBLIC keyword defines this class as a custom Automation server (or COM server) that can be instantiated from other applications. You have to BUILD DLL or BUILD EXE to use such classes as servers. (You can also use these classes inside a single VFP session, but we're not sure why you'd want to. Maybe for testing.) Creating a COM server is one of the most common reasons for defining a class in code—in that case, you probably want to base it on the Session class, which was designed specifically for this purpose. Check out the entry for the Session class for more information.Protection, in the OOP sense, has nothing to do with paying off the local mobster. A protected property, member or method can't be seen outside the object itself (except that its subclasses can see it, too). This is how you keep other objects from messing around with this one. If outside objects need to know or change the value of a protected property, you can provide a method to allow this. That method can make sure that the change doesn't foul things up. (If you need to make a property visible to the outside world but protect it from bad data, use an assign method, instead.)Hidden properties and methods are even more protected than protected ones. Not only can't other objects see them, even subclasses can't see them. Unfortunately, hidden properties and methods can't even be used by methods that are themselves called via the double-colon (::) scope resolution operator or DoDefault(). This means that hidden is pretty much useless.The property assignments serve a couple of purposes. First, you can use them to assign values to properties that already belong to the class, including those you just defined as protected or hidden. Second, you create new properties this way. If you don't know what values you want for them yet (perhaps they'll be assigned in the class's Init method), just give them empty values of the appropriate type.Beginning in VFP 7, you can specify properties (better known as attributes) of your properties and methods. For classes used only within VFP, this isn't terribly useful or important. But for COM objects, this ability lets you determine how other applications see your objects. There are two ways to specify the attributes of a property. If the only thing you want to change is the visibility or read-write status of the property, you can use the simpler form:
PropertyName_COMATTRIB = nFlags
nFlags is an additive flag that allows you to make a property read-only, write-only or read-write (the default). It also includes values that determine whether applications that read the Type Library (like Object Browsers) can see a property or method. The list of flag values is contained in the Help entry for Define Class and can be found in FoxPro.H.If you want to specify other attributes of a property (and you should), use the array form of COMATTRIB. In this case, declare an array of the form PropertyName_COMATTRIB and give it four elements. (You can give it five, but the last item is ignored for properties.) Then, assign values to the four items.The first element of the COMATTRIB array contains the nFlags value, the same as when using the scalar form. The second element specifies a help string for the property—this is the descriptive string that appears in tools like the VFP Object Browser and Office Object Browser. It's also used for the tip in IntelliSense's List Members display.The third array element specifies the way the property should appear. This overcomes VFP's tendency to convert property names to all caps in the Type Library. The fourth element indicates the type of the property.Specifying method attributes is a little different. You can use a COMATTRIB array, but it should have five elements. Only the first and fifth elements are used. As with properties, the first element specifies the flags. The fifth element indicates how many of the method's parameters are optional. This number is counted from the right-hand side of the list. So for a method, Actor, with four parameters, including this line:
Actor_COMATTRIB[5] = 1
indicates that the first three parameters are required and the last is optional.The other attributes of a method are specified as part of the method definition. The capitalization used in the class definition is passed on to the Type Library. A help string can be provided by using the new HELPSTRING clause of PROCEDURE. Similarly, you can specify the return type of the method by adding AS TypeName after the parameter list.If all that weren't enough COM stuff, there's a whole 'nother new COM-related capability, beginning in VFP 7. That's the ability to implement interfaces from other objects. For an explanation of interfaces and implementing them, see "It was Automation, You Know" in Section 1. The short version is that you can add methods to a VFP class that respond to events raised by other objects. For example, you can create a VFP class that responds to a user's actions in Word, or Excel, or Outlook. The key to responding to outside events is the IMPLEMENTS line of DEFINE CLASS. This line indicates that the class in question is going to provide code that executes when the events of a particular interface occur. Once you add that line to a class, it then must include a method definition for every event of the specified interface. How do you know what they are? Take the shortcut—open the appropriate Type Library in VFP's Object Browser (also new in VFP 7), drill down in the left pane until you find the interface you're interested in, and then drag that interface to an empty MODIFY COMMAND window. The structure you need is auto-magically created—all you have to do is fill the actual code to respond to the events. Well, that, and set things up right at runtime. See EventHandler() for more on that piece of the puzzle.When you implement an interface, you need some way to indicate whose interface it is. VFP accepts three forms of identification, but one of them is far inferior to the other two. Specifying the path and file name for the Type Library binds your class definition to a particular machine—why would you want to do that? We're puzzled as to why this is the form the Object Browser generates. Either of the other two forms is portable. On the whole, we think the ProgId version is better than the TypeLibGUID version—it's portable and readable.When the class you're defining is derived from a container class (like Form, Container and so forth), you can add member objects in the definition. In some cases, you're restricted to members of certain classes—see "Not Quite All There" in "OOP is Not an Accident."When you add objects this way, you can set some of their properties up front by using the WITH clause. The OOP experts recommend that you don't do this because it's tantamount to subclassing the thing on the fly. It's better to either create a subclass that has the properties you need or to make the changes in the container's Init method.The NOINIT clause says that the object should be added to this class without running its Init method. We haven't yet run into a case where this is useful, but we're sure it's there for a reason.Finally, you can define methods. Again, these can be new definitions for existing methods inherited from ParentClass or its ancestors, or brand-new methods that belong only to this class. Just about any FoxPro command is valid inside method code. As we said before, when you're defining COM objects, the method definition is the place to specify the right capitalization of the method name, the method's return type and the help string. There are two ways to specify parameters for a method. You can have LPARAMETERS as the first executable line inside the method, or you can specify the parameters as part of the PROCEDURE or FUNCTION line. For a COM object, the latter is a better choice, so the parameters show up in the Type Library. Whichever way you do it, starting in VFP 7, you can specify parameter types using the AS clause.There are two special things you can do in method code. Each of them relates to dealing with inherited behavior. First, you can explicitly call methods belonging to the parent class (or even its ancestors) by using the :: operator or the DoDefault() function. Second, you can suppress the default system behavior of an event by including the NoDefault keyword. For more information on both of these, see "OOP is Not an Accident." You can also define some special kinds of methods. Access and assign methods let you hook events to properties. To create an access method for a property, name the method with the name of the property followed by _Access; similarly, you create an assign method with the property name followed by _Assign. There's also a special global access method called This_Access. For details on when these methods fire and what they do, see the "Access, Assign" section.In earlier editions of this book, we said that we didn't think you should use DEFINE CLASS very often. In most cases, we still feel that way. There are great benefits to defining your classes visually using the Class Designer: drag and drop, builders, manipulation in the Class Browser and Component Gallery, and more. You get a lot more flexibility with visual design. However, the COM abilities added in VFP 7 (IMPLEMENTS and COMATTRIB) are available only in code classes. In addition, working in code classes isn't as hard as it used to be, thanks to an assortment of enhancements, from IntelliSense to Document View. The bottom line is that visual classes (forms, controls and the like) should be designed visually, while code is a better approach for many non-visual classes.So, today, if you ask us whether to define classes in code or in the Class Designer, we'll give you our favorite answer: "It depends."

Example

DEFINE CLASS EasyForm AS FORM
* Here's a pretty basic form that auto-centers, has a Close
* button and lets you pass it a background color and a window 
* caption.
 
   AutoCenter = .T.
 
   ADD OBJECT cmdClose AS CloseButton
 
   PROCEDURE Init(nBackColor, cCaption)
 
      IF TYPE("nBackColor") = "N"
         This.BackColor = nBackColor
      ENDIF
 
      IF TYPE("cCaption") = "C"
         This.Caption = cCaption
      ENDIF
      
      This.cmdClose.Left = (This.Width - This.cmdClose.Width) /2
      This.cmdClose.Top = This.Height - This.cmdClose.Height ;
                          - 10
   ENDPROC
ENDDEFINE
 
DEFINE CLASS PassForm AS EasyForm
* This is a subclass of our basic form above. It insists on 
* being passed a built-in password or you can't create it. Not 
* exactly user-friendly, is it?
 
   PROTECTED cPassWord
   cPassWord = "Yowza!"
   
   PROCEDURE Init(nBackColor, cCaption, cPass)
   
      IF TYPE("cPass") = "C" AND cPass = This.cPassWord
         DoDefault(nBackColor, cCaption)
      ELSE
         RETURN .F.
      ENDIF
   ENDPROC
   
ENDDEFINE
 
DEFINE CLASS CloseButton AS CommandButton
* Standard Close button. Caption is "Close" and it releases the 
* form when clicked.
 
   Name = "cmdClose"
   Caption = "Close"
   Height = 40
   Width = 60
   
   PROCEDURE Click
   
      ThisForm.Release()
   ENDPROC
   
ENDDEFINE
The example defines two form classes, one derived from the other, and a button class. The form classes have one member object, a Close button. The password property in PassForm is not an example of how to put a password on a form—just a demonstration of the kind of thing you might want to protect. To check out these classes, put all this code in a program file. Then, use code like the following:
SET PROCEDURE TO <the program you created>
oForm = CREATEOBJECT("PassForm", RGB(255,0,255), "Showing Off", "Yowza!")
oForm.Show()
In VFP 6 and later, you can use NewObject() instead of CreateObject() and skip the SET PROCEDURE line. If you do so, be sure to include both the file name containing the definition and the empty string for the application parameter before the values to pass to the form's Init method. You need a line like this:
oForm = NewObject("PassForm","MyForm.prg","",RGB(255,128,128),;
                  "Why not?","Yowza!")

Example

DEFINE CLASS MyServer AS SESSION OLEPUBLIC
 
PROTECTED cMessage
cMessage = "Hello, World!"
 
DIMENSION cMessage_COMATTRIB[4]
cMessage_COMATTRIB[1] = 0  && Read-write
cMessage_COMATTRIB[2] = "This is the message."
cMessage_COMATTRIB[3] = "cMessage"
cMessage_COMATTRIB[4] = "String"
 
PROCEDURE GetMessage AS String HelpString "Returns the message"
 
RETURN This.cMessage
 
PROCEDURE ChangeMessage( cNewMessage AS String ) AS VOID ;
   HelpString "Change the message"
 
This.cMessage = cNewMessage
RETURN
 
 
DIMENSION ChangeMessage_COMATTRIB[5]
ChangeMessage_COMATTRIB[1] = 0
ChangeMessage_COMATTRIB[5] = 0 && Parameter is required
 
ENDDEFINE
This example creates a very simple COM server that has a message that it can return or change. Once you've compiled and registered the class, you can instantiate it from VFP or other environments. In VFP, you'd do it like this, assuming you built it into Message.EXE.
oServer = CreateObject("Message.MyServer")
Then, you can call on the server's methods, like this:
?oServer.GetMessage()
oServer.ChangeMessage("Goodbye, cruel world")
You can also check out the server using any Object Browser to see the effect the COMATTRIB definitions have.

See Also

::, Access, Assign, Create Class, CreateObject(), CreateObjectEx(), DoDefault(), EventHandler(), LParameters, Modify Class, NewObject(), Set ClassLib, Set Procedure


Back to Table of Contents

Copyright © 2002-2018 by Tamar E. Granor, Ted Roche, Doug Hennig, and Della Martin. Click for license .