This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

MemberData and Custom Property Editors

Doug Hennig

A couple of common requests from VFP developers are capitalization support for custom properties and methods in the Properties window and in IntelliSense, and the ability to customize the Properties window. Microsoft listened: VFP 9 gives us both of these, plus the ability to create our own property editors, with a new feature called MemberData. Doug Hennig explains.

Something that's bugged me about VFP since its initial release is that custom properties and methods are forced to lowercase in a VCX or SCX file. This makes them appear at the bottom of the Properties window rather than interspersed with native properties, events, and methods (referred to as PEMs or members). It also means that IntelliSense doesn't display member names in a meaningful case, so I always end up correcting the name that IntelliSense inserts for me. I've also wished that there was a way to limit the Properties window to displaying just those PEMs that mattered to me, not the hundreds of obscure ones I never use.

	VFP 9 has answered my prayers. This new version provides a way to specify metadata about class members. This metadata, referred to as MemberData, contains attributes such as the case used to display a member (the name is still physically stored in the VCX or SCX in lowercase) and whether to display a member in the new Favorites tab in the Properties window.

	MemberData is implemented by adding a new property called _MemberData to a class and filling it with XML that contains the metadata for the members of the class. _MemberData isn't added to a class automatically, nor is the XML filled in; you have to do both yourself (I'll address how later in this article).

MemberData

Here's the XSD schema for MemberData:

  <?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="VFPData">
<xs:complexType>
<xs:sequence>
<xs:element name="memberdata">
<xs:complexType>
<xs:attribute name="name" type="xs:string"
use="required" />
<xs:attribute name="type" type="xs:string"/>
<xs:attribute name="display" type="xs:string"/>
<xs:attribute name="favorites" type="xs:boolean"/>
<xs:attribute name="override" type="xs:boolean"/>
<xs:attribute name="script" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

&#9;Okay, now here it is in English. Table 1 shows the attributes that make up a member's metadata.

****

Table 1. The attributes in MemberData contain the metadata for a member.

Attribute

Description

name

The name of the member.

type

The member type: "property," "event," or "method."

display

The text to use as the name of the member in the Properties window and in IntelliSense.

favorites

"True" if the member should appear in the Favorites tab of the Properties window or "False" if not.

override

"True" to ignore metadata from a parent class or "False" to inherit from a parent class.

script

Code to execute when the property editor button for this member is selected in the Properties window.

&#9;Here are some notes about these elements:

  • Since this is XML, the names of the elements and attributes are case-sensitive. With the exception of the script attribute, the contents of the attributes are also case-sensitive. The name and type attributes must be in lowercase. "True" and "False" must be specified in proper case in the favorites and override attributes.
  • The display attribute cannot be different from the name of the member except in case. For example, while "MyCustomProperty" is acceptable for display for the mycustomproperty member, "SomeOtherName" is not.
  • You can modify MemberData in subclasses without having to reproduce the entire XML string; unspecified attributes get their values from the parent class. However, if the override attribute is "True," the default behavior is used for unspecified attributes instead. For example, suppose you have a class that has metadata specifying a display attribute for MyCustomProperty. If the display attribute is omitted in a subclass, it will inherit the display attribute from the parent class. However, if override is "True," the property will display as "mycustomproperty" in the Properties window because the display attribute isn't inherited from the parent class and isn't specified in this class.
  • If the script attribute is specified for a member, you'll see a property editor button in the Properties window when you select the member. Clicking on that button executes the code specified in the script attribute, using EXECSCRIPT().
  • If the XML isn't valid, you won't get an error, but you won't see the effects of MemberData either.

&#9;Here's an example specifying that the property mycustomproperty should be displayed as MyCustomProperty and should appear in the Favorites tab of the Properties window, and VFP should call MyCustomPropertyEditor.PRG when you click on the property editor button in the Properties window.

  <?xml version = "1.0" encoding="Windows-1252"
standalone="yes"?>
<VFPData>
<memberdata
name="mycustomproperty"
type="property"
display="MyCustomProperty"
favorites="True"
override="False"
script="DO MyCustomPropertyEditor.PRG"/>
</VFPData>

&#9;Figure 1 shows how this property appears in the Properties window. Notice that it appears as MyCustomProperty, it's in the proper place in alphabetical order rather than at the end of the Properties window, and a property editor button appears to the right of the value textbox. In Figure 2, you can see that this property is one of the few shown in the Favorites tab. Figure 3 shows how the property appears in IntelliSense.

Global MemberData

Having class-specific MemberData is great, but it would be a drag if you had to specify the same metadata for the same property in every class. For example, if you add MyCustomProperty to several classes, you'll likely want to use the same MemberData for each class. Fortunately, the VFP team thought of this.

&#9;To create MemberData for a member at a global level (that is, for all classes having that member), add a record to the IntelliSense table that specifies the MemberData. (The IntelliSense table, specified in the _FOXCODE system variable, defaults to FOXCODE.DBF in your application data directory, which is the one returned by the HOME(7) function.)

&#9;Set the TYPE field to "E" (a new value in VFP 9 denoting a MemberData record), put the name of the member into ABBREV (case isn't important), and put the MemberData into TIP. If the _MemberData property for an object contains metadata for a member that also has global metadata, the _MemberData version will override the global.

&#9;Here's an example that creates global MemberData for MyCustomProperty by adding a record with the proper information to FOXCODE:

  local lcXML

* Create the MemberData.

text to lcXML noshow
<?xml version="1.0" encoding="Windows-1252"
standalone="yes"?>
<VFPData>
<memberdata
name="mycustomproperty"
type="property"
display="MyCustomProperty"
favorites="True"
override="False"
script="do MyCustomPropertyEditor.PRG"/>
</VFPData>
endtext

* Now create a record in FOXCODE that provides the
* MemberData for MyCustomProperty.

use (_foxcode) again shared alias FOXCODE
insert into FOXCODE (TYPE, ABBREV, TIP) values ('E', ;
  'MyCustomProperty', lcXML)
use

&#9;Check this out: Run the preceding code (GlobalMemberData.PRG in the Download file), and then type the following in the Command window:

  create class x of x as custom

&#9;Create a property called MyCustomProperty and notice that even though there's no _MemberData property for this class, MyCustomProperty appears in the proper case and in the Favorites tab.

&#9;MemberData doesn't apply to just custom properties and methods. For example, since Caption and Name are the properties I change most often for Form, Label, Checkbox, and other objects with those properties, I've created global MemberData for those properties to add them to the Favorites tab so I don't have to jump around in the Properties window to set them. This gives me a small productivity boost for every object, and over the life of a project, it can really add up.

Property editors

Like a builder, a property editor can make it easier to enter the value of a property. For example, the new Anchor property defines how a control reacts when its container is resized (for example, it could be resized or moved). However, the Anchor value consists of additive enumerations, such as 12 (8, anchored to the right + 4, anchored to the bottom). This isn't exactly intuitive, so a property editor would help with productivity.

&#9;Although you can specify multiple lines of code in the metadata script attribute, calling a PRG or form usually makes more sense. For global MemberData, you can also specify that you want to execute a script record in FOXCODE by using the following as the script attribute:

  do (_CODESENSE) with 'RunPropertyEditor', '', 'SomeValue'

&#9;SomeValue is a value you want passed to the script. Put "{ScriptName}" into CMD, where ScriptName is the name of the script. To create a script record, add a record to FOXCODE with "S" in TYPE, the name of the script in ABBREV, "{}" in CMD, and the code to execute in DATA. Using a script record has the benefit that it's more compact than a separate property editor (since the code is contained in FOXCODE) and there aren't any pathing issues.

&#9;Like a builder, a property editor is responsible for obtaining a reference to the object being edited and for writing to the object's property. In fact, you can think of a property editor as a subset of a builder (which is usually an editor for multiple properties of an object), with similar requirements and architecture issues.

Getting there from here

Now that you know how MemberData works, you can use it by adding a _MemberData property to every object and then filling in the appropriate XML in that property. Doesn't that seem like it would reduce your productivity rather than improve it? Fortunately, there are two things we can do about that–tell VFP to automatically create a _MemberData property for every class and automatically generate the XML we need.

&#9;The key to the first task is adding a record to the IntelliSense table that's executed whenever you open a class in the Class Designer or a form in the Form Designer. It turns out that when the Properties window displays the PEMs for an object, VFP looks in the IntelliSense table for a record with TYPE set to "E" and ABBREV set to "_GetMemberData". If it finds such a record, it will execute the code in the DATA memo field. So, we can add a record to FOXCODE that creates a _MemberData property in any class or form that doesn't already have it. Here's some code to do this:

  local lcCode

* Create the code we want inserted into FOXCODE.

text to lcCode noshow
lparameters toFoxcode
local laObjects[1], ;
  loObject
if aselobj(laObjects) = 0 and aselobj(laObjects, 1) = 0
  return ''
endif aselobj(laObjects) = 0 ...
loObject = laObjects[1]
if vartype(loObject) = 'O' and ;
  not pemstatus(loObject, '_memberdata', 5)
  loObject.AddProperty('_memberdata', '')
endif vartype(loObject) <> = 'O' ...
return ''
endtext

* Now create a record in FOXCODE that executes whenever a
* class is opened in the Class Designer.

use (_foxcode) again shared alias FOXCODE
locate for TYPE = 'E' and ABBREV = '_GetMemberData'
if not found()
  insert into FOXCODE (TYPE, ABBREV, DATA) values ('E', ;
    '_GetMemberData', lcCode)
endif not found()
use

&#9;This code checks whether the _GetMemberData record already exists or not; at the time of this writing, Microsoft hadn't decided whether this record will be part of the standard set of records in FOXCODE or not.

&#9;The code inserted into the DATA memo uses ASELOBJ() to get a reference to the class or form being edited, checks for the existence of _MemberData with PEMSTATUS(), and adds the property using AddProperty if it wasn't found.

&#9;To see how it works, run the code (UpdateFoxCode.PRG in the Download file), and then type the following in the Command window:

  create class x of x as custom

&#9;In the Properties window, you'll see that VFP automatically created a _MemberData property for this class.

&#9;Now that we have a _MemberData property for anything we open in the Class or Form Designer, how should we tackle the second task: generating the XML for the metadata?

The _MemberData property editor

This task turns out to be a no-brainer because VFP 9 includes a new tool called the MemberData Editor (see Figure 4). This tool allows you to visually specify the MemberData attributes for PEMs in an object. It then takes care of generating the necessary XML for the _MemberData property.

&#9;There are several ways you can launch the MemberData Editor:

  • The MemberData Editor can be globally registered as the property editor for the _MemberData property by simply running it (DO HOME() + 'MemberDataEditor.APP'). After doing so, you can select the _MemberData property in the Properties window and click on the property editor button to launch the MemberData Editor.
  • The Form and Class menus have a new "MemberData Editor" function that invokes the editor.
  • You can launch the MemberData Editor programmatically with DO HOME() + 'MemberDataEditor.APP'. If an object is selected in the Form Designer or Class Designer, the editor will open.
  • If you simply want to add a PEM to the Favorites page of the Properties window, right-click on the PEM and select the new "Add to Favorites" function. This function silently invokes the MemberData Editor, which updates the XML in _MemberData without displaying any UI. Note that there's no "Remove from Favorites" function; you have to use the MemberData Editor interface to do that.

&#9;To try this out, create or modify a class in the Class Designer. Add some custom properties and methods, and then select "MemberData Editor" from the Class menu. Modify the MemberData attributes for the custom properties and methods so they'll display with the proper capitalization, and turn on Favorites for some of them. Click on the OK button and notice that these members now appear the way you wish in the Properties window.

Summary

MemberData is a great productivity enhancement in VFP 9 because it allows you to specify how members should be displayed in the Properties window and in IntelliSense, making them easier to find (and saving editing if you let IntelliSense insert their names into code). Also, like builders, property editors can make it much easier to put the proper values into a property. I expect we'll see a number of property editors ship with VFP 9, and many others will be created by enterprising VFP developers.

Download 406HENNIG.ZIP

To find out more about FoxTalk and Pinnacle Publishing, visit their Web site at http://www.pinpub.com/

Note: This is not a Microsoft Corporation Web site. Microsoft is not responsible for its content.

This article is reproduced from the June 2004 issue of FoxTalk. Copyright 2004, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. FoxTalk is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.