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: |
|
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. |
PropertyName_COMATTRIB = nFlagsnFlags 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] = 1indicates 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 |
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 |
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 |