Mining for Gold in the FFC

Doug Hennig


Sometimes you have to sift through a lot of rocks before you find true nuggets of gold. Not so with the FoxPro Foundation Classes that ship with VFP 6; there's tons of gold in them thar hills.

One of the design goals for VFP 6 was to make it easier for programmers new to VFP to get up and running with the tool. The Application Wizard is one of the results of this goal, and the FoxPro Foundation Classes -- or FFC -- is another. The FFC, located in the FFC subdirectory of the VFP home directory, is a collection of class libraries that provide a wide range of functions. Don't think that just because previous versions of FoxPro have included some, shall we say, less useful (to be polite) example files that these fall into that category. While some of these do appear to be more demoware than really useful, there are still lots of great classes in here. It's well worth the effort to spend some time looking at these classes, picking through the rocks to find the nuggets.

The best way to check out the FFC is using a new VFP 6 tool called the Component Gallery, accessible from the VFP Tools menu. I won't discuss the features of the Component Gallery in this article; it's simple enough to use that you can navigate your way around just by playing with it. The FFC classes are displayed in the Foundation Classes folder of the Visual FoxPro Catalog (when I refer to where classes can be found later in this article, I won't specify this folder or this catalog, just the subfolder under Foundation Classes). Classes are grouped by type (for example, Buttons, Dialogs, and Utilities) and display a description in the status panel when you select them. Even better, right-clicking on a class displays a context menu giving you access to the Help topic and sample files (either to run or to view) for that class. This makes it very easy to look through the FFC and see which classes might interest you.

This article will look at some of the FFC classes and see how we might use them or even subclass them to make them even more useful. Before we get started, though, I'll discuss _BASE.VCX.

Base classes
VFP 6 includes a set of subclasses of VFP base classes in _BASE.VCX. Although these classes aren't located in the Foundation Classes folder in the Component Gallery (they're in the My Base Classes folder), this VCX is located in the FFC subdirectory, and all FFC classes are subclassed from _BASE classes. If we use FFC classes in our applications, _BASE.VCX comes along for the ride. So, a question arises: Although this column has been developing a robust set of base classes, and many of you have your own set, should we consider using _BASE classes for our base classes instead? The reason for even considering this is, why have two VCXs in our projects that provide essentially the same thing?

After looking at the _BASE classes, the conclusion I've come to is no; I'll continue using my own SFCTRLS.VCX classes. _BASE classes don't have the visual changes I've made to my base classes -- for example, the AutoSize property for classes such as _CheckBox and _Label is set at the default .F.; I almost always want it set to .T., so that's what I've done in SFCheckBox, SFLabel, and other classes with this property. Furthermore, _BASE classes also don't have the behavior I want -- for example, their Error methods pass the error on to an ON ERROR handler rather than using the Chain of Responsibility design pattern (up the class hierarchy and then the containership hierarchy) that my classes do, as I discussed in my January 1998 column (see "Error Handling Revisited"). Finally, these classes have a lot of custom properties and methods that support the Application Wizard. Since I don't plan to use the Application Wizard to create applications, these properties and methods would just complicate things. So, we'll just have to live with the fact that _BASE.VCX will be included in our projects, whether we want it or not, if we use FFC classes.

_SysToolbars
It's highly unlikely you'll want the VFP development environment toolbars (such as the Standard and Database toolbars) to be visible when an application is running. Unfortunately, there isn't a single command that hides them all, so most developers create an array of toolbar names, spin through the array and see whether the current toolbar is visible, and, if it is, hide it. Of course, you have to do just the opposite when the application closes down, at least in a development environment.

Because this is a common task, the FFC includes a class called _SysToolbars; this class is located in _APP.VCX and appears as "System Toolbars" in the Application subfolder of the Component Gallery. This class is very simple; it hides and restores the system toolbars either automatically or manually. To use it automatically, either pass .T. to its Init method when you instantiate it programmatically or set its lAutomatic property to .T. in the Property Sheet when you drop it on a form. In this case, any visible system toolbars are hidden when the object is instantiated and redisplayed when it's destroyed. To use it manually, call its HideSystemToolbars and ShowSystemToolbars methods. You'll likely want to instantiate it as soon as possible at application startup using code similar to this:

 oSysToolbars = newobject('_SysToolbars', '_app.vcx', ; 
   '', .T.) 


As long as oSysToolbars stays in scope, the toolbars stay hidden. You can either manually release this object or let it go out of scope when the application terminates.

_Resizable
This class, which is defined in _CONTROLS.VCX and appears as "Resize Object" in the User Controls subfolder of the Component Gallery, moves and resizes all the controls in a form when the form is resized. It takes care of all the mundane details of drilling down through containers (such as PageFrames), adjusting the Top, Left, Height, and Width properties of controls. I've done this kind of thing manually before in the Resize method of the form, writing reams of code to handle all the applicable controls. Believe me, anything that can automate this tedious chore is very welcome.

To see how _Resizable works, run the ResizeDemo form (by the way, this form also contains an _SysToolbars object with lAutomatic set to .T. so you can see how that class works). Leave "Resize" unchecked and resize the form. See how dumb it looks as more background appears when you make the form larger or controls get hidden as you make the form smaller? Not the sign of a professional application. Now check "Resize" (but leave "SFResizable" unchecked for now); when you resize the form, the edit box is resized, and the other controls are moved automatically.

However, there are a couple of problems with this class. First, it resizes or moves all the controls. Typically, when you resize a form, you want to resize certain controls (such as edit boxes) and move the ones below and to the right of them to account for the new size; the rest you want to leave alone. Another problem is that it moves everything proportional to the original size of the form; the effect is that a resized form looks like a blown up or shrunk version of the original, with controls further apart or closer together. The result is sort of like what happens to writing on a balloon as it's blown up. In my experience, what you really want is a form that looks just like it was but with some of the controls larger or smaller and the rest just moved relative to the resized controls. After all, the whole reason for the user to resize a form is to be able to see more of those controls they'd normally have to scroll (grids, list boxes, edit boxes, TreeView and ListView controls, and so forth), not to get a bigger form with more background.

The good news is that we don't need to throw _Resizable out and create a new class with the behavior we want. _Resizable has all of the logic we need; it just doesn't do things quite right. So, let's subclass it and make the subclass act the way we expect.

The subclass I created is called SFResizable, and it's contained in SFFFC.VCX in the accompanying download file. I changed both the Height and Width properties to 17 so the control doesn't take up as much space when it's dropped on a form. Since we need to treat some controls differently than others (some will be moved, while others might be resized), we need a way to indicate how each control should be treated. I originally considered adding new properties to my base classes (for example, lResize, which, if .T., would indicate that this control should be resized) but rejected that because then SFResizable would only work with a certain set of base classes, and that would make it far less reusable. Instead, I decided to make SFResizable self-contained, so I created the following new properties:


I also overrode the AddToArray and SetSize methods. AddToArray adds size and position information about a control to an array property of the class; it's called from the LoopThroughControls method, which processes all of the controls in the form. The problem with the original AddToArray is that it stores the size and position information of the control as values proportional to the size and position of the form rather than the actual values for the control. So, I simply used the Visual Basic method of subclassing (I copied the code from _Resizable.AddToArray and pasted it into SFResizable's method) and then modified the code to store the original values. SetSize is also called from LoopThroughControls; it adjusts the size and position of a control by the difference between the original (stored in the InitialFormHeight and InitialFormWidth properties) and current sizes of the form. Since I don't want every control resized and moved, I changed the code to use the following logic:


To use SFResizable, drop it on a form and call its AdjustControls method in the Resize method of the form. Enter the names of those controls that should be resized as the form is resized into the cResizeList property of the SFResizable object; grids, edit boxes, and other controls that can scroll are obvious candidates. Enter the names of those controls that should be moved up-down and left-right as the form is resized into the cRepositionList property. For example, controls below and to the right of an edit box might qualify for this adjustment. For those that should only be moved up and down, enter their names into the cRepositionTop property. Examples include controls below a grid, because these controls need to move up or down as the grid's Height is changed. Finally, enter the names of controls that should be moved left and right, such as those to the right of an edit box, as the form is resized into the cRepositionLeft property.

Run the ResizeDemo form again, but this time check both "Resize" and "SFResizable". When you resize the form, the text box beside "Label 1" doesn't move; the edit box and shape surrounding it don't move but are resized; the "Resize" and "SFResizable" check boxes and the check boxes beside the edit box move left and right but not up and down; the text box beside "Label 2" moves up and down but not left and right; and the "Reset" button moves both up-down and left-right. In other words, the form behaves as we'd expect when it's resized.

A perfect addition to SFResizable would be a builder to make it easy to specify which controls should be moved or resized, with perhaps a list of the controls in the form and check boxes indicating how the selected control should be treated.

Registry
As you know, the Registry is the "in" place to store configuration, preference, and other application settings; INI files are officially passe (although I know lots of developers who, like me, would rather lead a user through editing an INI file over the phone than dare have them use a tool like REGEDIT). Settings should normally be stored in keys with a path similar to HKEY_CURRENT_USER\Software\My Company Name\My Application\Version 1.1\Options.

The FFC Registry class (defined in REGISTRY.VCX and appearing as "Registry Access" in the Utilities subfolder of the Component Gallery) is a wrapper class for the Windows API calls that deal with the Registry. Rather than having to know that you must open a key using the RegOpenKey function before you can use RegQueryValueEx to read the key's value, all you have to know with the Registry class is that you call its GetRegKey method. Here's an example that gets the name of the VFP resource file:

 #include REGISTRY.H	&& located in HOME() + 'FFC' 
 loRegistry = newobject('Registry', 'Registry.vcx') 
 lcResource = '' 
 loRegistry.GetRegKey('ResourceTo', @lcResource, ; 
   'Software\Microsoft\VisualFoxPro\6.0\Options', ; 
   HKEY_CURRENT_USER) 


(Yeah, I know it's easier to use SET(`RESOURCE', 1), but this is just an example.)

REGISTRY.VCX also contains subclasses of Registry that make it easier to read and write VFP settings (FoxReg), ODBC settings (ODBCReg), applications by file extension (FileReg), and even (horrors!) INI files (OldINIReg). Registry-specific constants are defined in REGISTRY.H so you can specify HKEY_CURRENT_USER (as I did in the preceding code) rather than its value (-2147483647).

Here are the useful methods in REGISTRY.VCX. Unless otherwise specified, all return a code indicating success (0, or the constant ERROR_SUCCESS) or the WinAPI error code; see REGISTRY.H for error codes.


Registry can be instantiated at application startup (perhaps by an Application object) to get the settings the application needs: location of the data files on a LAN, names of the most recently used forms (so they can appear at the bottom of the File menu like Microsoft applications do), and so forth. It can also be dropped on a form to save and restore form-specific settings such as the Left, Top, Height, and Width values (so it has the same size and position as when this user closed it), grid column widths and positions (so users can rearrange grids and have them appear that way the next time), and so on.

Being a picky guy, I have two problems with the Registry class. First, because the return value of GetRegKey is a success or error code, you have to pass the variable you want the value placed into by reference. I'd prefer it to return the value of the key; after all, if an error occurs, the WinAPI error code is probably as useful to me as the "details" section of a GPF dialog, and even if I do want it, it could easily be stored in a property of Registry I can query. Second, it's likely that both the key path ("Software\Microsoft\VisualFoxPro\6.0\Options" in the previous example) and the user key (usually HKEY_CURRENT_USER) are going to be the same for every method call of a specific instance of a Registry object. Being the lazy sort, I'd rather put these values into properties and not pass them every time I call a method.

As with _Resizable, I've subclassed Registry into SFRegistry (also in SFFFC.VCX) to have the behavior I want. Although I planned to, it turned out that I didn't have to add properties for the default key path (cAppKeyPath) and user key (nUserKey) -- they already existed, even though they're not used by Registry (they're used by the subclasses in REGISTRY.VCX). Since Registry.Init sets nUserKey to HKEY_CURRENT_USER, there normally isn't even a need to change this property. I changed the GetRegKey, SetRegKey, DeleteKey, DeleteKeyValue, EnumOptions, and IsKey methods to accept different parameters than the matching Registry class methods (I rearranged the parameters so optional ones are at the end) and return a more appropriate value (the key value in the case of GetRegKey, the number of options in the array in the case of EnumOptions, and .T. if the method succeeded in the case of SetRegKey, DeleteKey, and DeleteKeyValue). These methods also store the WinAPI result code into a new nResult property, which can be used to determine what went wrong if a method fails. Here's an example; this is the code from EnumOptions:

 lparameters taRegOptions, ; 
   tlEnumKeys, ; 
   tcKeyPath, ; 
   tnUserKey 
 local lcKeyPath, ; 
   lnUserKey, ; 
   lnReturn 
  
 * If the key path and user key weren't passed, use the 
 * defaults. 
  
 lcKeyPath = This.GetKeyPath(tcKeyPath) 
 lnUserKey = This.GetUserKey(tnUserKey) 
  
 * Use the parent class method to enumerate the key, 
 * store the result code, and return the number of 
 * options it found. 
  
 lnSuccess = dodefault(@taRegOptions, lcKeyPath, ; 
   lnUserKey, tlEnumKeys) 
 This.nResult = lnSuccess 
 lnReturn = iif(lnSuccess = ERROR_SUCCESS, ; 
   alen(taRegOptions, 1), 0) 
 return lnReturn 


GetKeyPath and GetUserKey are new methods called by all the overridden methods to either use the key path and user key values passed, or the defaults (stored in the cAppPathKey and nUserKey properties) if they weren't passed.

Here's an example of the use of SFRegistry; this code would be used in the Init method of a form to restore the size and position it had the last time the user had it open. This code assumes that an SFRegistry object named oRegistry was dropped on the form and its cAppPathKey property was set in the Property Sheet to the key path for this application.

 lcKey = This.oRegistry.cAppPathKey + '\' + This.Name 
 lcTop = This.oRegistry.GetRegKey('Top', lcKey) 
 This.Top = iif(isnull(lcTop), This.Top, val(lcTop)) 
 * similar code for Left, Width, and Height 


(Since the Registry class only supports reading and writing strings, VAL() must be used on the return value. Also, if this is the first time this form is run, a key might not exist for it in the Registry, in which case .NULL. is returned, so this code handles that case.) A method of the form (such as Release) would use This.oRegistry.SetRegKey to save the current form size and position in the Registry.

For another example, run REGISTRYDEMO.PRG. It creates some new keys, displays their values, and shows how EnumOptions works. (It also deletes the keys it creates so it doesn't pollute your Registry permanently.)

_ObjectState
If you've been doing your homework on design patterns, you're probably aware of a pattern known as Memento. A Memento is intended to save the state of something, presumably so it can be restored after it's been changed. The FFC includes a class called _ObjectState that's sort of like a Memento: It saves the value of one or more properties of another object and can later restore them to their former values.

_ObjectState is defined in _APP.VCX and appears as "Object State" in the Application subfolder in the Component Gallery. It has an oObject property that contains an object reference to the object whose state it maintains; this property can be set by passing the object reference to the Init method of the _ObjectState object or by setting it manually. It has a Set method that sets the value of the specified property of the managed object to the specified value, optionally first saving the original value in an array property of itself. It also has a Restore method that restores the value of one or all saved properties. The Restore method is also called from the Destroy method, so when the _ObjectState object goes out of scope, everything is restored automatically. Since the array property has a single row for each saved property, you can't use this class to provide multiple levels of undo; however, you could subclass _ObjectState and add this behavior if desired.

Where might we use such a class? One situation involves things a user can change but might want to change back. For example, you might allow a user to rearrange and resize the columns in a grid, but provide a "Reset to Default" function that puts them back. Another place would be when you temporarily change the properties of an object (perhaps so it behaves differently), do something with the object, and then change them back again. Rather than having a set of local variables that save the property values and then having to manually restore them before the routine ends, you could do something like this:

 local loObjectState 
 loObjectState = newobject('_ObjectState', '_app.vcx', ; 
   '', This) 
 loObjectState.Set('<first property', <new value>) 
 loObjectState.Set('<second property', <new value>) 
 * do something here 


When this code ends, loObjectState goes out of scope, so the saved property values are automatically restored.

As usual, I have one slight quibble with the class implementation: Saving the current value of the property is optional. There really isn't a need to use _ObjectState if you're not going to save the value (after all, you can just store the new value to the property yourself in that case), so I think the default behavior should be to save and have it optionally not save. So, I created a subclass of _ObjectState called SFObjectState (in SFFFC.VCX) that simply overrides the Set method, as follows:

 lparameters tcProperty, ; 
   tuValue, ; 
   tlNoSave 
 return dodefault(tcProperty, tuValue, not tlNoSave) 


In other words, rather than passing .T. to save, you have to pass .T. to not save.

To see an example of this class in action, run the ResizeDemo form, check both "Resize" and "SFResizable", then resize the form. Now click on the Reset button and watch everything snap back to the original size and position. This was done by saving the Height and Width properties of the form in its Init method:

 with This.oObjectState 
   .oObject = This 
   .Set('Height', This.Height) 
   .Set('Width',  This.Width) 
 endwith 


and restoring them in the Click method of the button:

 Thisform.oObjectState.Restore() 


Of course, we didn't have to save the original size and position of every object on the form because changing the form Height and Width causes the Resize method to fire, which moves everything for us. Also, although I didn't specify individual properties to Restore (so it restored all saved values), I could have restored them individually if I wanted that control.

Conclusion

The FFC contains a lot of useful classes. Some of them are great as is, while others can be subclassed to add minor improvements. Either way, I suggest you spend some time looking at these classes and thinking about how you might use them. I'm sure you'll find some gems in the pile.

Download sample code for this article here.

Doug Hennig is a partner with Stonefield Systems Group Inc. in Regina, Saskatchewan, Canada. He is the author of Stonefield's add-on tools for FoxPro developers, including Stonefield Database Toolkit and Stonefield Query. He is also the author of The Visual FoxPro Data Dictionary in Pinnacle Publishing's The Pros Talk Visual FoxPro series. Doug has spoken at the 1997 and 1998 Microsoft FoxPro Developers Conferences (DevCon), as well as user groups and regional conferences all over North America. He is a Microsoft Most Valuable Professional (MVP). 75156.2326@compuserve.com, dhennig@stonefield.com.