04.08.2013 Views

Tame Your ActiveX Controls Doug Hennig - dFPUG-Portal

Tame Your ActiveX Controls Doug Hennig - dFPUG-Portal

Tame Your ActiveX Controls Doug Hennig - dFPUG-Portal

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

FoxTalk<br />

Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers<br />

<strong>Tame</strong> <strong>Your</strong><br />

<strong>ActiveX</strong> <strong>Controls</strong><br />

<strong>Doug</strong> <strong>Hennig</strong><br />

6.0<br />

<strong>ActiveX</strong> controls are forward, but not backward, compatible. This means that if<br />

you install a newer version of an OCX on your system, any applications you ship<br />

to people who have an older version installed will break. This article presents a<br />

reusable tool that solves this problem in a simple, easy-to-use manner.<br />

HOW was the period between Christmas and New Year’s for you? I hope<br />

it was relaxing. For me, it was awful. The reason: I posted an update<br />

to Stonefield Database Toolkit (SDT) on our Web site just before<br />

Christmas. Anyone who downloaded and installed it immediately got an<br />

“OLE class not registered” error when trying to use SDT. I fielded a ton of<br />

support calls and e-mails the week after Christmas and had to come up with<br />

a fast solution to solve the problem for those folks and prevent even more<br />

calls later.<br />

What caused this problem? The main SDT form uses a TreeView control,<br />

and I had installed some software on my system that installed a newer version<br />

of COMCTL32.OCX, the OCX file containing the TreeView control. Even<br />

though I didn’t change the TreeView on the form, simply opening the form<br />

and saving it caused information about the newer control to be written to the<br />

form. Of course, very few others had this newer control on their machines, so<br />

even though the form wanted a TreeView control and they had a TreeView<br />

control, it wasn’t the exact control the form was looking for. Hence, the “OLE<br />

class not registered” error.<br />

Is it just me, or do you think this is incredibly lame too? I don’t care about<br />

different versions of the TreeView. I’d understand if I used new properties or<br />

methods of this control that didn’t exist in older versions, but as I mentioned, I<br />

didn’t even touch it in my copy of the form.<br />

I guess I only have myself to blame for being in crisis mode after<br />

Christmas. After all, I’d encountered this behavior before when I upgraded<br />

from VFP 5.0 to 5.0a; it came with an updated COMCTL32.OCX, and everyone<br />

Continues on page 3<br />

June 1998<br />

Volume 10, Number 6<br />

1 <strong>Tame</strong> <strong>Your</strong> <strong>ActiveX</strong> <strong>Controls</strong><br />

<strong>Doug</strong> <strong>Hennig</strong><br />

2 Editorial: Enter Sandman<br />

Whil Hentzen<br />

6 Best Practices:<br />

Development Checklist:<br />

Requirements Analysis<br />

Jefferey A. Donnici<br />

11 What’s Really Inside:<br />

Traversing Transactions<br />

Jim Booth<br />

12 Transactions: Tahoe and<br />

Microsoft Transaction Server<br />

Gary DeWitt<br />

15 Use the Microsoft Scripting<br />

Control to Script with VFP<br />

Rod Paddock<br />

18 The Kit Box:<br />

Synchronized Swimming<br />

Paul Maskens and Andy Kramek<br />

24 June Subscriber Downloads<br />

EA Cool Tool: The Best Tool<br />

is Knowledge<br />

Whil Hentzen<br />

Extended Article available at<br />

www.pinpub.com/foxtalk<br />

EA <strong>ActiveX</strong> and Automation<br />

Review: Where Should <strong>Your</strong><br />

Automation Code Reside?<br />

John V. Petersen<br />

Extended Article available at<br />

www.pinpub.com/foxtalk


Enter Sandman<br />

Whil Hentzen<br />

WANT to get depressed really quickly? The next<br />

time you’re around a group of your peers, ask<br />

everyone to name the supergroups of the ’90s.<br />

By “supergroup” I mean the true superstar bands—the<br />

Beatles, the Stones, Zeppelin, The Who, Cream, CSN&Y—<br />

you get my drift.<br />

Yeah, I know. It’s currently chic to deride Mick, Keith,<br />

et al. It was also chic to rip on Cream. But you can still<br />

hear Badge being played at 110 decibels at the McKinley<br />

Marina on Milwaukee’s Lakefront any summer<br />

weekend—until the police show up. I don’t hear Boy<br />

George or Vanilla Ice (“I’m going to be the next Elvis<br />

Presley”) much, and Green Day and Jane’s Addiction<br />

aren’t going to warrant a footnote in Rolling Stone’s<br />

Seventy-Five Years of Rock ’n Roll retrospective.<br />

But my point is that there have been no new<br />

supergroups since about the mid-’80s. U2 is about the last<br />

one I can think of. Yeah, Hanson and the Spice Girls can<br />

fill a stadium in three hours now. But so could The Babies<br />

and Bananarama. These days, it’s crank out a couple of<br />

albums and disband. Make your $10 million and bail.<br />

“These youngsters—they don’t know good music<br />

anymore.” I yearn for the days of good old-fashioned rock<br />

and roll, when singers danced with live pythons on stage<br />

or bit the heads off of doves.<br />

Bands today lack staying power.<br />

Editor Whil Hentzen, Editorial Advisory Board Scott Malinowski, Walter Loughney, Les Pinter, Ken Levy, Publisher Robert Williford,<br />

Vice President/General Manager Connie Austin, Managing Editor Heidi Frost, Copy Editor Farion Grove<br />

FoxTalk (ISSN 1042-6302) is published monthly (12 times per year)<br />

by Pinnacle Publishing, Inc., 1503 Johnson Ferry Road, Suite 100,<br />

Marietta, GA 30062. The subscription price of domestic<br />

subscriptions is: 12 issues, $179; 24 issues, $259. Periodical postage<br />

paid at Marietta, GA and at additional mailing offices. USPS#005373.<br />

POSTMASTER: Send address changes to FoxTalk, PO Box 72255,<br />

Marietta, GA 30007-2255.<br />

Copyright © 1998 by Pinnacle Publishing, Inc. All rights reserved. No<br />

part of this periodical may be used or reproduced in any fashion<br />

whatsoever (except in the case of brief quotations embodied in<br />

critical articles and reviews) without the prior written consent of<br />

Pinnacle Publishing, Inc. Printed in the United States of America.<br />

Brand and product names are trademarks or registered trademarks<br />

of their respective holders. Microsoft is a registered trademark of<br />

Microsoft Corporation. The Fox Head logo, FoxBASE+, FoxPro, and<br />

Visual FoxPro are registered trademarks of Microsoft Corporation.<br />

FoxTalk is an independent publication not affiliated with Microsoft<br />

Corporation. Microsoft Corporation is not responsible in any way for<br />

the editorial policy or other contents of the publication.<br />

This publication is intended as a general guide. It covers a highly<br />

technical and complex subject and should not be used for making<br />

decisions concerning specific products or applications. This<br />

publication is sold as is, without warranty of any kind, either express<br />

or implied, respecting the contents of this publication, including<br />

but not limited to implied warranties for the publication,<br />

performance, quality, merchantability, or fitness for any particular<br />

purpose. Pinnacle Publishing, Inc., shall not be liable to the<br />

purchaser or any other person or entity with respect to any liability,<br />

loss, or damage caused or alleged to be caused directly or indirectly<br />

by this publication. Articles published in FoxTalk reflect the views of<br />

their authors; they may or may not reflect the view of Pinnacle<br />

Publishing, Inc. Inclusion of advertising inserts does not constitute<br />

an endorsement by Pinnacle Publishing, Inc. or FoxTalk.<br />

Subscription information: To order, call Pinnacle Customer Service<br />

at 800-788-1900, or 770-565-1763. Cost of domestic subscriptions:<br />

12 issues, $179; 24 issues, $259. Canada: 12 issues, $194; 24 issues,<br />

$289. Outside North America: 12 issues, $199; 24 issues, $299.<br />

Individual issues cost $17.50 ($20 in Canada, $22.50 outside North<br />

America). All funds must be in U.S. currency.<br />

For European newsletter orders, contact: Tomalin Associates,<br />

Unit 22, The Bardfield Centre, Braintree Road, Great Bardfield, Essex<br />

CM7 4SL, United Kingdom. E-mail: 100126.1003@compuserve.com.<br />

Tel: +44 (0)1371 811299. Fax: +44 (0)1371 811283. 12 issues: £179<br />

For Australian newsletter orders, contact: Ashpoint Pty., Ltd.,<br />

9 Arthur Street, Dover Heights, N.S.W. 2030, Australia.<br />

Phone: +61 2-9371-7399. Fax: +61 2-9371-0180.<br />

E-mail: sales@ashpoint.com.au. Web: http://www.ashpoint.com.au.<br />

FoxPro technical support: Call Microsoft at 206-635-7191<br />

(Windows) or 206-635-7192 (Macintosh).<br />

Send all other questions or requests via the options at right.<br />

From the Editor FoxTalk<br />

And so does today’s software.<br />

I have a friend who runs the MIS department of a<br />

Fortune 200 company, and he’s getting ready to pull the<br />

plug on a bunch of boxes that have been collecting shop<br />

data (time clock type of info) since 1984. Same boxes,<br />

same software. None of this “upgrade of the month”<br />

business. None of this “make the chassis easily opened so<br />

you can replace broken parts in 30 seconds” rot. He’s<br />

replaced one fixed disk in 14 years. He’s not had to<br />

upgrade the boxes or change the software even once. And<br />

the best part? At the time he got these boxes, they’d been<br />

around for nearly a decade. (The equipment in question is<br />

the IBM Series 1, in case you were curious. And he’s going<br />

to replace them with Windows 95, er, Windows NT, uh,<br />

Windows 98, no, wait, make that NT 5, or . . .)<br />

I’ll bet you’re still doing a lot of the same stuff now<br />

that you were 10 years ago. Writing memos and technical<br />

documents. Calculating budgets and making cost<br />

estimates. Writing quote tracking, health care claim<br />

management, and accounting database applications.<br />

How much of the same software from 10 years ago are<br />

you still using? On the same machines?<br />

Boy, I wish it would stop. Or at least slow down a<br />

bit. I bet you do, too. But despite our most fervent wishes,<br />

it’s not going to. Why? It’s all these young kids in the<br />

industry who don’t have any perspective. “It can be done,<br />

Continues on page 22<br />

Editorial and<br />

Subscription<br />

Information<br />

770-565-1763<br />

800-788-1900<br />

E-mail<br />

foxtalk@pinpub.com<br />

Pinnacle Web Site<br />

http://www.pinpub.com<br />

Applies to VFP v6.0 (Tahoe)<br />

Applies to VFP v3.0<br />

Fax<br />

770-565-8232<br />

Mail<br />

PO Box 72255<br />

Marietta, GA<br />

30007-2255<br />

Applies to VFP v5.0<br />

Applies to FoxPro v2.x<br />

Accompanying files available online<br />

at http://www.pinpub.com/foxtalk<br />

Applies specifically to one of these platforms.<br />

2 http://www.pinpub.com<br />

6.0


<strong>Tame</strong> <strong>Your</strong> <strong>ActiveX</strong> <strong>Controls</strong> . . .<br />

Continued from page 1<br />

who hadn’t upgraded to 5.0a got “OLE class not<br />

registered” errors when they tried to use SDT. My<br />

workaround at the time was to keep one machine in our<br />

office with VFP 5.0 (not 5.0a) installed and do any SDT<br />

development involving that one form with the TreeView<br />

on it on that one machine. If I ever forgot and modified<br />

the form on my machine . . .<br />

However, knowing I’d experienced the problem<br />

before didn’t make me feel any better when my Inbox had<br />

dozens of e-mails reporting the problem. That was the last<br />

straw; I had to come up with a permanent solution to this<br />

problem. The tool described in this article is the result.<br />

Background<br />

First, let’s look at the cause of the problem. If you open a<br />

form containing an <strong>ActiveX</strong> control as a table (in other<br />

words, USE MYFORM.SCX) and browse it, you’ll find<br />

binary information stored in the OLE field of the record<br />

for the <strong>ActiveX</strong> control. This binary information appears<br />

to contain some version-specific information about the<br />

control. As a result—at least in the case of the <strong>ActiveX</strong><br />

controls shipped by Microsoft—<strong>ActiveX</strong> controls are<br />

forward but not backward compatible. This means that if<br />

I drop version 2.01 of an <strong>ActiveX</strong> control on a form,<br />

people with a newer version of the <strong>ActiveX</strong> control (such<br />

as version 2.02) can open the form, but those with an<br />

older version (like version 2.00) can’t.<br />

Let’s dig a little deeper and see how <strong>ActiveX</strong> controls<br />

are handled on a system. As you’re probably aware,<br />

simply copying an OCX file to someone’s hard drive<br />

doesn’t allow them to use the <strong>ActiveX</strong> controls contained<br />

in that file. The controls need to be registered on the<br />

system either using REGSVR32.EXE (a copy of this<br />

program comes with VFP) or letting VFP’s Setup wizard<br />

handle it for you. Using REGEDIT, we can see that<br />

<strong>ActiveX</strong> controls are registered by a class ID value in<br />

HKEY_CLASSES_ROOT\CLSID. For example, the<br />

TreeView control is registered in HKEY_CLASSES_<br />

ROOT\CLSID\0713E8A2-850A-101B-AFC0-<br />

4210102A8DA7. There are several subkeys under this<br />

key, the most important ones (as far as we’re concerned)<br />

being ProgID, VersionIndependentProgID, and<br />

InProcServer32. ProgID contains the program ID, which<br />

might change from version to version—for example, on<br />

my system, the value of this subkey for the TreeView<br />

is COMCTL.TreeCtrl.1. VersionIndependentProgID<br />

contains just what the name implies (on my system,<br />

the value is COMCTL.TreeCtrl for the TreeView), and<br />

InProcServer32 contains the name and path of the OCX<br />

containing the control. This immediately suggests several<br />

potential problems:<br />

• The <strong>ActiveX</strong> control was never installed on a<br />

machine—there’s no entry in the Registry for the<br />

control, and the OCX doesn’t exist on the drive. The<br />

solution is to properly install it.<br />

• The OCX file was copied to the system but not<br />

registered—the file exists, but there’s no entry in<br />

the Registry. The solution is to use REGSRV32.EXE<br />

to register the <strong>ActiveX</strong> controls in the OCX. Hint:<br />

You must include the extension for the file<br />

you’re registering:<br />

"regsvr32 myfile.ocx" instead of "regsvr32 myfile"<br />

• The <strong>ActiveX</strong> control is registered, but the OCX can’t<br />

be found—it might have been moved or renamed,<br />

or the user might have restored from a backed-up<br />

Registry but didn’t restore all files. The best solution<br />

is to reinstall and register.<br />

• The version installed on the system is different<br />

than the one required by an application. While an<br />

obvious solution to this problem is to install the<br />

version of the control required, this might not always<br />

be feasible. For example, if I shipped my copy of<br />

COMCTL32.OCX along with SDT, that would get rid<br />

of my problems but would probably cause a whole lot<br />

of problems for you and any clients you’d created<br />

forms with a TreeView for.<br />

Another unforeseen problem can also occur. Some<br />

<strong>ActiveX</strong> controls look for a license (a key stored in the<br />

Registry or a file on your drive) in order to use them in a<br />

development environment. This makes sense; it allows<br />

you to develop and distribute applications using a<br />

vendor’s controls and have those controls work in a<br />

runtime environment while protecting the vendor from<br />

someone else being able to develop using those controls<br />

without paying for them. The problem this causes,<br />

though, is that if the license key gets lost on your system,<br />

you’ll get a “License not found” error when you try to<br />

drop the control on a form. Reinstalling doesn’t always<br />

solve the problem. Based on messages I’ve seen on<br />

CompuServe and the Universal Thread, this problem has<br />

been a common one. For the controls shipped with VFP,<br />

the solution is simple: Download a file called FOX.REG<br />

from the Universal Thread (there’s a great reason to join<br />

right there) and run it; it will register the proper licenses<br />

on your system. For other third-party controls, reinstall or<br />

contact your vendor.<br />

As you can see, most of the problems regarding<br />

<strong>ActiveX</strong> controls require reinstalling them. However, as I<br />

mentioned, it might not be possible to solve the version<br />

problem this way. The rest of this article discusses an<br />

easy-to-implement solution to this annoying problem.<br />

SF<strong>ActiveX</strong><br />

The solution is to instantiate <strong>ActiveX</strong> controls at runtime<br />

rather than using them at development time. This way,<br />

http://www.pinpub.com FoxTalk June 1998<br />

3


the control is instantiated from the version installed on<br />

the user’s system rather than looking for the version that<br />

was installed on your system. To make this job easier, I<br />

created an <strong>ActiveX</strong> loader class called SF<strong>ActiveX</strong>.<br />

SF<strong>ActiveX</strong> is based on SFCustom, our Custom base<br />

class defined in our base class library SFCTRLS.VCX. It<br />

has the custom properties shown in Table 1.<br />

Table 1. Custom properties of SF<strong>ActiveX</strong>.<br />

Property Description<br />

cClass The name of the class defining the <strong>ActiveX</strong> control<br />

or a subclass of it<br />

cClassID The Class ID for the object<br />

cLibrary The name of the library containing a subclass of<br />

the <strong>ActiveX</strong> control<br />

cNewObjectName The name to give the <strong>ActiveX</strong> control this object<br />

will create<br />

cObjectName The name of the placeholder object to replace with<br />

the <strong>ActiveX</strong> control<br />

cOLEClass The OLE class for the object<br />

cClass defaults to “OLEControl” because that’s<br />

the VFP class that <strong>ActiveX</strong> controls are contained in.<br />

However, if you want to use a subclass of an <strong>ActiveX</strong><br />

control (I’ll show at least one reason why you’d want to<br />

do that later in the article), enter the name of the class in<br />

this property and the name of the library file containing<br />

the class definition in cLibrary (specify an extension to<br />

identify VCX from PRG libraries).<br />

You can specify the <strong>ActiveX</strong> control to use in one of<br />

two ways: by cClassID or cOLEClass. If you specify one,<br />

leave the other blank. cClassID is the class ID for the<br />

<strong>ActiveX</strong> control, and cOLEClass is its ProgID. How do<br />

you know what to enter for these properties for a given<br />

control? For the cOLEClass, the simplest way is to drop<br />

the desired control on a form, then look at its OLEClass<br />

property. For example, the OLEClass for the TreeView<br />

control is “COMCTL.TreeCtrl” (you might see<br />

“COMCTL.TreeCtrl.1” in your instance; however, don’t<br />

worry about the “.1” for cOLEClass). cClassID is a little<br />

harder to determine. You’ll need to fire up REGEDIT and<br />

search for the OLEClass for the control, then see what<br />

class ID it appears under. For example, the TreeView<br />

control appears under “{0713E8A2-850A-101B-AFC0-<br />

4210102A8DA7}”. You don’t need to enter the curly braces<br />

when you enter this value into cClassID. It’s better to<br />

specify cClassID than cOLEClass because the name of the<br />

control might change when different versions are installed<br />

(for example, the TreeView control that shipped with VFP<br />

5.0a has a ProgID of “COMCTL.TreeCtrl.1”, which could<br />

cause problems under certain circumstances if you<br />

specified “COMCTL.TreeCtrl”).<br />

Some <strong>ActiveX</strong> controls have a visual appearance, and<br />

others don’t. For those that do, you’ll need to specify the<br />

location and size of the control. You could do it in the Init<br />

of the form by setting the Top, Left, Width, and Height<br />

properties in code, but I find it more intuitive to put a<br />

Shape object on the form and size and position it where I<br />

want the <strong>ActiveX</strong> control to go. This object becomes a<br />

placeholder for the <strong>ActiveX</strong> control, making it easier to<br />

size, position, and place other objects in relation to it. To<br />

tell SF<strong>ActiveX</strong> that this object is the placeholder, enter its<br />

name in the cObjectName property. SF<strong>ActiveX</strong> will<br />

remove this object and put the <strong>ActiveX</strong> control in its place.<br />

SF<strong>ActiveX</strong> doesn’t have a lot of code. Its Init method<br />

calls the custom method Load<strong>ActiveX</strong> (which does all<br />

the work) unless told not to do so by passing .T. as a<br />

parameter (this is provided so you can instantiate<br />

SF<strong>ActiveX</strong> in code, set the properties as appropriate, and<br />

then call Load<strong>ActiveX</strong> manually to load the control). I<br />

won’t address all the code in the Load<strong>ActiveX</strong> method<br />

here (you can check it out yourself), just the more<br />

interesting stuff.<br />

SF<strong>ActiveX</strong> relies heavily on being able to look<br />

stuff up in the Windows Registry, because that’s<br />

where <strong>ActiveX</strong> controls installed on a user’s machine<br />

are registered. In order to work with the Registry,<br />

Load<strong>ActiveX</strong> uses the services of another class,<br />

SFRegistry, defined in SFREGISTRY.VCX. We won’t<br />

look at this class here; suffice it to say that I stole most<br />

of the code from REGISTRY.PRG, a Registry-handling<br />

class that comes with VFP, but made the programmatic<br />

interface a bit simpler.<br />

In order to use SFRegistry, you have to instantiate<br />

it; that work is done by a call to NEWOBJECT.<br />

NEWOBJECT.PRG is an updated version of<br />

NEWOBJ.PRG that I presented in this column in the<br />

September 1997 issue. It was updated to support the same<br />

syntax as the Tahoe NEWOBJECT function. This means<br />

that in Tahoe applications, you can omit this program,<br />

and any code calling it will work just fine.<br />

The first job for Load<strong>ActiveX</strong> is to figure out whether<br />

the desired <strong>ActiveX</strong> control is installed on the user’s<br />

system and, if the class ID was specified rather than the<br />

OLE class, which OLE class to use. The following code<br />

does this. Note that if you specify the OLE class and it<br />

can’t be found, Load<strong>ActiveX</strong> adds a “.1” suffix to the class<br />

and tries that before giving up. (The method starts with<br />

WITH THIS, so all unspecified object references are the<br />

class itself.)<br />

loRegistry = newobject('SFRegistry', ;<br />

'SFRegistry.vcx', cnHKEY_CLASSES_ROOT)<br />

if empty(.cOLEClass)<br />

lcCLSID = iif(left(.cClassID, 1) = '{', ;<br />

.cClassID, '{' + .cClassID + '}')<br />

lcProgID = loRegistry.GetKey('CLSID\' + lcCLSID + ;<br />

'\ProgID')<br />

llOK = not empty(lcProgID)<br />

else<br />

lcProgID = .cOLEClass<br />

lcCLSID = loRegistry.GetKey(lcProgID + '\CLSID')<br />

if empty(lcCLSID)<br />

lcProgID = .cOLEClass + '.1'<br />

lcCLSID = loRegistry.GetKey(lcProgID + '\CLSID')<br />

endif empty(lcCLSID)<br />

llOK = not empty(lcCLSID)<br />

4 FoxTalk June 1998<br />

http://www.pinpub.com


endif empty(.cOLEClass)<br />

if llOK<br />

lcOCX = loRegistry.GetKey('CLSID\' + lcCLSID + ;<br />

'\InProcServer32')<br />

llOK = not empty(lcOCX) and file(lcOCX)<br />

endif llOK<br />

At the end of this code, lcProgID contains the OLE<br />

class (ProgID), lcCLSID contains the class ID (which we<br />

don’t care about after this point), and lcOCX contains the<br />

name and path of the OCX file on the user’s system<br />

(found in the InProcServer32 key in the Registry). llOK<br />

is only .T. if we found what we were looking for in the<br />

Registry and the OCX file exists. If not, the user will get<br />

an error message, and this method will return .F.<br />

If everything is okay, Load<strong>ActiveX</strong> checks whether a<br />

placeholder was specified and, if so, saves its size and<br />

position and then removes it.<br />

llObject = not empty(.cObjectName)<br />

if llObject<br />

lcObject = .cObjectName<br />

with .Parent.&lcObject<br />

lnTop = .Top<br />

lnLeft = .Left<br />

lnWidth = .Width<br />

lnHeight = .Height<br />

endwith<br />

.Parent.RemoveObject(lcObject)<br />

endif llObject<br />

If a class and class library were specified,<br />

Load<strong>ActiveX</strong> opens the library (using either SET<br />

PROCEDURE or SET CLASSLIB, depending on the<br />

library’s extension). It then adds the <strong>ActiveX</strong> control or<br />

<strong>ActiveX</strong> subclass to the container, giving it the name<br />

specified in the cNewObjectName property. If a<br />

placeholder object was specified, the control is sized and<br />

positioned to the saved values. Notice the ProgID (stored<br />

in lcProgID) must be specified in the call to AddObject; if<br />

this was left out, the user would get a dialog box asking<br />

which control to insert. This is why we have to look in the<br />

Registry to determine the ProgID for the <strong>ActiveX</strong> control.<br />

lcObject = .cNewObjectName<br />

.Parent.AddObject(lcObject, .cClass, lcProgID)<br />

with .Parent.&lcObject<br />

if llObject<br />

.Top = lnTop<br />

.Left = lnLeft<br />

.Width = lnWidth<br />

.Height = lnHeight<br />

endif llObject<br />

.Visible = .T.<br />

endwith<br />

Using SF<strong>ActiveX</strong><br />

It’s easy to use SF<strong>ActiveX</strong>: Simply drop it on a form (or<br />

other container, such as a page in a PageFrame where the<br />

<strong>ActiveX</strong> control should go), fill in some properties, and<br />

put code in the Init method of the form to set any<br />

properties of the instantiated <strong>ActiveX</strong> control as desired.<br />

To make it even easier to use, I’ve created subclasses of<br />

SF<strong>ActiveX</strong> for the <strong>ActiveX</strong> controls I use most frequently:<br />

SFCommonDialog, SFImageList, and SFTreeView. These<br />

subclasses just have the appropriate class ID entered<br />

into the cClassID property so you don’t have to look it<br />

up. Drop one of these on a form to create the desired<br />

<strong>ActiveX</strong> control.<br />

Ah, but there’s one complication: What if you need to<br />

put some code in a method of the <strong>ActiveX</strong> control? This is<br />

frequently the case with visual controls—for example,<br />

with the TreeView and ListView controls, you need to take<br />

some action when the user clicks on a node or item in the<br />

list. In the case of the TreeView control, you’d put code<br />

into the NodeClick method. The problem, of course, is<br />

that you can’t add code to an object. The only way to<br />

accomplish this is to create a subclass of the <strong>ActiveX</strong><br />

control, put the desired code into the subclass, and tell<br />

SF<strong>ActiveX</strong> (via the cClass and cLibrary properties) to<br />

instantiate that class. One slight “gotcha”: You can’t create<br />

the subclass in a VCX because, like dropping an <strong>ActiveX</strong><br />

control directly on a form, VFP puts binary information<br />

about the control into the VCX, and that’s the whole cause<br />

of the problem SF<strong>ActiveX</strong> was designed to solve. Instead,<br />

you need to create the subclass in a PRG. Here’s the code<br />

from the sample ACLASSES.PRG that defines a subclass<br />

of the TreeView control; it has code in the NodeClick<br />

method (albeit simple code) that fires when the user clicks<br />

on a node. Note that OLEClass must be specified, or the<br />

user will get a dialog box asking which control to insert.<br />

define class MyTreeView as OLEControl<br />

OLEClass = 'COMCTL.TreeCtrl'<br />

Name = 'MyTreeView'<br />

procedure NodeClick<br />

lparameters toNode<br />

wait window 'You clicked on node ' + toNode.Text<br />

endproc<br />

enddefine<br />

Believe it or not, with all the problems we’ve solved<br />

using this scheme, we’re not quite out of the woods yet!<br />

There’s one last “gotcha”: Although you might think you<br />

could set some properties of the control in the subclass<br />

definition (such as Style and LineStyle in the case of the<br />

TreeView control), don’t do it. For a reason that escapes<br />

me, setting properties in the subclass seems to cause those<br />

properties to be carved in stone. Trying to change them<br />

later in a form the control is on fails completely. This<br />

means you have to set the properties after the control has<br />

been instantiated, usually in the Init of the form (which<br />

you can do, since by the time the form’s Init fires, the<br />

<strong>ActiveX</strong> controls have been instantiated). I’ll show an<br />

example of this in a moment.<br />

One final heads-up about the subclass: If you create<br />

an Init method in the subclass, it’ll need to accept a<br />

parameter. The OLE class is passed to the Init method<br />

(notice the third parameter in the AddObject call in<br />

Load<strong>ActiveX</strong>); although you don’t need it, you’ll get an<br />

error if you don’t have an LPARAMETERS statement.<br />

The sample TREEVIEW.SCX form shows how<br />

SF<strong>ActiveX</strong> is used. This form contains three objects: an<br />

SFTreeView object (SF<strong>ActiveX</strong> subclass specific for<br />

TreeViews) called oTreeViewLoader, an SFImageList<br />

object (SF<strong>ActiveX</strong> subclass specific for ImageLists) called<br />

oImageListLoader, and a Shape called shpTreeView that<br />

Continues on page 23<br />

http://www.pinpub.com FoxTalk June 1998<br />

5


Development Checklist:<br />

Requirements Analysis<br />

Jefferey A. Donnici<br />

With this month’s checklist column, Jeff takes a look at the<br />

process of gathering requirements for a development project.<br />

This column is the latest in a series of checklists designed to<br />

provide a starting point for your own development checklists.<br />

Building a set of system requirements is typically one of the<br />

first things to happen in a project and, as such, is an excellent<br />

opportunity to make sure that the project gets off on the<br />

right foot.<br />

AFTER covering coding, peer reviews, and a multipart<br />

segment on object-oriented design, I’m now<br />

going to take a look at the task of “gathering<br />

requirements.” This process in a development project can<br />

be labeled many things, and you might typically hear it<br />

called “requirements analysis,” “the analysis phase,”<br />

“specification construction,” or any number of variants.<br />

For the remainder of this column, I’ll simply use the term<br />

“analysis” to refer to the process of determining what the<br />

problem at hand is and what needs to be developed to<br />

solve that problem. In the world of object-orientation,<br />

this is a sub-field all its own, typically referred to as<br />

“object-oriented analysis.” Covering all the different<br />

concepts and ideas associated with object-oriented<br />

analysis is well beyond what can be covered in a single<br />

article, but I’ll list a couple of good references for those<br />

who are interested in learning more. While much of my<br />

own experience and focus these days is on object-oriented<br />

development, you might notice that not every item on<br />

this particular checklist is specific only to object-oriented<br />

projects. In fact, a number of the checklist items are<br />

applicable to nearly every software project, regardless<br />

of its development language, size, or cost.<br />

So, what exactly is “analysis”? Working backwards<br />

in the lifecycle of a software project, the “coding” is<br />

the process of implementing a software solution. The<br />

“design” phase of a project is used to determine how the<br />

system should be built (which parts it will contain and<br />

how they work together to form the system). The<br />

“analysis” phase, however, is the process of determining<br />

what needs to be built. In other words, an analysis process<br />

is meant to figure out the scope of the problem that the<br />

software must solve. I like to think of it as a process of<br />

“putting limits on the project” because the analysis phase,<br />

more than any other, will determine the scope of what the<br />

Best Practices FoxTalk<br />

system will (and won’t) do.<br />

Like any other process, the analysis phase of a project<br />

needs to have a result or end product. Typically, this will<br />

come in the form of a “requirements specification” that<br />

details what the system is expected to do. The form of<br />

these specifications can vary from project to project,<br />

depending on project size and a variety of other factors,<br />

but it might consist of a three-page narrative describing<br />

the system or a multi-binder set of diagrams, tables, and<br />

plain-English text. It’s difficult to know in advance when<br />

enough requirements information has been gathered, but<br />

you’ll definitely know it after the fact. If the developers<br />

working on the design phase of a project keep going back<br />

to the clients (or others involved in the analysis) for more<br />

information or clarification, then you know that the<br />

specification could have been more complete.<br />

With these ideas and definitions out of the way,<br />

let’s get started on the checklist of issues to watch out<br />

for during this early phase of a project.<br />

Is a true “analysis effort” being made<br />

at the start of the project?<br />

Yes, I know it sounds incredibly obvious to bring this up,<br />

but many projects get started with someone sitting down<br />

to write code and “prototype” the application in advance.<br />

It’s important that everyone involved with the project be<br />

aware of the importance of doing the analysis and<br />

gathering requirements. Even the smallest of software<br />

applications is intended to solve some sort of problem, so<br />

make sure there’s a clear understanding of what that<br />

problem is before beginning any design or coding effort.<br />

This can save a lot of embarrassment, headache, and,<br />

most importantly, money by avoiding situations wherein<br />

a certain “something” gets developed that isn’t at all the<br />

“something” that the end-users truly need.<br />

Unfortunately, some management types have the<br />

impression that a software developer can just sit down,<br />

close the door, push a few buttons, and magically deliver<br />

the software that management sees in their heads. There<br />

isn’t always a lot you can do to convince these people<br />

otherwise, but you can try to get the eventual users of the<br />

software involved early to make it clear that some form of<br />

requirements gathering needs to be performed. These are<br />

the people who will likely be “living with” your software<br />

6 FoxTalk June 1998<br />

http://www.pinpub.com<br />

6.0


on a daily basis and, presumably, will have their jobs<br />

made easier through your efforts. They naturally have<br />

a vested interest in making sure the developer(s)<br />

completely understands the problem domain that the<br />

software will address. Their involvement will make it<br />

clear to management that it’s important for you to have<br />

as much clarity as possible when it comes to the true<br />

goals of the software.<br />

Are all of the necessary people being involved to<br />

define the system’s intent and requirements?<br />

In light of the preceding section, it’s important to realize<br />

that it really takes a variety of people, at a variety of<br />

organizational levels, to make a software project<br />

successfully meet the needs for which it’s intended.<br />

Typically, you’ll have the management types who approve<br />

the project and ultimately compensate you for your work.<br />

Whether you’re a corporate developer or an independent<br />

consultant makes no difference other than the name that<br />

appears at the top of everyone’s paychecks. Since these<br />

are the people paying you, you want to make sure they’re<br />

involved, happy, and see progress happening. As I’ve<br />

already mentioned, you also need the end-users to be<br />

represented in the analysis phase because they’re going to<br />

be putting the fruits of your labor to work. While these<br />

people probably don’t approve your compensation, I tend<br />

to think they’re more important to the developer than the<br />

management type. That’s because they’re likely to be<br />

vocal when it comes to judging the results of your work.<br />

A manager who’s happy with your efforts isn’t likely to<br />

stay that way if he’s got an end-user mutiny on his hands.<br />

The point here is that it’s important for both types of<br />

people to be involved. While the end-users can ultimately<br />

tell you what needs to be done, the managers must be<br />

involved to tell you the constraints within which it must<br />

be done (budgets, schedules, and so forth).<br />

If possible, try to get feedback from more than just<br />

one “future end-user” for your system. If you’re getting<br />

all of your requirements and input from one person, you<br />

run the risk of developing software that meets the needs<br />

of the way that one person works. This can come at the<br />

expense of everyone else who must use the system,<br />

many of whom may have different needs or methods for<br />

approaching their job. Where I work, we write software<br />

applications for a wide variety of clients in the energy<br />

industry and ship that software with large volumes of<br />

data. The purpose of our software is to give the users a<br />

variety of tools and mechanisms for interpreting the<br />

data that they’ve licensed. One of our internal product<br />

managers will often come up with a variety of new ideas<br />

and functionality for his product and then explain to me<br />

that he’s “the typical end-user” for that product. Much as<br />

I wish he were “typical,” he’s significantly more familiar<br />

with the energy industry and our data than any “typical<br />

end-user” is, and that has to be factored into any requests<br />

or suggestions he makes. The point I’m hoping to make<br />

here is that it’s important to “know the audience” for<br />

your software application. With the input of only a small<br />

cross-section of the potential user base, you run the risk of<br />

developing software that meets the needs and desires of<br />

the few—at the expense of the many.<br />

Another point that’s worth making concerns other<br />

developers. If possible, get the people who will be doing<br />

the system design and implementation involved with the<br />

analysis effort. While it’s nice to have so complete a<br />

specification that it can be handed with confidence to a<br />

designer who wasn’t involved with that process, having<br />

other developers there can add clarity in the later phases<br />

of the project. They might ask different questions than the<br />

person assigned the requirements responsibility, and<br />

asking questions is the analysis phase.<br />

Has a common vocabulary been defined<br />

for developers and users so they can<br />

clearly communicate?<br />

Every type of business and job function has its own<br />

language. As a software developer, it’s likely that you’ll be<br />

exposed to a wide variety of businesses and industries.<br />

Personally, I know that’s one of the most exciting aspects<br />

of software development for me. As you get into each new<br />

project, however, you’re probably going to hear a variety<br />

of new terms and even some old terms that have<br />

meanings other than the definition you’re familiar with.<br />

As you’re meeting with the other parties involved with<br />

the project, make sure to stop and get definitions and<br />

explanations whenever they refer to something that<br />

you’re not clear on. It’s pretty common for this process to<br />

begin with someone saying, “Let’s start with the basics,”<br />

as they proceed to give you the background information<br />

they think you need. If you’re not completely clear on that<br />

background information, you’re really going to be lost<br />

when they get to the explanation of their problem. Once<br />

again, the point of an analysis effort is to get questions<br />

answered, so make sure to ask them early. Having a<br />

shared vocabulary established early in the process will<br />

make all communications much easier throughout the life<br />

of the project.<br />

This is also a good time to point out the importance<br />

of remembering that not everyone you’re working with<br />

in this phase of a project is a software developer. Terms<br />

like “inheritance,” “encapsulation,” and “referential<br />

integrity” might be commonplace in this newsletter and<br />

at programming conferences, but they don’t mean a<br />

thing to your average computer user or business<br />

manager. Because this phase of a project is primarily<br />

for the end-users, try to keep the development-related<br />

technical jargon to a minimum and speak to them in<br />

their language. You can translate their needs and desires<br />

into your programming terms in the design phase of<br />

the project.<br />

http://www.pinpub.com FoxTalk June 1998<br />

7


Is a completely different approach being forced<br />

on the eventual users of the system?<br />

The typical goal of a software project is to accurately<br />

model some process that the end-users would otherwise<br />

have to go through manually so that their job is ultimately<br />

made easier. The chances are pretty good that these endusers<br />

are already performing the task that the software<br />

will model, and they’re used to doing it in a certain way<br />

or using a specific methodology (perhaps even with an<br />

existing software application). As the analysis phase of a<br />

project begins, it’s not uncommon for users to come up<br />

with new ways of approaching their job, sometimes at the<br />

suggestion of the developer gathering the requirements<br />

(no hidden motives there, huh?). When the analysis of<br />

the new system begins to look like a completely new<br />

approach for the business process, proceed very carefully.<br />

As much as I hate to borrow an incredibly over-used<br />

term, what you’re watching out for here is a “paradigm<br />

shift” in the approach the software takes to solve the<br />

business problem.<br />

I should clarify that this caveat isn’t intended to stifle<br />

anyone’s innovation or creativity. I think that users are<br />

generally excited by being a part of the requirements<br />

gathering process, and it’s easy for them to lose sight of<br />

the fact that this software will likely become either a very<br />

good friend or another drudgery added to their day. If the<br />

approach the software uses to solve a problem isn’t a<br />

sound or “tried and true” approach that the users can<br />

work with, they’re not going to be happy once the system<br />

is deployed and the “honeymoon period” is over. The one<br />

area that I think is a slight exception to this rule is when a<br />

shift will mean a large increase in the user’s efficiency.<br />

Anyone can get used to something that makes their day<br />

easier or frees up more of their time, but make sure that<br />

changing the “business process” isn’t going to ultimately<br />

have the opposite effect. Innovate where it makes sense,<br />

but don’t change things on the user purely for the sake<br />

of change.<br />

Have all of the necessary—as well as potentially<br />

new—components of the system been discussed?<br />

With some projects, you’ll be writing a system that<br />

replaces an older, obsolete, or possibly cumbersome<br />

system that’s already in place. Other times, you’ll be<br />

creating an application from scratch to streamline an<br />

existing business process. In the latter case, the users and<br />

their managers might not have any idea what they need<br />

or could use until you come right out and suggest it. In<br />

the former case, they might be so used to dealing with the<br />

existing software that they don’t see past it for new ideas<br />

and functionality. For that reason, I’m using this checklist<br />

item as a catch-all to list the types of things you might<br />

suggest to them. These are components that appear in<br />

many different types of applications and might or might<br />

not be important for any given project.<br />

For starters, do they have any existing reports,<br />

spreadsheets, or documents that the system will need to<br />

be capable of generating or reading? If there’s already a<br />

system in place, should the existing data be converted to<br />

a new format, or should it simply be archived so they<br />

can start fresh? Should the application have any security<br />

mechanisms built into it and, if so, to what degree? Now<br />

that everyone and their pet has an e-mail address, should<br />

the application be capable of sending (or even receiving)<br />

e-mail messages? Is there a need for users to do offline<br />

work with their data, separated from the primary data<br />

sources? Could they use remote access capabilities in<br />

the application? Do they have a need for specific file<br />

structures or file types? Will the system need to have<br />

any export or import capabilities? Does the system need<br />

backup functionality built into it, or will they use some<br />

other backup solution? How much flexibility do they<br />

need for customizing the user interface or business<br />

logic within the system? Do they need access to file<br />

maintenance capabilities in the system, or should those<br />

be handled automatically? To what extent should the<br />

system provide online help or user assistance? Should<br />

the help system only aid them with the use of the system,<br />

or should it also provide assistance with the business<br />

process that the system is designed to model?<br />

Obviously, there are a lot of these types of questions<br />

to be asked when you sit down to work out the<br />

requirements for a new software application. Trying to<br />

come up with an exhaustive list would be impossible for<br />

me to do here, but the preceding suggestions should give<br />

you some ideas of the types of things that might not occur<br />

to users until they’re suggested. Going on the assumption<br />

that you want this application to be as useful as possible<br />

for your end-users, ask lots of questions and make sure<br />

you’re clear enough on the answers to meet their needs<br />

with a true solution.<br />

Has the specification been built with scalability<br />

and future expandability in mind?<br />

An application that can “scale up” as the needs placed on<br />

it grow must be intended that way from the onset. While<br />

it’s usually an interesting “employee interview” question,<br />

you might find it useful to find out where the users and<br />

buyers of this system expect themselves and the software to<br />

be in a couple of years. You might be writing a small,<br />

standalone sales system for a bookstore today, but what if<br />

they plan to grow into a new space next year and have<br />

three different sales computers? Most businesses have<br />

growth as a primary goal, so it’s important to find out<br />

how much expansion capability the users and managers<br />

would like to see. Chances are that your software will<br />

need to be replaced if they become the next “Barnes &<br />

Noble” or “Amazon.com,” but you don’t want it to be<br />

obsolete as soon as they need a little more storage<br />

capability either.<br />

I should note here that I’m not referring specifically to<br />

the software’s internal flexibility and expandability. That<br />

8 FoxTalk June 1998<br />

http://www.pinpub.com


comes as a function of the design phase in the project and<br />

should be built into every system you work on. Even if<br />

the business never grows, the needs of the software might<br />

simply change, and it’s important to have that capability<br />

inherent in the components of the application. Instead,<br />

this topic is intended to get you thinking about the actual<br />

growth that a system might undergo. For example, you<br />

might write a relatively simple application for a small<br />

business that doesn’t have any networking or multi-user<br />

needs at all. One day, the owner calls you and says he’d<br />

like to be able to access the software from home so that he<br />

doesn’t have to go into the office every day. That’s easy<br />

enough, but he’d like to be able to do that even if one of<br />

his employees is in the office and using the system.<br />

Suddenly that simple application you built needs remote<br />

access and multi-user capabilities. A year later, the owner<br />

calls you and tells you he needs the software to be<br />

running around the clock with zero downtime, but he also<br />

wants nightly backups made of all that data in the<br />

background. Now it makes more sense for the Visual<br />

FoxPro application you built to be using SQL Server as a<br />

back-end instead of native VFP tables. Suddenly all the<br />

data-related code you wrote won’t do, and you wish<br />

you’d been using view definitions from the beginning.<br />

Once again, these are issues that can be resolved with a<br />

solid application design effort, but it’s good to get them<br />

out in the open during the analysis phase, if only to get<br />

feedback and suggestions from the true “owners” of<br />

the system.<br />

Have all “use cases” and “actors” that<br />

might be required within the system<br />

been discussed and clarified?<br />

In his book Object-Oriented Software Engineering: A Use<br />

Case Driven Approach (Addison-Wesley, ISBN 0201544350),<br />

Ivar Jacobson discusses the idea of using “use cases” and<br />

“actors” to work out the requirements of an objectoriented<br />

application. A “use case” is roughly defined as a<br />

description of the tasks that must be completed as part of<br />

an overall process. For example, an automated teller<br />

machine might have a “withdraw cash” use case, a<br />

“deposit check” use case, and a “balance inquiry” use<br />

case. On its own, the description of a use case doesn’t<br />

have much value other than to provide a convenient way<br />

to refer to a larger process or series of steps. An “actor” is<br />

described as an “external agent” or “user” involved with<br />

the process that a use case describes. For example, a<br />

purchasing system for a company might have a “fulfill<br />

order” use case as one of its needs. The steps involved<br />

with this process might include an employee filling out a<br />

request form, a supervisor approving the request, a<br />

purchaser making the purchase, a supplier providing the<br />

requested materials, and a mailroom employee delivering<br />

it. Each of these persons is considered an “actor” within<br />

the “fulfill order” use case because the process isn’t<br />

complete until each of them has performed his or her role.<br />

My goal here isn’t to convince you that Jacobson’s<br />

approach is the best or only way to go for your own<br />

analysis, though I do highly recommend his book.<br />

Instead, it’s to provide you with an additional way to<br />

determine whether or not all the questions have been<br />

asked when analyzing the needs of a system. In truth, I<br />

have to agree with the author of another excellent book—<br />

Object-Oriented Software Construction, Second Edition by<br />

Bertrand Meyer (Prentice Hall, ISBN 0136291554)—in<br />

suggesting that unless a development team has plenty of<br />

experience developing object-oriented applications, the<br />

use case approach can be somewhat limiting during the<br />

analysis and design phases of a project. As Meyer points<br />

out, the use case approach requires that the process be<br />

described in a fairly rigid fashion with a specific sequence<br />

of events. This can be at odds with the abstract, stateless<br />

approach that an object-oriented application should<br />

take—an approach where various self-contained objects<br />

can receive a type of request and proceed to work in<br />

conjunction with other objects as necessary to meet that<br />

request. As such, I don’t believe that a use case approach<br />

should be the only method of determining the needs of an<br />

object-oriented application. I do, however, think that use<br />

cases can be a valuable tool for looking at a system’s<br />

requirements from a new and unique perspective. When<br />

taking the time to describe a system as a series of use<br />

cases, each of which requires a set of actors for<br />

completion, you might find that an important<br />

requirement or consideration has been overlooked.<br />

Are there clear limits to what the system<br />

will and won’t do when complete?<br />

Here’s another seemingly obvious item that I think is<br />

often overlooked in requirements gathering efforts. It’s<br />

common for the analysis to indicate everything that an<br />

application should do, but much less common is for some<br />

attention to be devoted to what it shouldn’t do when<br />

complete. For example, if your application must have an<br />

option to let the user export a table to another file format,<br />

should the user also have the ability to choose which<br />

fields in the table are exported? If the application has a<br />

charting or graphing component, will it also have the<br />

ability to export the chart as a graphic file?<br />

When the developers working on the design effort get<br />

the results of the analysis phase, they’re naturally going to<br />

have questions about the extent to which they should<br />

account for the unknown. When possible, indicating what<br />

the application doesn’t need to do can help them rule out<br />

some of those unknowns and focus more clearly on the<br />

things that the application does need.<br />

Is there an end result to all of the analysis<br />

and requirements work?<br />

A requirements effort is really only as good as the<br />

specifications and documentation it generates at the<br />

end of the process. Getting a variety of people involved,<br />

http://www.pinpub.com FoxTalk June 1998<br />

9


combining opinions for a consensus, and determining<br />

every last need for a software project is great, but it’s<br />

useless without some way of communicating those<br />

results to the people who will design and implement<br />

the solution.<br />

As I mentioned earlier in this column, the end result<br />

for an analysis effort could be anything from a free-form<br />

memo that’s a few pages in length to a truckload of<br />

documentation that details every last need in the finest<br />

level of detail. Where your needs fall within those<br />

extremes should be determined early in the analysis<br />

phase so that everyone knows going in what’s expected at<br />

the end of the process. Make sure that the specifications<br />

are provided in writing and don’t consist of a<br />

“specification meeting” with the people who will<br />

design and implement the system. Invariably, intents<br />

and desires will be left out of a meeting format, and the<br />

miscommunications that result can be the death of a<br />

software project.<br />

Have all the necessary people approved<br />

the final analysis results?<br />

Once you’ve got the requirements put together and put in<br />

writing, make sure that everyone who has an interest in<br />

the project has “signed off” on the results. This doesn’t<br />

need to be a formalized process with people signing their<br />

lives away, but they should have an opportunity to review<br />

and make comments on the results of the process they’re<br />

involved in.<br />

Getting the results of the process approved by<br />

everyone greatly increases the chance of success for a<br />

project because it avoids the “it does what I said, but not<br />

what I meant” problem that often pops up during a<br />

project. When someone reads through the description of<br />

their needs later, well after they initially expressed those<br />

desires, they can often clarify those thoughts so that what<br />

they’re agreeing to is truly what they want. Having<br />

management review the requirements will ensure that the<br />

system requirements are in line with the goals of the<br />

business, and having the end-users take part in the review<br />

will make sure that the application will truly be of use to<br />

them in their job functions. In short, it’s just a matter of<br />

making sure that everyone with a vested interest in the<br />

project is on the same page. <strong>Your</strong> development staff<br />

thanks you for this. Trust me.<br />

Wrap it up<br />

That brings to a close my list of items to watch out for<br />

during the analysis, or requirements gathering, phase of a<br />

software project. I know it’s by no means an exhaustive<br />

list, but I hope it triggers a variety of new ideas and<br />

checklist topics for you to use on your own. As always,<br />

feel free to send me your feedback and thoughts on this<br />

(or any other) topic, as well as your suggestions for future<br />

checklists or columns.<br />

If you’re interested in reading some more in-depth<br />

discussions of the analysis phase, I highly recommend<br />

each of the books I’ve mentioned in this column. And<br />

speaking of great books, I finally got around to finishing<br />

What Every Programmer Should Know About Object-Oriented<br />

Design by Meilir Page-Jones (Dorset House, ISBN<br />

0932633315). Right there at the end of the book, in<br />

Appendix A, was an exhaustive checklist of 51 things to<br />

watch for during an object-oriented design. If you were<br />

left hungry for more ideas or in-depth discussion after the<br />

object-oriented design checklists I presented over the past<br />

two months, I’d definitely recommend this book. It’s<br />

written in a very readable, light style and covers the<br />

design phase of a project in great detail. It just occurred<br />

to me that, while my last couple of columns might or<br />

might not have provided any additional food for thought,<br />

they could have inadvertently blown your budget for<br />

“development books.” I should probably stop here and<br />

give some thought to making next month’s column the<br />

kick-off for a new 12-step program for book addicts like<br />

myself. See you then! ▲<br />

Jefferey A. Donnici is an applications developer who specializes in<br />

reusability and design issues with Resource Data International, Inc. in<br />

Boulder, CO. Jeff is a Microsoft Certified Professional in Visual FoxPro<br />

and a three-time Microsoft Developer Most Valuable Professional.<br />

303-444-7788, fax 303-444-1286, jdonnici@compuserve.com,<br />

jdonnici@resdata.com.<br />

10 FoxTalk June 1998<br />

http://www.pinpub.com


Traversing Transactions<br />

Jim Booth<br />

Last month, Jim discussed TableUpdate() and TableRevert()<br />

and showed how they enable you to control updates while<br />

using buffering on your cursors and tables. These two<br />

functions are really very good for handling the updating or<br />

reverting of a single table or cursor. This begs the question,<br />

“How can I coordinate the updates or reverts to a number of<br />

related tables?” Gary DeWitt discussed transactions in the<br />

January and March 1998 issues of FoxTalk in preparation for<br />

MTS; this month, Jim reviews that and discusses what can fail<br />

during a transaction.<br />

THERE are situations where you need to update more<br />

than one table or cursor, and the combination of the<br />

updates represents one action on the database. This<br />

action has to be an “all or none” operation—that is, either<br />

all of it happens or none of it happens. For example, in<br />

writing the code to save a new invoice, you need to add<br />

a record to the invoice header table, add a group of<br />

records to the invoice line item table, update the balance<br />

in the customer record, and add a record to the accounts<br />

receivable table. If any of these operations fail, you’ll need<br />

to reverse the earlier ones that did succeed and make sure<br />

that none of these updates get into the data. Transactions<br />

are designed for this very purpose.<br />

You use the BEGIN TRANSACTION command to<br />

start the transaction tracking in Visual FoxPro, and the<br />

ENDTRANSACTION command to complete the<br />

transaction or the ROLLBACK command to reverse<br />

the transaction.<br />

How do transactions work?<br />

When VFP encounters a BEGIN TRANSACTION, it starts<br />

to track the file update operations that occur. For each<br />

update action, VFP secures the required locks, but it<br />

doesn’t do the update yet. When an ENDTRANSACTION<br />

is encountered, VFP then does all of the file writes<br />

involved in the transaction and releases the locks that<br />

were obtained. If a ROLLBACK command is encountered,<br />

VFP will release the locks without doing any of the file<br />

write operations.<br />

Through this process, VFP is able to assure that either<br />

all of the file updates are done or none of them are done.<br />

It’s a very good idea to keep the BEGIN TRANSACTION<br />

and ENDTRANSACTION/ROLLBACK commands as<br />

physically close to each other in the code as possible.<br />

Doing this reduces the period of time that the resources<br />

are locked.<br />

What’s Really Inside FoxTalk<br />

Which tables and cursors<br />

can participate in a transaction?<br />

Transactions in VFP are only available for tables that are<br />

in a database (DBC). Free tables can’t participate in a<br />

transaction, and a rollback or other abnormal end of the<br />

transaction doesn’t affect any updates made to free tables<br />

during a transaction.<br />

This can cause some interesting effects if a free table is<br />

the source of data for a local view that’s managed by a<br />

transaction. I’ll examine the steps in the process of a<br />

fictitious transaction—in this transaction, there’s one table<br />

in a database named Table1 and a view named View1 that<br />

gets its data from a free table named Table2. Here’s the<br />

code for the transaction:<br />

BEGIN TRANSACTION<br />

IF TableUpdate( 1, .F., "View1")<br />

IF TableUpdate ( 1, .F., "Table1")<br />

END TRANSACTION<br />

ELSE<br />

ROLLBACK<br />

ENDIF<br />

ELSE<br />

ROLLBACK<br />

ENDIF<br />

If the TableUpdate for Table1 fails, will the<br />

ROLLBACK undo the changes to View1 and set it back to<br />

its state prior to the transaction? Yes, it will roll back the<br />

changes to View1. Will the changes to Table2 that were a<br />

result of updating View1 be reversed by the ROLLBACK?<br />

No, because Table2 is a free table and therefore isn’t<br />

participating in the transaction. This can leave your data<br />

in an undefined state, so you have to manage the free<br />

table yourself. It’s best not to depend on transactions<br />

when free tables are involved in any way.<br />

Show us the code!<br />

How do you integrate transactions into your forms? It<br />

can be done on a form-by-form basis, or you can do it<br />

generically in a form class. The following is some<br />

pseudo-code that will give you the outline of the structure<br />

for incorporating transactions into your updates.<br />

* Set a variable for tracking a failure.<br />

LOCAL llRollBack<br />

llRollBack = .F.<br />

* Wait until all data processing is complete<br />

* before beginning the transaction.<br />

BEGIN TRANSACTION<br />

* Now do each update checking the result.<br />

IF NOT TableUpdate( … )<br />

llRollBack = .T.<br />

ELSE<br />

Continues on page 23<br />

http://www.pinpub.com FoxTalk June 1998<br />

11


Transactions: Tahoe and<br />

Microsoft Transaction Server<br />

Gary DeWitt<br />

Component reuse is one of the key goals of object-oriented<br />

programming and one of the difficulties with transaction<br />

processing—until now. Here’s how Tahoe and Microsoft<br />

Transaction Server can work together to create and deploy<br />

robust, reusable transactional components.<br />

AT the First National Bank of Dad, the CFO, Dad,<br />

has realized he can no longer give his account<br />

holders personalized service. Instead, he’ll install<br />

an ATM (automatic teller machine) to handle the everincreasing<br />

demand for the bank’s services. The ATM must<br />

allow a depositor to deposit his or her allowance into<br />

savings or checking accounts, withdraw allowance from<br />

either account, and transfer allowance from one account<br />

to the other.<br />

In the January 1998 issue of FoxTalk (see<br />

“Transactions: Take the ACID Test”), I introduced you to<br />

transaction processing at the First National Bank of Dad. I<br />

described an account object with two methods: Deposit()<br />

and Withdraw(). Each of these methods uses transactions<br />

to ensure that the database is updated correctly. The<br />

transaction is started and ended within the context of a<br />

deposit or a withdrawal.<br />

In the March 1998 issue of FoxTalk (see “Transactions:<br />

Keeping Them in Context”), I discussed ways that the<br />

account class can be written so that it can work in the<br />

context of a funds transfer, where both a deposit into one<br />

account and a simultaneous withdrawal from another<br />

account must both succeed or both be rolled back. You<br />

learned about nested transactions and the often<br />

complicated ways of writing components to work in the<br />

context of other transactions. Writing robust, reusable<br />

transactional components for client/server databases<br />

can be particularly difficult. I ended by wishing for a<br />

better way.<br />

A better way<br />

Warning: You need to know this stuff. The techniques I<br />

discuss in this article are applicable to client/server<br />

databases, but there are some key events on the horizon<br />

that are likely to make you want to do more client/server<br />

applications, even for desktop deployment.<br />

I recently visited Microsoft’s Web site (http://<br />

www.microsoft.com) and executed a search on the phrase<br />

FoxTalk<br />

“SQL Server 7.” I found numerous Web pages,<br />

presentations, and press releases about the next version<br />

of Microsoft SQL Server, code-named Sphinx. One of the<br />

key features I found for SQL Server 7 is scalability. Not<br />

only will it scale to very large databases, but to very small<br />

ones too. One PowerPoint presentation on the Microsoft<br />

site called this “SQL Desktop” and “Embeddable SQL.”<br />

According to Microsoft, SQL Server 7 will run under both<br />

Windows NT and Windows 9x. The embedded version<br />

will have a small footprint, and you’ll be able to distribute<br />

it with your applications to run on desktop systems and<br />

even laptops. It will finally be not only desirable, but<br />

also possible to use SQL Server for small database<br />

applications.<br />

The other key event is the release of Tahoe. Tahoe<br />

allows you to write components that take full advantage<br />

of MTS (Microsoft Transaction Server) and Microsoft<br />

SQL Server.<br />

What is MTS?<br />

MTS, currently in version 2.0, is a powerful environment<br />

that makes it easier to develop and deploy robust, highperformance<br />

transactional components. Last September at<br />

the Visual FoxPro DevCon, Microsoft demonstrated some<br />

of the new features of Tahoe, including its ability to work<br />

with MTS. I was so excited about the possibilities that I<br />

rushed home and immediately started experimenting<br />

with MTS 1.0, SQL Server 6.5, and Visual Basic 5.0. As I<br />

write this (in early April), I’ve been working with MTS<br />

2.0, SQL Server 6.5, and Tahoe nearly every day for<br />

about a month.<br />

Here are some of the advantages I’ve discovered<br />

from using this combination of tools: Well-written<br />

components can be reused in any transactional context<br />

with no modifications; valuable resources, such as ODBC<br />

connections, can be pooled with no modifications; granular,<br />

easy-to-develop components can be combined with no<br />

modifications; complicated distributed transactions can be<br />

implemented as easily as simple ones with no modifications;<br />

performance of frequently used components can be<br />

enhanced with no modifications.<br />

At the risk of being accused of belaboring my point,<br />

let me reiterate that all these advantages can be achieved<br />

in components written for MTS with no modifications.<br />

12 FoxTalk June 1998<br />

http://www.pinpub.com<br />

6.0


Basic MTS transactional components<br />

MTS components you create with Tahoe are in-process<br />

servers. Unlike other in-process servers, these run in a<br />

Transaction Server process, rather than your application’s<br />

process. MTS assigns transactional components a<br />

transaction context. <strong>Your</strong> components can get an object<br />

reference to the transaction context by using one of the<br />

MTS <strong>ActiveX</strong> servers.<br />

*-- Obtain a reference to transaction context.<br />

oServer = CREATEOBJECT("MTXaS.Appserver.1")<br />

oContext = oServer.GetObjectContext()<br />

The context object is used to commit or roll back<br />

transactions. Commit a transaction by calling the context<br />

object’s SetComplete() method:<br />

oContext.SetComplete()<br />

Roll back a transaction by calling the context object’s<br />

SetAbort() method:<br />

oContext.SetAbort()<br />

That’s it. That’s all you have to do to write a<br />

transactional component. Use the exact same technique<br />

for all transactional components with no modifications.<br />

How does it work? MTS creates a new transaction<br />

context for a new transactional component. In this case,<br />

GetObjectContext() returns a reference to the new<br />

transaction context. But if one transactional component<br />

creates an instance of another transactional component,<br />

then GetObjectContext() returns a reference to the original<br />

object’s context. How do you know which is which? You<br />

don’t. It doesn’t matter. If any method in a transaction<br />

calls the context object’s SetAbort() method, then the<br />

transaction will be rolled back. The component calling<br />

SetAbort() doesn’t need to know whether it has its own<br />

transaction or is part of another component’s transaction.<br />

The code runs the same either way.<br />

In the first two articles in this series, I created an<br />

account class with Deposit() and Withdraw() methods.<br />

Both called the Post() method, Deposit() passing the<br />

amount as a positive number, and Withdraw() passing it<br />

as a negative number. The Post() method fails if the new<br />

balance in the account is less than zero. Let me create<br />

a new, simplified Post() method using MTS. For the<br />

purposes of this simple example, assume I have a<br />

remote view of the account table. I also have three<br />

constants for return values: ERROR_SUCCESS,<br />

ERROR_OVERDRAWN, and ERROR_OTHER. First I get<br />

a reference to the object context and open the database<br />

and the remote view:<br />

LPARAMETERS nAccountNo, nAmount<br />

*-- Obtain a reference to transaction context.<br />

oServer = CREATEOBJECT("MTXaS.Appserver.1")<br />

oContext = oServer.GetObjectContext()<br />

*-- Open database and view.<br />

OPEN DATABASE Test<br />

USE Account<br />

In the next step, I add the amount of the transaction<br />

to the balance in the account. Note that in a withdrawal,<br />

nAmount would be a negative number:<br />

*-- Attempt to post the amount to the account.<br />

REPLACE balance WITH balance + nAmount<br />

Here’s where all the decisions are made:<br />

IF balance < 0<br />

*-- Insufficient funds, roll back.<br />

oContext.SetAbort()<br />

nSuccess = ERROR_OVERDRAWN<br />

ELSE<br />

*-- Sufficient funds, attempt to update.<br />

IF TABLEUPDATE(2, .T.)<br />

*-- Update successful.<br />

oContext.SetComplete()<br />

nSuccess = ERROR_SUCCESS<br />

ELSE<br />

*-- Update failed.<br />

oContext.SetAbort()<br />

nSuccess = ERROR_OTHER<br />

ENDIF<br />

ENDIF<br />

The first thing to check is whether or not there are<br />

sufficient funds for the transaction. If the balance is less<br />

than zero, the account would be overdrawn. Such a<br />

condition isn’t allowed, so I abort the transaction by<br />

calling SetAbort() and set the return value to my constant<br />

that indicates an overdraft.<br />

If no overdraft condition exists, I attempt to update<br />

the database with TABLEUPDATE(). If it succeeds, all is<br />

well and the transaction can be committed and the return<br />

value set to the constant indicating success. If the update<br />

fails, there’s some other problem. In this example, rather<br />

than dealing with the myriad reasons why an update<br />

could fail, I simply abort the transaction and set the return<br />

value to the constant indicating a problem.<br />

All that remains now is to clean up after the<br />

transaction and return the value:<br />

CLOSE DATABASES ALL<br />

RETURN nSuccess<br />

When the account component is added to an MTS<br />

package and used, the Post() method can be used to<br />

manage deposits and withdrawals. The Transaction<br />

Monitor in the Transaction Server Explorer can be used to<br />

track the number of transactions attempted along with the<br />

number of active transactions, the number of successful<br />

transactions, and the number of aborted transactions.<br />

Managing resources<br />

I see some hands going up right now—people who want<br />

to know why I open the database and the view without<br />

checking to see whether they’re already open and why I<br />

close everything at the end rather than leaving them open<br />

for future use. Good question.<br />

Without MTS, I’d have to write plenty of code to<br />

ensure that I’m not clashing with already-opened views. I<br />

also wouldn’t want to close everything as soon as I’m<br />

done with it. After all, opening a remote view requires<br />

that VFP load the ODBC manager, driver manager, and<br />

http://www.pinpub.com FoxTalk June 1998<br />

13


driver DLLs and open an ODBC connection to the<br />

database. Without MTS, I’d want to keep a connection<br />

open so that VFP doesn’t have to do all that work every<br />

time you call a method.<br />

MTS keeps DLLs in memory for a while after they’re<br />

no longer used. It also pools ODBC connections. If an<br />

object uses a connection, MTS keeps it open so that other<br />

objects can use the same connection. In this way, MTS<br />

can improve the performance of future requests for an<br />

object and can reduce the number of ODBC connections<br />

required. Remember that in client/server databases, more<br />

connections often means paying higher licensing fees and<br />

a higher requirement for resources.<br />

Transaction Server’s resource pooling, combined with<br />

Tahoe’s new apartment-model threading, provides some<br />

phenomenal performance improvements. I’ve conducted<br />

comparisons between components created and run in VFP<br />

5.0 and those created with Tahoe and run in Tahoe and<br />

MTS, and I’ve seen performance improvements of over<br />

100-fold when creating multiple instances of transactional<br />

components!<br />

To take advantage of resource pooling, I subclass all<br />

my transactional components from VFP Forms. Forms<br />

can have private datasessions so that multiple instances<br />

of in-process servers don’t step on the toes of other<br />

instances. If you do this, be sure you remember to make<br />

the appropriate environment settings. I can’t tell you how<br />

many times I’ve elevated my blood pressure by forgetting<br />

to SET EXCLUSIVE OFF in a component and received an<br />

OLE error (worse yet, with beta versions of these tools, I<br />

became an expert at causing GPFs). I’ll talk more about<br />

resource pooling later in this article.<br />

The promise comes true<br />

For several months now, I’ve been promising to show you<br />

how to easily reuse transactional components. MTS makes<br />

the promise come true. As I’ve shown with the account<br />

object, individual MTS transactional components never<br />

need to know their context. New components can be<br />

created that combine existing components into new<br />

transactions. The First National Bank of Dad will use an<br />

ATM component that can combine the Deposit() and<br />

Withdraw() methods of the existing account component to<br />

transfer allowance from one account to another with no<br />

modification to the account component.<br />

To do this, the Transfer() method of the ATM<br />

component uses the CreateInstance() method of its MTS<br />

context object to create each instance of the account<br />

component.<br />

*-- Obtain a reference to transaction context.<br />

oServer = CREATEOBJECT("MTXaS.Appserver.1")<br />

oContext = oServer.GetObjectContext()<br />

*-- Create an instance of account component.<br />

oAcct = oContext.CreateInstance("acct.account")<br />

When you create an instance of the account<br />

component with CreateInstance() instead of<br />

CREATEOBJECT(), you ensure that the account object<br />

inherits the transaction context of the calling object. The<br />

account component doesn’t need to know this, however.<br />

It knows how to get a reference to the transaction context;<br />

MTS takes care of the rest.<br />

Here’s what the Transfer() method of the ATM<br />

component looks like:<br />

*-- Obtain a reference to transaction context.<br />

oServer = CREATEOBJECT("MTXaS.Appserver.1")<br />

oContext = oServer.GetObjectContext()<br />

*-- Create an instance of account component.<br />

oAcct = oContext.CreateInstance("acct.account")<br />

nSuccess = oAcct.Deposit(nAccount1, nAmount)<br />

IF nSuccess = ERROR_SUCCESS<br />

*-- Create an instance of account component.<br />

oAcct = oContext.CreateInstance("acct.account")<br />

nSuccess = oAcct.Withdraw(nAccount2, nAmount)<br />

IF nSuccess = ERROR_SUCCESS<br />

*-- Deposit and withdrawal successful.<br />

oContext.SetComplete()<br />

ELSE<br />

*-- Withdrawal failed.<br />

oContext.SetAbort()<br />

ELSE<br />

*-- Deposit failed.<br />

oContext.SetAbort()<br />

ENDIF<br />

What’s going on here? First, remember that the<br />

Deposit() and Withdraw() methods of the account<br />

component simply call the Post() method after ensuring<br />

that the amount is positive for deposits and negative for<br />

withdrawals. Otherwise, the account component is being<br />

used unchanged in a new context. Notice that I deposit<br />

money in one account before withdrawing from the other.<br />

I did this deliberately to illustrate the point that it makes<br />

no difference what order components are used. If one<br />

method calls SetAbort(), then everything fails and is<br />

rolled back together. After running this code a few times,<br />

I firmly believe in magic.<br />

More on resources<br />

A few more hands have gone up. Why, you ask, did I<br />

create two instances of the account object—using the<br />

same reference memvar, no less—rather than reuse the<br />

same instance?<br />

One of the side effects of using a context object’s<br />

SetComplete() or SetAbort() method is that MTS will<br />

release the object as soon as the method calling<br />

SetComplete() or SetAbort() ends. This is what might be<br />

termed the MTS garbage collection. In the case of the<br />

account component, its resources are freed up as soon<br />

as the call to Deposit() or Withdraw() returns. The<br />

disadvantage to this is that you have to create another<br />

instance when you want to call another method. But<br />

there’s an advantage, too: MTS frees resources sooner,<br />

so they can be used again.<br />

Here’s an example of the account component’s<br />

GetBalance() method. Note that it makes no changes to<br />

the database, but calls SetComplete() anyway to ensure<br />

Continues on page 17<br />

14 FoxTalk June 1998<br />

http://www.pinpub.com


Use the Microsoft Scripting<br />

Control to Script with VFP<br />

Rod Paddock<br />

Have you ever wanted to add a programming language to<br />

your Visual FoxPro applications? If you answered yes, you’ve<br />

come to the right place. Microsoft has released an <strong>ActiveX</strong><br />

control for adding programming languages to your VFP<br />

applications—the Microsoft scripting control.<br />

VISUAL FoxPro supports two constructs for adding<br />

custom scripting to your applications. You can use<br />

Macro expansion—the & operator—or the EVAL()<br />

function. The problem with these constructs is they limit<br />

you to using simple commands and expressions. You can’t<br />

use control structures like IF/ENDIF, DO/LOOP, FOR/<br />

NEXT, or calling subroutines. This is where VFP’s native<br />

capabilities end and the advantages of the scripting<br />

control begins.<br />

Where to find the Microsoft scripting control<br />

The first step in using the Microsoft scripting control<br />

is to download and install it—it can be found at<br />

www.microsoft.com/scripting. Once you’ve installed the<br />

scripting control, add an instance of the control to a form,<br />

and get ready to rock.<br />

A quick note about a great utility . . .<br />

A utility program called CodeBlock by Randy Pearson<br />

was created a few years ago to address the limitation of<br />

combining macro expansion capabilities with looping<br />

constructs like DO WHILE and FOR/NEXT loops.<br />

Essentially, Randy built a runtime interpreter for FoxPro.<br />

With the exception of those commands that aren’t allowed<br />

outside the FoxPro IDE—such as COMPILE—just about<br />

any other block of code is allowed. The program allows<br />

you to store code in memo fields—and have that code<br />

executed on an interpreted basis—as if it were compiled.<br />

So if you have FoxPro or VFP applications that you’d like<br />

to equip with this capability, check out CodeBlock. A copy<br />

is included in this month’s Subscriber Downloads at<br />

www.pinpub.com/foxtalk.<br />

However, if you’re interested in adding new language<br />

capabilities like VBScript or JavaScript to your VFP<br />

applications, then read on. (Tip: If you’re interested in<br />

acquiring a VBScript reference, take a look at this URL:<br />

http://www.microsoft.com/scripting/default.htm?/<br />

FoxTalk<br />

scripting/vbscript/default.h. Or, if you already own<br />

Visual Studio, take a look at the Help files that come<br />

with Visual Interdev—it includes VBScript and<br />

JavaScript references.)<br />

Scripting control basics<br />

Using the scripting control in its basic form only requires<br />

four steps:<br />

1. Add an instance to a form.<br />

2. Set the language property. <strong>Your</strong> choices of languages<br />

are VBScript or JScript.<br />

3. Add the code you want to execute to the control using<br />

the AddCode method.<br />

4. Execute the code using the Execute Statement method<br />

of the control.<br />

The following code creates a basic message box<br />

function using VBScript:<br />

#DEFINE CRLF chr(10) + chr(13)<br />

*-- Set the language<br />

Thisform.oleScriptingControl.Language = "VBScript"<br />

*-- Add a function<br />

Thisform.oleScriptingControl.AddCode([Function Test] ;<br />

+ CRLF + [msgbox "hello"] ;<br />

+ CRLF + [ End Function ])<br />

*-- Execute the function<br />

Thisform.oleScriptingControl.ExecuteStatement("Test")<br />

That seems pretty straightforward, doesn’t it? Here’s<br />

how the code works: The first step to using the scripting<br />

control is to set the controls language property. You have<br />

two choices: VBScript and JScript. If you specify the<br />

language property as VBScript, the code you add with the<br />

AddCode method must be VBScript (a subset of Visual<br />

Basic for Applications). If you specify JScript, the code<br />

added must be in JavaScript.<br />

Next, use the AddCode method to load the control<br />

with source code to execute. The AddCode method<br />

accepts one parameter: a string that contains multiple<br />

lines of code delimited by CR+LF characters.<br />

Finally, run your code using either the Run or<br />

ExecuteStatement methods. The Run method executes<br />

functions or subroutines. The ExecuteStatement method<br />

http://www.pinpub.com FoxTalk June 1998<br />

15


executes a single statement (like “x = 1”) or a simple call<br />

to a function. There’s one aspect of this method that’s<br />

rather tedious—the requirement for you to code manually,<br />

delimiting each programming line with CR+LF sequences.<br />

As a result, wouldn’t it be convenient to have a<br />

mechanism similar to the VFP command window? I<br />

thought so, so I created a Scripting Control testbed.<br />

Figure 1 displays my Visual Basic command window<br />

style interface.<br />

Manipulating FoxPro objects with VBScript<br />

Now for something really cool. One of the most powerful<br />

aspects of the scripting control is its ability to manipulate<br />

VFP (or Visual Basic) native objects. Calling the<br />

AddObject method of the scripting control does this. Yes,<br />

the scripting control has a method called AddObject, so<br />

you can add an object to the scripting object container.<br />

The syntax for this method consists of two parameters.<br />

The first is the name that the object will be referenced by<br />

in your VBScript or JScript code. The second parameter is<br />

a handle to your object.<br />

To add a form to the scripting control, use the<br />

following syntax:<br />

thisform.oleScriptingControl.Object.AddObject( ;<br />

"frmFoxProForm",Thisform)<br />

One item you’ll quickly notice is the inclusion of the<br />

Object keyword. VFP provides this property to deal with<br />

<strong>ActiveX</strong> controls that share similar properties and<br />

methods with VFP objects. If you attempt to call the<br />

AddObject method without the Object keyword, VFP will<br />

address the FoxPro version of the AddObject method and<br />

not the AddObject method of the scripting control.<br />

Upon adding a reference to your VFP object, you can<br />

begin addressing the object in your VBScript using the<br />

first parameter of the AddObject method. The following<br />

code demonstrates changing properties on a VFP form<br />

Figure 1. This form demonstrates the capabilities of the Microsoft<br />

scripting control.<br />

using VBScript:<br />

Function SetFormProperties()<br />

If IsObject(frmFoxProForm) Then<br />

frmFoxProForm.Caption = "Hello World"<br />

frmFoxProForm.BackColor = 100000<br />

End If<br />

End Function<br />

This example demonstrates setting properties on a<br />

VFP object. You can also call VFP methods. The following<br />

code shows how to call the click event of a button on the<br />

scripting form:<br />

Function CallFormMethod()<br />

If IsObject(frmFoxProForm) Then<br />

frmFoxProForm.BackColor = 400000<br />

frmFoxProForm.cmdClickTest.Click()<br />

End If<br />

End Function<br />

Scripting “gotchas”<br />

When developing applications using the Microsoft<br />

scripting control, there are some “gotchas” you need to<br />

take into account. The first deals with adding VFP objects<br />

to the scripting control using the AddObject method. It’s<br />

important to make sure the scripting control releases your<br />

objects when it’s done with them. You can release your<br />

VFP controls by calling the scripting control’s Reset<br />

method. If you don’t use the Reset method, VFP might<br />

get a visit from Dr. Watson when you release the form<br />

with the scripting control on it. The best place to put the<br />

Reset call is in the QueryUnload event of the scripting<br />

control’s form.<br />

While I’m talking about Dr. Watson, there’s another<br />

item to take into account. If you use <strong>ActiveX</strong> controls in<br />

your applications, you need to turn FoxPro’s <strong>ActiveX</strong><br />

Dual Interface option off. You do this by putting the<br />

following call in either your main program or in the Load<br />

method of your form:<br />

*-- Turn <strong>ActiveX</strong> Dual Interface Option Off<br />

SYS(2333,0)<br />

If you don’t do this, FoxPro won’t release your<br />

forms correctly.<br />

Finally, for good measure, it’s always a good idea to<br />

call the scripting control’s Reset method after executing<br />

code. The reason for this is that the control has a tendency<br />

to become unstable if you call the AddCode method<br />

repeatedly.<br />

Conclusion<br />

Now that you have a basic understanding of using the<br />

scripting controls, you might say, “So what good does this<br />

do me?” Here are some uses I’ve already found handy:<br />

• Add your own macro language to your applications.<br />

(Hint: Use a memo field for storing code.)<br />

• Adopt existing Visual Basic or JavaScript code, like<br />

16 FoxTalk June 1998<br />

http://www.pinpub.com


functions stored in Active Server Pages.<br />

• Even use an alternate language for building your<br />

FoxPro applications.<br />

You’re only limited by your creativity when it comes<br />

to adding scripting to your applications.<br />

Finally, I’d like to thank two people for assisting me<br />

with this article: Ken Levy, for inspiring it in the first<br />

place, and John Petersen, for making sure my article<br />

Transactions: Tahoe and MTS . . .<br />

Continued from page 14<br />

that resources get released:<br />

*-- Obtain a reference to transaction context.<br />

oServer = CREATEOBJECT("MTXaS.Appserver.1")<br />

oContext = oServer.GetObjectContext()<br />

*-- Open database and view.<br />

OPEN DATABASE Test<br />

USE Account<br />

*-- Check the balance.<br />

nBalance = account.balance<br />

*-- Complete transaction.<br />

oContext.SetComplete()<br />

CLOSE DATABASES ALL<br />

RETURN nBalance<br />

“Gotchas” galore<br />

Those of you who attend the 1998 Visual FoxPro DevCon<br />

might notice that my beard is a little grayer this year than<br />

last. Working with new features of beta software when<br />

there’s nobody to go to with questions sometimes seems<br />

to take many years off your life. Furthermore, in a<br />

distributed environment such as I’ve described, it can be<br />

tough to know which product’s documentation to refer to<br />

when you get an error. I hope you’ll benefit from my own<br />

accelerated aging process.<br />

MTS components can have no user interface.<br />

They can’t use commands like WAIT WINDOW or<br />

MESSAGEBOX(). Nor can ODBC connections open<br />

ODBC login screens. When you create connections for<br />

remote views, be certain to use DBSETPROP() to set the<br />

connection’s DispLogin property to 3 to ensure that the<br />

user is never prompted.<br />

MTS components are always in-process servers. In the<br />

past, there was no difference in VFP between multi-use inprocess<br />

servers and single-use in-process servers. Tahoe<br />

OLEPUBLIC classes that will be used in MTS must be set<br />

for multi-use instancing.<br />

Debugging servers running in MTS can be a little<br />

tricky to debug. The process goes something like this:<br />

Create DLL, create MTS package, run code, crash. So then<br />

would be useful for other VFP developers. ▲<br />

06PADDOC.ZIP at www.pinpub.com/foxtalk<br />

Rod Paddock is president of Dash Point Software, Inc., which is<br />

based in Seattle, and specializes in developing applications using Visual<br />

Interdev, VFP, VB, and SQL Server. DPSI was a finalist in the 1998 Visual<br />

FoxPro Excellence Awards. Rod is currently working with John Petersen<br />

on his new book, Visual FoxPro 6 Enterprise Development<br />

(Prima Publishing). www.dashpoint.com, rpaddock@dashpoint.com.<br />

you make a change in your code and attempt to rebuild<br />

the DLL. But the DLL is in use and can’t be overwritten.<br />

The problem is that MTS keeps components in memory<br />

for faster reuse. In the Transaction Server Explorer, you<br />

can select the correct computer, right-click, and choose the<br />

Shutdown Server Processes option. If you try to build the<br />

DLL again and get the same error, you might want to<br />

resort to using the Task Manager to end the processes<br />

instead. Open the Task Manager and look for “MTX.EXE”<br />

on the Processes page. You might see it in the list more<br />

than once. Select it and click the End Process button.<br />

Don’t try this except on development machines.<br />

Don’t forget that your in-process servers aren’t<br />

running in your application’s process, they’re running in<br />

an MTS process. Any environment settings you’ve made<br />

in your application don’t apply in the MTS process. I aged<br />

seven years just because of a file-in-use error due to my<br />

failure to remember to SET EXCLUSIVE OFF.<br />

More to come<br />

No, I’m not planning another article in this series, but be<br />

sure to look for Jim Falino’s upcoming how-to article that<br />

discusses the specific implementation tricks and tips.<br />

I’m very excited about the possibilities presented by<br />

Tahoe and MTS. Although there’s much room for<br />

improvement, the combination offers many advantages<br />

to client/server application developers today. And<br />

when SQL Server 7 is available later this year with its<br />

embeddable SQL Desktop, those advantages will be<br />

available for all applications. There will be nothing to stop<br />

me from delivering the most powerful, robust database<br />

applications to all my clients, whether they have a<br />

hundred workstations or one laptop. ▲<br />

Gary DeWitt is software magician at Sunpro, Inc., the leader in fire service<br />

software. He lives on the California shore of Lake Tahoe and is convinced<br />

the next version of Microsoft Visual FoxPro, code-named Tahoe, was<br />

named with him in mind. While Visual FoxPro is his favorite development<br />

tool, he also programs in C++, Java, and Visual Basic. Gary is a Microsoft<br />

Certified Professional and Microsoft Most Valuable Professional.<br />

gdewitt@sierra.net.<br />

http://www.pinpub.com FoxTalk June 1998<br />

17


Synchronized Swimming<br />

Paul Maskens and Andy Kramek<br />

This month, Paul and Andy show how to make multiple forms<br />

with private Datasessions navigate to the same record.<br />

Andy: Here’s a nice little problem, Paul. I’ve got a form<br />

(running in a private DataSession) that’s used to display<br />

saved Visual FoxPro forum messages, and obviously I<br />

want to be able to search the underlying table to find a<br />

particular message. This search might be by Subject, by<br />

Author Name, by Thread number, and so on. I just don’t<br />

know which will be needed at any particular moment. I<br />

could simply use a popup form with, say, a grid on it,<br />

select the message I want, pass its record number back to<br />

the main form, and then do a LOCATE and REFRESH<br />

there. However, it seemed to me that linking the two<br />

forms in some way would be a cleaner (and, more<br />

importantly, more generic) approach.<br />

My first thought was to have the forms share a<br />

common DataSession—after all, that’s pretty easy to do<br />

when the child form is modal. You just set its DataSession<br />

to 1 (Default), and it will inherit its DataSession from the<br />

parent form.<br />

Paul: Yeah, but that’s undocumented behavior, isn’t it?<br />

Can we really rely on it? And doesn’t it also cause<br />

some problems with DataSession names? Despite the<br />

DataSession name getting lost, this technique worked in<br />

VFP 3. But I’ll have to retest it in 5.0 and 6.0 if I ever<br />

recompile that application.<br />

Andy: Well, it’s true that VFP does seem to lose track of<br />

the DataSession name upon returning from a child form<br />

set up in this way—it just shows “Unknown” as the<br />

DataSession name. But it doesn’t seem to affect anything.<br />

As for relying on behavior that’s undocumented—you<br />

have a point. Remember—back in 3.0—the “Empty” base<br />

class that’s used when you do a “SCATTER NAME”? That<br />

was undocumented and exposed in VFP 3.0, but it<br />

disappeared without warning in 3.0b!<br />

On second thought, better to make the child form<br />

switch itself to the parent form’s DataSession. We can do<br />

that by passing the parent form’s DataSessionID property<br />

to the child form like this:<br />

DO FORM frmChild WITH ThisForm.DataSessionID<br />

In the receiving form, just set the DataSessionID property<br />

in the Init() method, like this:<br />

LPARAMETERS tnDSID<br />

ThisForm.DataSessionID = tnDSID<br />

The Kit Box FoxTalk<br />

Paul: Oh, great! But what happened to my bound controls<br />

when I did that? After all, they’ve already been initialized<br />

in the child form’s DataSession—now I’m switching it! Is<br />

that what made all my controls lose their controlsources?<br />

Andy: Hmm. A good point. Any bound controls will<br />

irrevocably lose their binding. I suppose we could reset<br />

them all in the Init() (or a custom method called by<br />

the Init())?<br />

Paul: Yes, that would work. However, I prefer a method<br />

called by the Init, although this would be a bit of a pain if<br />

you had a pageframe with 40 controls on each of three<br />

pages! I suppose you could go through the .controls[]<br />

collection and reset the controlsource properties, but there<br />

has to be a better way than recursion into every container<br />

you have on your form.<br />

Andy: Okay, wise guy—what do you suggest, then?<br />

Paul: Pass the Primary Key of the current record between<br />

the forms! However, instead of diving in and creating my<br />

own methods to send and receive the Primary Key, I<br />

cheated. I used Drag and Drop to link two forms!<br />

On the “sending” form, I had a command<br />

button that assigned the table’s Primary Key (well,<br />

ALLTRIM(STR(ID), actually) to its caption as I moved the<br />

mouse over it. Then I set the DragMode of the button to<br />

Automatic in the Properties window.<br />

The “receiving” form looked at the caption of the<br />

object it received in its DragDrop method and then did a<br />

SEEK() on VAL() of that object’s caption.<br />

So I had two forms that were sort of loosely coupled.<br />

The receiving form could navigate separately because it<br />

used a private DataSession. The sending form would<br />

always navigate separately, again with a private<br />

DataSession. Just drag the button from the sending<br />

form to the receiving form—voila! Record pointers<br />

synchronized . . .<br />

Andy: That’s pretty neat! It does depend on user<br />

intervention, though, doesn’t it? I mean, what happens if<br />

they close the child form without dragging the button to<br />

it? Not much, I suppose?<br />

Paul: Absolutely right! You see, I intended this mechansim<br />

to be a way of linking an overview to a detail form when<br />

required, not permanently. You got me thinking, though.<br />

18 FoxTalk June 1998<br />

http://www.pinpub.com


It’s only a moment’s work to add a “LINK” check box,<br />

and another moment to use Refresh() to set the cmdLink<br />

caption instead of waiting for the mouse. Refresh() is<br />

called after record navigation to update the contents of all<br />

the controls. Next, add a test in the sending form’s<br />

Refresh() like so:<br />

IF chkLink.value = .t.<br />

Form2.DragDrop(THISFORM.cmdLink).<br />

ENDIF<br />

Andy: Whoa! Slow down a bit there, you’ve lost me.<br />

Explain how these two forms link themselves again—<br />

how does one form “know” about the other one?<br />

Paul: Okay, I know the name of Form2, right? So I can<br />

loop through the _SCREEN.Forms[] collection looking for<br />

the Name. I could have used the Caption property, or<br />

Comment, or Tag—Name seems easiest.<br />

When I find it, I can fake a DragDrop event dropping<br />

on Form2 by calling the DragDrop() method with<br />

parameters that look like a real DragDrop has happened.<br />

You know that events happen. What VFP does when<br />

an event happens is to run the code in the method with<br />

the same name to handle any special actions when the<br />

event happens. So if I call that method directly, the<br />

program can’t tell the difference.<br />

I set the command button’s caption, then call this:<br />

_SCREEN.Forms[i].DragDrop(THISFORM.cmdDrag,0,0)<br />

to fool VFP into running my navigation code in the form.<br />

LPARAMETERS oSource, nXCoord, nYCoord<br />

SELECT (THISFORM.MainTable)<br />

lnOldRec = RECNO()<br />

SEEK VAL(oSource.caption)<br />

ENDIF<br />

I made it a little more complex, because my program<br />

fakes DragDrop with coordinates of 0,0, and I assume<br />

anything other than that is a real drop. So the focus should<br />

be set to THISFORM—Form2.<br />

Without any extra custom methods and with only one<br />

object (perhaps an invisible Label), you could link two<br />

forms together. Yes, before you say it, I think properly<br />

named methods are a good idea. I’m overloading<br />

DragDrop to make it do something it isn’t meant to do.<br />

However, a better way is to have a set of interacting<br />

objects in a Mediator-Colleague pattern.<br />

Andy: Huh? What does that mean?<br />

Paul: The Mediator receives messages and sends them to<br />

every Colleague it knows about, except the sender. The<br />

Colleague registers with a Mediator and processes any<br />

message it receives.<br />

Andy: Ah, I see. So in our example, the parent form would<br />

originate a message and send it to the Mediator object,<br />

which in turn would simply pass it on to the objects it<br />

knows about. The child form is then responsible for<br />

registering with the Mediator so that it can receive<br />

(and presumably send) messages by that route. That’s<br />

pretty cool!<br />

Paul: Right (or possibly wrong ). In your Application<br />

object, you’d need to have a Register(toColleague)<br />

method where other objects sign up to receive messages.<br />

Any object that wants to receive messages has to have a<br />

HandleMessage() method.<br />

Andy: Hang on there, fellah—this all sounds a bit specific.<br />

I don’t use an Application object all the time. Wouldn’t<br />

it be better if you made your Mediator a class in its<br />

own right?<br />

Paul: I think so. This would mean all the pointers to the<br />

registered objects and the code that passes messages to<br />

them would be encapsulated in an object. That’s proper<br />

OOP. For now, though, I’d like to keep it simple and<br />

assume your Application object exists and is called<br />

goApp.<br />

Andy: Okay, I’ll buy that for now. So how does it all work<br />

in practice?<br />

Paul: Easy. In the Init(), your form class has a call to<br />

goApp.Register(THIS). The Application object’s<br />

Register(toColleague) method stores the object pointer<br />

in a 2-D array, along with SYS(1272,toColleague). That<br />

SYS() function gets the containership hierarchy and<br />

the name of the object—which will be needed later!<br />

So, to recap: goApp.aColleague[i,1] is the object,<br />

goApp.aColleague[i,2] is its name.<br />

<strong>Your</strong> Application object should also have a<br />

Mediate(tcMessage, toSender) method. This loops<br />

through the stored object array (why use i? It’s a sort<br />

of tradition ) and checks—using the<br />

SYS(1272,toSender)—to see whether the entry in<br />

.aColleague[i,2] matches. If it does, ignore it—you<br />

generally don’t want to send the message back<br />

to the sender. Otherwise, call the<br />

.aColleague[i,1].HandleMessage(tcMessage).<br />

That’s it!<br />

Andy: Aha! I get it now. That SYS(1272) call is working<br />

around the fact that we can’t compare objects with each<br />

other. So my Form class has to have an extra method<br />

HandleMessage() and a call (presumably conditional) in<br />

its Init() to the mediator Register(), like this:<br />

PROCEDURE INIT<br />

IF TYPE( 'goApp') = "O" AND ! ISNULL( goApp )<br />

goApp.Register( THIS )<br />

ENDIF<br />

But what goes into the HandleMessage() method?<br />

Paul: That depends on what messages you want the object<br />

to react to. Of course, you need to define some<br />

standards—like perhaps “SEEK:“.<br />

http://www.pinpub.com FoxTalk June 1998<br />

19


Then you can just use a CASE statement in the<br />

HandleMessage() method to define which messages to<br />

recognize:<br />

DO CASE<br />

CASE tcMessage = "SEEK:"<br />

*** React to this message<br />

lnPK = VAL( SUBSTR( tcMessage, 6 ))<br />

SEEK lnPK<br />

OTHERWISE<br />

*** Do nothing - ignore the message<br />

ENDCASE<br />

Andy: Just one thought. Haven’t we now got an external<br />

reference to an object in the Mediator? Doesn’t this mean<br />

that we can no longer release the object?<br />

Paul: Correct! So we also need an UnRegister() method<br />

that’s called from each Form’s Unload() method. Like<br />

Register(), goApp.UnRegister(THIS) would suffice.<br />

All it needs is to loop through .aColleague[i,1] again,<br />

testing SYS(1272, toSender) as before, but this time, on<br />

finding a match, just set .aColleague[I,2] to .NULL. so that<br />

the object reference is discarded.<br />

Oh, and about your desire for reusable classes? Here’s<br />

one I prepared earlier, in the best Blue Peter tradition . . .<br />

Class cusMediator is the Mediator half of the pattern.<br />

This can either be added to a form at design time, in<br />

which case it should be named “oMediator”, or it can be<br />

created as an uncontained object, in which case it should<br />

be referred to by a global variable goMediator.<br />

**************************************************<br />

*-- Class: cusMediator<br />

*<br />

#DEFINE _CR CHR(10)<br />

*<br />

DEFINE CLASS cusMediator AS custom<br />

*-- Number of registered colleague controls<br />

nColleagues = 0<br />

Name = "cusMediator"<br />

*-- Array of colleague control Hierarchy, oRef.<br />

DIMENSION acolleagues[1,2]<br />

*-- Called by colleague to register itself with<br />

*-- the Mediator<br />

PROCEDURE register<br />

LPARAMETERS toColleague<br />

WITH THIS<br />

.nColleagues = .nColleagues +1<br />

DIMENSION .aColleagues[ .nColleagues, 2]<br />

.aColleagues[ .nColleagues, 1] ;<br />

= SYS( 1272, toColleague )<br />

.aColleagues[ .nColleagues, 2] = toColleague<br />

ENDWITH<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT THIS.Name +" register()" +_CR ;<br />

+SYS( 1272, toColleague ) +_CR ;<br />

ELSE<br />

WAIT WINDOW THIS.Name +" register()" +_CR ;<br />

+SYS( 1272, toColleague ) +_CR ;<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

ENDPROC<br />

*-- Removes all object references to colleagues,<br />

*-- call before the Destroy() to prevent GPFs<br />

PROCEDURE cleanup<br />

LOCAL lnCount<br />

WITH THIS<br />

FOR lnCount = 1 TO .nColleagues<br />

.aColleagues[ lnCount, 1] = ""<br />

.aColleagues[ lnCount, 2] = .NULL.<br />

NEXT<br />

.nColleagues = 0<br />

ENDWITH<br />

ENDPROC<br />

*-- Receives Source object and message to<br />

*-- mediate (String)<br />

PROCEDURE mediate<br />

LPARAMETERS toColleague, tcMessage<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT THIS.Name +" mediate()" +_CR ;<br />

+tcMessage +_CR ;<br />

+SYS( 1272, toColleague )<br />

ELSE<br />

WAIT WINDOW THIS.Name +" mediate()" +_CR ;<br />

+tcMessage +_CR ;<br />

+SYS( 1272, toColleague );<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

RETURN THIS.SendMessage( toColleague, tcMessage )<br />

ENDPROC<br />

*-- Sends the message received by Mediate() to all<br />

*-- the colleagues registered EXCEPT the one<br />

*-- that sent it<br />

PROCEDURE sendmessage<br />

LPARAMETERS toColleague, tcMessage<br />

LOCAL lcColleague, lnColleague, llStatus<br />

WITH THIS<br />

lcColleague = SYS( 1272, toColleague )<br />

llStatus = .T.<br />

FOR lnColleague = 1 to .nColleagues<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT THIS.Name ;<br />

+"sendmessage("+allt(str(lnColleague))+")";<br />

+_CR ;<br />

+.aColleagues[ lnColleague, 1]<br />

ELSE<br />

WAIT WINDOW THIS.Name ;<br />

+"sendmessage("+allt(str(lnColleague))+")";<br />

+_CR ;<br />

+.aColleagues[ lnColleague, 1] ;<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

IF NOT( .aColleagues[ lnColleague, 1] ;<br />

== lcColleague )<br />

IF .aColleagues[lnColleague,2] ;<br />

.Receive(tcMessage)<br />

*<br />

* Do nothing, assumes all colleagues receive OK<br />

*<br />

ELSE<br />

*<br />

* Note colleague failed to handle message<br />

*<br />

llStatus = .F.<br />

ENDIF<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT THIS.Name ;<br />

+" sendmessage()" +_CR ;<br />

+.aColleagues[ lnColleague, 1] +_CR ;<br />

+IIF(llStatus,"OK","Fail")<br />

ELSE<br />

WAIT WINDOW ;<br />

THIS.Name +" sendmessage()" +_CR ;<br />

+.aColleagues[ lnColleague, 1] +_CR ;<br />

+IIF(llStatus,"OK","Fail") ;<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

NEXT<br />

ENDWITH<br />

20 FoxTalk June 1998<br />

http://www.pinpub.com<br />

*


* If any colleague failed to handle the message,<br />

* the status will be .F. and not .T. as set<br />

* earlier<br />

*<br />

RETURN llStatus<br />

ENDPROC<br />

PROCEDURE unregister<br />

LPARAMETERS toColleague<br />

LOCAL lnColleague, llRetVal<br />

WITH THIS<br />

lnColleague = ASCAN( .aColleagues, ;<br />

SYS( 1272, toColleague ))<br />

IF lnColleague != 0<br />

lnColleague = ASUBSCRIPT( .aColleagues, ;<br />

lnColleague, 1 )<br />

llRetVal = .t.<br />

.aColleagues[ lnColleague, 1] = ""<br />

.aColleagues[ lnColleague, 2] = .NULL.<br />

= ADEL( .aColleagues, lnColleague )<br />

DIMENSION .aColleagues[ .nColleagues, 2 ]<br />

ELSE<br />

llRetVal = .f.<br />

ENDIF<br />

ENDWITH<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT THIS.Name +" UnRegister()" +_CR ;<br />

+SYS( 1272, toColleague ) +_CR ;<br />

ELSE<br />

WAIT WINDOW THIS.Name +" UnRegister()" +_CR ;<br />

+SYS( 1272, toColleague ) +_CR ;<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

RETURN llRetVal<br />

ENDPROC<br />

ENDDEFINE<br />

*<br />

*-- EndDefine: cusMediator<br />

**************************************************<br />

Class cusColleague is the Colleague half of the<br />

pattern. This class can be added to any container, in which<br />

case the DoAction() method will probably do things to<br />

THIS.parent—I usually just call a method. I expect that<br />

the DoAction() method will be overridden in any instance<br />

or subclass.<br />

You could make it collaborate with any object, with a<br />

little modification—like passing THIS to the Init(toClient)<br />

method and storing that in a property of the Colleague for<br />

use in the DoAction() method.<br />

**************************************************<br />

*-- Class: cusColleague<br />

*<br />

#DEFINE _CR CHR(10)<br />

*<br />

DEFINE CLASS cusColleague AS Custom<br />

Name = "cusColleague"<br />

*-- Message is sent here by mediator, this passes<br />

*-- it on to DoAction() and returns the result.<br />

PROCEDURE Receive<br />

LPARAMETERS tcMessage<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT SYS(1272, THIS) +" receive()" +_CR ;<br />

+ tcMessage<br />

ELSE<br />

WAIT WINDOW ;<br />

SYS(1272, THIS) +" receive()" +_CR ;<br />

+tcMessage ;<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

RETURN THIS.DoAction( tcMessage )<br />

ENDPROC<br />

*-- Performs anything necessary to handle the<br />

*-- incoming message. Returns success/fail.<br />

PROCEDURE DoAction<br />

LPARAMETERS tcMessage<br />

*<br />

* This will of course be overridden in an instance<br />

* of this class!<br />

*<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT SYS(1272, THIS) +CHR(13) ;<br />

+"DoAction( "+ tcMessage +" )"<br />

ELSE<br />

WAIT WINDOW SYS(1272, THIS) +CHR(13) ;<br />

+"DoAction( "+ tcMessage +" )" ;<br />

TIMEOUT 2<br />

ENDIF<br />

RETURN<br />

ENDPROC<br />

*-- Given a message (String) it sends it off to<br />

*-- the mediator referenced through<br />

*-- THISFORM.oMediator.Mediate()<br />

PROCEDURE send<br />

LPARAMETERS tcMessage<br />

*<br />

* Send message via form mediator and return.<br />

*<br />

IF TYPE("THISFORM.oMediator") != "U"<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT SYS(1272, THIS) +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"Form Mediator"<br />

ELSE<br />

WAIT WINDOW ;<br />

SYS(1272, THIS) +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"Form Mediator";<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

RETURN THISFORM.oMediator.Mediate( ;<br />

THIS, tcMessage )<br />

ENDIF<br />

* Unless there isn't one!<br />

* Send message via global mediator and return.<br />

*<br />

IF TYPE("goMediator") != "U"<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT SYS(1272, THIS) +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"Global Mediator"<br />

ELSE<br />

WAIT WINDOW THIS.Name +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"Global Mediator";<br />

TIMEOUT 2<br />

ENDIF<br />

ENDIF<br />

RETURN goMediator.Mediate( THIS, tcMessage )<br />

ENDIF<br />

*<br />

* No mediators on standard interfaces, fail.<br />

*<br />

IF TYPE("glDebug") != "U"<br />

IF "Visual FoxPro 05" $ VERS()<br />

DEBUGOUT SYS(1272, THIS) +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"NO Mediator - FAILED"<br />

ELSE<br />

WAIT WINDOW THIS.Name +" send()" +_CR ;<br />

+tcMessage +_CR ;<br />

+"NO Mediator - FAILED";<br />

TIMEOUT 2<br />

ENDIF<br />

http://www.pinpub.com FoxTalk June 1998<br />

21


ENDIF<br />

RETURN .F.<br />

ENDPROC<br />

PROCEDURE Init<br />

*<br />

* Register with form mediator and return.<br />

*<br />

IF TYPE("THISFORM.oMediator") != "U"<br />

RETURN THISFORM.oMediator.Register( THIS, .F. )<br />

ENDIF<br />

* Unless there isn't one!<br />

* Register with global mediator and return.<br />

*<br />

IF TYPE("goMediator") != "U"<br />

RETURN goMediator.Register( THIS, .F. )<br />

ENDIF<br />

*<br />

* No mediators on standard interfaces, fail.<br />

* In failing this will prevent FORM<br />

* instantiation under VFP 3, but this object<br />

* will merely not exist under VFP 5<br />

*<br />

RETURN .F.<br />

ENDPROC<br />

PROCEDURE Destroy<br />

*<br />

* Remove from form mediator and return.<br />

*<br />

IF TYPE("THISFORM.oMediator") != "U"<br />

RETURN THISFORM.oMediator.UnRegister( THIS )<br />

ENDIF<br />

* Unless there isn't one!<br />

* Remove from global mediator and return.<br />

*<br />

IF TYPE("goMediator") != "U"<br />

RETURN goMediator.UnRegister( THIS )<br />

ENDIF<br />

*<br />

* No mediators on standard interfaces, fail.<br />

*<br />

Editorial: Enter Sandman . . .<br />

Continued from page 2<br />

so let’s do it. I mean, wouldn’t it be cooooool?” Who gives<br />

a rip if it should be done? They’ve got the business in a<br />

perpetual death grip of one-upmanship. Their processor<br />

runs at 410 MHz. Let’s make ours run at 450—and we’ll<br />

price it lower!<br />

I’m not saying I want to go back to dBASE II. We’ve<br />

had some wonderful advances that are a joy to work<br />

with. Visual FoxPro is still a thrill a minute (including the<br />

thrills you get when Dr. Watson comes to visit ).<br />

And the rumor is that Office will be using a single<br />

code-base worldwide next year. That’ll be a nice change,<br />

don’t you think?<br />

It’s the same in the music industry. If you’re going to<br />

follow bands these days, you have to be nimble. This<br />

month’s hot news is next month’s history. I’m reminded<br />

of the scene in the movie “Clueless,” where several<br />

teenage girls are checking out a jacket in the mall. One<br />

derides the jacket as “so five minutes ago.” The other<br />

decides not to buy. Fashionable today, boring tomorrow.<br />

RETURN .F.<br />

ENDPROC<br />

ENDDEFINE<br />

*<br />

*-- EndDefine: cusColleague<br />

**************************************************<br />

Paul: I’ve left it in my debugging code, which I control<br />

using the existence of the global variable glDebug. This is<br />

a hangover from VFP 3, where we had no debug output<br />

window, yet I wanted to control whether or not my debug<br />

output appeared. It’s not defined because I’m not usually<br />

debugging this code. If you want to see those messages,<br />

simply type “glDebug = .t.” and press Enter in the<br />

Command window.<br />

You’ll notice that I’ve adopted a belt-and-braces<br />

approach in the Colleague class. I’ve checked for both a<br />

global mediator object (goMediator) and a mediator object<br />

added to the current form (THISFORM.oMediator)<br />

because I wasn’t sure how it might be used. I haven’t<br />

added a third test for goApp.oMediator because I believe<br />

in using separate public variables for various managers.<br />

Practical OOP here, Andy.<br />

Andy: I’m speechless with admiration! ▲<br />

Paul Maskens is a VFP product specialist who works for the Visual<br />

Development Studio of Chelmsford, based in Oxford, England.<br />

pmaskens@compuserve.com.<br />

Andy Kramek is a long-time FoxPro developer, independent<br />

contractor, and occasional author based in Birmingham, England.<br />

andykr@solihull.bytenet.co.uk.<br />

So you’re going to have to be just as nimble to follow<br />

the software trends. Yesterday’s big news is today’s<br />

yawn. Don’t you feel a little cheated if you’ve made a big<br />

investment in last year’s alphabet soup of database access<br />

acronyms, and then did it again this year, only to find out<br />

it doesn’t all really work yet, and you’re going to have to<br />

do the learning curve again next year?<br />

Where I’m heading with this, though, is that the next<br />

couple of years are going to be decision time for a lot of<br />

us. People in my user group complain because we want to<br />

send out meeting notices via e-mail. “Sure, I have e-mail.<br />

But I only check it about once a month.” C’mon! As a<br />

friend of mine said (and as I’ve echoed in this column<br />

before), “If you’re not willing to make a professional<br />

investment in your career here, then run, don’t walk, to<br />

the exit of this industry.”<br />

I’m tired. Time to go home. I’m going to pull a cold<br />

one from the fridge, turn off all the lights, and listen to<br />

one of the all-time greats in heavy metal. Classics never<br />

go out of style, and neither will “Enter Sandman.” I’ll<br />

have more energy tomorrow. I’ll need it, to stay up with<br />

these young whippersnappers . . . ▲<br />

22 FoxTalk June 1998<br />

http://www.pinpub.com


Traversing Transactions . . .<br />

Continued from page 11<br />

IF NOT TableUpdate( … )<br />

llRollBack = .T.<br />

ELSE<br />

IF NOT TableUpdate( … )<br />

llRollBack = .T.<br />

ENDIF<br />

ENDIF<br />

ENDIF<br />

IF llRollBack<br />

ROLLBACK<br />

ELSE<br />

ENDTRANSACTION<br />

ENDIF<br />

The preceding code is pseudo-code, so don’t take it<br />

literally. Here I used a series of nested IF statements to<br />

control the actions. I could have put all of the aliases in an<br />

array and used a FOR/ENDFOR loop to go through them,<br />

exiting the loop when they were all done or when any one<br />

of them failed.<br />

What can go wrong?<br />

In the preceding pseudo-code, assume that the transaction<br />

failed and that you issued the ROLLBACK. What, exactly,<br />

do you roll back to? Are the buffers returned to their<br />

pre-edit state, or are the buffers still dirty with the<br />

user’s edits?<br />

The answer is that the buffers are restored to the<br />

dirty state that they were in prior to the BEGIN<br />

TRANSACTION. This means that you still have to deal<br />

with the user’s edits. You have the control here—you can<br />

put the user back into the form and let him or her decide<br />

what to do, or you can TableRevert all the aliases and<br />

discard the user’s work. This is all up to you in the code<br />

that you write.<br />

What happens if the computer shuts off during the<br />

transaction? Obviously, VFP has no record of the<br />

transaction when the system is restarted. So did some of<br />

the tables get updated or not? None of the tables got<br />

updated, none of the buffers are dirty, and the result is<br />

just as if a ROLLBACK had been executed.<br />

Another situation that’s occurred and caused a<br />

<strong>Tame</strong> <strong>Your</strong> <strong>ActiveX</strong> <strong>Controls</strong> . . .<br />

Continued from page 5<br />

acts as the placeholder for the TreeView control. The<br />

cNewObjectName properties for oTreeViewLoader<br />

and oImageListLoader specify the names of the controls<br />

to create (oTree and oImageList, respectively).<br />

oTreeViewLoader also has the name of the placeholder<br />

object (shpTreeView) in its cObjectName property and<br />

the name of the TreeView subclass we want to use<br />

(MyTreeView) and its location (ACLASSES.PRG) in the<br />

cClass and cLibrary properties. The Init method of the<br />

form sets some properties for the <strong>ActiveX</strong> controls,<br />

loading images in the case of the ImageList and nodes in<br />

the case of the TreeView.<br />

lot of problems is when the developer puts together a<br />

form and puts a BEGIN TRANSACTION in the Init and<br />

issues either an ENDTRANSACTION or a ROLLBACK<br />

in the destroy, depending on which button the user<br />

chose to exit the form. What problems could this cause?<br />

The first is that no other user can do anything with any<br />

of the tables involved in this form until the current user<br />

exits the form and releases the locks. I’ve seen this<br />

situation used as an argument against transactions—well,<br />

come on now, anything that’s used incorrectly can cause<br />

problems. Transactions used correctly will limit the<br />

time between the beginning and ending to the smallest<br />

period possible. The Init to Destroy of a form is<br />

certainly not the smallest time period possible—it’s<br />

a time period that’s not even under the control of<br />

the developer.<br />

It’s been said that VFP’s transactions aren’t as<br />

“robust” as the transaction handling of client/server<br />

database servers. I say, so what? Is the fact that a semi<br />

tractor has brakes that are more “robust” than the brakes<br />

on my car a reason to not use my car’s brakes? No, it’s<br />

not. VFP’s transactions are helpful, and they serve a<br />

purpose. They should be used, even when the data is<br />

stored in a client/server database.<br />

Conclusion<br />

Transactions in VFP are a very valuable feature of the<br />

product. They allow you to group a number of update<br />

operations into one “all or none” operation. In today’s<br />

world, you seldom build forms that only manage a single<br />

table—the multi-table form has become the norm.<br />

Managing these multi-table updates becomes a very<br />

important aspect of your work, and transactions are<br />

invaluable in doing this. ▲<br />

Jim Booth is a Visual FoxPro developer and trainer. He has spoken<br />

at FoxPro conferences in North America and Europe. Jim has been<br />

a recipient of the Microsoft Most Valuable Professional Award<br />

every year since it was first presented in 1993. 203-758-6942,<br />

72130.2570@compuserve.com.<br />

local loPicture<br />

* Load the ImageList images.<br />

with This.oImageList<br />

loPicture = loadpicture('AUDIO.ICO')<br />

.ListImages.Add(, 'Audio', loPicture)<br />

loPicture = loadpicture('DESKTOP.ICO')<br />

.ListImages.Add(, 'Desktop', loPicture)<br />

endwith<br />

* Set some TreeView properties.<br />

with This.oTree<br />

.ImageList = This.oImageList.Object<br />

.Style = 7<br />

.LineStyle = 1<br />

.LabelEdit = 1<br />

.HideSelection = .F.<br />

.Indentation = 25<br />

* Load the TreeView with sample nodes.<br />

.Nodes.Add(, 1, 'Top1', 'First Top Node', 'Audio')<br />

http://www.pinpub.com FoxTalk June 1998<br />

23


.Nodes.Add(, 1, 'Top2', 'Second Top Node', 'Audio')<br />

.Nodes.Add('Top1', 4, 'Child1', 'First Child Node', ;<br />

'Desktop')<br />

.Nodes.Add('Top1', 4, 'Child2', 'Second Child Node', ;<br />

'Desktop')<br />

.Nodes.Add('Top2', 4, 'Child3', 'Third Child Node', ;<br />

'Desktop')<br />

.Nodes.Add('Top2', 4, 'Child4', 'Fourth Child Node', ;<br />

'Desktop')<br />

endwith<br />

Summary<br />

<strong>ActiveX</strong> controls are both wonderful and terrible. They’re<br />

wonderful because they can give your applications the<br />

professional, polished look users expect from modern<br />

32-bit applications (the TreeView control that comes with<br />

VFP and the ctListBar control from dbi technologies inc.<br />

are good examples), or they can provide advanced<br />

capabilities that would either be impossible or very<br />

time-consuming to create in VFP code (the DynamiCube<br />

control I discussed in my March 1998 column is an<br />

Downloads<br />

June Subscriber Downloads<br />

• 06DHENSC.ZIP—Source code described in <strong>Doug</strong> <strong>Hennig</strong>’s<br />

article. SFCTRLS.VCX and SFACTIVEX.VCX are class libraries<br />

used to support TREEVIEW.SCX, a sample form that shows<br />

how to add <strong>ActiveX</strong> controls at runtime. SFREGISTRY.VCX<br />

provides access to the Windows Registry.<br />

• 06PADDOC.ZIP—Source code for two items discussed in Rod<br />

Paddock’s article. CODEBLCK.PRG is Randy Pearson’s Code<br />

Block command window replacement. SCRIPTING.SCX is a<br />

sample form that demonstrates how to use the scripting<br />

control in VFP.<br />

Extended Articles<br />

• 06COOL.HTM—Extended Article. How do you keep current<br />

Help is a Mouse-Click Away: Introducing Developer Solutions–Online at www.pinpub.com<br />

When a programming crisis or dilemma comes up, you need<br />

dependable solutions fast. Technical support calls are eating your<br />

time and costing hundreds of dollars—even when you don’t get<br />

solid answers from them. In response to your needs, we’re proud<br />

to announce a new online search mechanism to help you find the<br />

information you need when you need it.<br />

Check out the continually updated Pinnacle Publishing<br />

index of expert-written articles at www.pinpub.com (tell your<br />

colleagues!). You’ll be able to access each monthly issue of<br />

FoxTalk, SQL Server Professional, Smart Access, and Visual Basic<br />

Developer from 1996 through the present. You can view the entire<br />

online Table of Contents of a newsletter or enter a search term to<br />

pinpoint all of the available related articles. The search is free, tips<br />

are free, and articles (including any corresponding Subscriber<br />

Download files) can be downloaded for a nominal fee.<br />

Here’s how it works: Go to www.pinpub.com and click on<br />

Developer Solutions—Online. Enter the search term of your<br />

Portions of the FoxTalk Web site are available only to paid<br />

subscribers of FoxTalk. Subscribers have access to additional<br />

resources to give you an edge in FoxPro development.<br />

example of that). They’re terrible because when “OLE<br />

class not registered” or other OLE errors occur, you can’t<br />

just roll up your sleeves and use the VFP Debugger to<br />

track down the problems. SF<strong>ActiveX</strong> has been a life saver<br />

for me; it handles the main problem I have with <strong>ActiveX</strong><br />

controls. I’m sure you’ll find it as useful as I have. ▲<br />

06DHENSC.ZIP at www.pinpub.com/foxtalk<br />

<strong>Doug</strong> <strong>Hennig</strong> is a partner with Stonefield Systems Group Inc. in Regina,<br />

Saskatchewan, Canada. He is the author of Stonefield’s add-on tools for<br />

FoxPro developers, including Stonefield Database Toolkit and Stonefield<br />

Query. He is also the author of The Visual FoxPro Data Dictionary in<br />

Pinnacle Publishing’s The Pros Talk Visual FoxPro series. <strong>Doug</strong> has spoken<br />

at the 1997 and 1998 Microsoft FoxPro Developers Conferences<br />

(DevCon), as well as user groups and regional conferences all over North<br />

America. He is a Microsoft Most Valuable Professional (MVP).<br />

75156.2326@compuserve.com, dhennig@stonefield.com.<br />

with everything that’s happening to the wide array of tools<br />

you’re using? Woody’s Office Watch is a free weekly e-mail<br />

that covers everything that’s happening with Microsoft<br />

Office. Also covered: Directory Toolkit, a shareware tool that<br />

pulls together a number of features that you’d like to see in<br />

Windows Explorer.<br />

• 06COOL.ZIP—Contains SETUPDT.EXE, the installation file for<br />

Directory Toolkit, a shareware utility described in this<br />

month’s Cool Tool column.<br />

• 06PETER.HTM—Extended Article. This month, John Petersen<br />

addresses the topic of where to place your Automation<br />

code: in the VFP application, in the Office application, or, in<br />

the case of Tahoe, in a VFP COM object.<br />

choice to access any of the material we’ve published on<br />

this topic. Alternatively, you can click on “Contents” on the upperleft<br />

side of the screen to list any of the published material by<br />

issue date.<br />

Possible uses:<br />

• Use the search function to find an article you can’t locate<br />

in your back issues.<br />

• Find all of our published articles on a given topic.<br />

• Use the pay-per-view option to access articles published<br />

before your subscription started by following the online<br />

sign-on instructions. (You’ll be able to access the download<br />

file as well.)<br />

• Browse the “Contents” portion for summaries of articles and<br />

free tips that have been published in FoxTalk and our other<br />

publications.<br />

Coming soon: Online subscription options!<br />

User ser name<br />

Passw assw asswor or ord<br />

kerosene<br />

lantern<br />

24 FoxTalk June 1998<br />

http://www.pinpub.com

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!