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.

Mining for Gold in XSource

Doug Hennig

Visual FoxPro comes with source code for most of the "Xbase" tools that ship with the product, including the Class Browser, Code References, Toolbox, and Task Pane. Looking at source code written by top VFP gurus often gives insight into new, powerful coding techniques. In this article, the first in a series, Doug Hennig looks at various files in XSource to show you cool ideas and code you can use in your applications today to save custom settings in a resource file and create object-oriented shortcut menus.

Starting with the December 1998 issue, I wrote a three-part series in FoxTalk called "Mining for Gold in the FFC," which discussed looking for useful bits of source code in the FFC (FoxPro Foundation Classes) subdirectory of the VFP home directory. However, VFP comes with a lot more source code than that!

	Many of the tools that come with VFP were actually written in VFP rather than C++ (the language used to create VFP). These are often referred to as "Xbase" tools to distinguish them from internal components. Some of the Xbase tools are available from the Tools menus, such as the Class Browser, IntelliSense Manager, Toolbox, Task Pane Manager, and Report Wizard. Others are available in context from the appropriate places, such as the Referential Integrity Builder, CursorAdapter Builder, and in VFP 9, ReportBuilder.APP, which provides the new dialogs for the Report Designer.

	This article is the first in a series that explores XSource, looking at interesting techniques and source code we can use in our own applications. I'm not going to explore everything in XSource in this series; that would take a whole book. Instead, I'll discuss certain things I think are useful, including:

  • How the Toolbox's Outlook-like bar works.
  • How to use the FOXUSER resource file as an alternative to INI files or the Windows Registry.
  • Displaying AVI files in VFP forms.
  • Creating object-oriented shortcut menus.

Finding XSource

For several versions now, VFP has shipped with the source code for nearly all of the Xbase tools. (Beautify.APP, which provides the functionality of the Beautify menu item, is the one exception.) Because there's a lot of code, it comes as a ZIP file: XSource.ZIP in the Tools\XSource subdirectory of the VFP home directory. Unzipping this file results in a subdirectory of Tools\XSource called VFPSource, which contains the source code for the Xbase tools. Table 1 lists the purpose of each directory in XSource and the location of each APP file generated from the source.

****

Table 1. The purpose and location of applications created with XSource source code.

XSource directory

Purpose

Location

AddLabel

AddLabel application

Tools\AddLabel\AddLabel.APP

Browser

Class Browser and Component Gallery

Browser.APP and Gallery.APP

Builders

Main builder program (delegates to appropriate builder APP) and main wizard program (delegates to appropriate wizard APP)

Builder.APP and Wizard.APP

Builders\Builders

Common files used in builders

-

Builders\CmdBldr

CommandGroup Builder

Wizards\CmdBldr.APP

Builders\EditBldr

EditBox Builder

Wizards\EditBldr.APP

Builders\FormBldr

Form Builder

Wizards\FormBldr.APP

Builders\GridBldr

Grid Builder

Wizards\GridBldr.APP

Builders\ListBldr

ListBox Builder

Wizards\ListBldr.APP

Builders\RIBuildr

Referential Integrity Builder

Wizards\RIBuildr.APP

Builders\StylBldr

Style Builder

Wizards\StylBldr.APP

Convert

Converter (converts files to newer versions)

Convert.APP

Coverage

Coverage Profiler

Coverage.APP

DataExplorer (VFP 9)

Data Explorer (available in Task Pane Manager)

DataExplorer.APP

EnvMgr

Environment Manager (available in Task Pane Manager)

EnvMgr.APP

FoxCode

IntelliSense Manager

FoxCode.APP

FoxRef

Code References

FoxRef.APP

OBrowser

Object Browser

ObjectBrowser.APP

ReportBuilder (VFP 9)

Dialogs and other behavior for Report Designer

ReportBuilder.APP

ReportOutput (VFP 9)

Report listeners (for example, XML and HTML)

ReportOutput.APP

ReportPreview (VFP 9)

New report preview dialog

ReportPreview.APP

TaskList

Task List

TaskList.APP

TaskPane

Task Pane Manager

TaskPane.APP

ToolBox

ToolBox

ToolBox.APP

VFPClean

Utility to restore VFP user files and Registry settings

VFPClean.APP

WebService

Web Services Publisher

Wizards\WebService.APP

Wizards\Automate

Common files used in Graph, Mail Merge, and PivotTable Wizards

-

Wizards\DEBuilder

CursorAdapter and DataEnvironment Builders

Wizards\DEBuilder.APP

Wizards\WZApp

Application Wizard

Wizards\WZApp.APP

Wizards\WZCommon

Common files used in wizards and builders

-

Wizards\WZForm

Form Wizard

Wizards\WZForm.APP

Wizards\WZFoxDoc

Documenting Wizard

Wizards\WZFoxDoc.APP

Wizards\WZGraph

Graph Wizard

Wizards\WZGraph.APP

Wizards\WZImport

Import Wizard

Wizards\WZImport.APP

Wizards\WZIntNet

WWW Search Page Wizard

No longer included as tool

Wizards\WZMail

Mail Merge Wizard

Wizards\WZMail.APP

Wizards\WZPivot

PivotTable Wizard

Wizards\WZPivot.APP

Wizards\WZQuery

Query Wizard

Wizards\WZQuery.APP

Wizards\WZReport

Report and Label Wizards

Wizards\WZReport.APP

Wizards\WZTable

Table and Database Wizards

Wizards\WZTable.APP and Wizards\WZDBC.APP

Wizards\WZUpsize

Upsizing Wizard

Wizards\WZUpsize.APP

Wizards\WZWeb

Web Publishing Wizard

Wizards\WZWeb.APP

	Note: Some other subdirectories of Tools–Analyzer, CPZero, Filer, GenDBC, HexEdit, MSAA, and Test–contain source code for the tools in those directories. These tools aren't accessible from menus in VFP; they're standalone tools available by running the appropriate file in the appropriate directory. I won't discuss these tools in this series.

Benefits of the XSource code

What's so good about having the source for these tools? At least two things:

  • If one of these tools doesn't do exactly what you want, you can change it and build a new APP file.
  • I always like to leverage good work done by others, and the VFP Xbase tools were written by some of the best and brightest stars in the VFP universe. (Many were written by non-Microsoft employees, such as Markus Egger, who wrote the Object Browser.) Very interesting techniques were used in some of these tools, and I've learned a lot by spelunking through the source code. More importantly, some of the classes, images, and PRGs are useful in other applications.

	If you decide to use some of the XSource source code in your own applications, be aware that some classes are subclasses of classes in different VCX files, and some of the VCXs have a lot of classes in them. Even if you need only one of them, all of the related VCXs, PRGs, and image files will come along for the ride when you build your project.

	You can either live with it (the only negative effect is the increased EXE size, and disk space is cheap these days) or pull the classes you want out of the VCX into your own smaller VCX. Tools such as the Class Browser and White Light Computing's HackCX (a tool formerly from Geeks and Gurus that I wrote about in my December 2003 article, "Tools for and by Geeks and Gurus," and highly recommend; see www.whitelightcomputing.com) can help with this, but it still might be a bit of a chore to get it right.

	Note: The VFP Help file is a little hazy on the topic of reusing code from XSource, but Microsoft recently indicated that it's possible to use code from the Xbase source as long as the code is slightly modified, and that we can even use entire class libraries if they're included in the project and in the application build. This is similar to the rules for using source code and libraries from the Samples directory, which is discussed in the Help topic "Distributable and Restricted Visual FoxPro Features and Files."

A rich source of images

When I'm looking for images for buttons or icons for forms, one of the first places I look is XSource, especially the directories for the newer Xbase tools (those added in VFP 8 or later). I often find exactly what I'm looking for, or something close that I just need to tweak a bit in an editor like Paint. Figure 1 shows some of the useful images I've found in XSource directories.

Saving settings in the resource file

A professionally written application saves certain settings so that the next time the user runs it, those same settings are used again. For example, it's nice when an application "remembers" the size and position of forms, the location from which you last imported a file, what type of export file you last created, and so forth. There are several places an application can store such settings, including INI files, the Windows Registry, an XML file, or a table.

	The VFP interactive development environment (IDE) does a great job of remembering things like the size, position, and selected text in code files, and the last opened code window in a form or class. It saves these settings in your resource file, typically FoxUser.DBF in your HOME(7) directory. Wouldn't it be nice to use the resource file for your own settings?

	That's exactly what the FoxResource class in FoxResource.PRG does. This class, found in several XSource directories including Toolbox, provides methods to read from and write to a resource file. It doesn't have to be the current resource file (that is, the one returned by SYS(2005)); you can specify the name of the file to use in the ResourceFile property.

	A resource file consists of the following fields:

  • TYPE–This contains the type of record. Most records have "PREFW" in this field, but you can use any value you wish by setting the ResourceType property of FoxResource (it defaults to "PREFW").
  • ID–The ID for the record. This can be any value you wish, but it should be something unique.
  • NAME–The name for the record. This can be left blank if you wish.
  • READONLY–.T. if the record shouldn't be changed.
  • CKVAL–The checksum for the DATA field.
  • DATA–The settings. FoxResource saves your settings as an array using SAVE TO MEMO DATA.
  • UPDATED–The date the record was last updated.

	The FoxResource class uses the Load method to load a set of settings from a record in the resource file. Pass the method the ID and the name (matching the ID and NAME fields in the file) and it will load the settings (if such a record exists) into its internal collection of name-value items. Once you've loaded a set, you can retrieve values by passing to the Get method the name of the setting you want to retrieve.

	You don't have to worry about data types; since the settings are saved as an array in the resource file, the values come back with the same data type used when they were saved. This is a nice change from INI files, where everything is a string. If the setting you request doesn't exist, Get returns .NULL., so you should probably use the NVL() function on the return value.

	Here's an example that leaves Top alone if the setting doesn't exist:

  This.Top = nvl(oFoxResource.Get('Top'), This.Top)

	You can check whether a setting exists by using the OptionExists method. Pass it the name of the setting and it returns .T. if the setting exists.

	To store a setting, call the Set method, passing it the name and value of the setting. Once you've stored a set of settings, you can save the entire set to the resource file with the Save method. This method expects the same ID and name parameters as the Load method. Save also updates the UPDATED and CKVAL columns and supports read-only records in the resource file by refusing to update the record if the READONLY field is .T.

	There are a few other, less useful, methods. Clear clears the collection, GetData returns the contents of the DATA memo field for the specified record, SaveTo saves to a specified memo field rather than the DATA memo, and RestoreFrom restores from the specified memo field.

	TestMenu.SCX, included in this month's Download file, demonstrates the use of FoxResource. The Init method instantiates FoxResource, loads the setting for the TESTMENU name, calls SetFormPosition to restore the form size and position, and then uses FoxResource's Get method to restore the record pointer and filter.

  local lnRecno, ;
  lcFilter
with This
  .oResourceOptions = newobject('FoxResource', ;
    home() + 'Tools\XSource\VFPSource\Toolbox\' + ;
    'FoxResource.prg')
  .oResourceOptions.Load('TESTMENU')
  .SetFormPosition()
  lnRecno = nvl(.oResourceOptions.Get('RecNo'), 0)
  if between(lnRecno, 1, reccount())
    go lnRecno
  endif between(lnRecno, 1, reccount())
  lcFilter = nvl(.oResourceOptions.Get('Filter'), '')
  .SetFilter(lcFilter)
endwith

	Speaking of shamelessly lifting–oops, I mean leveraging–code, the code for the SetFormPosition method comes straight from the same named method in the cFoxForm class in ToolboxCtrls.VCX in the Toolbox source directory. This code does more than simply restoring the Top, Left, Width, and Height properties for the form; it also ensures that these values don't cause the form to be off-screen, such as if the form was originally placed far to the right on a high-resolution system and now is being opened on a lower-resolution display.

  LOCAL nTop, nLeft, nWidth, nHeight

IF !ISNULL(THIS.oResourceOptions)
  m.nHeight = THIS.oResourceOptions.Get("HEIGHT")
  IF VARTYPE(m.nHeight) == 'N' AND m.nHeight >= 0
    THIS.Height = m.nHeight
  ENDIF
  m.nWidth = THIS.oResourceOptions.Get("WIDTH")
  IF VARTYPE(m.nWidth) == 'N' AND m.nWidth >= 0
    THIS.Width = m.nWidth
  ENDIF

  m.nTop = THIS.oResourceOptions.Get("TOP")
  IF VARTYPE(m.nTop) == 'N'
    * make sure we're visible
    IF !THIS.Desktop
      IF m.nTop > _SCREEN.Height
        m.nTop = MAX(_SCREEN.Height - THIS.Height, 0)
      ELSE
        IF m.nTop + THIS.Height < 0
          m.nTop = 0
        ENDIF
      ENDIF
    ENDIF

    THIS.Top = m.nTop
  ENDIF

  m.nLeft = THIS.oResourceOptions.Get("LEFT")
  IF VARTYPE(m.nLeft) == 'N'
    * make sure we're visible
    IF !THIS.Desktop
      IF m.nLeft > _SCREEN.Width
        m.nLeft = MAX(_SCREEN.Width - THIS.Width, 0)
      ELSE
        IF m.nLeft + THIS.Width < 0
          m.nLeft = 0
        ENDIF
      ENDIF
    ENDIF

    THIS.Left = m.nLeft
  ENDIF
ENDIF

&#9;The Destroy method saves the settings we want preserved and then writes them all to the TESTMENU set in the resource file.

  with This
  .oResourceOptions.Set('Left',   .Left)
  .oResourceOptions.Set('Top',    .Top)
  .oResourceOptions.Set('Height', .Height)
  .oResourceOptions.Set('Width',  .Width)
  .oResourceOptions.Set('Filter', .cFilterName)
  .oResourceOptions.Set('RecNo',  recno())
  .oResourceOptions.Save('TESTMENU')
endwith

&#9;To try this out, run TestMenu.SCX and move the form anywhere on the screen. Right-click on the form and choose a filter setting from the Set Filter submenu, and then right-click and choose Next several times. (I'll discuss how the shortcut menu was created next.) Close the form and then re-run it; it should open in the same position with the same filter and the same record displayed as when you closed it.

Object-oriented shortcut menus

VFP's menu system is one of the few product features that remain procedural rather than object-based. While you can create your own menus using the various menu-related commands (such as DEFINE POPUP and DEFINE BAR), almost no one does because VFP includes the Menu Designer, which allows us to create menus visually. However, the biggest downside to using menus generated by the Menu Designer is that they can't be altered at runtime. That means you can't easily localize them or show or hide specific bars under certain conditions.

&#9;ContextMenu to the rescue! This class, defined in FoxMenu.PRG in the Toolbox source directory, allows you to create a shortcut menu on the fly without having to write ugly DEFINE POPUP and DEFINE BAR commands. Simply instantiate the class, set up the bars as desired (which could use conditional code to localize the menu or decide which bars to include), and tell it to display the menu. You could even data-drive the menu if you wish.

&#9;To add bars to the menu, call the AddMenu method. This method accepts up to six parameters (the last four are optional): the prompt for the bar, an expression to execute when the bar is selected, the name of an image file to use for the bar's picture, .T. if the bar's checkmark is turned on, .T. if the bar is enabled, and .T. if the bar appears in bold.

&#9;Because VFP's menu system lives outside its object system, references like This or Thisform won't work in the expression to execute, so put a reference to the form or object into a private variable and use that variable in the expression. The sample code in the Download file illustrates this.

&#9;AddMenu returns a reference to an object containing properties about the menu bar: Caption, Picture, Checked, ActionCode, IsEnabled, and Bold. More interestingly, however, it also contains a SubMenu property, which contains a reference to another ContextMenu object. So, to create a submenu for a bar, simply call the AddMenu method of the bar's SubMenu object.

&#9;To display the menu, call the Show method. In the VFP 8 version, you can optionally pass two parameters: the row and the column at which the menu should be displayed. The VFP 9 version adds a third optional parameter: the name of the form in which the menu should appear (ContextMenu uses the IN WINDOW clause of the DEFINE POPUP command in this case). If you call Show with no parameters, the menu won't quite be placed in the right spot. Instead, pass either MROW('') and MCOL('') for the row and column and omit the form name, or omit the row and column and pass This.Name for the form name.

&#9;A bug in the BuildMenu method of the VFP 9 Public Beta version of this class prevents this from working quite right. There's a missing semicolon following "m.nCol" in the following code. Be sure to add that in your copy of FoxMenu.PRG. (Note: This may be fixed in the release version of VFP 9.)

  DEFINE POPUP (m.cMenuName) SHORTCUT ;
  RELATIVE FROM m.nRow, m.nCol 
  IN WINDOW (m.cFormName)

&#9;ContextMenu has a couple of properties. MenuBarCount contains the number of bars in the menu. ShowInScreen is supposed to indicate that the menu is displayed in _SCREEN, but all code referencing that property is commented out, so you can ignore it.

&#9;Here's an example, taken from the ShowMenu method of TestMenu.SCX (see Figure 2). It instantiates the ContextMenu class and calls its AddMenu method to create the various bars to display. The code creating the navigation bars (First, Next, Previous, and Last) passes the picture and enabled parameters so the bars have images and are enabled only when appropriate. The Set Filter bar has a submenu of different types of filters that can be set, and the bar matching the current filter has its checkmark turned on.

  local loMenu, ;
  loMenuItem
private poForm

* Create the menu object.

loMenu = newobject('ContextMenu', ;
  home() + ;
  'Tools\XSource\VFPSource\Toolbox\FoxMenu.prg')

* Create a private reference to this form so the menu
* actions will work.

poForm = This

* Define the menu bars.

loMenu.AddMenu('First',    'poForm.FirstRecord()', ;
  'frsrec_s.bmp', .F., not This.lFirstRecord)
loMenu.AddMenu('Next',     'poForm.NextRecord()', ;
  'nxtrec_s.bmp', .F., not This.lLastRecord)
loMenu.AddMenu('Previous', 'poForm.PreviousRecord()', ;
  'prvrec_s.bmp', .F., not This.lFirstRecord)
loMenu.AddMenu('Last',     'poForm.LastRecord()', ;
  'lstrec_s.bmp', .F., not This.lLastRecord)
loMenu.AddMenu('\-')
loMenuItem = loMenu.AddMenu('Set Filter')
loMenuItem.SubMenu.AddMenu('North American Customers', ;
  'poForm.SetFilter("NA")', '', This.cFilterName = 'NA')
loMenuItem.SubMenu.AddMenu('European Customers', ;
  'poForm.SetFilter("EU")', '', This.cFilterName = 'EU')
loMenuItem.SubMenu.AddMenu('South American Customers', ;
  'poForm.SetFilter("SA")', '', This.cFilterName = 'SA')
loMenuItem.SubMenu.AddMenu('\-')
loMenuItem.SubMenu.AddMenu('Clear Filter', ;
  'poForm.SetFilter("")', '', empty(This.cFilterName))
loMenu.AddMenu('\-')
loMenu.AddMenu('Close', 'poForm.Release()')

* Display the menu.

loMenu.Show(, , This.Name)

**Summary
**XSource isn't just the source code for the Xbase tools that come with VFP; it's also a rich source of ideas, techniques, and reusable code. Next month, I'll look at techniques and code for displaying video files in a VFP form, showing a progress meter during a lengthy process, and adding an Outlook bar to your applications.

Download 410HENNIG.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 October 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.