FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I...

24
1 Tame Your ActiveX Controls Doug Hennig 2 Editorial: Enter Sandman Whil Hentzen 6 Best Practices: Development Checklist: Requirements Analysis Jefferey A. Donnici 11 What’s Really Inside: Traversing Transactions Jim Booth 12 Transactions: Tahoe and Microsoft Transaction Server Gary DeWitt 15 Use the Microsoft Scripting Control to Script with VFP Rod Paddock 18 The Kit Box: Synchronized Swimming Paul Maskens and Andy Kramek 24 June Subscriber Downloads EA Cool Tool: The Best Tool is Knowledge Whil Hentzen Extended Article available at www.pinpub.com/foxtalk EA ActiveX and Automation Review: Where Should Your Automation Code Reside? John V. Petersen Extended Article available at www.pinpub.com/foxtalk June 1998 Volume 10, Number 6 Fox Talk Continues on page 3 Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers Tame Your ActiveX Controls Doug Hennig ActiveX controls are forward, but not backward, compatible. This means that if you install a newer version of an OCX on your system, any applications you ship to people who have an older version installed will break. This article presents a reusable tool that solves this problem in a simple, easy-to-use manner. H OW was the period between Christmas and New Year’s for you? I hope it was relaxing. For me, it was awful. The reason: I posted an update to Stonefield Database Toolkit (SDT) on our Web site just before Christmas. Anyone who downloaded and installed it immediately got an “OLE class not registered” error when trying to use SDT. I fielded a ton of support calls and e-mails the week after Christmas and had to come up with a fast solution to solve the problem for those folks and prevent even more calls later. What caused this problem? The main SDT form uses a TreeView control, and I had installed some software on my system that installed a newer version of COMCTL32.OCX, the OCX file containing the TreeView control. Even though I didn’t change the TreeView on the form, simply opening the form and saving it caused information about the newer control to be written to the form. Of course, very few others had this newer control on their machines, so even though the form wanted a TreeView control and they had a TreeView control, it wasn’t the exact control the form was looking for. Hence, the “OLE class not registered” error. Is it just me, or do you think this is incredibly lame too? I don’t care about different versions of the TreeView. I’d understand if I used new properties or methods of this control that didn’t exist in older versions, but as I mentioned, I didn’t even touch it in my copy of the form. I guess I only have myself to blame for being in crisis mode after Christmas. After all, I’d encountered this behavior before when I upgraded from VFP 5.0 to 5.0a; it came with an updated COMCTL32.OCX, and everyone 6.0 6.0

Transcript of FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I...

Page 1: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

1 Tame Your ActiveX ControlsDoug Hennig

2 Editorial: Enter SandmanWhil Hentzen

6 Best Practices:Development Checklist:Requirements AnalysisJefferey A. Donnici

11 What’s Really Inside:Traversing TransactionsJim Booth

12 Transactions: Tahoe andMicrosoft Transaction ServerGary DeWitt

15 Use the Microsoft ScriptingControl to Script with VFPRod Paddock

18 The Kit Box:Synchronized SwimmingPaul Maskens and Andy Kramek

24 June Subscriber Downloads

EA Cool Tool: The Best Toolis KnowledgeWhil HentzenExtended Article available atwww.pinpub.com/foxtalk

EA ActiveX and AutomationReview: Where Should YourAutomation Code Reside?John V. PetersenExtended Article available atwww.pinpub.com/foxtalk

June 1998Volume 10, Number 6

FoxTalk

Continues on page 3

Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers

Tame YourActiveX ControlsDoug Hennig

ActiveX controls are forward, but not backward, compatible. This means that ifyou install a newer version of an OCX on your system, any applications you shipto people who have an older version installed will break. This article presents areusable tool that solves this problem in a simple, easy-to-use manner.

HOW was the period between Christmas and New Year’s for you? I hopeit was relaxing. For me, it was awful. The reason: I posted an updateto Stonefield Database Toolkit (SDT) on our Web site just before

Christmas. Anyone who downloaded and installed it immediately got an“OLE class not registered” error when trying to use SDT. I fielded a ton ofsupport calls and e-mails the week after Christmas and had to come up witha fast solution to solve the problem for those folks and prevent even morecalls later.

What caused this problem? The main SDT form uses a TreeView control,and I had installed some software on my system that installed a newer versionof COMCTL32.OCX, the OCX file containing the TreeView control. Eventhough I didn’t change the TreeView on the form, simply opening the formand saving it caused information about the newer control to be written to theform. Of course, very few others had this newer control on their machines, soeven though the form wanted a TreeView control and they had a TreeViewcontrol, it wasn’t the exact control the form was looking for. Hence, the “OLEclass not registered” error.

Is it just me, or do you think this is incredibly lame too? I don’t care aboutdifferent versions of the TreeView. I’d understand if I used new properties ormethods of this control that didn’t exist in older versions, but as I mentioned, Ididn’t even touch it in my copy of the form.

I guess I only have myself to blame for being in crisis mode afterChristmas. After all, I’d encountered this behavior before when I upgradedfrom VFP 5.0 to 5.0a; it came with an updated COMCTL32.OCX, and everyone

6.06.0

Page 2: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

From the Editor FFFoooxxxTalk

2 http://www.pinpub.com

Editorial andSubscriptionInformation770-565-1763800-788-1900

[email protected]

Pinnacle Web Sitehttp://www.pinpub.com

Fax770-565-8232

MailPO Box 72255Marietta, GA30007-2255

FoxTalk (ISSN 1042-6302) is published monthly (12 times per year)by Pinnacle Publishing, Inc., 1503 Johnson Ferry Road, Suite 100,Marietta, GA 30062. The subscription price of domesticsubscriptions is: 12 issues, $179; 24 issues, $259. Periodical postagepaid at Marietta, GA and at additional mailing offices. USPS#005373.POSTMASTER: Send address changes to FoxTalk, PO Box 72255,Marietta, GA 30007-2255.

Copyright © 1998 by Pinnacle Publishing, Inc. All rights reserved. Nopart of this periodical may be used or reproduced in any fashionwhatsoever (except in the case of brief quotations embodied incritical articles and reviews) without the prior written consent ofPinnacle Publishing, Inc. Printed in the United States of America.

Brand and product names are trademarks or registered trademarksof their respective holders. Microsoft is a registered trademark ofMicrosoft Corporation. The Fox Head logo, FoxBASE+, FoxPro, andVisual FoxPro are registered trademarks of Microsoft Corporation.FoxTalk is an independent publication not affiliated with MicrosoftCorporation. Microsoft Corporation is not responsible in any way forthe editorial policy or other contents of the publication.

This publication is intended as a general guide. It covers a highlytechnical and complex subject and should not be used for makingdecisions concerning specific products or applications. Thispublication is sold as is, without warranty of any kind, either expressor implied, respecting the contents of this publication, includingbut not limited to implied warranties for the publication,performance, quality, merchantability, or fitness for any particular

purpose. Pinnacle Publishing, Inc., shall not be liable to thepurchaser or any other person or entity with respect to any liability,loss, or damage caused or alleged to be caused directly or indirectlyby this publication. Articles published in FoxTalk reflect the views oftheir authors; they may or may not reflect the view of PinnaclePublishing, Inc. Inclusion of advertising inserts does not constitutean endorsement by Pinnacle Publishing, Inc. or FoxTalk.

Subscription information: To order, call Pinnacle Customer Serviceat 800-788-1900, or 770-565-1763. Cost of domestic subscriptions:12 issues, $179; 24 issues, $259. Canada: 12 issues, $194; 24 issues,$289. Outside North America: 12 issues, $199; 24 issues, $299.Individual issues cost $17.50 ($20 in Canada, $22.50 outside NorthAmerica). All funds must be in U.S. currency.

For European newsletter orders, contact: Tomalin Associates,Unit 22, The Bardfield Centre, Braintree Road, Great Bardfield, EssexCM7 4SL, United Kingdom. E-mail: [email protected]: +44 (0)1371 811299. Fax: +44 (0)1371 811283. 12 issues: £179

For Australian newsletter orders, contact: Ashpoint Pty., Ltd.,9 Arthur Street, Dover Heights, N.S.W. 2030, Australia.Phone: +61 2-9371-7399. Fax: +61 2-9371-0180.E-mail: [email protected]. Web: http://www.ashpoint.com.au.

FoxPro technical support: Call Microsoft at 206-635-7191(Windows) or 206-635-7192 (Macintosh).

Send all other questions or requests via the options at right.

Editor Whil Hentzen, Editorial Advisory Board Scott Malinowski, Walter Loughney, Les Pinter, Ken Levy, Publisher Robert Williford,Vice President/General Manager Connie Austin, Managing Editor Heidi Frost, Copy Editor Farion Grove

Applies to FoxPro v2.x

Accompanying files available onlineat http://www.pinpub.com/foxtalk

Applies to VFP v5.0

Applies specifically to one of these platforms.

Applies to VFP v3.0

Applies to VFP v6.0 (Tahoe)

6.06.06.0

Enter SandmanWhil Hentzen

WANT to get depressed really quickly? The nexttime you’re around a group of your peers, askeveryone to name the supergroups of the ’90s.

By “supergroup” I mean the true superstar bands—theBeatles, the Stones, Zeppelin, The Who, Cream, CSN&Y—you get my drift.

Yeah, I know. It’s currently chic to deride Mick, Keith,et al. It was also chic to rip on Cream. But you can stillhear Badge being played at 110 decibels at the McKinleyMarina on Milwaukee’s Lakefront any summerweekend—until the police show up. I don’t hear BoyGeorge or Vanilla Ice (“I’m going to be the next ElvisPresley”) much, and Green Day and Jane’s Addictionaren’t going to warrant a footnote in Rolling Stone’sSeventy-Five Years of Rock ’n Roll retrospective.

But my point is that there have been no newsupergroups since about the mid-’80s. U2 is about the lastone I can think of. Yeah, Hanson and the Spice Girls canfill a stadium in three hours now. But so could The Babiesand Bananarama. These days, it’s crank out a couple ofalbums and disband. Make your $10 million and bail.“These youngsters—they don’t know good musicanymore.” I yearn for the days of good old-fashioned rockand roll, when singers danced with live pythons on stageor bit the heads off of doves.

Bands today lack staying power.

And so does today’s software.I have a friend who runs the MIS department of a

Fortune 200 company, and he’s getting ready to pull theplug on a bunch of boxes that have been collecting shopdata (time clock type of info) since 1984. Same boxes,same software. None of this “upgrade of the month”business. None of this “make the chassis easily opened soyou can replace broken parts in 30 seconds” rot. He’sreplaced one fixed disk in 14 years. He’s not had toupgrade the boxes or change the software even once. Andthe best part? At the time he got these boxes, they’d beenaround for nearly a decade. (The equipment in question isthe IBM Series 1, in case you were curious. And he’s goingto replace them with Windows 95, er, Windows NT, uh,Windows 98, no, wait, make that NT 5, or . . .)

I’ll bet you’re still doing a lot of the same stuff nowthat you were 10 years ago. Writing memos and technicaldocuments. Calculating budgets and making costestimates. Writing quote tracking, health care claimmanagement, and accounting database applications.How much of the same software from 10 years ago areyou still using? On the same machines?

Boy, I wish it would stop. Or at least slow down abit. I bet you do, too. But despite our most fervent wishes,it’s not going to. Why? It’s all these young kids in theindustry who don’t have any perspective. “It can be done,

Continues on page 22

Page 3: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 3FoxTalk June 1998

who hadn’t upgraded to 5.0a got “OLE class notregistered” errors when they tried to use SDT. Myworkaround at the time was to keep one machine in ouroffice with VFP 5.0 (not 5.0a) installed and do any SDTdevelopment involving that one form with the TreeViewon it on that one machine. If I ever forgot and modifiedthe form on my machine . . .

However, knowing I’d experienced the problembefore didn’t make me feel any better when my Inbox haddozens of e-mails reporting the problem. That was the laststraw; I had to come up with a permanent solution to thisproblem. The tool described in this article is the result.

BackgroundFirst, let’s look at the cause of the problem. If you open aform containing an ActiveX control as a table (in otherwords, USE MYFORM.SCX) and browse it, you’ll findbinary information stored in the OLE field of the recordfor the ActiveX control. This binary information appearsto contain some version-specific information about thecontrol. As a result—at least in the case of the ActiveXcontrols shipped by Microsoft—ActiveX controls areforward but not backward compatible. This means that ifI drop version 2.01 of an ActiveX control on a form,people with a newer version of the ActiveX control (suchas version 2.02) can open the form, but those with anolder version (like version 2.00) can’t.

Let’s dig a little deeper and see how ActiveX controlsare handled on a system. As you’re probably aware,simply copying an OCX file to someone’s hard drivedoesn’t allow them to use the ActiveX controls containedin that file. The controls need to be registered on thesystem either using REGSVR32.EXE (a copy of thisprogram comes with VFP) or letting VFP’s Setup wizardhandle it for you. Using REGEDIT, we can see thatActiveX controls are registered by a class ID value inHKEY_CLASSES_ROOT\CLSID. For example, theTreeView control is registered in HKEY_CLASSES_ROOT\CLSID\0713E8A2-850A-101B-AFC0-4210102A8DA7. There are several subkeys under thiskey, the most important ones (as far as we’re concerned)being ProgID, VersionIndependentProgID, andInProcServer32. ProgID contains the program ID, whichmight change from version to version—for example, onmy system, the value of this subkey for the TreeViewis COMCTL.TreeCtrl.1. VersionIndependentProgIDcontains just what the name implies (on my system,the value is COMCTL.TreeCtrl for the TreeView), andInProcServer32 contains the name and path of the OCXcontaining the control. This immediately suggests severalpotential problems:

• The ActiveX control was never installed on a

machine—there’s no entry in the Registry for thecontrol, and the OCX doesn’t exist on the drive. Thesolution is to properly install it.

• The OCX file was copied to the system but notregistered—the file exists, but there’s no entry inthe Registry. The solution is to use REGSRV32.EXEto register the ActiveX controls in the OCX. Hint:You must include the extension for the fileyou’re registering:

"regsvr32 myfile.ocx" instead of "regsvr32 myfile"

• The ActiveX control is registered, but the OCX can’tbe found—it might have been moved or renamed,or the user might have restored from a backed-upRegistry but didn’t restore all files. The best solutionis to reinstall and register.

• The version installed on the system is differentthan the one required by an application. While anobvious solution to this problem is to install theversion of the control required, this might not alwaysbe feasible. For example, if I shipped my copy ofCOMCTL32.OCX along with SDT, that would get ridof my problems but would probably cause a whole lotof problems for you and any clients you’d createdforms with a TreeView for.

Another unforeseen problem can also occur. SomeActiveX controls look for a license (a key stored in theRegistry or a file on your drive) in order to use them in adevelopment environment. This makes sense; it allowsyou to develop and distribute applications using avendor’s controls and have those controls work in aruntime environment while protecting the vendor fromsomeone else being able to develop using those controlswithout paying for them. The problem this causes,though, is that if the license key gets lost on your system,you’ll get a “License not found” error when you try todrop the control on a form. Reinstalling doesn’t alwayssolve the problem. Based on messages I’ve seen onCompuServe and the Universal Thread, this problem hasbeen a common one. For the controls shipped with VFP,the solution is simple: Download a file called FOX.REGfrom the Universal Thread (there’s a great reason to joinright there) and run it; it will register the proper licenseson your system. For other third-party controls, reinstall orcontact your vendor.

As you can see, most of the problems regardingActiveX controls require reinstalling them. However, as Imentioned, it might not be possible to solve the versionproblem this way. The rest of this article discusses aneasy-to-implement solution to this annoying problem.

SFActiveXThe solution is to instantiate ActiveX controls at runtimerather than using them at development time. This way,

Tame Your ActiveX Controls . . .Continued from page 1

Page 4: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

4 http://www.pinpub.comFoxTalk June 1998

the control is instantiated from the version installed onthe user’s system rather than looking for the version thatwas installed on your system. To make this job easier, Icreated an ActiveX loader class called SFActiveX.

SFActiveX is based on SFCustom, our Custom baseclass defined in our base class library SFCTRLS.VCX. Ithas the custom properties shown in Table 1.

Table 1. Custom properties of SFActiveX.

Property DescriptioncClass The name of the class defining the ActiveX control

or a subclass of it

cClassID The Class ID for the object

cLibrary The name of the library containing a subclass ofthe ActiveX control

cNewObjectName The name to give the ActiveX control this objectwill create

cObjectName The name of the placeholder object to replace withthe ActiveX control

cOLEClass The OLE class for the object

cClass defaults to “OLEControl” because that’sthe VFP class that ActiveX controls are contained in.However, if you want to use a subclass of an ActiveXcontrol (I’ll show at least one reason why you’d want todo that later in the article), enter the name of the class inthis property and the name of the library file containingthe class definition in cLibrary (specify an extension toidentify VCX from PRG libraries).

You can specify the ActiveX control to use in one oftwo ways: by cClassID or cOLEClass. If you specify one,leave the other blank. cClassID is the class ID for theActiveX control, and cOLEClass is its ProgID. How doyou know what to enter for these properties for a givencontrol? For the cOLEClass, the simplest way is to dropthe desired control on a form, then look at its OLEClassproperty. For example, the OLEClass for the TreeViewcontrol is “COMCTL.TreeCtrl” (you might see“COMCTL.TreeCtrl.1” in your instance; however, don’tworry about the “.1” for cOLEClass). cClassID is a littleharder to determine. You’ll need to fire up REGEDIT andsearch for the OLEClass for the control, then see whatclass ID it appears under. For example, the TreeViewcontrol appears under “{0713E8A2-850A-101B-AFC0-4210102A8DA7}”. You don’t need to enter the curly braceswhen you enter this value into cClassID. It’s better tospecify cClassID than cOLEClass because the name of thecontrol might change when different versions are installed(for example, the TreeView control that shipped with VFP5.0a has a ProgID of “COMCTL.TreeCtrl.1”, which couldcause problems under certain circumstances if youspecified “COMCTL.TreeCtrl”).

Some ActiveX controls have a visual appearance, andothers don’t. For those that do, you’ll need to specify thelocation and size of the control. You could do it in the Init

of the form by setting the Top, Left, Width, and Heightproperties in code, but I find it more intuitive to put aShape object on the form and size and position it where Iwant the ActiveX control to go. This object becomes aplaceholder for the ActiveX control, making it easier tosize, position, and place other objects in relation to it. Totell SFActiveX that this object is the placeholder, enter itsname in the cObjectName property. SFActiveX willremove this object and put the ActiveX control in its place.

SFActiveX doesn’t have a lot of code. Its Init methodcalls the custom method LoadActiveX (which does allthe work) unless told not to do so by passing .T. as aparameter (this is provided so you can instantiateSFActiveX in code, set the properties as appropriate, andthen call LoadActiveX manually to load the control). Iwon’t address all the code in the LoadActiveX methodhere (you can check it out yourself), just the moreinteresting stuff.

SFActiveX relies heavily on being able to lookstuff up in the Windows Registry, because that’swhere ActiveX controls installed on a user’s machineare registered. In order to work with the Registry,LoadActiveX uses the services of another class,SFRegistry, defined in SFREGISTRY.VCX. We won’tlook at this class here; suffice it to say that I stole mostof the code from REGISTRY.PRG, a Registry-handlingclass that comes with VFP, but made the programmaticinterface a bit simpler.

In order to use SFRegistry, you have to instantiateit; that work is done by a call to NEWOBJECT.NEWOBJECT.PRG is an updated version ofNEWOBJ.PRG that I presented in this column in theSeptember 1997 issue. It was updated to support the samesyntax as the Tahoe NEWOBJECT function. This meansthat in Tahoe applications, you can omit this program,and any code calling it will work just fine.

The first job for LoadActiveX is to figure out whetherthe desired ActiveX control is installed on the user’ssystem and, if the class ID was specified rather than theOLE class, which OLE class to use. The following codedoes this. Note that if you specify the OLE class and itcan’t be found, LoadActiveX adds a “.1” suffix to the classand tries that before giving up. (The method starts withWITH THIS, so all unspecified object references are theclass itself.)

loRegistry = newobject('SFRegistry', ; 'SFRegistry.vcx', cnHKEY_CLASSES_ROOT)if empty(.cOLEClass) lcCLSID = iif(left(.cClassID, 1) = '{', ; .cClassID, '{' + .cClassID + '}') lcProgID = loRegistry.GetKey('CLSID\' + lcCLSID + ; '\ProgID') llOK = not empty(lcProgID)else lcProgID = .cOLEClass lcCLSID = loRegistry.GetKey(lcProgID + '\CLSID') if empty(lcCLSID) lcProgID = .cOLEClass + '.1' lcCLSID = loRegistry.GetKey(lcProgID + '\CLSID') endif empty(lcCLSID) llOK = not empty(lcCLSID)

Page 5: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 5FoxTalk June 1998

endif empty(.cOLEClass)if llOK lcOCX = loRegistry.GetKey('CLSID\' + lcCLSID + ; '\InProcServer32') llOK = not empty(lcOCX) and file(lcOCX)endif llOK

At the end of this code, lcProgID contains the OLEclass (ProgID), lcCLSID contains the class ID (which wedon’t care about after this point), and lcOCX contains thename and path of the OCX file on the user’s system(found in the InProcServer32 key in the Registry). llOKis only .T. if we found what we were looking for in theRegistry and the OCX file exists. If not, the user will getan error message, and this method will return .F.

If everything is okay, LoadActiveX checks whether aplaceholder was specified and, if so, saves its size andposition and then removes it.

llObject = not empty(.cObjectName)if llObject lcObject = .cObjectName with .Parent.&lcObject lnTop = .Top lnLeft = .Left lnWidth = .Width lnHeight = .Height endwith .Parent.RemoveObject(lcObject)endif llObject

If a class and class library were specified,LoadActiveX opens the library (using either SETPROCEDURE or SET CLASSLIB, depending on thelibrary’s extension). It then adds the ActiveX control orActiveX subclass to the container, giving it the namespecified in the cNewObjectName property. If aplaceholder object was specified, the control is sized andpositioned to the saved values. Notice the ProgID (storedin lcProgID) must be specified in the call to AddObject; ifthis was left out, the user would get a dialog box askingwhich control to insert. This is why we have to look in theRegistry to determine the ProgID for the ActiveX control.

lcObject = .cNewObjectName.Parent.AddObject(lcObject, .cClass, lcProgID)with .Parent.&lcObject if llObject .Top = lnTop .Left = lnLeft .Width = lnWidth .Height = lnHeight endif llObject .Visible = .T.endwith

Using SFActiveXIt’s easy to use SFActiveX: Simply drop it on a form (orother container, such as a page in a PageFrame where theActiveX control should go), fill in some properties, andput code in the Init method of the form to set anyproperties of the instantiated ActiveX control as desired.To make it even easier to use, I’ve created subclasses ofSFActiveX for the ActiveX controls I use most frequently:SFCommonDialog, SFImageList, and SFTreeView. Thesesubclasses just have the appropriate class ID enteredinto the cClassID property so you don’t have to look itup. Drop one of these on a form to create the desired

ActiveX control.Ah, but there’s one complication: What if you need to

put some code in a method of the ActiveX control? This isfrequently the case with visual controls—for example,with the TreeView and ListView controls, you need to takesome action when the user clicks on a node or item in thelist. In the case of the TreeView control, you’d put codeinto the NodeClick method. The problem, of course, isthat you can’t add code to an object. The only way toaccomplish this is to create a subclass of the ActiveXcontrol, put the desired code into the subclass, and tellSFActiveX (via the cClass and cLibrary properties) toinstantiate that class. One slight “gotcha”: You can’t createthe subclass in a VCX because, like dropping an ActiveXcontrol directly on a form, VFP puts binary informationabout the control into the VCX, and that’s the whole causeof the problem SFActiveX was designed to solve. Instead,you need to create the subclass in a PRG. Here’s the codefrom the sample ACLASSES.PRG that defines a subclassof the TreeView control; it has code in the NodeClickmethod (albeit simple code) that fires when the user clickson a node. Note that OLEClass must be specified, or theuser will get a dialog box asking which control to insert.

define class MyTreeView as OLEControl OLEClass = 'COMCTL.TreeCtrl' Name = 'MyTreeView' procedure NodeClick lparameters toNode wait window 'You clicked on node ' + toNode.Text endprocenddefine

Believe it or not, with all the problems we’ve solvedusing this scheme, we’re not quite out of the woods yet!There’s one last “gotcha”: Although you might think youcould set some properties of the control in the subclassdefinition (such as Style and LineStyle in the case of theTreeView control), don’t do it. For a reason that escapesme, setting properties in the subclass seems to cause thoseproperties to be carved in stone. Trying to change themlater in a form the control is on fails completely. Thismeans you have to set the properties after the control hasbeen instantiated, usually in the Init of the form (whichyou can do, since by the time the form’s Init fires, theActiveX controls have been instantiated). I’ll show anexample of this in a moment.

One final heads-up about the subclass: If you createan Init method in the subclass, it’ll need to accept aparameter. The OLE class is passed to the Init method(notice the third parameter in the AddObject call inLoadActiveX); although you don’t need it, you’ll get anerror if you don’t have an LPARAMETERS statement.

The sample TREEVIEW.SCX form shows howSFActiveX is used. This form contains three objects: anSFTreeView object (SFActiveX subclass specific forTreeViews) called oTreeViewLoader, an SFImageListobject (SFActiveX subclass specific for ImageLists) calledoImageListLoader, and a Shape called shpTreeView that

Continues on page 23

Page 6: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

6 http://www.pinpub.comFoxTalk June 1998

Best Practices FFFoooxxxTalk

Development Checklist:Requirements AnalysisJefferey A. Donnici

With this month’s checklist column, Jeff takes a look at theprocess of gathering requirements for a development project.This column is the latest in a series of checklists designed toprovide a starting point for your own development checklists.Building a set of system requirements is typically one of thefirst things to happen in a project and, as such, is an excellentopportunity to make sure that the project gets off on theright foot.

AFTER covering coding, peer reviews, and a multi-part segment on object-oriented design, I’m nowgoing to take a look at the task of “gathering

requirements.” This process in a development project canbe labeled many things, and you might typically hear itcalled “requirements analysis,” “the analysis phase,”“specification construction,” or any number of variants.For the remainder of this column, I’ll simply use the term“analysis” to refer to the process of determining what theproblem at hand is and what needs to be developed tosolve that problem. In the world of object-orientation,this is a sub-field all its own, typically referred to as“object-oriented analysis.” Covering all the differentconcepts and ideas associated with object-orientedanalysis is well beyond what can be covered in a singlearticle, but I’ll list a couple of good references for thosewho are interested in learning more. While much of myown experience and focus these days is on object-orienteddevelopment, you might notice that not every item onthis particular checklist is specific only to object-orientedprojects. In fact, a number of the checklist items areapplicable to nearly every software project, regardlessof its development language, size, or cost.

So, what exactly is “analysis”? Working backwardsin the lifecycle of a software project, the “coding” isthe process of implementing a software solution. The“design” phase of a project is used to determine how thesystem should be built (which parts it will contain andhow they work together to form the system). The“analysis” phase, however, is the process of determiningwhat needs to be built. In other words, an analysis processis meant to figure out the scope of the problem that thesoftware must solve. I like to think of it as a process of“putting limits on the project” because the analysis phase,more than any other, will determine the scope of what the

system will (and won’t) do.Like any other process, the analysis phase of a project

needs to have a result or end product. Typically, this willcome in the form of a “requirements specification” thatdetails what the system is expected to do. The form ofthese specifications can vary from project to project,depending on project size and a variety of other factors,but it might consist of a three-page narrative describingthe system or a multi-binder set of diagrams, tables, andplain-English text. It’s difficult to know in advance whenenough requirements information has been gathered, butyou’ll definitely know it after the fact. If the developersworking on the design phase of a project keep going backto the clients (or others involved in the analysis) for moreinformation or clarification, then you know that thespecification could have been more complete.

With these ideas and definitions out of the way,let’s get started on the checklist of issues to watch outfor during this early phase of a project.

Is a true “analysis effort” being madeat the start of the project?Yes, I know it sounds incredibly obvious to bring this up,but many projects get started with someone sitting downto write code and “prototype” the application in advance.It’s important that everyone involved with the project beaware of the importance of doing the analysis andgathering requirements. Even the smallest of softwareapplications is intended to solve some sort of problem, somake sure there’s a clear understanding of what thatproblem is before beginning any design or coding effort.This can save a lot of embarrassment, headache, and,most importantly, money by avoiding situations whereina certain “something” gets developed that isn’t at all the“something” that the end-users truly need.

Unfortunately, some management types have theimpression that a software developer can just sit down,close the door, push a few buttons, and magically deliverthe software that management sees in their heads. Thereisn’t always a lot you can do to convince these peopleotherwise, but you can try to get the eventual users of thesoftware involved early to make it clear that some form ofrequirements gathering needs to be performed. These arethe people who will likely be “living with” your software

6.06.0

Page 7: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 7FoxTalk June 1998

on a daily basis and, presumably, will have their jobsmade easier through your efforts. They naturally havea vested interest in making sure the developer(s)completely understands the problem domain that thesoftware will address. Their involvement will make itclear to management that it’s important for you to haveas much clarity as possible when it comes to the truegoals of the software.

Are all of the necessary people being involved todefine the system’s intent and requirements?In light of the preceding section, it’s important to realizethat it really takes a variety of people, at a variety oforganizational levels, to make a software projectsuccessfully meet the needs for which it’s intended.Typically, you’ll have the management types who approvethe project and ultimately compensate you for your work.Whether you’re a corporate developer or an independentconsultant makes no difference other than the name thatappears at the top of everyone’s paychecks. Since theseare the people paying you, you want to make sure they’reinvolved, happy, and see progress happening. As I’vealready mentioned, you also need the end-users to berepresented in the analysis phase because they’re going tobe putting the fruits of your labor to work. While thesepeople probably don’t approve your compensation, I tendto think they’re more important to the developer than themanagement type. That’s because they’re likely to bevocal when it comes to judging the results of your work.A manager who’s happy with your efforts isn’t likely tostay that way if he’s got an end-user mutiny on his hands.The point here is that it’s important for both types ofpeople to be involved. While the end-users can ultimatelytell you what needs to be done, the managers must beinvolved to tell you the constraints within which it mustbe done (budgets, schedules, and so forth).

If possible, try to get feedback from more than justone “future end-user” for your system. If you’re gettingall of your requirements and input from one person, yourun the risk of developing software that meets the needsof the way that one person works. This can come at theexpense of everyone else who must use the system,many of whom may have different needs or methods forapproaching their job. Where I work, we write softwareapplications for a wide variety of clients in the energyindustry and ship that software with large volumes ofdata. The purpose of our software is to give the users avariety of tools and mechanisms for interpreting thedata that they’ve licensed. One of our internal productmanagers will often come up with a variety of new ideasand functionality for his product and then explain to methat he’s “the typical end-user” for that product. Much asI wish he were “typical,” he’s significantly more familiarwith the energy industry and our data than any “typicalend-user” is, and that has to be factored into any requests

or suggestions he makes. The point I’m hoping to makehere is that it’s important to “know the audience” foryour software application. With the input of only a smallcross-section of the potential user base, you run the risk ofdeveloping software that meets the needs and desires ofthe few—at the expense of the many.

Another point that’s worth making concerns otherdevelopers. If possible, get the people who will be doingthe system design and implementation involved with theanalysis effort. While it’s nice to have so complete aspecification that it can be handed with confidence to adesigner who wasn’t involved with that process, havingother developers there can add clarity in the later phasesof the project. They might ask different questions than theperson assigned the requirements responsibility, andasking questions is the analysis phase.

Has a common vocabulary been definedfor developers and users so they canclearly communicate?Every type of business and job function has its ownlanguage. As a software developer, it’s likely that you’ll beexposed to a wide variety of businesses and industries.Personally, I know that’s one of the most exciting aspectsof software development for me. As you get into each newproject, however, you’re probably going to hear a varietyof new terms and even some old terms that havemeanings other than the definition you’re familiar with.As you’re meeting with the other parties involved withthe project, make sure to stop and get definitions andexplanations whenever they refer to something thatyou’re not clear on. It’s pretty common for this process tobegin with someone saying, “Let’s start with the basics,”as they proceed to give you the background informationthey think you need. If you’re not completely clear on thatbackground information, you’re really going to be lostwhen they get to the explanation of their problem. Onceagain, the point of an analysis effort is to get questionsanswered, so make sure to ask them early. Having ashared vocabulary established early in the process willmake all communications much easier throughout the lifeof the project.

This is also a good time to point out the importanceof remembering that not everyone you’re working within this phase of a project is a software developer. Termslike “inheritance,” “encapsulation,” and “referentialintegrity” might be commonplace in this newsletter andat programming conferences, but they don’t mean athing to your average computer user or businessmanager. Because this phase of a project is primarilyfor the end-users, try to keep the development-relatedtechnical jargon to a minimum and speak to them intheir language. You can translate their needs and desiresinto your programming terms in the design phase ofthe project.

Page 8: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

8 http://www.pinpub.comFoxTalk June 1998

Is a completely different approach being forcedon the eventual users of the system?The typical goal of a software project is to accuratelymodel some process that the end-users would otherwisehave to go through manually so that their job is ultimatelymade easier. The chances are pretty good that these end-users are already performing the task that the softwarewill model, and they’re used to doing it in a certain wayor using a specific methodology (perhaps even with anexisting software application). As the analysis phase of aproject begins, it’s not uncommon for users to come upwith new ways of approaching their job, sometimes at thesuggestion of the developer gathering the requirements(no hidden motives there, huh?). When the analysis ofthe new system begins to look like a completely newapproach for the business process, proceed very carefully.As much as I hate to borrow an incredibly over-usedterm, what you’re watching out for here is a “paradigmshift” in the approach the software takes to solve thebusiness problem.

I should clarify that this caveat isn’t intended to stifleanyone’s innovation or creativity. I think that users aregenerally excited by being a part of the requirementsgathering process, and it’s easy for them to lose sight ofthe fact that this software will likely become either a verygood friend or another drudgery added to their day. If theapproach the software uses to solve a problem isn’t asound or “tried and true” approach that the users canwork with, they’re not going to be happy once the systemis deployed and the “honeymoon period” is over. The onearea that I think is a slight exception to this rule is when ashift will mean a large increase in the user’s efficiency.Anyone can get used to something that makes their dayeasier or frees up more of their time, but make sure thatchanging the “business process” isn’t going to ultimatelyhave the opposite effect. Innovate where it makes sense,but don’t change things on the user purely for the sakeof change.

Have all of the necessary—as well as potentiallynew—components of the system been discussed?With some projects, you’ll be writing a system thatreplaces an older, obsolete, or possibly cumbersomesystem that’s already in place. Other times, you’ll becreating an application from scratch to streamline anexisting business process. In the latter case, the users andtheir managers might not have any idea what they needor could use until you come right out and suggest it. Inthe former case, they might be so used to dealing with theexisting software that they don’t see past it for new ideasand functionality. For that reason, I’m using this checklistitem as a catch-all to list the types of things you mightsuggest to them. These are components that appear inmany different types of applications and might or mightnot be important for any given project.

For starters, do they have any existing reports,

spreadsheets, or documents that the system will need tobe capable of generating or reading? If there’s already asystem in place, should the existing data be converted toa new format, or should it simply be archived so theycan start fresh? Should the application have any securitymechanisms built into it and, if so, to what degree? Nowthat everyone and their pet has an e-mail address, shouldthe application be capable of sending (or even receiving)e-mail messages? Is there a need for users to do offlinework with their data, separated from the primary datasources? Could they use remote access capabilities inthe application? Do they have a need for specific filestructures or file types? Will the system need to haveany export or import capabilities? Does the system needbackup functionality built into it, or will they use someother backup solution? How much flexibility do theyneed for customizing the user interface or businesslogic within the system? Do they need access to filemaintenance capabilities in the system, or should thosebe handled automatically? To what extent should thesystem provide online help or user assistance? Shouldthe help system only aid them with the use of the system,or should it also provide assistance with the businessprocess that the system is designed to model?

Obviously, there are a lot of these types of questionsto be asked when you sit down to work out therequirements for a new software application. Trying tocome up with an exhaustive list would be impossible forme to do here, but the preceding suggestions should giveyou some ideas of the types of things that might not occurto users until they’re suggested. Going on the assumptionthat you want this application to be as useful as possiblefor your end-users, ask lots of questions and make sureyou’re clear enough on the answers to meet their needswith a true solution.

Has the specification been built with scalabilityand future expandability in mind?An application that can “scale up” as the needs placed onit grow must be intended that way from the onset. Whileit’s usually an interesting “employee interview” question,you might find it useful to find out where the users andbuyers of this system expect themselves and the software tobe in a couple of years. You might be writing a small,standalone sales system for a bookstore today, but what ifthey plan to grow into a new space next year and havethree different sales computers? Most businesses havegrowth as a primary goal, so it’s important to find outhow much expansion capability the users and managerswould like to see. Chances are that your software willneed to be replaced if they become the next “Barnes &Noble” or “Amazon.com,” but you don’t want it to beobsolete as soon as they need a little more storagecapability either.

I should note here that I’m not referring specifically tothe software’s internal flexibility and expandability. That

Page 9: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 9FoxTalk June 1998

comes as a function of the design phase in the project andshould be built into every system you work on. Even ifthe business never grows, the needs of the software mightsimply change, and it’s important to have that capabilityinherent in the components of the application. Instead,this topic is intended to get you thinking about the actualgrowth that a system might undergo. For example, youmight write a relatively simple application for a smallbusiness that doesn’t have any networking or multi-userneeds at all. One day, the owner calls you and says he’dlike to be able to access the software from home so that hedoesn’t have to go into the office every day. That’s easyenough, but he’d like to be able to do that even if one ofhis employees is in the office and using the system.Suddenly that simple application you built needs remoteaccess and multi-user capabilities. A year later, the ownercalls you and tells you he needs the software to berunning around the clock with zero downtime, but he alsowants nightly backups made of all that data in thebackground. Now it makes more sense for the VisualFoxPro application you built to be using SQL Server as aback-end instead of native VFP tables. Suddenly all thedata-related code you wrote won’t do, and you wishyou’d been using view definitions from the beginning.Once again, these are issues that can be resolved with asolid application design effort, but it’s good to get themout in the open during the analysis phase, if only to getfeedback and suggestions from the true “owners” ofthe system.

Have all “use cases” and “actors” thatmight be required within the systembeen discussed and clarified?In his book Object-Oriented Software Engineering: A UseCase Driven Approach (Addison-Wesley, ISBN 0201544350),Ivar Jacobson discusses the idea of using “use cases” and“actors” to work out the requirements of an object-oriented application. A “use case” is roughly defined as adescription of the tasks that must be completed as part ofan overall process. For example, an automated tellermachine might have a “withdraw cash” use case, a“deposit check” use case, and a “balance inquiry” usecase. On its own, the description of a use case doesn’thave much value other than to provide a convenient wayto refer to a larger process or series of steps. An “actor” isdescribed as an “external agent” or “user” involved withthe process that a use case describes. For example, apurchasing system for a company might have a “fulfillorder” use case as one of its needs. The steps involvedwith this process might include an employee filling out arequest form, a supervisor approving the request, apurchaser making the purchase, a supplier providing therequested materials, and a mailroom employee deliveringit. Each of these persons is considered an “actor” withinthe “fulfill order” use case because the process isn’tcomplete until each of them has performed his or her role.

My goal here isn’t to convince you that Jacobson’sapproach is the best or only way to go for your ownanalysis, though I do highly recommend his book.Instead, it’s to provide you with an additional way todetermine whether or not all the questions have beenasked when analyzing the needs of a system. In truth, Ihave to agree with the author of another excellent book—Object-Oriented Software Construction, Second Edition byBertrand Meyer (Prentice Hall, ISBN 0136291554)—insuggesting that unless a development team has plenty ofexperience developing object-oriented applications, theuse case approach can be somewhat limiting during theanalysis and design phases of a project. As Meyer pointsout, the use case approach requires that the process bedescribed in a fairly rigid fashion with a specific sequenceof events. This can be at odds with the abstract, statelessapproach that an object-oriented application shouldtake—an approach where various self-contained objectscan receive a type of request and proceed to work inconjunction with other objects as necessary to meet thatrequest. As such, I don’t believe that a use case approachshould be the only method of determining the needs of anobject-oriented application. I do, however, think that usecases can be a valuable tool for looking at a system’srequirements from a new and unique perspective. Whentaking the time to describe a system as a series of usecases, each of which requires a set of actors forcompletion, you might find that an importantrequirement or consideration has been overlooked.

Are there clear limits to what the systemwill and won’t do when complete?Here’s another seemingly obvious item that I think isoften overlooked in requirements gathering efforts. It’scommon for the analysis to indicate everything that anapplication should do, but much less common is for someattention to be devoted to what it shouldn’t do whencomplete. For example, if your application must have anoption to let the user export a table to another file format,should the user also have the ability to choose whichfields in the table are exported? If the application has acharting or graphing component, will it also have theability to export the chart as a graphic file?

When the developers working on the design effort getthe results of the analysis phase, they’re naturally going tohave questions about the extent to which they shouldaccount for the unknown. When possible, indicating whatthe application doesn’t need to do can help them rule outsome of those unknowns and focus more clearly on thethings that the application does need.

Is there an end result to all of the analysisand requirements work?A requirements effort is really only as good as thespecifications and documentation it generates at theend of the process. Getting a variety of people involved,

Page 10: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

10 http://www.pinpub.comFoxTalk June 1998

combining opinions for a consensus, and determiningevery last need for a software project is great, but it’suseless without some way of communicating thoseresults to the people who will design and implementthe solution.

As I mentioned earlier in this column, the end resultfor an analysis effort could be anything from a free-formmemo that’s a few pages in length to a truckload ofdocumentation that details every last need in the finestlevel of detail. Where your needs fall within thoseextremes should be determined early in the analysisphase so that everyone knows going in what’s expected atthe end of the process. Make sure that the specificationsare provided in writing and don’t consist of a“specification meeting” with the people who willdesign and implement the system. Invariably, intentsand desires will be left out of a meeting format, and themiscommunications that result can be the death of asoftware project.

Have all the necessary people approvedthe final analysis results?Once you’ve got the requirements put together and put inwriting, make sure that everyone who has an interest inthe project has “signed off” on the results. This doesn’tneed to be a formalized process with people signing theirlives away, but they should have an opportunity to reviewand make comments on the results of the process they’reinvolved in.

Getting the results of the process approved byeveryone greatly increases the chance of success for aproject because it avoids the “it does what I said, but notwhat I meant” problem that often pops up during aproject. When someone reads through the description oftheir needs later, well after they initially expressed thosedesires, they can often clarify those thoughts so that whatthey’re agreeing to is truly what they want. Havingmanagement review the requirements will ensure that thesystem requirements are in line with the goals of thebusiness, and having the end-users take part in the reviewwill make sure that the application will truly be of use tothem in their job functions. In short, it’s just a matter ofmaking sure that everyone with a vested interest in theproject is on the same page. Your development staffthanks you for this. Trust me.

Wrap it upThat brings to a close my list of items to watch out forduring the analysis, or requirements gathering, phase of asoftware project. I know it’s by no means an exhaustivelist, but I hope it triggers a variety of new ideas andchecklist topics for you to use on your own. As always,feel free to send me your feedback and thoughts on this(or any other) topic, as well as your suggestions for futurechecklists or columns.

If you’re interested in reading some more in-depthdiscussions of the analysis phase, I highly recommendeach of the books I’ve mentioned in this column. Andspeaking of great books, I finally got around to finishingWhat Every Programmer Should Know About Object-OrientedDesign by Meilir Page-Jones (Dorset House, ISBN0932633315). Right there at the end of the book, inAppendix A, was an exhaustive checklist of 51 things towatch for during an object-oriented design. If you wereleft hungry for more ideas or in-depth discussion after theobject-oriented design checklists I presented over the pasttwo months, I’d definitely recommend this book. It’swritten in a very readable, light style and covers thedesign phase of a project in great detail. It just occurredto me that, while my last couple of columns might ormight not have provided any additional food for thought,they could have inadvertently blown your budget for“development books.” I should probably stop here andgive some thought to making next month’s column thekick-off for a new 12-step program for book addicts likemyself. See you then! ▲

Jefferey A. Donnici is an applications developer who specializes in

reusability and design issues with Resource Data International, Inc. in

Boulder, CO. Jeff is a Microsoft Certified Professional in Visual FoxPro

and a three-time Microsoft Developer Most Valuable Professional.

303-444-7788, fax 303-444-1286, [email protected],

[email protected].

Page 11: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 11FoxTalk June 1998

What’s Really Inside FFFoooxxxTalk

Traversing TransactionsJim Booth

Last month, Jim discussed TableUpdate() and TableRevert()and showed how they enable you to control updates whileusing buffering on your cursors and tables. These twofunctions are really very good for handling the updating orreverting of a single table or cursor. This begs the question,“How can I coordinate the updates or reverts to a number ofrelated tables?” Gary DeWitt discussed transactions in theJanuary and March 1998 issues of FoxTalk in preparation forMTS; this month, Jim reviews that and discusses what can failduring a transaction.

THERE are situations where you need to update morethan one table or cursor, and the combination of theupdates represents one action on the database. This

action has to be an “all or none” operation—that is, eitherall of it happens or none of it happens. For example, inwriting the code to save a new invoice, you need to adda record to the invoice header table, add a group ofrecords to the invoice line item table, update the balancein the customer record, and add a record to the accountsreceivable table. If any of these operations fail, you’ll needto reverse the earlier ones that did succeed and make surethat none of these updates get into the data. Transactionsare designed for this very purpose.

You use the BEGIN TRANSACTION command tostart the transaction tracking in Visual FoxPro, and theENDTRANSACTION command to complete thetransaction or the ROLLBACK command to reversethe transaction.

How do transactions work?When VFP encounters a BEGIN TRANSACTION, it startsto track the file update operations that occur. For eachupdate action, VFP secures the required locks, but itdoesn’t do the update yet. When an ENDTRANSACTIONis encountered, VFP then does all of the file writesinvolved in the transaction and releases the locks thatwere obtained. If a ROLLBACK command is encountered,VFP will release the locks without doing any of the filewrite operations.

Through this process, VFP is able to assure that eitherall of the file updates are done or none of them are done.It’s a very good idea to keep the BEGIN TRANSACTIONand ENDTRANSACTION/ROLLBACK commands asphysically close to each other in the code as possible.Doing this reduces the period of time that the resourcesare locked.

Which tables and cursorscan participate in a transaction?Transactions in VFP are only available for tables that arein a database (DBC). Free tables can’t participate in atransaction, and a rollback or other abnormal end of thetransaction doesn’t affect any updates made to free tablesduring a transaction.

This can cause some interesting effects if a free table isthe source of data for a local view that’s managed by atransaction. I’ll examine the steps in the process of afictitious transaction—in this transaction, there’s one tablein a database named Table1 and a view named View1 thatgets its data from a free table named Table2. Here’s thecode for the transaction:

BEGIN TRANSACTIONIF TableUpdate( 1, .F., "View1") IF TableUpdate ( 1, .F., "Table1") END TRANSACTION ELSE ROLLBACK ENDIFELSE ROLLBACKENDIF

If the TableUpdate for Table1 fails, will theROLLBACK undo the changes to View1 and set it back toits state prior to the transaction? Yes, it will roll back thechanges to View1. Will the changes to Table2 that were aresult of updating View1 be reversed by the ROLLBACK?No, because Table2 is a free table and therefore isn’tparticipating in the transaction. This can leave your datain an undefined state, so you have to manage the freetable yourself. It’s best not to depend on transactionswhen free tables are involved in any way.

Show us the code!How do you integrate transactions into your forms? Itcan be done on a form-by-form basis, or you can do itgenerically in a form class. The following is somepseudo-code that will give you the outline of the structurefor incorporating transactions into your updates.

* Set a variable for tracking a failure.LOCAL llRollBackllRollBack = .F.

* Wait until all data processing is complete* before beginning the transaction.BEGIN TRANSACTION

* Now do each update checking the result.IF NOT TableUpdate( … ) llRollBack = .T.ELSE

Continues on page 23

Page 12: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

12 http://www.pinpub.comFoxTalk June 1998

FFFoooxxxTalk

Transactions: Tahoe andMicrosoft Transaction ServerGary DeWitt

Component reuse is one of the key goals of object-orientedprogramming and one of the difficulties with transactionprocessing—until now. Here’s how Tahoe and MicrosoftTransaction Server can work together to create and deployrobust, reusable transactional components.

AT the First National Bank of Dad, the CFO, Dad,has realized he can no longer give his accountholders personalized service. Instead, he’ll install

an ATM (automatic teller machine) to handle the ever-increasing demand for the bank’s services. The ATM mustallow a depositor to deposit his or her allowance intosavings or checking accounts, withdraw allowance fromeither account, and transfer allowance from one accountto the other.

In the January 1998 issue of FoxTalk (see“Transactions: Take the ACID Test”), I introduced you totransaction processing at the First National Bank of Dad. Idescribed an account object with two methods: Deposit()and Withdraw(). Each of these methods uses transactionsto ensure that the database is updated correctly. Thetransaction is started and ended within the context of adeposit or a withdrawal.

In the March 1998 issue of FoxTalk (see “Transactions:Keeping Them in Context”), I discussed ways that theaccount class can be written so that it can work in thecontext of a funds transfer, where both a deposit into oneaccount and a simultaneous withdrawal from anotheraccount must both succeed or both be rolled back. Youlearned about nested transactions and the oftencomplicated ways of writing components to work in thecontext of other transactions. Writing robust, reusabletransactional components for client/server databasescan be particularly difficult. I ended by wishing for abetter way.

A better wayWarning: You need to know this stuff. The techniques Idiscuss in this article are applicable to client/serverdatabases, but there are some key events on the horizonthat are likely to make you want to do more client/serverapplications, even for desktop deployment.

I recently visited Microsoft’s Web site (http://www.microsoft.com) and executed a search on the phrase

“SQL Server 7.” I found numerous Web pages,presentations, and press releases about the next versionof Microsoft SQL Server, code-named Sphinx. One of thekey features I found for SQL Server 7 is scalability. Notonly will it scale to very large databases, but to very smallones too. One PowerPoint presentation on the Microsoftsite called this “SQL Desktop” and “Embeddable SQL.”According to Microsoft, SQL Server 7 will run under bothWindows NT and Windows 9x. The embedded versionwill have a small footprint, and you’ll be able to distributeit with your applications to run on desktop systems andeven laptops. It will finally be not only desirable, butalso possible to use SQL Server for small databaseapplications.

The other key event is the release of Tahoe. Tahoeallows you to write components that take full advantageof MTS (Microsoft Transaction Server) and MicrosoftSQL Server.

What is MTS?MTS, currently in version 2.0, is a powerful environmentthat makes it easier to develop and deploy robust, high-performance transactional components. Last September atthe Visual FoxPro DevCon, Microsoft demonstrated someof the new features of Tahoe, including its ability to workwith MTS. I was so excited about the possibilities that Irushed home and immediately started experimentingwith MTS 1.0, SQL Server 6.5, and Visual Basic 5.0. As Iwrite this (in early April), I’ve been working with MTS2.0, SQL Server 6.5, and Tahoe nearly every day forabout a month.

Here are some of the advantages I’ve discoveredfrom using this combination of tools: Well-writtencomponents can be reused in any transactional contextwith no modifications; valuable resources, such as ODBCconnections, can be pooled with no modifications; granular,easy-to-develop components can be combined with nomodifications; complicated distributed transactions can beimplemented as easily as simple ones with no modifications;performance of frequently used components can beenhanced with no modifications.

At the risk of being accused of belaboring my point,let me reiterate that all these advantages can be achievedin components written for MTS with no modifications.

6.06.0

Page 13: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 13FoxTalk June 1998

Basic MTS transactional componentsMTS components you create with Tahoe are in-processservers. Unlike other in-process servers, these run in aTransaction Server process, rather than your application’sprocess. MTS assigns transactional components atransaction context. Your components can get an objectreference to the transaction context by using one of theMTS ActiveX servers.

*-- Obtain a reference to transaction context.oServer = CREATEOBJECT("MTXaS.Appserver.1")oContext = oServer.GetObjectContext()

The context object is used to commit or roll backtransactions. Commit a transaction by calling the contextobject’s SetComplete() method:

oContext.SetComplete()

Roll back a transaction by calling the context object’sSetAbort() method:

oContext.SetAbort()

That’s it. That’s all you have to do to write atransactional component. Use the exact same techniquefor all transactional components with no modifications.How does it work? MTS creates a new transactioncontext for a new transactional component. In this case,GetObjectContext() returns a reference to the newtransaction context. But if one transactional componentcreates an instance of another transactional component,then GetObjectContext() returns a reference to the originalobject’s context. How do you know which is which? Youdon’t. It doesn’t matter. If any method in a transactioncalls the context object’s SetAbort() method, then thetransaction will be rolled back. The component callingSetAbort() doesn’t need to know whether it has its owntransaction or is part of another component’s transaction.The code runs the same either way.

In the first two articles in this series, I created anaccount class with Deposit() and Withdraw() methods.Both called the Post() method, Deposit() passing theamount as a positive number, and Withdraw() passing itas a negative number. The Post() method fails if the newbalance in the account is less than zero. Let me createa new, simplified Post() method using MTS. For thepurposes of this simple example, assume I have aremote view of the account table. I also have threeconstants for return values: ERROR_SUCCESS,ERROR_OVERDRAWN, and ERROR_OTHER. First I geta reference to the object context and open the databaseand the remote view:

LPARAMETERS nAccountNo, nAmount

*-- Obtain a reference to transaction context.oServer = CREATEOBJECT("MTXaS.Appserver.1")oContext = oServer.GetObjectContext()

*-- Open database and view.OPEN DATABASE TestUSE Account

In the next step, I add the amount of the transactionto the balance in the account. Note that in a withdrawal,nAmount would be a negative number:

*-- Attempt to post the amount to the account.REPLACE balance WITH balance + nAmount

Here’s where all the decisions are made:

IF balance < 0 *-- Insufficient funds, roll back. oContext.SetAbort() nSuccess = ERROR_OVERDRAWNELSE *-- Sufficient funds, attempt to update. IF TABLEUPDATE(2, .T.) *-- Update successful. oContext.SetComplete() nSuccess = ERROR_SUCCESS ELSE *-- Update failed. oContext.SetAbort() nSuccess = ERROR_OTHER ENDIFENDIF

The first thing to check is whether or not there aresufficient funds for the transaction. If the balance is lessthan zero, the account would be overdrawn. Such acondition isn’t allowed, so I abort the transaction bycalling SetAbort() and set the return value to my constantthat indicates an overdraft.

If no overdraft condition exists, I attempt to updatethe database with TABLEUPDATE(). If it succeeds, all iswell and the transaction can be committed and the returnvalue set to the constant indicating success. If the updatefails, there’s some other problem. In this example, ratherthan dealing with the myriad reasons why an updatecould fail, I simply abort the transaction and set the returnvalue to the constant indicating a problem.

All that remains now is to clean up after thetransaction and return the value:

CLOSE DATABASES ALLRETURN nSuccess

When the account component is added to an MTSpackage and used, the Post() method can be used tomanage deposits and withdrawals. The TransactionMonitor in the Transaction Server Explorer can be used totrack the number of transactions attempted along with thenumber of active transactions, the number of successfultransactions, and the number of aborted transactions.

Managing resourcesI see some hands going up right now—people who wantto know why I open the database and the view withoutchecking to see whether they’re already open and why Iclose everything at the end rather than leaving them openfor future use. Good question.

Without MTS, I’d have to write plenty of code toensure that I’m not clashing with already-opened views. Ialso wouldn’t want to close everything as soon as I’mdone with it. After all, opening a remote view requiresthat VFP load the ODBC manager, driver manager, and

Page 14: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

14 http://www.pinpub.comFoxTalk June 1998

driver DLLs and open an ODBC connection to thedatabase. Without MTS, I’d want to keep a connectionopen so that VFP doesn’t have to do all that work everytime you call a method.

MTS keeps DLLs in memory for a while after they’reno longer used. It also pools ODBC connections. If anobject uses a connection, MTS keeps it open so that otherobjects can use the same connection. In this way, MTScan improve the performance of future requests for anobject and can reduce the number of ODBC connectionsrequired. Remember that in client/server databases, moreconnections often means paying higher licensing fees anda higher requirement for resources.

Transaction Server’s resource pooling, combined withTahoe’s new apartment-model threading, provides somephenomenal performance improvements. I’ve conductedcomparisons between components created and run in VFP5.0 and those created with Tahoe and run in Tahoe andMTS, and I’ve seen performance improvements of over100-fold when creating multiple instances of transactionalcomponents!

To take advantage of resource pooling, I subclass allmy transactional components from VFP Forms. Formscan have private datasessions so that multiple instancesof in-process servers don’t step on the toes of otherinstances. If you do this, be sure you remember to makethe appropriate environment settings. I can’t tell you howmany times I’ve elevated my blood pressure by forgettingto SET EXCLUSIVE OFF in a component and received anOLE error (worse yet, with beta versions of these tools, Ibecame an expert at causing GPFs). I’ll talk more aboutresource pooling later in this article.

The promise comes trueFor several months now, I’ve been promising to show youhow to easily reuse transactional components. MTS makesthe promise come true. As I’ve shown with the accountobject, individual MTS transactional components neverneed to know their context. New components can becreated that combine existing components into newtransactions. The First National Bank of Dad will use anATM component that can combine the Deposit() andWithdraw() methods of the existing account component totransfer allowance from one account to another with nomodification to the account component.

To do this, the Transfer() method of the ATMcomponent uses the CreateInstance() method of its MTScontext object to create each instance of the accountcomponent.

*-- Obtain a reference to transaction context.oServer = CREATEOBJECT("MTXaS.Appserver.1")oContext = oServer.GetObjectContext()

*-- Create an instance of account component.oAcct = oContext.CreateInstance("acct.account")

When you create an instance of the account

component with CreateInstance() instead ofCREATEOBJECT(), you ensure that the account objectinherits the transaction context of the calling object. Theaccount component doesn’t need to know this, however.It knows how to get a reference to the transaction context;MTS takes care of the rest.

Here’s what the Transfer() method of the ATMcomponent looks like:

*-- Obtain a reference to transaction context.oServer = CREATEOBJECT("MTXaS.Appserver.1")oContext = oServer.GetObjectContext()

*-- Create an instance of account component.oAcct = oContext.CreateInstance("acct.account")

nSuccess = oAcct.Deposit(nAccount1, nAmount)IF nSuccess = ERROR_SUCCESS *-- Create an instance of account component. oAcct = oContext.CreateInstance("acct.account") nSuccess = oAcct.Withdraw(nAccount2, nAmount) IF nSuccess = ERROR_SUCCESS *-- Deposit and withdrawal successful. oContext.SetComplete() ELSE *-- Withdrawal failed. oContext.SetAbort()ELSE *-- Deposit failed. oContext.SetAbort()ENDIF

What’s going on here? First, remember that theDeposit() and Withdraw() methods of the accountcomponent simply call the Post() method after ensuringthat the amount is positive for deposits and negative forwithdrawals. Otherwise, the account component is beingused unchanged in a new context. Notice that I depositmoney in one account before withdrawing from the other.I did this deliberately to illustrate the point that it makesno difference what order components are used. If onemethod calls SetAbort(), then everything fails and isrolled back together. After running this code a few times,I firmly believe in magic.

More on resourcesA few more hands have gone up. Why, you ask, did Icreate two instances of the account object—using thesame reference memvar, no less—rather than reuse thesame instance?

One of the side effects of using a context object’sSetComplete() or SetAbort() method is that MTS willrelease the object as soon as the method callingSetComplete() or SetAbort() ends. This is what might betermed the MTS garbage collection. In the case of theaccount component, its resources are freed up as soonas the call to Deposit() or Withdraw() returns. Thedisadvantage to this is that you have to create anotherinstance when you want to call another method. Butthere’s an advantage, too: MTS frees resources sooner,so they can be used again.

Here’s an example of the account component’sGetBalance() method. Note that it makes no changes tothe database, but calls SetComplete() anyway to ensure

Continues on page 17

Page 15: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 15FoxTalk June 1998

FFFoooxxxTalk

Use the Microsoft ScriptingControl to Script with VFP Rod Paddock

Have you ever wanted to add a programming language toyour Visual FoxPro applications? If you answered yes, you’vecome to the right place. Microsoft has released an ActiveXcontrol for adding programming languages to your VFPapplications—the Microsoft scripting control.

VISUAL FoxPro supports two constructs for addingcustom scripting to your applications. You can useMacro expansion—the & operator—or the EVAL()

function. The problem with these constructs is they limityou to using simple commands and expressions. You can’tuse control structures like IF/ENDIF, DO/LOOP, FOR/NEXT, or calling subroutines. This is where VFP’s nativecapabilities end and the advantages of the scriptingcontrol begins.

Where to find the Microsoft scripting controlThe first step in using the Microsoft scripting controlis to download and install it—it can be found atwww.microsoft.com/scripting. Once you’ve installed thescripting control, add an instance of the control to a form,and get ready to rock.

A quick note about a great utility . . .A utility program called CodeBlock by Randy Pearsonwas created a few years ago to address the limitation ofcombining macro expansion capabilities with loopingconstructs like DO WHILE and FOR/NEXT loops.Essentially, Randy built a runtime interpreter for FoxPro.With the exception of those commands that aren’t allowedoutside the FoxPro IDE—such as COMPILE—just aboutany other block of code is allowed. The program allowsyou to store code in memo fields—and have that codeexecuted on an interpreted basis—as if it were compiled.So if you have FoxPro or VFP applications that you’d liketo equip with this capability, check out CodeBlock. A copyis included in this month’s Subscriber Downloads atwww.pinpub.com/foxtalk.

However, if you’re interested in adding new languagecapabilities like VBScript or JavaScript to your VFPapplications, then read on. (Tip: If you’re interested inacquiring a VBScript reference, take a look at this URL:http://www.microsoft.com/scripting/default.htm?/

scripting/vbscript/default.h. Or, if you already ownVisual Studio, take a look at the Help files that comewith Visual Interdev—it includes VBScript andJavaScript references.)

Scripting control basicsUsing the scripting control in its basic form only requiresfour steps:

1. Add an instance to a form.

2. Set the language property. Your choices of languagesare VBScript or JScript.

3. Add the code you want to execute to the control usingthe AddCode method.

4. Execute the code using the Execute Statement methodof the control.

The following code creates a basic message boxfunction using VBScript:

#DEFINE CRLF chr(10) + chr(13)*-- Set the languageThisform.oleScriptingControl.Language = "VBScript"*-- Add a functionThisform.oleScriptingControl.AddCode([Function Test] ; + CRLF + [msgbox "hello"] ; + CRLF + [ End Function ])*-- Execute the functionThisform.oleScriptingControl.ExecuteStatement("Test")

That seems pretty straightforward, doesn’t it? Here’show the code works: The first step to using the scriptingcontrol is to set the controls language property. You havetwo choices: VBScript and JScript. If you specify thelanguage property as VBScript, the code you add with theAddCode method must be VBScript (a subset of VisualBasic for Applications). If you specify JScript, the codeadded must be in JavaScript.

Next, use the AddCode method to load the controlwith source code to execute. The AddCode methodaccepts one parameter: a string that contains multiplelines of code delimited by CR+LF characters.

Finally, run your code using either the Run orExecuteStatement methods. The Run method executesfunctions or subroutines. The ExecuteStatement method

Page 16: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

16 http://www.pinpub.comFoxTalk June 1998

executes a single statement (like “x = 1”) or a simple callto a function. There’s one aspect of this method that’srather tedious—the requirement for you to code manually,delimiting each programming line with CR+LF sequences.

As a result, wouldn’t it be convenient to have amechanism similar to the VFP command window? Ithought so, so I created a Scripting Control testbed.Figure 1 displays my Visual Basic command windowstyle interface.

Manipulating FoxPro objects with VBScriptNow for something really cool. One of the most powerfulaspects of the scripting control is its ability to manipulateVFP (or Visual Basic) native objects. Calling theAddObject method of the scripting control does this. Yes,the scripting control has a method called AddObject, soyou can add an object to the scripting object container.The syntax for this method consists of two parameters.The first is the name that the object will be referenced byin your VBScript or JScript code. The second parameter isa handle to your object.

To add a form to the scripting control, use thefollowing syntax:

thisform.oleScriptingControl.Object.AddObject( ; "frmFoxProForm",Thisform)

One item you’ll quickly notice is the inclusion of theObject keyword. VFP provides this property to deal withActiveX controls that share similar properties andmethods with VFP objects. If you attempt to call theAddObject method without the Object keyword, VFP willaddress the FoxPro version of the AddObject method andnot the AddObject method of the scripting control.

Upon adding a reference to your VFP object, you canbegin addressing the object in your VBScript using thefirst parameter of the AddObject method. The followingcode demonstrates changing properties on a VFP form

using VBScript:

Function SetFormProperties() If IsObject(frmFoxProForm) Then frmFoxProForm.Caption = "Hello World" frmFoxProForm.BackColor = 100000 End IfEnd Function

This example demonstrates setting properties on aVFP object. You can also call VFP methods. The followingcode shows how to call the click event of a button on thescripting form:

Function CallFormMethod() If IsObject(frmFoxProForm) Then frmFoxProForm.BackColor = 400000 frmFoxProForm.cmdClickTest.Click()End IfEnd Function

Scripting “gotchas”When developing applications using the Microsoftscripting control, there are some “gotchas” you need totake into account. The first deals with adding VFP objectsto the scripting control using the AddObject method. It’simportant to make sure the scripting control releases yourobjects when it’s done with them. You can release yourVFP controls by calling the scripting control’s Resetmethod. If you don’t use the Reset method, VFP mightget a visit from Dr. Watson when you release the formwith the scripting control on it. The best place to put theReset call is in the QueryUnload event of the scriptingcontrol’s form.

While I’m talking about Dr. Watson, there’s anotheritem to take into account. If you use ActiveX controls inyour applications, you need to turn FoxPro’s ActiveXDual Interface option off. You do this by putting thefollowing call in either your main program or in the Loadmethod of your form:

*-- Turn ActiveX Dual Interface Option OffSYS(2333,0)

If you don’t do this, FoxPro won’t release yourforms correctly.

Finally, for good measure, it’s always a good idea tocall the scripting control’s Reset method after executingcode. The reason for this is that the control has a tendencyto become unstable if you call the AddCode methodrepeatedly.

ConclusionNow that you have a basic understanding of using thescripting controls, you might say, “So what good does thisdo me?” Here are some uses I’ve already found handy:

• Add your own macro language to your applications.(Hint: Use a memo field for storing code.)

• Adopt existing Visual Basic or JavaScript code, like

Figure 1. This form demonstrates the capabilities of the Microsoftscripting control.

Page 17: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 17FoxTalk June 1998

functions stored in Active Server Pages.

• Even use an alternate language for building yourFoxPro applications.

You’re only limited by your creativity when it comesto adding scripting to your applications.

Finally, I’d like to thank two people for assisting mewith this article: Ken Levy, for inspiring it in the firstplace, and John Petersen, for making sure my article

would be useful for other VFP developers. ▲

06PADDOC.ZIP at www.pinpub.com/foxtalk

Rod Paddock is president of Dash Point Software, Inc., which is

based in Seattle, and specializes in developing applications using Visual

Interdev, VFP, VB, and SQL Server. DPSI was a finalist in the 1998 Visual

FoxPro Excellence Awards. Rod is currently working with John Petersen

on his new book, Visual FoxPro 6 Enterprise Development

(Prima Publishing). www.dashpoint.com, [email protected].

Transactions: Tahoe and MTS . . .Continued from page 14

that resources get released:

*-- Obtain a reference to transaction context.oServer = CREATEOBJECT("MTXaS.Appserver.1")oContext = oServer.GetObjectContext()

*-- Open database and view.OPEN DATABASE TestUSE Account

*-- Check the balance.nBalance = account.balance

*-- Complete transaction.oContext.SetComplete()

CLOSE DATABASES ALLRETURN nBalance

“Gotchas” galoreThose of you who attend the 1998 Visual FoxPro DevConmight notice that my beard is a little grayer this year thanlast. Working with new features of beta software whenthere’s nobody to go to with questions sometimes seemsto take many years off your life. Furthermore, in adistributed environment such as I’ve described, it can betough to know which product’s documentation to refer towhen you get an error. I hope you’ll benefit from my ownaccelerated aging process.

MTS components can have no user interface.They can’t use commands like WAIT WINDOW orMESSAGEBOX(). Nor can ODBC connections openODBC login screens. When you create connections forremote views, be certain to use DBSETPROP() to set theconnection’s DispLogin property to 3 to ensure that theuser is never prompted.

MTS components are always in-process servers. In thepast, there was no difference in VFP between multi-use in-process servers and single-use in-process servers. TahoeOLEPUBLIC classes that will be used in MTS must be setfor multi-use instancing.

Debugging servers running in MTS can be a littletricky to debug. The process goes something like this:Create DLL, create MTS package, run code, crash. So then

you make a change in your code and attempt to rebuildthe DLL. But the DLL is in use and can’t be overwritten.The problem is that MTS keeps components in memoryfor faster reuse. In the Transaction Server Explorer, youcan select the correct computer, right-click, and choose theShutdown Server Processes option. If you try to build theDLL again and get the same error, you might want toresort to using the Task Manager to end the processesinstead. Open the Task Manager and look for “MTX.EXE”on the Processes page. You might see it in the list morethan once. Select it and click the End Process button.Don’t try this except on development machines.

Don’t forget that your in-process servers aren’trunning in your application’s process, they’re running inan MTS process. Any environment settings you’ve madein your application don’t apply in the MTS process. I agedseven years just because of a file-in-use error due to myfailure to remember to SET EXCLUSIVE OFF.

More to comeNo, I’m not planning another article in this series, but besure to look for Jim Falino’s upcoming how-to article thatdiscusses the specific implementation tricks and tips.

I’m very excited about the possibilities presented byTahoe and MTS. Although there’s much room forimprovement, the combination offers many advantagesto client/server application developers today. Andwhen SQL Server 7 is available later this year with itsembeddable SQL Desktop, those advantages will beavailable for all applications. There will be nothing to stopme from delivering the most powerful, robust databaseapplications to all my clients, whether they have ahundred workstations or one laptop. ▲

Gary DeWitt is software magician at Sunpro, Inc., the leader in fire service

software. He lives on the California shore of Lake Tahoe and is convinced

the next version of Microsoft Visual FoxPro, code-named Tahoe, was

named with him in mind. While Visual FoxPro is his favorite development

tool, he also programs in C++, Java, and Visual Basic. Gary is a Microsoft

Certified Professional and Microsoft Most Valuable Professional.

[email protected].

Page 18: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

18 http://www.pinpub.comFoxTalk June 1998

The Kit Box FFFoooxxxTalk

Synchronized SwimmingPaul Maskens and Andy Kramek

This month, Paul and Andy show how to make multiple formswith private Datasessions navigate to the same record.

Andy: Here’s a nice little problem, Paul. I’ve got a form(running in a private DataSession) that’s used to displaysaved Visual FoxPro forum messages, and obviously Iwant to be able to search the underlying table to find aparticular message. This search might be by Subject, byAuthor Name, by Thread number, and so on. I just don’tknow which will be needed at any particular moment. Icould simply use a popup form with, say, a grid on it,select the message I want, pass its record number back tothe main form, and then do a LOCATE and REFRESHthere. However, it seemed to me that linking the twoforms in some way would be a cleaner (and, moreimportantly, more generic) approach.

My first thought was to have the forms share acommon DataSession—after all, that’s pretty easy to dowhen the child form is modal. You just set its DataSessionto 1 (Default), and it will inherit its DataSession from theparent form.

Paul: Yeah, but that’s undocumented behavior, isn’t it?Can we really rely on it? And doesn’t it also causesome problems with DataSession names? Despite theDataSession name getting lost, this technique worked inVFP 3. But I’ll have to retest it in 5.0 and 6.0 if I everrecompile that application.

Andy: Well, it’s true that VFP does seem to lose track ofthe DataSession name upon returning from a child formset up in this way—it just shows “Unknown” as theDataSession name. But it doesn’t seem to affect anything.As for relying on behavior that’s undocumented—youhave a point. Remember—back in 3.0—the “Empty” baseclass that’s used when you do a “SCATTER NAME”? Thatwas undocumented and exposed in VFP 3.0, but itdisappeared without warning in 3.0b!

On second thought, better to make the child formswitch itself to the parent form’s DataSession. We can dothat by passing the parent form’s DataSessionID propertyto the child form like this:

DO FORM frmChild WITH ThisForm.DataSessionID

In the receiving form, just set the DataSessionID propertyin the Init() method, like this:

LPARAMETERS tnDSID ThisForm.DataSessionID = tnDSID

Paul: Oh, great! But what happened to my bound controlswhen I did that? After all, they’ve already been initializedin the child form’s DataSession—now I’m switching it! Isthat what made all my controls lose their controlsources?

Andy: Hmm. A good point. Any bound controls willirrevocably lose their binding. I suppose we could resetthem all in the Init() (or a custom method called bythe Init())?

Paul: Yes, that would work. However, I prefer a methodcalled by the Init, although this would be a bit of a pain ifyou had a pageframe with 40 controls on each of threepages! I suppose you could go through the .controls[]collection and reset the controlsource properties, but therehas to be a better way than recursion into every containeryou have on your form.

Andy: Okay, wise guy—what do you suggest, then?

Paul: Pass the Primary Key of the current record betweenthe forms! However, instead of diving in and creating myown methods to send and receive the Primary Key, Icheated. I used Drag and Drop to link two forms!

On the “sending” form, I had a commandbutton that assigned the table’s Primary Key (well,ALLTRIM(STR(ID), actually) to its caption as I moved themouse over it. Then I set the DragMode of the button toAutomatic in the Properties window.

The “receiving” form looked at the caption of theobject it received in its DragDrop method and then did aSEEK() on VAL() of that object’s caption.

So I had two forms that were sort of loosely coupled.The receiving form could navigate separately because itused a private DataSession. The sending form wouldalways navigate separately, again with a privateDataSession. Just drag the button from the sendingform to the receiving form—voila! Record pointerssynchronized . . .

Andy: That’s pretty neat! It does depend on userintervention, though, doesn’t it? I mean, what happens ifthey close the child form without dragging the button toit? Not much, I suppose?

Paul: Absolutely right! You see, I intended this mechansimto be a way of linking an overview to a detail form whenrequired, not permanently. You got me thinking, though.

Page 19: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 19FoxTalk June 1998

It’s only a moment’s work to add a “LINK” check box,and another moment to use Refresh() to set the cmdLinkcaption instead of waiting for the mouse. Refresh() iscalled after record navigation to update the contents of allthe controls. Next, add a test in the sending form’sRefresh() like so:

IF chkLink.value = .t. Form2.DragDrop(THISFORM.cmdLink).ENDIF

Andy: Whoa! Slow down a bit there, you’ve lost me.Explain how these two forms link themselves again—how does one form “know” about the other one?

Paul: Okay, I know the name of Form2, right? So I canloop through the _SCREEN.Forms[] collection looking forthe Name. I could have used the Caption property, orComment, or Tag—Name seems easiest.

When I find it, I can fake a DragDrop event droppingon Form2 by calling the DragDrop() method withparameters that look like a real DragDrop has happened.

You know that events happen. What VFP does whenan event happens is to run the code in the method withthe same name to handle any special actions when theevent happens. So if I call that method directly, theprogram can’t tell the difference.

I set the command button’s caption, then call this:

_SCREEN.Forms[i].DragDrop(THISFORM.cmdDrag,0,0)

to fool VFP into running my navigation code in the form.

LPARAMETERS oSource, nXCoord, nYCoord

SELECT (THISFORM.MainTable) lnOldRec = RECNO() SEEK VAL(oSource.caption)ENDIF

I made it a little more complex, because my programfakes DragDrop with coordinates of 0,0, and I assumeanything other than that is a real drop. So the focus shouldbe set to THISFORM—Form2.

Without any extra custom methods and with only oneobject (perhaps an invisible Label), you could link twoforms together. Yes, before you say it, I think properlynamed methods are a good idea. I’m overloadingDragDrop to make it do something it isn’t meant to do.

However, a better way is to have a set of interactingobjects in a Mediator-Colleague pattern.

Andy: Huh? What does that mean?

Paul: The Mediator receives messages and sends them toevery Colleague it knows about, except the sender. TheColleague registers with a Mediator and processes anymessage it receives.

Andy: Ah, I see. So in our example, the parent form wouldoriginate a message and send it to the Mediator object,which in turn would simply pass it on to the objects itknows about. The child form is then responsible for

registering with the Mediator so that it can receive(and presumably send) messages by that route. That’spretty cool!

Paul: Right (or possibly wrong <g>). In your Applicationobject, you’d need to have a Register(toColleague)method where other objects sign up to receive messages.Any object that wants to receive messages has to have aHandleMessage() method.

Andy: Hang on there, fellah—this all sounds a bit specific.I don’t use an Application object all the time. Wouldn’tit be better if you made your Mediator a class in itsown right?

Paul: I think so. This would mean all the pointers to theregistered objects and the code that passes messages tothem would be encapsulated in an object. That’s properOOP. For now, though, I’d like to keep it simple andassume your Application object exists and is calledgoApp.

Andy: Okay, I’ll buy that for now. So how does it all workin practice?

Paul: Easy. In the Init(), your form class has a call togoApp.Register(THIS). The Application object’sRegister(toColleague) method stores the object pointerin a 2-D array, along with SYS(1272,toColleague). ThatSYS() function gets the containership hierarchy andthe name of the object—which will be needed later!So, to recap: goApp.aColleague[i,1] is the object,goApp.aColleague[i,2] is its name.

Your Application object should also have aMediate(tcMessage, toSender) method. This loopsthrough the stored object array (why use i? It’s a sortof tradition <g>) and checks—using theSYS(1272,toSender)—to see whether the entry in.aColleague[i,2] matches. If it does, ignore it—yougenerally don’t want to send the message backto the sender. Otherwise, call the.aColleague[i,1].HandleMessage(tcMessage).That’s it!

Andy: Aha! I get it now. That SYS(1272) call is workingaround the fact that we can’t compare objects with eachother. So my Form class has to have an extra methodHandleMessage() and a call (presumably conditional) inits Init() to the mediator Register(), like this:

PROCEDURE INIT IF TYPE( 'goApp') = "O" AND ! ISNULL( goApp ) goApp.Register( THIS ) ENDIF

But what goes into the HandleMessage() method?

Paul: That depends on what messages you want the objectto react to. Of course, you need to define somestandards—like perhaps “SEEK:<primarykeyvalue>“.

Page 20: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

20 http://www.pinpub.comFoxTalk June 1998

Then you can just use a CASE statement in theHandleMessage() method to define which messages torecognize:

DO CASE CASE tcMessage = "SEEK:" *** React to this message lnPK = VAL( SUBSTR( tcMessage, 6 )) SEEK lnPK OTHERWISE *** Do nothing - ignore the messageENDCASE

Andy: Just one thought. Haven’t we now got an externalreference to an object in the Mediator? Doesn’t this meanthat we can no longer release the object?

Paul: Correct! So we also need an UnRegister() methodthat’s called from each Form’s Unload() method. LikeRegister(), goApp.UnRegister(THIS) would suffice.

All it needs is to loop through .aColleague[i,1] again,testing SYS(1272, toSender) as before, but this time, onfinding a match, just set .aColleague[I,2] to .NULL. so thatthe object reference is discarded.

Oh, and about your desire for reusable classes? Here’sone I prepared earlier, in the best Blue Peter tradition . . .

Class cusMediator is the Mediator half of the pattern.This can either be added to a form at design time, inwhich case it should be named “oMediator”, or it can becreated as an uncontained object, in which case it shouldbe referred to by a global variable goMediator.

***************************************************-- Class: cusMediator*#DEFINE _CR CHR(10)*DEFINE CLASS cusMediator AS custom

*-- Number of registered colleague controls nColleagues = 0 Name = "cusMediator"

*-- Array of colleague control Hierarchy, oRef. DIMENSION acolleagues[1,2]

*-- Called by colleague to register itself with *-- the Mediator PROCEDURE register LPARAMETERS toColleague

WITH THIS .nColleagues = .nColleagues +1 DIMENSION .aColleagues[ .nColleagues, 2] .aColleagues[ .nColleagues, 1] ; = SYS( 1272, toColleague ) .aColleagues[ .nColleagues, 2] = toColleague ENDWITH

IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT THIS.Name +" register()" +_CR ; +SYS( 1272, toColleague ) +_CR ; ELSE WAIT WINDOW THIS.Name +" register()" +_CR ; +SYS( 1272, toColleague ) +_CR ; TIMEOUT 2 ENDIF ENDIF ENDPROC

*-- Removes all object references to colleagues, *-- call before the Destroy() to prevent GPFs PROCEDURE cleanup

LOCAL lnCount

WITH THIS FOR lnCount = 1 TO .nColleagues .aColleagues[ lnCount, 1] = "" .aColleagues[ lnCount, 2] = .NULL. NEXT .nColleagues = 0 ENDWITH ENDPROC

*-- Receives Source object and message to *-- mediate (String) PROCEDURE mediate

LPARAMETERS toColleague, tcMessage

IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT THIS.Name +" mediate()" +_CR ; +tcMessage +_CR ; +SYS( 1272, toColleague ) ELSE WAIT WINDOW THIS.Name +" mediate()" +_CR ; +tcMessage +_CR ; +SYS( 1272, toColleague ); TIMEOUT 2 ENDIF ENDIF RETURN THIS.SendMessage( toColleague, tcMessage ) ENDPROC

*-- Sends the message received by Mediate() to all *-- the colleagues registered EXCEPT the one *-- that sent it PROCEDURE sendmessage

LPARAMETERS toColleague, tcMessage

LOCAL lcColleague, lnColleague, llStatus

WITH THIS lcColleague = SYS( 1272, toColleague ) llStatus = .T. FOR lnColleague = 1 to .nColleagues IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT THIS.Name ; +"sendmessage("+allt(str(lnColleague))+")"; +_CR ; +.aColleagues[ lnColleague, 1] ELSE WAIT WINDOW THIS.Name ; +"sendmessage("+allt(str(lnColleague))+")"; +_CR ; +.aColleagues[ lnColleague, 1] ; TIMEOUT 2 ENDIF ENDIF IF NOT( .aColleagues[ lnColleague, 1] ; == lcColleague ) IF .aColleagues[lnColleague,2] ; .Receive(tcMessage) * * Do nothing, assumes all colleagues receive OK * ELSE * * Note colleague failed to handle message * llStatus = .F. ENDIF IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT THIS.Name ; +" sendmessage()" +_CR ; +.aColleagues[ lnColleague, 1] +_CR ; +IIF(llStatus,"OK","Fail") ELSE WAIT WINDOW ; THIS.Name +" sendmessage()" +_CR ; +.aColleagues[ lnColleague, 1] +_CR ; +IIF(llStatus,"OK","Fail") ; TIMEOUT 2 ENDIF ENDIF NEXT ENDWITH

*

Page 21: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 21FoxTalk June 1998

* If any colleague failed to handle the message, * the status will be .F. and not .T. as set * earlier * RETURN llStatus ENDPROC

PROCEDURE unregister

LPARAMETERS toColleague

LOCAL lnColleague, llRetVal

WITH THIS lnColleague = ASCAN( .aColleagues, ; SYS( 1272, toColleague )) IF lnColleague != 0 lnColleague = ASUBSCRIPT( .aColleagues, ; lnColleague, 1 ) llRetVal = .t.

.aColleagues[ lnColleague, 1] = "" .aColleagues[ lnColleague, 2] = .NULL.

= ADEL( .aColleagues, lnColleague ) DIMENSION .aColleagues[ .nColleagues, 2 ] ELSE llRetVal = .f. ENDIF ENDWITH

IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT THIS.Name +" UnRegister()" +_CR ; +SYS( 1272, toColleague ) +_CR ; ELSE WAIT WINDOW THIS.Name +" UnRegister()" +_CR ; +SYS( 1272, toColleague ) +_CR ; TIMEOUT 2 ENDIF ENDIF

RETURN llRetVal ENDPROC

ENDDEFINE**-- EndDefine: cusMediator**************************************************

Class cusColleague is the Colleague half of thepattern. This class can be added to any container, in whichcase the DoAction() method will probably do things toTHIS.parent—I usually just call a method. I expect thatthe DoAction() method will be overridden in any instanceor subclass.

You could make it collaborate with any object, with alittle modification—like passing THIS to the Init(toClient)method and storing that in a property of the Colleague foruse in the DoAction() method.

***************************************************-- Class: cusColleague*#DEFINE _CR CHR(10)*DEFINE CLASS cusColleague AS Custom

Name = "cusColleague"

*-- Message is sent here by mediator, this passes *-- it on to DoAction() and returns the result. PROCEDURE Receive

LPARAMETERS tcMessage IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT SYS(1272, THIS) +" receive()" +_CR ; + tcMessage ELSE WAIT WINDOW ;

SYS(1272, THIS) +" receive()" +_CR ; +tcMessage ; TIMEOUT 2 ENDIF ENDIF RETURN THIS.DoAction( tcMessage ) ENDPROC

*-- Performs anything necessary to handle the *-- incoming message. Returns success/fail. PROCEDURE DoAction

LPARAMETERS tcMessage * * This will of course be overridden in an instance * of this class! * IF "Visual FoxPro 05" $ VERS() DEBUGOUT SYS(1272, THIS) +CHR(13) ; +"DoAction( "+ tcMessage +" )" ELSE WAIT WINDOW SYS(1272, THIS) +CHR(13) ; +"DoAction( "+ tcMessage +" )" ; TIMEOUT 2 ENDIF RETURN ENDPROC

*-- Given a message (String) it sends it off to *-- the mediator referenced through *-- THISFORM.oMediator.Mediate() PROCEDURE send

LPARAMETERS tcMessage

* * Send message via form mediator and return. * IF TYPE("THISFORM.oMediator") != "U" IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT SYS(1272, THIS) +" send()" +_CR ; +tcMessage +_CR ; +"Form Mediator" ELSE WAIT WINDOW ; SYS(1272, THIS) +" send()" +_CR ; +tcMessage +_CR ; +"Form Mediator"; TIMEOUT 2 ENDIF ENDIF RETURN THISFORM.oMediator.Mediate( ; THIS, tcMessage ) ENDIF

* Unless there isn't one! * Send message via global mediator and return. * IF TYPE("goMediator") != "U" IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT SYS(1272, THIS) +" send()" +_CR ; +tcMessage +_CR ; +"Global Mediator" ELSE WAIT WINDOW THIS.Name +" send()" +_CR ; +tcMessage +_CR ; +"Global Mediator"; TIMEOUT 2 ENDIF ENDIF RETURN goMediator.Mediate( THIS, tcMessage ) ENDIF

* * No mediators on standard interfaces, fail. * IF TYPE("glDebug") != "U" IF "Visual FoxPro 05" $ VERS() DEBUGOUT SYS(1272, THIS) +" send()" +_CR ; +tcMessage +_CR ; +"NO Mediator - FAILED" ELSE WAIT WINDOW THIS.Name +" send()" +_CR ; +tcMessage +_CR ; +"NO Mediator - FAILED"; TIMEOUT 2 ENDIF

Page 22: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

22 http://www.pinpub.comFoxTalk June 1998

ENDIF RETURN .F. ENDPROC

PROCEDURE Init

* * Register with form mediator and return. * IF TYPE("THISFORM.oMediator") != "U" RETURN THISFORM.oMediator.Register( THIS, .F. ) ENDIF

* Unless there isn't one! * Register with global mediator and return. * IF TYPE("goMediator") != "U" RETURN goMediator.Register( THIS, .F. ) ENDIF

* * No mediators on standard interfaces, fail. * In failing this will prevent FORM * instantiation under VFP 3, but this object * will merely not exist under VFP 5 * RETURN .F. ENDPROC

PROCEDURE Destroy

* * Remove from form mediator and return. * IF TYPE("THISFORM.oMediator") != "U" RETURN THISFORM.oMediator.UnRegister( THIS ) ENDIF

* Unless there isn't one! * Remove from global mediator and return. * IF TYPE("goMediator") != "U" RETURN goMediator.UnRegister( THIS ) ENDIF

* * No mediators on standard interfaces, fail. *

RETURN .F. ENDPROC

ENDDEFINE**-- EndDefine: cusColleague**************************************************

Paul: I’ve left it in my debugging code, which I controlusing the existence of the global variable glDebug. This isa hangover from VFP 3, where we had no debug outputwindow, yet I wanted to control whether or not my debugoutput appeared. It’s not defined because I’m not usuallydebugging this code. If you want to see those messages,simply type “glDebug = .t.” and press Enter in theCommand window.

You’ll notice that I’ve adopted a belt-and-bracesapproach in the Colleague class. I’ve checked for both aglobal mediator object (goMediator) and a mediator objectadded to the current form (THISFORM.oMediator)because I wasn’t sure how it might be used. I haven’tadded a third test for goApp.oMediator because I believein using separate public variables for various managers.Practical OOP here, Andy.

Andy: I’m speechless with admiration! ▲

Paul Maskens is a VFP product specialist who works for the Visual

Development Studio of Chelmsford, based in Oxford, England.

[email protected].

Andy Kramek is a long-time FoxPro developer, independent

contractor, and occasional author based in Birmingham, England.

[email protected].

Editorial: Enter Sandman . . .Continued from page 2

so let’s do it. I mean, wouldn’t it be cooooool?” Who givesa rip if it should be done? They’ve got the business in aperpetual death grip of one-upmanship. Their processorruns at 410 MHz. Let’s make ours run at 450—and we’llprice it lower!

I’m not saying I want to go back to dBASE II. We’vehad some wonderful advances that are a joy to workwith. Visual FoxPro is still a thrill a minute (including thethrills you get when Dr. Watson comes to visit <sigh>).And the rumor is that Office will be using a singlecode-base worldwide next year. That’ll be a nice change,don’t you think?

It’s the same in the music industry. If you’re going tofollow bands these days, you have to be nimble. Thismonth’s hot news is next month’s history. I’m remindedof the scene in the movie “Clueless,” where severalteenage girls are checking out a jacket in the mall. Onederides the jacket as “so five minutes ago.” The otherdecides not to buy. Fashionable today, boring tomorrow.

So you’re going to have to be just as nimble to followthe software trends. Yesterday’s big news is today’syawn. Don’t you feel a little cheated if you’ve made a biginvestment in last year’s alphabet soup of database accessacronyms, and then did it again this year, only to find outit doesn’t all really work yet, and you’re going to have todo the learning curve again next year?

Where I’m heading with this, though, is that the nextcouple of years are going to be decision time for a lot ofus. People in my user group complain because we want tosend out meeting notices via e-mail. “Sure, I have e-mail.But I only check it about once a month.” C’mon! As afriend of mine said (and as I’ve echoed in this columnbefore), “If you’re not willing to make a professionalinvestment in your career here, then run, don’t walk, tothe exit of this industry.”

I’m tired. Time to go home. I’m going to pull a coldone from the fridge, turn off all the lights, and listen toone of the all-time greats in heavy metal. Classics nevergo out of style, and neither will “Enter Sandman.” I’llhave more energy tomorrow. I’ll need it, to stay up withthese young whippersnappers . . . ▲

Page 23: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

http://www.pinpub.com 23FoxTalk June 1998

Traversing Transactions . . .Continued from page 11

IF NOT TableUpdate( … ) llRollBack = .T. ELSE IF NOT TableUpdate( … ) llRollBack = .T. ENDIF ENDIFENDIFIF llRollBack ROLLBACKELSE ENDTRANSACTIONENDIF

The preceding code is pseudo-code, so don’t take itliterally. Here I used a series of nested IF statements tocontrol the actions. I could have put all of the aliases in anarray and used a FOR/ENDFOR loop to go through them,exiting the loop when they were all done or when any oneof them failed.

What can go wrong?In the preceding pseudo-code, assume that the transactionfailed and that you issued the ROLLBACK. What, exactly,do you roll back to? Are the buffers returned to theirpre-edit state, or are the buffers still dirty with theuser’s edits?

The answer is that the buffers are restored to thedirty state that they were in prior to the BEGINTRANSACTION. This means that you still have to dealwith the user’s edits. You have the control here—you canput the user back into the form and let him or her decidewhat to do, or you can TableRevert all the aliases anddiscard the user’s work. This is all up to you in the codethat you write.

What happens if the computer shuts off during thetransaction? Obviously, VFP has no record of thetransaction when the system is restarted. So did some ofthe tables get updated or not? None of the tables gotupdated, none of the buffers are dirty, and the result isjust as if a ROLLBACK had been executed.

Another situation that’s occurred and caused a

lot of problems is when the developer puts together aform and puts a BEGIN TRANSACTION in the Init andissues either an ENDTRANSACTION or a ROLLBACKin the destroy, depending on which button the userchose to exit the form. What problems could this cause?The first is that no other user can do anything with anyof the tables involved in this form until the current userexits the form and releases the locks. I’ve seen thissituation used as an argument against transactions—well,come on now, anything that’s used incorrectly can causeproblems. Transactions used correctly will limit thetime between the beginning and ending to the smallestperiod possible. The Init to Destroy of a form iscertainly not the smallest time period possible—it’sa time period that’s not even under the control ofthe developer.

It’s been said that VFP’s transactions aren’t as“robust” as the transaction handling of client/serverdatabase servers. I say, so what? Is the fact that a semitractor has brakes that are more “robust” than the brakeson my car a reason to not use my car’s brakes? No, it’snot. VFP’s transactions are helpful, and they serve apurpose. They should be used, even when the data isstored in a client/server database.

ConclusionTransactions in VFP are a very valuable feature of theproduct. They allow you to group a number of updateoperations into one “all or none” operation. In today’sworld, you seldom build forms that only manage a singletable—the multi-table form has become the norm.Managing these multi-table updates becomes a veryimportant aspect of your work, and transactions areinvaluable in doing this. ▲

Jim Booth is a Visual FoxPro developer and trainer. He has spoken

at FoxPro conferences in North America and Europe. Jim has been

a recipient of the Microsoft Most Valuable Professional Award

every year since it was first presented in 1993. 203-758-6942,

[email protected].

acts as the placeholder for the TreeView control. ThecNewObjectName properties for oTreeViewLoaderand oImageListLoader specify the names of the controlsto create (oTree and oImageList, respectively).oTreeViewLoader also has the name of the placeholderobject (shpTreeView) in its cObjectName property andthe name of the TreeView subclass we want to use(MyTreeView) and its location (ACLASSES.PRG) in thecClass and cLibrary properties. The Init method of theform sets some properties for the ActiveX controls,loading images in the case of the ImageList and nodes inthe case of the TreeView.

Tame Your ActiveX Controls . . .Continued from page 5

local loPicture

* Load the ImageList images.

with This.oImageList loPicture = loadpicture('AUDIO.ICO') .ListImages.Add(, 'Audio', loPicture) loPicture = loadpicture('DESKTOP.ICO') .ListImages.Add(, 'Desktop', loPicture)endwith

* Set some TreeView properties.

with This.oTree .ImageList = This.oImageList.Object .Style = 7 .LineStyle = 1 .LabelEdit = 1 .HideSelection = .F. .Indentation = 25

* Load the TreeView with sample nodes.

.Nodes.Add(, 1, 'Top1', 'First Top Node', 'Audio')

Page 24: FoxTalkportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/ft0698... · 2008. 12. 8. · though I didnÕt change the TreeView on the form, simply opening the form and saving it caused

24 http://www.pinpub.comFoxTalk June 1998

Downloads

Portions of the FoxTalk Web site are available only to paidsubscribers of FoxTalk. Subscribers have access to additionalresources to give you an edge in FoxPro development.

UUUser nameser nameser name

PPPasswasswassworororddd

kerosene

lantern

Help is a Mouse-Click Away: Introducing Developer Solutions–Online at www.pinpub.comWhen a programming crisis or dilemma comes up, you needdependable solutions fast. Technical support calls are eating yourtime and costing hundreds of dollars—even when you don’t getsolid answers from them. In response to your needs, we’re proudto announce a new online search mechanism to help you find theinformation you need when you need it.

Check out the continually updated Pinnacle Publishingindex of expert-written articles at www.pinpub.com (tell yourcolleagues!). You’ll be able to access each monthly issue ofFoxTalk, SQL Server Professional, Smart Access, and Visual BasicDeveloper from 1996 through the present. You can view the entireonline Table of Contents of a newsletter or enter a search term topinpoint all of the available related articles. The search is free, tipsare free, and articles (including any corresponding SubscriberDownload files) can be downloaded for a nominal fee.

Here’s how it works: Go to www.pinpub.com and click onDeveloper Solutions—Online. Enter the search term of your

choice to access any of the material we’ve published onthis topic. Alternatively, you can click on “Contents” on the upper-left side of the screen to list any of the published material byissue date.

Possible uses:• Use the search function to find an article you can’t locate

in your back issues.• Find all of our published articles on a given topic.• Use the pay-per-view option to access articles published

before your subscription started by following the onlinesign-on instructions. (You’ll be able to access the downloadfile as well.)

• Browse the “Contents” portion for summaries of articles andfree tips that have been published in FoxTalk and our otherpublications.

Coming soon: Online subscription options!

June Subscriber Downloads• 06DHENSC.ZIP—Source code described in Doug Hennig’s

article. SFCTRLS.VCX and SFACTIVEX.VCX are class librariesused to support TREEVIEW.SCX, a sample form that showshow to add ActiveX controls at runtime. SFREGISTRY.VCXprovides access to the Windows Registry.

• 06PADDOC.ZIP—Source code for two items discussed in RodPaddock’s article. CODEBLCK.PRG is Randy Pearson’s CodeBlock command window replacement. SCRIPTING.SCX is asample form that demonstrates how to use the scriptingcontrol in VFP.

Extended Articles• 06COOL.HTM—Extended Article. How do you keep current

with everything that’s happening to the wide array of toolsyou’re using? Woody’s Office Watch is a free weekly e-mailthat covers everything that’s happening with MicrosoftOffice. Also covered: Directory Toolkit, a shareware tool thatpulls together a number of features that you’d like to see inWindows Explorer.

• 06COOL.ZIP—Contains SETUPDT.EXE, the installation file forDirectory Toolkit, a shareware utility described in thismonth’s Cool Tool column.

• 06PETER.HTM—Extended Article. This month, John Petersenaddresses the topic of where to place your Automationcode: in the VFP application, in the Office application, or, inthe case of Tahoe, in a VFP COM object.

.Nodes.Add(, 1, 'Top2', 'Second Top Node', 'Audio') .Nodes.Add('Top1', 4, 'Child1', 'First Child Node', ; 'Desktop') .Nodes.Add('Top1', 4, 'Child2', 'Second Child Node', ; 'Desktop') .Nodes.Add('Top2', 4, 'Child3', 'Third Child Node', ; 'Desktop') .Nodes.Add('Top2', 4, 'Child4', 'Fourth Child Node', ; 'Desktop')endwith

SummaryActiveX controls are both wonderful and terrible. They’rewonderful because they can give your applications theprofessional, polished look users expect from modern32-bit applications (the TreeView control that comes withVFP and the ctListBar control from dbi technologies inc.are good examples), or they can provide advancedcapabilities that would either be impossible or verytime-consuming to create in VFP code (the DynamiCubecontrol I discussed in my March 1998 column is an

example of that). They’re terrible because when “OLEclass not registered” or other OLE errors occur, you can’tjust roll up your sleeves and use the VFP Debugger totrack down the problems. SFActiveX has been a life saverfor me; it handles the main problem I have with ActiveXcontrols. I’m sure you’ll find it as useful as I have. ▲

06DHENSC.ZIP at www.pinpub.com/foxtalk

Doug Hennig is a partner with Stonefield Systems Group Inc. in Regina,

Saskatchewan, Canada. He is the author of Stonefield’s add-on tools for

FoxPro developers, including Stonefield Database Toolkit and Stonefield

Query. He is also the author of The Visual FoxPro Data Dictionary in

Pinnacle Publishing’s The Pros Talk Visual FoxPro series. Doug has spoken

at the 1997 and 1998 Microsoft FoxPro Developers Conferences

(DevCon), as well as user groups and regional conferences all over North

America. He is a Microsoft Most Valuable Professional (MVP).

[email protected], [email protected].