FoxTalk - dFPUG

24
1 Using Crystal Reports 7 with VFP 6 Nigel Reburn 2 Editorial: The Top Five Reasons to Take the Beta VFP 6.0 Certification Exam Whil Hentzen 8 Best Practices: Seeing Patterns: The Template Method Jefferey A. Donnici 12 Reusable Tools: Long Live PRGs! Doug Hennig 18 What’s Really Inside: Multiple Personalities Jim Booth 19 Who’s Afraid of the Registry? Kristyne McDaniel 24 November Subscriber Downloads EA Driving the Data Bus: Using the Windows Shell API to Display a GetFolder Dialog Box Andrew Coates EA Deployment: Getting Ready Richard A. Schummer EA The Kit Box: INCLUDE Me Out! Paul Maskens and Andy Kramek EA VB for Dataheads: What’s This Business About Visual Basic Classes? Whil Hentzen November 1999 Volume 11, Number 11 Fox Talk Continues on page 3 Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers Accompanying files available online at http://www.pinpub.com/foxtalk Applies specifically to one of these platforms. Applies to FoxPro v2.x Applies to VFP v5.0 Applies to VFP v3.0 Applies to VFP v6.0 (Tahoe) 6.0 6.0 Using Crystal Reports 7 with VFP 6 Nigel Reburn The promise of Windows is interoperability between applications. Unfortunately, that promise is often broken due to vendors not living up to their requirements. Integrating Crystal and Visual FoxPro should be one of those “slam dunk” connections. Unfortunately, deficiencies in both products make the process harder than it should be be. In this article, Nigel Reburn shows you how to navigate the landmines you’ll encounter, and how to make this pair of suitable matched applications fit. E ARLIER this year, Crystal launched version 7.0 of its excellent reporting product, and I decided it was time to start using it within my own Visual Fox applications. The decision was based mainly on the fact that Microsoft appears to have no short-term plans to upgrade its Fox reporting tools as they remain almost exactly as they have since version 2.0, back in 1993. To add further insult, VFP has even been overtaken by the likes of Access, which has always offered Word and HTML outputs, as well as a raft of other very useful formats. Anyway, enough Microsoft bashing—for a moment. The sad fact is that if you want to publish reports on the Web or send them via e-mail as I do, then you have to discard your trusted steed and go to the stables for a new mount. So what’s available to developers who have a business need to do these things? The fact is there are very few realistic options open to you. Of course, you could send your data in and out of Excel or Access using “automation,” but that would necessitate the installation of those applications at the target PC. No, I needed a robust runtime solution that could be deployed with my applications. For a while, I relied on a third-party tool called FoxWord, which was advertised in this newsletter by a U.S. company called Bell Consulting. FoxWord was a printer driver to which Fox reports could be sent for conversion to RTF text documents. The only drawbacks were that it’s very fussy, swapping printer drivers caused a few problems, and it was limited in 6.0 6.0

Transcript of FoxTalk - dFPUG

Page 1: FoxTalk - dFPUG

1 Using Crystal Reports 7with VFP 6Nigel Reburn

2 Editorial: The Top FiveReasons to Take the BetaVFP 6.0 Certification ExamWhil Hentzen

8 Best Practices: Seeing Patterns:The Template MethodJefferey A. Donnici

12 Reusable Tools: Long Live PRGs!Doug Hennig

18 What’s Really Inside:Multiple PersonalitiesJim Booth

19 Who’s Afraid of the Registry?Kristyne McDaniel

24 NovemberSubscriber Downloads

EA Driving the Data Bus: Usingthe Windows Shell API toDisplay a GetFolder Dialog BoxAndrew Coates

EA Deployment: Getting ReadyRichard A. Schummer

EA The Kit Box: INCLUDE Me Out!Paul Maskens and Andy Kramek

EA VB for Dataheads: What’sThis Business About VisualBasic Classes?Whil Hentzen

November 1999Volume 11, Number 11

FoxTalk

Continues on page 3

Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers

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

Applies specifically to one of these platforms.

Applies toFoxPro v2.x

Applies toVFP v5.0

Applies toVFP v3.0

Applies to VFPv6.0 (Tahoe)

6.06.0

Using CrystalReports 7 with VFP 6Nigel Reburn

The promise of Windows is interoperability between applications. Unfortunately,that promise is often broken due to vendors not living up to their requirements.Integrating Crystal and Visual FoxPro should be one of those “slam dunk”connections. Unfortunately, deficiencies in both products make the process harderthan it should be be. In this article, Nigel Reburn shows you how to navigate thelandmines you’ll encounter, and how to make this pair of suitable matchedapplications fit.

EARLIER this year, Crystal launched version 7.0 of its excellent reportingproduct, and I decided it was time to start using it within my own VisualFox applications. The decision was based mainly on the fact that

Microsoft appears to have no short-term plans to upgrade its Fox reportingtools as they remain almost exactly as they have since version 2.0, back in 1993.To add further insult, VFP has even been overtaken by the likes of Access,which has always offered Word and HTML outputs, as well as a raft of othervery useful formats.

Anyway, enough Microsoft bashing—for a moment. The sad fact is thatif you want to publish reports on the Web or send them via e-mail as I do,then you have to discard your trusted steed and go to the stables for a newmount. So what’s available to developers who have a business need to dothese things? The fact is there are very few realistic options open to you. Ofcourse, you could send your data in and out of Excel or Access using“automation,” but that would necessitate the installation of those applicationsat the target PC. No, I needed a robust runtime solution that could bedeployed with my applications.

For a while, I relied on a third-party tool called FoxWord, which wasadvertised in this newsletter by a U.S. company called Bell Consulting.FoxWord was a printer driver to which Fox reports could be sent forconversion to RTF text documents. The only drawbacks were that it’s veryfussy, swapping printer drivers caused a few problems, and it was limited in

6.06.0

Page 2: FoxTalk - dFPUG

2 http://www.pinpub.comFoxTalk November 1999

From the Editor FoxTalk

The Top Five Reasons to Take theBeta VFP 6.0 Certification ExamWhil Hentzen

IN the May 1999 issue of FoxTalk, I wrote an editorialwarning you about taking certification exams for thewrong reasons (see “Certification: Should You Care?”).

If you don’t remember it, go back and re-read it. I’llwait, because it’s important for this editorial to have theproper background.

Okay, you’re back. For those of you who havebeen whining about the lack of a VFP 5.0 exam butweren’t paying attention, the VFP 6.0 Certification Examis being worked on as I type this editorial, and it shouldbe ready by the end of the year. However, you don’thave to wait until the end of the year, and, in fact, youprobably shouldn’t.

Just like the product itself, the VFP 6.0 exam comes intwo pieces—a beta exam and the final exam. The purposeof the beta exam is to allow people to “test drive” theexam before it’s unleashed on the general populace. Butthe beta exam is a bit different from the final exam—here’s how.

First of all, it’s longer than a standard exam. Why?Because the exam was written by humans who are just asfallible as humans who write software—and we all knowwhat kind of ridiculous bugs show up in beta software.There are probably questions that are just as ridiculous ona beta exam, so additional questions are included in the

exam so that there will be enough for the final exam oncethe bad ones get weeded out. Second, you get to includeyour comments on specific questions as well as the entireexam while you’re taking it. (Wouldn’t you have loved tobe able to do that in college?)

Finally, unlike beta tests for software, the beta exam isavailable for anyone to take. Well, anyone with 50 bucks.So you, simply by virtue of knowing about the betaversion of the exam, can take it as soon as it’s ready.

But you’re a busy little geek. Why should you bother?I’ll get to that in a second—but first, a word from oursponsor, er, from the Justice Department, which requiresfull disclosure in a situation like this (see the sidebar).

Okay, so what are five good reasons?

• It counts toward certification, just like the final exam.Pretend the exam is like Castor Oil—take the darnstuff now and get it over with. Once you pass (If youpass? When you pass? In the far-fetched case that youpass?) the exam, you’ll be a card-carrying MicrosoftCertified Professional. Well, actually, I don’t know ifyou really get a card, but I imagine you could asknicely. The VFP 6.0 beta exam also counts towardMicrosoft Certified Database Administratorcertification and either core or elective credit towardMicrosoft Certified Solution Developer certification.

• It’s a lot easier than the final exam. You like thisreason, don’t you? Unfortunately, I’m lying. The examisn’t necessarily easier, but it’s different. You’veprobably heard rumors that the grading curve iseasier than on the final exam, or that the passing scorefor the beta exam is lower than the final exam. Thatmight be. Or maybe not. It kinda makes sense,doesn’t it? On the other hand, before you get alla-sweaty and start dialing the test center for yourreservation, remember that this is a beta exam. Thatmeans that you’re also going to be dealing withquestions that, er, aren’t quite ready for prime time.So even if the grading curve is easier, there’sdefinitely a trade-off here.

• It’s a lot cheaper than the final exam. The final exam

Continues on page 23

Full DisclosureEver wonder exactly who writes these exams? Do you picture

some Seattle-based dweeb in a white Oxford Cloth shirt

with the top button buttoned, and flair-legged corduroys

that hover about eight inches off the floor? One with the

double pocket protectors and a six-inch circular slide-rule,

just in case the backup battery for their calculator goes dead?

Yeah, me too.

Well, that is, until they asked me to take on the role of

Lead Author, along with a conspirator at a competing VFP

magazine who’s losing just as much hair and whose beard is

even grayer than mine. So now you know why I can shill for

the exam with such authority—‘cuz I really do know what’s

going on underneath the covers.

Page 3: FoxTalk - dFPUG

http://www.pinpub.com 3FoxTalk November 1999

Crystal Reports 7 . . .Continued from page 1

its ability to convert Fox reports reliably to RTF—a fairlymajor failing, come to think of it. It also hated lines andboxes, and it was unable to handle large graphics, butapart from that, it was great! The final nail in the coffin forFoxWord was that it didn’t work with NT, and afterpestering Bell Consulting for 18 months to get them toupgrade it, I finally had to give up. In addition toFoxWord, I was also using the Generic MS ASCII driver toproduce pure text outputs—and with about the samelevel of success, I should add.

So, you can now understand my eagerness to find areplacement. Moreover, it became clear that the onlyviable solution seemed to be Crystal. After severaltorturous weeks of fiddling about—I’ll never trustanother software salesperson in my life—Crystal andFox are now finally working together like old friends.What follows is a step-by-step guide on how to implantCrystal into VFP and the rationality behind what mightseem to be a rather convoluted process. I should pointout that this solution is really the price of failure ofapplication developers to communicate with each other.Perhaps this could be forgivable across an intercontinentaldivide, but some of those concerned even work withinthe same company! I think it’s been worth the effort, butplease—if you know of a better way to do this, I’d loveto hear from you!

Stage 1: Install Crystal Reports 7on the development machineCrystal Reports 7.00 can be obtained for around $395from Seagate Software, and for that you get the abilityto output your reports to more than 20 formats, includingWord, RTF, HTML, Excel, text, delimited, and evene-mail. One other great advantage is the previewwindow, which gives a data drilldown function. Anotherhas to be the ability to design graphs based on the datawithin multi-group bands. This in itself offers greatadditional and interactive data analysis to users. In fact,the advantages of Crystal over Fox are far too numerousto detail here; the only real disadvantage, as alwayswith new tools, is the learning curve in having to masterit to any good effect. That said, I was able to knock outsome very reasonable reports after only a couple ofattempts. Of course, you can do all of the Crystal reportdevelopment outside of the Fox language, so reportdevelopment can now be delegated to those who haveno Fox knowledge. This in itself has been a greatdevelopment cost- and time-saver.

Stage 2: Crystal and Fox tables,and the first annoying thing . . .For some reason (or sad lack of judgment), Crystal 7.00won’t natively open VFP 6.00 free tables, and I spent

many days trying to convince it that it should. I eventoyed with the idea of using ODBC but realized that itwould be far too difficult to deploy to users in a finishedapplication. In the end, the best way was to COPY TO theFox tables to FOX2X format, which Crystal liked verymuch and was only too happy to open natively. Thereseems to be nothing much lost in the conversion, as far asI can see. But check out the Fox Help files on this facilityfor further details on what the implications might be foryour data when a retrograde data conversion using COPYTO is performed. This is the simple command line in Foxto convert the data:

USE C:\BIN\<mytableV6>COPY TO C:\BIN\<mytable1> TYPE FOX2X

Stage 3: Creating a Crystal reportWhat a paradox—it’s nearly 2000, and Fox has a 1993reporting engine, and Crystal won’t open files newerthan 1994!

Anyway, that aside (and I don’t know what willhappen if MS removes FOX2X backward compatibility),you now need to design a report that opens the FOX2Xfile and save it as <myreport1>. I suggest that you startwith something very simple. Selecting File>New willbring up the designer screen shown in Figure 1, fromwhich you can select the basic report type you want todesign. Selecting “Standard” is probably the best optionat this stage.

This will bring up an intuitive report design wizard,as shown in Figure 2 and Figure 3. Here you can point atyour <mytable1> free table and then work through all ofthe other wizard stages to a finished report, even addinggraphs if you feel up to it. When you’re happy with thefinal output, save the report, but remember not to savethe data with the report, as this obviously increases thereport’s file size by the same order as the data source. Andthis would also be a bad thing because, obviously, yourfinal EXE will also become larger than it needs to be.

Stage 4: Creating the OCX objectOnce you have a working report and table, the nextthing is to control them seamlessly within the VFP

Figure 1. Report designer selection gallery.

Page 4: FoxTalk - dFPUG

4 http://www.pinpub.comFoxTalk November 1999

environment. To do this, you’ll need to add the OCXobject to a form, either using the form designer or in code.I prefer the latter, but the result is the same.

V_CRYSTAL=CREATEOBJECT("Crystal.CrystalReport")

Note: If you do create the object in code, you’ll needto make sure that the variable V_CRYSTAL is defined at ahigh level if the Crystal preview window is to be used;otherwise, it will appear and instantly disappear as Foxreleases the variable on procedure exit.

You’re now ready to harness the power of Crystal by

changing the various OCX parameters in code. Inaddition to the parameters normally available, the controlyou have over the Crystal OCX is extensive and, again, fartoo large to give it justice here. The parameters we’ll beusing are shown in Table 1.

As you can see from the DataFiles() parameter, it’salso possible to “force-feed” the OCX with a differenttable or tables (as long as it/they contain the samestructure) for output to the same report design. The reportfile itself can also be changed. So in effect, for me, once theOCX is set up, I can simply push parameters to it,

Table 1. Crystal OCX parameters.

Parameter DescriptionReportFilename This is the name of the Crystal .RPT report file to use.

WindowTitle This is the title of the report preview window (if used).

DataFiles(n) This specifies the tables that are to be used, where n is a value that ties up with the Crystal table number generatedwhen the report was designed. Generally, this value is 0 if you only used one table.

WindowState This specifies the state of the preview window (if used) with a value of 0-2 for minimized, maximized, or normal.

PrintFileName This is the name of the output file for the Crystal output format.

PrintFileType This is the Crystal output format type. There are about 20 different outputs, and the value ranges from 0 for apreview window to more obscure formats like Lotus 123.

WindowShowGroupTree This is a great little feature that allows the user to drill into the data in the preview window.

WindowShowSearchBtn This is another bit of user candy to allow report searching in the preview window.

WindowShowCloseBtn This allows or disables the preview window close button.

Destination This is used in conjunction with PrintFileType to control where the output goes:0 = To a window1 = To printer2 = To file3 = E-mail via MAPI (gasp!)6 = To exchange folder

Action This executes the Crystal OCX report object with its parameters as currently set.

Figure 2. The report design wizard’s data selection. Figure 3. The report design wizard’s graph selection.

Page 5: FoxTalk - dFPUG

http://www.pinpub.com 5FoxTalk November 1999

producing thousands of reports from hundreds of files toHTML, Word, and anything else. There’s also a handyprogress meter option to pacify users during thesometimes long report production. I should also mentionthat Crystal also has a fairly powerful built-in queryengine that might be of interest to some developers. Iprefer to let Fox do all of the data crunching and thenpass the resultant final table into the report, as it’s farfaster. So let’s look at how we’d output a report to, say, apreview, then HTML, RTF, and Excel from <myreport1>and <mytable1>. As you might have noticed, I’m usinga bogus directory “C:\BIN\” for all of my files in thisexample, but of course you can put them anywhereyou’d like.

ClearClose AllSet Safety OFF**********************************************=CRYSTAL_REPORT("c:\bin\<myreport1>",; "c:\bin\<mytable1>","",0)=CRYSTAL_REPORT("c:\bin\<myreport1>","c:\bin\<mytable1>",; "c:\bin\OUTFILE.HTM",21)=CRYSTAL_REPORT("c:\bin\<myreport1>","c:\bin\<mytable1>",; "c:\bin\OUTFILE.RTF",15)=CRYSTAL_REPORT("c:\bin\<myreport1>","c:\bin\<mytable1>",; "c:\bin\OUTFILE.XLS",10)**********************************************FUNCTION CRYSTAL_REPORTPARAMETERS V_CRFILE,V_TABLE,V_OUTFILE,V_OUTTYPEPRIVATE V_CRFILE,V_OUTFILE,V_OUTTYPE,V_RTN* V_CRFILE = Fully pathed Crystal report to run* V_TABLE = Fully pathed Fox 2X table to use* V_OUTFILE = Fully pathed Output filename* V_OUTTYPE = Output type

V_RTN=.T.ON ERROR V_RTN=.F.IF FILE(V_CRFILE) AND FILE(V_TABLE) * Create and set up the Crystal OCX V_CRYSTAL=CREATEOBJECT("Crystal.CrystalReport") V_CRYSTAL.REPORTFILENAME=V_CRFILE V_CRYSTAL.WINDOWTITLE="Report Preview" V_CRYSTAL.DATAFILES(0)=V_TABLE V_CRYSTAL.WINDOWSTATE=2 V_CRYSTAL.PRINTFILENAME=V_OUTFILE V_CRYSTAL.PRINTFILETYPE=V_OUTTYPE DO CASE CASE V_OUTTYPE=0 V_CRYSTAL.DESTINATION=0 CASE V_OUTTYPE=99 V_CRYSTAL.DESTINATION=1 OTHERWISE V_CRYSTAL.DESTINATION=2 ENDCASE V_CRYSTAL.WindowShowGroupTree=.T. V_CRYSTAL.WindowShowSearchBtn=.T. V_CRYSTAL.WindowShowCloseBtn=.T. V_CRYSTAL.ACTION=1* This isn't required, as we have V_CRYSTAL* as PUBLIC ABOVE* IF V_OUTTYPE=0 * This is required to stop the procedure * from dropping out during a preview. * =MESSAGEBOX("Holding report preview, press OK "; + "to continue",64,"CrystFox")* ENDIFELSE V_RTN=.F. =MESSAGEBOX("Missing Crystal report or Fox2x table!",; 48,"CrystFox")ENDIFON ERROR

RETURN V_RTN**********************************************

Note: The hold message during preview is to stop theV_CRYSTAL object from being released. There are severalother ways of doing this, but this is the most code-efficient for this example. It’s also worth noting that anygraphs/graphics in the HTML output will be converted toJPG files and saved alongside the output file as (in thisexample) OUTF000.JPG to OUTFFFF.JPG, where 000 andFFF are three-digit hex!

Stage 5: Creating an EXEOkay, so now you have a working application producingreports from Fox tables. The next stage is to embed theminto the EXE so that they can be deployed with yourapplication. Here you have two choices—as you can’tactually “embed” the Crystal report design files into yourVFP application, you have to instead send them out asseparate files with the Setup Wizard. Alternatively, as Imust do for audit reasons, you can include them in theEXE, where they can’t be tampered with, and extract themwith code when they’re required. This is really a hybrid ofthe two methods—the report files form a Trojan army offiles within the EXE but aren’t really a part of it. Withcode, however, the whole thing becomes invisible as far asthe end user is concerned. To choose the latter option willmean that you need to include this additional stage inyour code.

Crystal reports aren’t recognized as reports by Fox,and rightly so. Fox reports are actually just tables thatdescribe object positions, layout, and field names (inmuch the same way that VFP reports are stored in tables).Crystal reports (with the .RPT extension) are foreign filesas far as Fox is concerned and must be included as“other” files either in the project manager or by using thefollowing commands in your application (note that theextension is required):

EXTERNAL FILE C:\BIN\<myreport1.RPT>EXTERNAL FILE C:\BIN\<myreport2.RPT>

Now that the files are within the EXE, we have to finda way of extracting them without corruption and on thefly whenever we need to produce a report. The way to dothis is very simple—there are a few Fox commands thatwill interact with files that have been included within theEXE. Of course, COPY FILE would have been just great,but, alas, it wasn’t to be. I tried the next-best thing—thesuite of low-level file functions FOPEN(), FGETS(), andFPUTS()—only to find that they caused too much filecorruption. Presumably, this is due to some characterswithin the .RPT being read as end of line or file markersand so on.

I suppose I could have embarked on a code-around tofix the problem; after all, by now I had reached the pointof no return in terms of time invested. But even if I didsucceed in trapping all instances of spurious corruptions

Page 6: FoxTalk - dFPUG

6 http://www.pinpub.comFoxTalk November 1999

for now, I couldn’t risk something new appearing the dayafter I release the upgrade. Anyway, with further research,I found that FILETOSTR() and STRTOFILE() workedadmirably. I was a little concerned that the sizes of theCrystal report files might cause a problem, as thesefunctions swap the entire file to and from memory.However, when I checked, I found that even the mostambitious and complex of my report designs seldomtopped 50K—well within the redundant memorycapability of the most modest of user-end PCs. So if youwant to include your Crystal reports within the EXE inthis way, you’ll also have to add this bit of code at thestart of the application:

R1="<myreport1>" && from within the EXER2="C:\BIN\<myreport1>" && Onto the local drive* Read the file in the EXE to a memory variableA=FILETOSTR(R1)* Copy the variable back to a "real" file* on the local drive=STRTOFILE(A,R2)

Stage 6: Creating a setup, and the second very,very annoying thing . . .If all has gone well, by now you should have anapplication EXE that will open a VFP 6.0 table, copy itto a FOX 2.X table, and then extract and run a Crystalreport to some sort of file format or preview window.The only problem is that the application will only workon a machine that also has Crystal 7 installed. This isbecause the Crystal Reports OCX requires about 50supporting DLLs, and these are only deployed with theCrystal installation.

Now brace yourself for a breathtaking oversight onthe part of Microsoft. If you now create a Setup with theSetup Wizard in VFP, you’ll find that when you use it toinstall your application at the target PC, it will fail to runand give an error like “Crystal Reports Object failed toregister…” This is because all of those important littleDLLs haven’t been included in the Setup. The reason forthis is that the VFP Setup Wizard won’t read the .DEP(dependency) file that Crystal very kindly supplies withthe Crystal OCX. Seagate informed me that this file isincluded for the sole purpose of ensuring that SetupWizards correctly install and register their product—whata waste!

However, don’t get too depressed; things aren’t asbleak as they might seem. As luck would have it—and Ithank Hue at the FoxWare Visual Development Studio forpointing this out—the Visual Basic Setup Wizard doesread .DEP files and will include them in the Setup bybuilding them into the .CAB file. Yes, that’s right: VB andVFP are both written by the same company and are evenpart of the same Visual Studio package—why do you ask?So far, Microsoft has failed to give me a credibleexplanation for an omission that, to me, stands out like ablind cobbler’s thumb!

So all you need to do now is create a VB applicationthat uses the OCX on a form and then make a VB Setup toinclude the DLLs. In fact, you can probably get awaywithout even having a form, but it makes no difference,as the user will never execute this particular VBapplication. Of course, VB comes as part of the VisualStudio suite, so you could do this for yourself, but if youjust can’t be bothered to get into VB, then I’ve includedmy version of the app in the Subscriber Downloads atwww.pinpub.com/foxtalk. But now that you have a VBapplication (something like a “MYVBAPP.EXE,” right?),what do you do with it? I had to find some way to sendthis VB application setup to the user so that it could beinstalled, thus registering the Crystal components for me.

Stage 7: Creating the final setup,and the final annoying thing . . .If you’ve ever used the VB Setup Wizard, you’ll see thatit’s a very different beast indeed from the VFP version.

Of course, this would normally go unnoticed, as fewdevelopers actually use all of the languages they’ve paidfor within the studio. There seems to be no way to make aWeb Executable Setup from VB, as is possible with Fox. Ifyou haven’t used this useful VFP feature, then let meexplain how it works. Very simply, if you select the“Create Web Executable” from the final screen in theSetup Wizard, VFP will bundle together all of the Setupfiles into a single EXE file that can be placed on the Webfor download and easy installation. When it’s run at theuser’s end, the setup files are extracted and then executedto install the application as normal.

Prior to this facility being available, like most people,I was using the excellent WinZip self-extractor, which, ofcourse, does exactly the same thing. And this is what younow have to do with the VB Setup files—make a self-extracting ZIP file that will extract the Setup.exe and itsassociated CAB files and then run the Setup. That VB self-extracting Setup you just made can be reused for all ofyour projects, so once you have it working (or use mine),then you can use it for all of your applications. That is,until a Crystal upgrade comes out, of course, when you’llhave to re-build it.

Well, we’re very nearly finished. What I do now istake the VB Setup.exe and place it in a subdirectory belowthe VFP application (\temp) so that the VFP Setup Wizardwill see it and include it as just another file. Then you caneither fill in the “File to execute when setup is complete”with this VB installation EXE or get your Fox applicationto look for the Crystal OCX and, if it doesn’t exist, run theVB installation to install it—simple. Here’s the code to dojust that within our application:

IF NOT FILE("C:\WINDOWS\CRYST.OCX") RUN /N C:\<myapp>\CRYSINST.EXE QUITENDIF

Page 7: FoxTalk - dFPUG

http://www.pinpub.com 7FoxTalk November 1999

Don’t forget that at this point, if the installation isrun, then the user will be launched on quite a lengthyinstallation Setup. For that reason, I prefer to “daisychain” the Crystal/VB installation setup from theapplication Setup, although I haven’t used this method inthe example that’s available in the Subscriber Downloads.

ConclusionThe job of the software developer has never beenmore demanding that it is today. We work in anenvironment where there’s zero tolerance for errors andnothing less than perfection is expected of us. If userssee a facility or useful capability in other applicationstoday, they’ll urgently “need” it to be built into theirown applications for tomorrow. I actually have nothingagainst this healthy evolutionary process, as the toolsand languages available to developers have never beenmore powerful.

What I do object to is the declining quality andreliability of some of these developments. We’re nowin a situation where the pace of “progress” in languagedesign is outstripping the development communities’ability to understand it—let alone implement some ofit. Over the past five years, I have a feeling that thedevelopment ideologies and methodologies of a smallgroup of “experts” have been imposed on us, ratherthan offered to us.

In their rush to produce the latest and greatesttools, they’ve forgotten about the real world ofdevelopment, where we’re just trying to build appsfor our imperfect customers. Take the example in thisarticle, for instance. The solution to this problem istremendously labor-intensive. Both applications involvedcome in nice, glossy packaging emblazoned with claimslike “application portability,” “seamless compatibility,”“fast and reliable,” and, most annoying of all, “all you’llever need . . .”

In some respects, we have a panoply of powerfulfeatures that we couldn’t have dreamed of 10 years ago.On the other hand, these features don’t do us much goodif they only take us 95 percent of the way, or if they forceus to slog through the mud to get the last 5 percent towork. I hope this article has been helpful for you inlearning how to cross the “last 5 percent chasm” until thetool vendors get their act together. ▲

11REBURN.ZIP at www.pinpub.com/foxtalk

Nigel Reburn is a senior application development manager with a

leading U.K. communications company. He’s been developing corporate

solutions in dBase, Fox, VB, and VFP for the past 10 years. Nigel also has a

U.K.-based company called Quantum Information Technologies Ltd. that

develops specialized electronics and business software solutions.

[email protected].

Page 8: FoxTalk - dFPUG

8 http://www.pinpub.comFoxTalk November 1999

Best Practices FoxTalk

Seeing Patterns:The Template MethodJefferey A. Donnici 6.06.0

This month, Jefferey Donnici continues his in-depth lookat design patterns with an examination of the TemplateMethod pattern. This pattern describes how an algorithm“skeleton” can be used to allow some of the steps within aprocess to vary in subclasses. As with other Behavioralpatterns, the Template Method pattern describes theinternal, encapsulated behavior of a class or component.

WHEN writing framework classes or, for thatmatter, any class destined to be subclassed, it’soften difficult to determine how the subclasses

of a class might need to vary from their superclass. Someoperations and behaviors simply can’t be defined in asuperclass and are left abstract so that the subclasses canprovide the implementation. But how do you handlesituations where an operation can be partially defined?That is, a part of the operation must remain consistentwithin all subclasses, but another part of that operationneeds to vary with each subclass.

This month, the “Seeing Patterns” series takes alook at the Template Method pattern, which describesa solution to this design dilemma. By implementingonly a skeleton of a certain algorithm or operation ina superclass, subclasses are free to implement thevariable portions of the operation to suit their morespecialized needs.

Now for the obligatory introductory stuff: If you’rejust joining this in-progress series, the purpose of the“Seeing Patterns” series is to examine object-orienteddesign patterns in a Visual FoxPro context and usingVFP examples. If you have access to them, I’d suggestthat you go back and look through some of the earlier“Best Practices” columns in this series. The first articles inthe series, which began with the Bridge pattern in theNovember 1998 issue of FoxTalk, contain an introductionto the concepts behind design patterns and also attempt toexplain the various types of patterns. The August andSeptember 1999 columns went on hiatus from the“pattern-of-the-month” style to answer some questionsthat have come up since the series began. Taking a lookthrough some of these previous columns might help ifyou’re joining the series with this issue.

Also, all of the patterns in this series, including theTemplate Method pattern, were definitively presented to

the object-oriented community in Design Patterns:Elements of Reusable Object-Oriented Software, by E.Gamma, R. Helm, R. Johnson, and J. Vlissides (publishedby Addison-Wesley, ISBN 0-201-63361-2). This book isnow available on CD-ROM and comes with all of thesource code examples and lots of hyperlinks betweenthe various topics. While I still use my dog-eared andtattered copy of the book on a regular basis, I’m findingthe CD-ROM version to be increasingly useful for quicklyfinding things I remember from past readings. Whenreading any of the “Best Practices” columns in this series,you might find it helpful to have either the book orCD-ROM nearby for reference.

While Visual FoxPro is the language I’m using todemonstrate these patterns, I want to once againmention that these are object-oriented design patterns andnot Visual FoxPro design patterns. Each of these patternscan be used in Java, C++, or any other object-orienteddevelopment environment. Also, because a design patterndescribes a solution to an object-oriented design problem,the implementation examples used in this series aren’tthe only possible implementations of a design based onthese patterns.

The Template MethodAs with last month’s Command pattern, the TemplateMethod pattern is classified as a Behavioral pattern. Inother words, it describes a possible solution to a designproblem having to do with the internal, encapsulatedbehavior of a class or component. This is different fromStructural patterns, which describe how two or morepatterns work together to form a structure within anapplication, and Creational patterns, which describe whenand how classes within a system are instantiated asobjects at runtime.

As described in Design Patterns, the purpose of theTemplate Method pattern is to “define the skeleton of analgorithm in an operation, deferring some steps tosubclasses.” This allows subclasses of an abstractsuperclass to “redefine certain steps of an algorithmwithout changing the algorithm’s structure.”

So where might you find a Template Method used? Tobe honest, it’s everywhere. If you’ve ever modularized aprocess into several methods, calling each in succession,

Page 9: FoxTalk - dFPUG

http://www.pinpub.com 9FoxTalk November 1999

and decided to leave some of those methods un-implemented in your abstract class, you’ve probablyapplied the Template Method to a design. In DesignPatterns, they simply state that “template methods are sofundamental that they can be found in almost everyabstract class.”

As illustrated in Figure 1, the idea is that an algorithmor operation can be broken into multiple steps, and thatthe implementation for some of those steps can be left tothe concrete subclasses. Using the Template Method inthis fashion lets you increase the flexibility of yoursuperclasses so that you can get as much use out ofthem—and their subclasses—as possible. Furthermore, itprevents the scenarios we’ve all seen where a potentialclass definition is needed that’s “almost like” an existingclass, except that there’s undesirable behavior occurringsomewhere within one of the superclass methods. I like tocall this situation “painting yourself into a corner”because there are few elegant ways out of it. Using theTemplate Method pattern, the designer of the superclasscan leave the class’s operations as granular as possible soas to let subclasses implement those steps in the operationthat might not apply to every possible subclass.

Obviously, there’s not a lot of rocket science to theTemplate Method pattern. In fact, if you’ve beenfollowing the “Seeing Patterns” series from the beginning,you probably agree that the Template Method is one ofthe easier patterns to get your head around. Referringto it now as a “Template Method” helps, however, asone of the purposes behind design patterns is to providea common “pattern language” that developers can useto refer to design solutions. While you might not havereferred to it as such in the past, I’m guessing thatmany of your successful object-oriented designs have

included some form of the Template Method pattern.And, who knows, maybe some of the less-than-successfuldesigns could have been improved by looking at theTemplate Method.

Putting it into practiceLet’s take a look at one simple scenario where a TemplateMethod pattern would allow for increased flexibility:in this case, a payroll system that has to calculate thecompensation for a company’s employees.

Within a corporation, there might be a couple ofdifferent types of employees: full-time salaried employeesand part-time hourly employees. Each of these employeetypes needs to get a check on payday, they each needto be tracked for tax purposes, and they might each bedelegating a portion of their pay to benefits like a401(k) or employee stock purchase program. For eachof those common attributes, however, there might alsobe a number of differences associated with their taxresponsibilities and benefits, what they pay out to ahealthcare plan, as well as the company’s responsibilityto collect Social Security tax from the employee.

How do you handle these different scenarios inyour application? One way is to create an employee classwith a CalculateNetPay method that figures it out for you.The pseudo-code for this method might look somethinglike this:

LPARAMETERS EmployeeID, GrossPay

*-- What type of employee are we dealing with?EmployeeType = GetType(EmployeeID)

IF EmployeeType = Hourly *-- Determine gross pay based on hours.ELSE *-- Determine salary pay for this period.ENDIF

DO CASE

CASE EmployeeType = Hourly AND ; EmployeeType = Contract *-- Provide gross pay to the 1099 personnel.

CASE EmployeeType = Hourly AND ; EmployeeType <> Contract *-- Make adjustments for hourly employees.

CASE EmployeeType <> Hourly *-- Make adjustments for salaried employees.

ENDCASE

RETURN NetPay

If you’ve been following along with the “BestPractices” columns, you already know how I feel aboutthis type of code. It’s ugly, hard to maintain, not veryflexible, and has a lot of potential to become monolithicwhen the Administration department comes up with newemployee types. In other words, it stinks.

Instead, consider the following class definitions (alsoin pseudo-code):

Figure 1. The Template Method pattern provides a skeleton for aprocess and specifies that the implementation of some of thesteps in the process—in this case, Step2() and Step4()—can beleft for concrete subclasses.

Page 10: FoxTalk - dFPUG

10 http://www.pinpub.comFoxTalk November 1999

*-- EmployeeDEFINE CLASS Employee AS Custom PROCEDURE CalculateNetPay

THIS.SubtractTax() THIS.SubtractSocialSecurity() THIS.SubtractRetirement() THIS.SubtractBenefits()

RETURN NetPay ENDPROC

PROCEDURE SubtractTax *-- Figure out the tax bracket and subtract. ENDPROC

PROCEDURE SubtractSocialSecurity *-- Take out the Social Security contribution. ENDPROC

PROCEDURE SubtractRetirement *-- !!! TEMPLATE METHOD !!! ENDPROC

PROCEDURE SubtractBenefits *-- !!! TEMPLATE METHOD !!! ENDPROCENDDEFINE

*-- HourlyEmpDEFINE CLASS HourlyEmp AS Employee PROCEDURE SubtractRetirement *-- Implement the retirement contributions *-- for an hourly employee. ENDPROC

PROCEDURE SubtractBenefits *-- Implement the healthcare contributions *-- for an hourly employee. ENDPROCENDDEFINE

*-- SalaryEmpDEFINE CLASS SalaryEmp AS Employee PROCEDURE SubtractRetirement *-- Implement the retirement contributions *-- for a salaried employee. ENDPROC

PROCEDURE SubtractBenefits *-- Implement the healthcare contributions *-- for a salaried employee. ENDPROCENDDEFINE

In this case, there’s a common Employeesuperclass that implements some of the commonparts of the payroll process, such as dealing with thevarious tax responsibilities. Below that, however, aretwo subclasses—the HourlyEmp class for hourlyemployees and the SalaryEmp class for salariedemployees. The parts of the process that are differentfor the two—in this case, the SubtractRetirement andSubtractBenefits steps—are left as un-implementedTemplate Methods in the superclass so that they can behandled by each employee type (see Figure 2).

So what do you gain by using this type of approach?• Simplified maintenance—Making a change to the

payroll operation is easier when there’s no redundantcode in the class hierarchy. The specialized subclassescontain only the code that makes them specialized.

• Increased reuse—It’s easy to add different types ofemployees. Simply create a new subclass of theEmployee class and implement the differences. All ofthe common steps in the process are already in place.

• Reduced coupling—Other objects that must use them

remain ignorant (decoupled) of the specifics of theEmployee classes. By always referencing thepolymorphic CalculateNetPay method, client objectsdon’t need to know what type of employee they’redealing with, even if new employee types are addeddown the road (see “Seeing Patterns: The Strategy” inthe February 1999 issue of FoxTalk for a discussion onhow to vary client objects at runtime in this way).

Remember last month?In last month’s Best Practices column (“Seeing Patterns:The Command”), I used the Codebook menu system as anexample of encapsulating commands into classes so thatthey could be re-ordered within, added to, and subtractedfrom a process at runtime. My motivation for using thatexample was partially hidden, though, because I knew itwould provide a good illustration for the TemplateMethod pattern as well.

Specifically, I’ll use Codebook’s concept of a “ChildCollection” class to demonstrate another use of theTemplate Method. This example, however, occurs ata more abstract and framework-centric level. ThecChildCollection and cReferenceCollection classes aresubclasses of the cCollection class. The latter superclassdefines a basic interface for dealing with a collectionof objects (Add, Remove, GetObjectCount, and so forth).The cReferenceCollection functions similarly to thebuilt-in collections in VFP, except that the object membersin the collection don’t need to be members in the“containership” sense. That is, the object can exist

Figure 2. By implementing only the common parts of the payrollprocess in the Employee class, the specialized classes are free toimplement the parts that are unique to each employee type.

Page 11: FoxTalk - dFPUG

http://www.pinpub.com 11FoxTalk November 1999

anywhere in the system and still be an object memberof the collection via an object reference. This is the classyou’d use for a “forms manager” because the formswill only exist by reference within the collection. ThecChildCollection, however, is different because it expectsthat each member of the collection will be a child ofthe collection (that is, its Parent property is an objectreference to the collection object, which is possiblebecause all of the collection classes are subclasses of theContainer base class).

As I pointed out last month, the Codebook menucollection is a series of nested containers wherein a“menu” container contains a number of “pad” containers.Each pad container contains a single “popup” container,which, in turn, contains a number of “bar” containers.This builds a tree-like containership hierarchy that, wheninstantiated, builds the application’s menu. For thepurposes of this discussion, I’m going to look at a singlepopup container—the cFilePopup class within Codebook.

Load ’em up and move ’em out!To start, look at the diagram in Figure 3, which illustratesthe hierarchy of class definitions that leads to thecFilePopup class. Note that the cCollection class is anabstract class that defines the interface for collectionsthroughout the Codebook framework. Beneath it is thecChildCollection, which implements those methods withthe expectation that all “children” of the collection will beobject members of that collection. Beneath that is thecPopup class, which overrides only the Add method. Thismethod deals with the unique nature of adding menubars to the cPopup collection—specifically, building aseries of DEFINE BAR commands so that the menu barappears within the VFP menu system.

For our purposes, the Template Method pattern canbe found in the approach the framework designers tookto allow developers to specify which menu bars appearwithin the instantiated cPopup class. As with allsubclasses of the cCollection class, the cPopup class has aLoadChildren method. This method allows the developerto specify which children (menu bars, in this case) areautomatically loaded as object members of the collection.In the cCollection class, this method is empty and, in thecChildCollection class, contains only a comment that themethod is intended to be overridden in a subclass. Inother words, implementation for that part of the collectionoperation is left to the subclasses.

The following code, from the Init of thecChildCollection class, ensures that the LoadChildrenmethod (our Template Method) is run when thecollection is created. Assuming that this method loads theaChildren array, the return value will be the number of“children” to create. Any return value greater than zerowill cause the CreateChildren method to run, whichactually instantiates the child objects.

IF THIS.LoadChildren() > 0 RETURN THIS.CreateChildren() ENDIF

So what does the LoadChildren method look like

Figure 3. The hierarchy of collections within Codebook makesit possible for an object-oriented menu system to be built ofcontainers, with many of the containers implementing atemplate LoadChildren method.

Continues on page 16

Page 12: FoxTalk - dFPUG

12 http://www.pinpub.comFoxTalk November 1999

Reusable Tools FoxTalk

Long Live PRGs!Doug Hennig 6.06.0

Although classes get most of the attention today, there’s still aplace for PRGs. In this month’s article, Doug Hennig looks atsome library routines packaged as PRGs.

ALTHOUGH I have a class that provides a largenumber of general library routines, I also havesome library routines that exist as PRGs. Why

do PRGs still have a place in an object-orientedenvironment like VFP?

• PRGs are easier to use, since there’s nothingto instantiate.

• Functions are easier to call, since you leave offthe “Object” syntax.

• Functions in PRGs execute a little faster thanmethods of objects.

• User-defined function (UDF) calls “look” like nativefunction calls (the next paragraph explains why thiscan be a good thing).

In this article, we’ll look at nearly a dozen little PRGsthat provide useful enough functionality that I use themin nearly every application and tool I write. Many of thesefunctions are named the same as native VFP 6 functionsso they implement the same behavior in VFP 5. Why do Icare about VFP 5? Believe it or not, a lot of developershaven’t upgraded from VFP 5 to 6 yet, and as a developerof VFP tools, I don’t want to limit my market to just thosewho have the latest version. Thus, these routines mean Idon’t need to have one version of my code for VFP 5 andone for VFP 6; when a function like VARTYPE() is calledin VFP 6, the native function will be used, but in VFP 5,VARTYPE.PRG will be called instead.

FOXTOOLS functionsVFP 6 added as native functions a number of filenameprocessing routines built into FOXTOOLS.FLL,eliminating the need to load this library. These functionsare very useful anytime you need to do something witha filename or path. For example, I often use temporaryfiles for certain processes and then want to delete themafterward, so I use code like this:

lcTempFile = sys(3) + '.DBF'* create and do something with this fileerase (lcTempFile)erase forceext(lcTempFile, 'CDX')erase forceext(lcTempFile, 'FPT')

FORCEEXT() forces the specified extension onto afilename, so the CDX and FPT files for the table canbe deleted.

I’ve created PRG versions of most of the filenameprocessing functions so I can use these functionswithout worrying about whether someone will use mycode in VFP 5. I’ve already discussed FORCEEXT();here’s the code:

lparameters tcName, ; tcExtensionlocal lcPath, ; lcNamelcPath = addbs(justpath(tcName))lcName = juststem(tcName) + iif(empty(tcName) or ; empty(tcExtension), '', '.' + tcExtension)return lcPath + lcName

FORCEEXT.PRG uses three other “FOXTOOLS”functions: ADDBS() (if you’ve read my articles before,you’re probably thinking I use this function a lot <g>),which adds a backslash to the end of a path; JUSTPATH(),which returns just the drive and path of a filename(without a trailing backslash, which is why ADDBS()is used here); and JUSTSTEM(), which returns justthe stem (the part of the name before the period andwith no drive and path) of a filename. Here’s the codefor ADDBS.PRG:

lparameters tcNamelocal lcNamelcName = alltrim(tcName)return lcName + iif(right(lcName, 1) <> '\' and ; not empty(lcName), '\', '')

Here’s JUSTPATH.PRG:

lparameters tcNamereturn left(tcName, rat('\', tcName))

This is the code for JUSTSTEM.PRG:

lparameters tcNamelocal lcNamelcName = substr(tcName, rat('\', tcName) + 1)return iif('.' $ lcName, ; left(lcName, rat('.', lcName) - 1), lcName)

JUSTFNAME() returns a filename (stem andextension) without the drive and path:

lparameters tcNamereturn substr(tcName, rat('\', tcName) + 1)

Page 13: FoxTalk - dFPUG

http://www.pinpub.com 13FoxTalk November 1999

The last one, JUSTEXT.PRG, returns just the extensionof a filename:

lparameters tcNamelocal lcNamelcName = justfname(tcName)return iif('.' $ lcName, ; substr(lcName, rat('.', lcName) + 1), '')

In case you’re wondering why the call toJUSTFNAME() is necessary in this routine, think aboutwhat would be returned if the path had a period in itbut the filename had no extension.

Other VFP 6 replacement functionsI use two other functions new to VFP 6 a lot:NEWOBJECT(), which instantiates an object even whenthe class library it’s in isn’t open, and VARTYPE(), a fasterand simpler version of TYPE(). As with the “FOXTOOLS”functions, I’ve created PRGs that provide the samefunctionality in VFP 5. Here’s NEWOBJECT.PRG—Iarbitrarily chose six as the number of parameters thatcan be passed to the new object:

lparameters tcClass, ; tcLibrary, ; tcInApp, ; tuParm1, ; tuParm2, ; tuParm3, ; tuParm4, ; tuParm5, ; tuParm6local lcLibrary, ; lcInApp, ; loObject

* If the class library is specified, SET CLASSLIB to it* if necessary.

do case case type('tcLibrary') <> 'C' or empty(tcLibrary) or ; upper(tcLibrary) $ upper(set('CLASSLIB')) lcLibrary = '' case file(tcLibrary) lcLibrary = upper(tcLibrary) otherwise lcLibrary = upper(locfile(tcLibrary, ; 'Visual Class Library (*.vcx):VCX;Program ' + ; '(*.prg):PRG', tcLibrary))endcaseif not empty(lcLibrary) lcInApp = iif(type('tcInApp') = 'C' and ; not empty(tcInApp), 'in ' + tcInApp, '') set classlib to (lcLibrary) &lcInApp additiveendif not empty(lcLibrary)

* Create the object.

do case case pcount() < 4 loObject = createobject(tcClass) case pcount() = 4 loObject = createobject(tcClass, @tuParm1) case pcount() = 5 loObject = createobject(tcClass, @tuParm1, ; @tuParm2) case pcount() = 6 loObject = createobject(tcClass, @tuParm1, ; @tuParm2, @tuParm3) case pcount() = 7 loObject = createobject(tcClass, @tuParm1, ; @tuParm2, @tuParm3, @tuParm4) case pcount() = 8

loObject = createobject(tcClass, @tuParm1, ; @tuParm2, @tuParm3, @tuParm4, @tuParm5) case pcount() = 9 loObject = createobject(tcClass, @tuParm1, ; @tuParm2, @tuParm3, @tuParm4, @tuParm5, @tuParm6)endcase

* Release the library if we opened it (and it's still* open) and return a reference to the object we created.

if not empty(lcLibrary) and ; lcLibrary $ upper(set('CLASSLIB')) release classlib (lcLibrary)endif not empty(lcLibrary) ...return loObject

The code for VARTYPE.PRG is quite simple. Noticethat VARTYPE() returns “X” for a .NULL. value and thedata type for anything else.

lparameters tuObjectreturn iif(isnull(tuObject), 'X', type('tuObject'))

A better NEWOBJECT()The native VFP 6 NEWOBJECT() function has onebehavior that annoys me, to the point of making itessentially useless for my purposes: If you pass it a classlibrary, the function ignores any open class libraries andinsists on looking for the specified library file. Why isthis a problem? Well, I frequently run code from theCommand window for testing purposes (and the toolsI build are often designed to work within the VFPdevelopment environment). I don’t want to hard-code apath in my calls to NEWOBJECT() (or else the code wouldonly work on my machine, and only if I don’t movethings around), so I just specify the class library without apath, which works perfectly in a runtime environmentsince all of the files are built into the EXE. To help locatethe library in a development environment, and forperformance reasons, I use SET CLASSLIB to open theclass library prior to instantiating classes from it, but Ican’t expect that others who use my code have done thesame thing. So, if NEWOBJECT() would simply look atwhich class libraries were already open, it would doexactly what I need under all conditions: If the classlibrary is in the path or EXE and not already open, it willbe temporarily opened, and if the class library isn’t in thepath but is already open, it will be used. Unfortunately, inthe latter case, it doesn’t use the open library; it gives anerror that the class library can’t be found.

Necessity is the mother of invention, so I createdMAKEOBJECT.PRG. This function uses the same set ofparameters as NEWOBJECT() but has the behavior I need.If you pass it a class library and that library is alreadyopen, it will be used (it does this by passing an emptyclass library name to NEWOBJECT(), which in that caseacts just like CREATEOBJECT(), expecting that the classlibrary is already open). Otherwise, it tries to find theclass library and presents a dialog box for you to locate itif it can’t. Here’s the code:

Page 14: FoxTalk - dFPUG

14 http://www.pinpub.comFoxTalk November 1999

lparameters tcClass, ; tcLibrary, ; tcInApp, ; tuParm1, ; tuParm2, ; tuParm3, ; tuParm4, ; tuParm5, ; tuParm6local lcLibrary, ; llLibrary, ; lnParms, ; loObjectlcLibrary = iif(empty(tcLibrary) or ; upper(tcLibrary) $ set('CLASSLIB') or ; upper(tcLibrary) $ set('PROCEDURE'), '', tcLibrary)llLibrary = empty(lcLibrary) or file(tcLibrary) or ; file(tcLibrary + '.VCX') or ; file(tcLibrary + '.PRG') or file(tcLibrary + '.FXP')if not llLibrary lcLibrary = locfile(lcLibrary, ; 'Visual Class Library (*.vcx):VCX;Program ' + ; '(*.prg):PRG', lcLibrary) llLibrary = not empty(lcLibrary)endif not llLibrarylnParms = pcount()do case case lnParms = 1 loObject = createobject(tcClass) case not llLibrary loObject = .NULL. case lnParms = 2 loObject = newobject(tcClass, lcLibrary) case lnParms = 3 loObject = newobject(tcClass, lcLibrary, tcInApp) case lnParms = 4 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1) case lnParms = 5 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1, @tuParm2) case lnParms = 6 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1, @tuParm2, @tuParm3) case lnParms = 7 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1, @tuParm2, @tuParm3, @tuParm4) case lnParms = 8 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1, @tuParm2, @tuParm3, @tuParm4, @tuParm5) case lnParms = 9 loObject = newobject(tcClass, lcLibrary, tcInApp, ; @tuParm1, @tuParm2, @tuParm3, @tuParm4, @tuParm5, ; @tuParm6)endcasereturn loObject

Locating application piecesOften, an application needs to find additional files, suchas FRX, FLL, or graphic files, in the same directory as theapplication is located. The problem is that the applicationdirectory might not be the current directory, and themethod to determine the directory in which theapplication is located depends on whether the applicationis an in-process DLL server, out-of-process EXE server, orstandalone EXE. So, I use GETAPPDIRECTORY.PRG todetermine the directory the application is in. This routineis adapted from code posted in a forum by Rick Strahl:

local lcProgram, ; lcPath, ; lcFileName, ; lnByteslcProgram = sys(16, 0)do case

* In-process DLL server or Active Document.

case atc('.VFD', lcProgram) > 0 or ; Application.StartMode = 3 lcPath = home()

* Out-of-process EXE server.

case Application.StartMode = 2 declare integer GetModuleFileName in Win32API ; integer hInst,; string @lpszFileName,; integer @cbFileName lcFileName = space(256) lnBytes = 255 GetModuleFileName(0, @lcFilename, @lnBytes) lnBytes = at(chr(0), lcFilename) lcFileName = iif(lnBytes > 1, ; substr(lcFileName, 1, lnBytes - 1), '') lcPath = justpath(lcFileName)

* Standalone EXE or VFP development.

otherwise lcPath = justpath(lcProgram) if atc('PROCEDURE', lcPath) > 0 lcPath = substr(lcPath, rat(':', lcPath) - 1) endif atc('PROCEDURE', lcPath) > 0endcasereturn addbs(upper(lcPath))

Loading imagesIf, like me, you instantiate ActiveX controls at runtimerather than design time (see my June 1998 article “TameYour ActiveX Controls” for a class that can help with this),you know that most ActiveX controls need an imageobject rather than the name of the graphic file. So, you usethe VFP LOADPICTURE() function to open a graphic fileand return an object reference to the image. However,LOADPICTURE() has a bug: If the graphic file is includedin the EXE, LOADPICTURE() gives an OLE error even ifthe graphic file is also available on disk.

So, if you use an ICO file as the icon for a form (inwhich case VFP will automatically include it in the EXE)and also use that same image in an ActiveX control, you’llhave a problem. There are a couple of solutions to this—have two copies of the graphic file (one built-in and onenot) or mark the graphic file as excluded from theproject—but they still require you to ship a bunch ofgraphic files with your application. Note that a solutionproposed by Microsoft—copy the graphic file outfrom the EXE to a temporary file on disk, then useLOADPICTURE() on that temporary file—doesn’t workbecause VFP stores the full name of the file as it existed onyour machine in the EXE. Thus, the command COPYMYICON.ICO TO TEMP.ICO will fail on someone else’smachine because VFP passes the original path (such asF:\GRAPHICS\MYICON.ICO) to the Windows APIfunction that copies a file, and it’s unlikely the user willhave this path on their system.

My preference is to use a table with all of the graphicfiles used in an application stored in memo fields, andhave this table built into the EXE so it isn’t a separate fileto ship (or doesn’t accidentally get deleted). I created aroutine called LOADIMAGE that accepts the name of agraphic file and optionally the name of a table containing

Page 15: FoxTalk - dFPUG

http://www.pinpub.com 15FoxTalk November 1999

that file, and it returns an object for that image. If a tablename is specified, or if a table called _GRAPHICS.DBFcan be found, this routine looks for the name of the imagein the table and, if found, copies the contents of theGRAPHIC memo field to a temporary file. If a table isn’tused, this routine then looks for the specified graphic filein various places: the directory specified with the graphicfile (if there is one), the current directory, the VFP path,the same directory as the application, or a GRAPHICSsubdirectory of the application directory. If the graphicfile can be found (or was copied out from the table), it’sloaded using LOADPICTURE() (and the temporary file isdeleted). Otherwise, the user will get an error that thegraphic file couldn’t be found.

lparameters tcFile, ; tcTablelocal lcTable, ; lcAlias, ; llUsed, ; lnSelect, ; lcDirectory, ; llDelete, ; laFiles[1], ; lcFile, ; loPicture

* If tcTable is specified, ensure that it's* open or exists.

assert pcount() = 1 or empty(tcTable) or ; (vartype(tcTable) = 'C' and (used(tcTable) or ; file(tcTable))) ; message 'LoadImage: improper table specified'do case

* If a table name was specified, use it.

case vartype(tcTable) = 'C' lcTable = tcTable

* If a file called _GRAPHICS.DBF can be found, use it.

case file('_GRAPHICS.DBF') lcTable = '_GRAPHICS.DBF'

* We don't have a table, so we'll look for a* file on disk.

otherwise lcTable = ''endcase

* Figure out what directory we're in.

lcDirectory = GetAppDirectory()llDelete = .F.do case

* If we have a table to look in, open it if necessary* and try to find the image file there.

case not empty(lcTable) lcAlias = juststem(lcTable) llUsed = used(lcAlias) lnSelect = select() if llUsed select (lcAlias) else select 0 use (lcTable) again shared alias (lcAlias) endif llUsed lcFile = upper(justfname(trim(tcFile))) do case

* We can't open the table.

case not used(lcAlias)

lcFile = ''

* We can't find the specified image.

case not seek(lcFile, lcAlias, 'NAME') error 1, tcFile lcFile = ''

* We did find it, so copy it to a temporary file.

otherwise lcFile = lcDirectory + sys(2015) + '.' + ; justext(lcFile) copy memo GRAPHIC to (lcFile) llDelete = .T. endcase select (lnSelect) if not llUsed and used(lcAlias) use in (lcAlias) endif not llUsed ...

* If the file exists in the specified path,* we'll use it.

case adir(laFiles, tcFile) = 1 lcFile = tcFile

* If the file exists in the application directory,* we'll use it.

case adir(laFiles, lcDirectory + tcFile) = 1 lcFile = lcDirectory + tcFile

* If the file exists in a GRAPHICS subdirectory of the* application directory, we'll use it.

case adir(laFiles, lcDirectory + 'Graphics\' + ; tcFile) = 1 lcFile = lcDirectory + 'Graphics\' + tcFile

* We can't find the file.

otherwise lcFile = ''endcase

* If we can't find the file, raise an error.* Otherwise, load it.

if empty(lcFile) error 1, tcFile loPicture = .NULL.else loPicture = loadpicture(lcFile)endif empty(lcFile)

* If we copied the file to disk, erase it now.

if llDelete erase (lcFile)endif llDeletereturn loPicture

If you wish to use a table containing the graphicfiles, create it with two columns: NAME C(40) (or somereasonable width) and GRAPHIC M. Create a tag calledNAME on UPPER(NAME). To load an image into thetable, add a record to the table, specify a value for theNAME field, and use APPEND MEMO GRAPHIC FROM<name of the graphic file> to load the graphic file into thememo field.

LOADIMAGE.PRG makes it easy to load images,since they can exist on disk in one of several placesor can be loaded into a table built into the EXE (eitherexplicitly named in calls to this function or named_GRAPHICS.DBF). This versatility means that I can useit in a variety of ways anytime I need to load an image.

Page 16: FoxTalk - dFPUG

16 http://www.pinpub.comFoxTalk November 1999

ConclusionEven though we’re living in an object-oriented world,PRGs still have their place. The 11 routines presented hererepresent almost all of the functions I use in applicationsand tools today. ▲

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

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

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

FoxPro developers, including Stonefield Database Toolkit and Stonefield

Query. He’s 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, 1998, and 1999 Microsoft FoxPro Developers Conferences (DevCon)

as well as user groups and regional conferences all over North America.

He’s a Microsoft Most Valuable Professional (MVP). [email protected].

when implemented by the subclasses? Using thecFilePopup as an example, you can see that the methodsimply loads the private aChildren array with the classnames and eventual object names for the children to becreated. Again, every possible subclass of cPopup(there are several in the Codebook framework) and,for that matter, subclasses of the cCollection class, canautomatically load child objects in this way. Here’sa portion of the code from the cFilePopup class’sLoadChildren method:

#DEFINE NUMCHILDREN 8LOCAL laChildren[NUMCHILDREN, ; ALEN(this.aChildren,2)]

laChildren[1,CHILD_CLASS] = "Cfilenewbar"laChildren[1,CHILD_NAME] = "oFileNewBar"laChildren[2,CHILD_CLASS] = "Cfileclosebar"laChildren[2,CHILD_NAME] = "oFileCloseBar"laChildren[3,CHILD_CLASS] = "Cseparatorbar"laChildren[3,CHILD_NAME] = "oSeparatorBar"laChildren[4,CHILD_CLASS] = "Cfilesavebar"laChildren[4,CHILD_NAME] = "oFileSaveBar"laChildren[5,CHILD_CLASS] = "Cfilecancelbar"laChildren[5,CHILD_NAME] = "oFileCancelBar"laChildren[6,CHILD_CLASS] = "Cfiledeletebar"laChildren[6,CHILD_NAME] = "oFileDeleteBar"laChildren[7,CHILD_CLASS] = "Cseparatorbar"laChildren[7,CHILD_NAME] = "oSeparatorBar"laChildren[8,CHILD_CLASS] = "Cfileexitbar"laChildren[8,CHILD_NAME] = "oFileExitBar"

DIMENSION this.aChildren[NUMCHILDREN, ; ALEN(laChildren,2)]=ACOPY(laChildren,this.aChildren)

RETURN NUMCHILDREN

Note that the array is created locally and then copiedto the aChildren property for performance reasons—it’sfaster to work with a local array and perform the copyonce than to reference THIS.aChildren numerous times insuccession. Because the return value from the

Seeing Patterns . . .Continued from page 11

LoadChildren method will be greater than zero, theCreateChildren method (the next step in this operation)will be run. With a populated aChildren array, theCreateChildren method does not need to vary insubclasses, so that part of the operation was implementedfurther up the class hierarchy.

Though I’m using the File menu popup as an examplehere, all of the Codebook collection classes support thissame Template Method approach, allowing the subclassesof cCollection to implement specialized portions of thecollection operation on their own.

See ya next time!As I mentioned previously, the Template Method patternis one of the easier patterns to understand. Wheneveryou’re working on an object-oriented design, it makessense to break, or “modularize,” functionality into twoor more pieces. Sometimes that means creating morecohesive classes and using several as a component, whileother times it means breaking a long, procedural series ofcommands into more cohesive methods. In the latter case,you might want the behavior of those methods to varyin more specialized subclasses. If so, the Template Methodpattern is the quick and easy answer—simply implementin the superclass those portions of an operation thatare common to all specializations, and leave theimplementation of other portions up to the subclasses.

I’m sure you’re coming up with other ideas for theTemplate Method pattern all the time. If so, or if you haveany other comments or suggestions for the “BestPractices” column, I’d love to hear about them. ▲

Jefferey A. Donnici is the senior Internet developer at Resource Data

International, Inc., in Boulder, CO. Jeff is a Microsoft Certified Professional

and a five-time Microsoft Developer Most Valuable Professional.

303-444-7788, fax 303-928-6605, [email protected], or

[email protected].

Earn Money!. . . and see y our name in pr int

Go ahead, add a line to your résumé—“Published Articles.”If your tip shows up in the pages of FoxTalk,

we’ll send you $25. See the back page for theaddress where you can send your tips.

Page 17: FoxTalk - dFPUG

http://www.pinpub.com 17FoxTalk November 1999

Page 18: FoxTalk - dFPUG

18 http://www.pinpub.comFoxTalk November 1999

What’s Really Inside FoxTalk

Multiple PersonalitiesJim Booth 6.06.0

Have you ever needed to create a form that included theability to search for a record and edit a record? The controlscan get pretty crowded when using search controls and editcontrols in the same form. You could always create a searchform and call that form the edit form, but that causes the userto deal with two different forms instead of one. Jim Boothshows how we can solve this problem.

THE PageFrame is Visual FoxPro’s way of allowing aform to contain a large number of controls withoutbecoming crowded. You can use the PageFrame

with a number of pages to separate the controls intological groups that are smaller and less dense, therebymaking the form less crowded and less intimidating.

Figure 1 and Figure 2 show a PageFrame being usedto supply the searching functionality just described.

This design helps in reducing the controls’ densityin the form, but is it what we really want? Is selecting apage the best way for a user to signify the desire to searchfor a person?

An insightful use of a PageFrameConsider Figure 3 and Figure 4. They look like they’redifferent forms, but they’re not. The effect seen here isaccomplished by using a tabless PageFrame. Essentially,the same PageFrame was used with the Tabs property setto .F. and the BorderWidth property set to 0. A Searchcommand button was added to page 1. The code in theclick on the search button is simply as follows:

This.Parent.Parent.ActivePage = 2

This code changes the active page of the PageFrameto page 2. Page 2 is where the search controls reside. Thebuttons on the search page also change the ActivePage ofthe PageFrame back to page 1.

SummaryBy using a tabless PageFrame in this situation, we’vereduced the form count of the application, and we’ve

Figure 1. An edit page in a PageFrame. Figure 2. A search page in a PageFrame.

Figure 3. The editing form. Figure 4. The searching form.

What’s the purposefor a control?User interface design includesselecting the appropriate control fora specific job. Tabs on a PageFrameare usually used to change the databeing displayed in a form. The dataon any given page should be relatedsomehow. On the other hand,the usual control for altering thefunction to some other operationis a command button.

In the case of our form, we haveone page that displays and allowsediting of data and another pagethat searches for a record. Thesearen’t variations on the data beingdisplayed, but rather are completelydifferent functions. The use of tabsto change between these functionscould cause confusion on the partof the system users, since the tabis being used in an unusual way.A better design might be to use acommand button to switch betweenedit and search modes of operation.

Continues on page 23

Page 19: FoxTalk - dFPUG

http://www.pinpub.com 19FoxTalk November 1999

FoxTalk

Who’s Afraid of the Registry?Kristyne McDaniel 6.06.0

Storing user preferences, paths to data, and other applicationdetails through the Registry doesn’t have to be complicated.It’s easy to do once you understand the basics. KristyneMcDaniel shows you how.

OVER the past few years, I’ve written the code towrite user settings to some location four times. Itgets a little old. Frustrating. It does seem like

everyone does it a little bit differently, and that there areno rules. Well, not being the great FoxPro god, I can’timpose any rules either, but I can certainly offer up someideas. I can even present some basic code to handle it all.

In the land of 32-bit Windows, we all know by nowthat the “accepted” practice is to write settings to theWindows Registry. If we do Macs, we write settings topreference files. Of course, the old ways also work, too.

Where do you want to store user settings?You don’t have to store settings in the Registry. Likeanything in FoxPro, there are several perfectly reasonablealternative approaches to this job. I’m the first toacknowledge that there are shortcomings to using theRegistry every time for every setting in every application.These are a few of the basic questions I ask myself:

• How can keys be accessible from any workstationon a network?

• How do the settings get there the first time theapplication runs?

• How do Registry keys get set during an install?• How are Registry keys removed?• How are keys upgraded for a new version?• How are characters, numbers, and binary

values handled?• What can I do to keep my Registry settings

from disappearing?

The short answer to these questions is that all of theseissues can be handled with a basic class for managingapplication settings. Longer answers will follow after wetake a closer look the Registry itself.

What is the Registry, anyhow?In the most basic of terms, the Registry is a database. Ihave no idea what the actual format of the database is, butI do know it isn’t a FoxPro database. It lives in a set ofinnocuously named files (for example, ntuser.dat), has afinite initial size (which is usually more than sufficient),

and can be backed up or expanded as needed usingtools designed for that purpose. The exact makeupand maintenance of the Registry is beyond the scopeof this article.

The Registry parses different types of informationinto a set of root keys. For our purposes, we really don’tneed to know much about all of the information types andwhere they go. The important information for storingsettings boils down to this:

• What is a root key?• What is a subkey?• Where do settings generally get stored for

an application?

Choosing the right ROOT keysAs you’ve probably heard, it isn’t safe to go into RegEditand just muck around with the settings you find. Someactions are relatively safe, though. I like to stay within theboundaries of safety when dealing with the Windows OS.I’m sure that’s what you want to do as well.

The first thing you need to know about is the Registryroot keys. I like to think of the root keys as subdivisionsfor different types of data. When storing and retrievingapplication settings, there’s no need to get complicated.Stick to the two main keys—HKEY_CURRENT_USER,which holds settings that apply to the currently logged-inuser, and HKEY_LOCAL_MACHINE, which holdssettings that apply to any user that logs in to theworkstation. Registry settings only apply to a singlemachine at a time, and they can’t be shared betweenmachines. Table 1 shows a little map through theoperating systems.

Table 1. Main Registry keys in the Windows OS.

OS Registry Key(s) NotesWindows 3.1 HKEY_CLASSES_ROOT Win32s uses only one

Registry key

Windows 9x/NT HKEY_CURRENT_USER Applies to the currentlylogged-in user

HKEY_LOCAL_MACHINE Applies to any user onthe computer

To get a good feel for what’s happening in theRegistry, you’ll need to run RegEdit. You can do thisthrough the Windows Run facility. Just type the wordregedit in the dialog box; Windows will be able to find the

Page 20: FoxTalk - dFPUG

20 http://www.pinpub.comFoxTalk November 1999

program easily, because it lives in your Windowsdirectory. From within RegEdit, you can see the availableRegistry root keys. They do vary somewhat betweenoperating systems and versions, but don’t let that concernyou just yet. I just stick with the same two keys for any ofthe 32-bit operating systems.

I do stay away from the Win32s Registry that’s usedin Windows 3.1. Like most, I haven’t even used Windows3.1 for some time now, and I’d like to keep it that way.The lack of a user key would mean that settings forindividual users would be more difficult to handle. In myopinion, this makes the Registry no more appealing as aplace to store settings than an INI file. Since I do believein using the least complicated storage method whenadded complexity adds no extra capability, I don’t evenbother with it myself, and I prefer not to mess with theWin32s Registry at all. The class included in theSubscriber Downloads at www.pinpub.com/foxtalkdoes contain code to handle the Win32s Registry, but it’scompletely untested.

The root keys available in the Windows NT Registryare represented in Figure 1. The view for Windows 95/98is very similar.

Now for the subkeysYou might have picked up on the naming convention bynow. It’s different from what we’re used to in FoxPro.Since it’s pretty obtuse to me, I hope this namingconvention doesn’t spread. Refer to Figure 2 while Itry to uncomplicate it for you.

The left side of RegEdit’s display has items with iconsthat look like folders. Everything on this side is called akey. I refer to the top-level keys as root keys, while each ofthe keys under those top levels is referred to as a subkey.On the right side, you see two columns—Name and Data.Both of these columns refer to a value within the subkey.Think of the columns as the value name and value data.Table 2 summarizes the elements of the RegEdit display.

Table 2. Elements of the RegEdit display.

Term DefinitionKey One of the root keysSubkey The path to the value (for instance, Software\

McStyles Software, LLC\Movie Manager\1.0\)Value The name of the valueData The data for the value

On more careful examination of your ownWindows Registry, you can see that most companiesfollow a pattern in their placement of Registry settings.Under each of the root keys we’re concerned about(HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE) are a number of subkeys.

The subkey we care about is always called eitherSoftware or SOFTWARE. Yes, the case of the name doesvary, and all subkey names are case-sensitive. What thatmeans is that we have to handle the difference in oursettings class.

Under that heading are a list of software companieslike Microsoft, Intuit, Logitech, and my personal favorite,McStyles Software. Your Registry editor will show you adisplay that looks something like mine.

I want to point out that the one subkey namedSOFTWARE is found under the Windows NT root keyHKEY_LOCAL_MACHINE (see Figure 3). Since we don’twant to wind up creating a second Software subkey in thiscase, we’ll need to code around the anomaly.

While there are apparently no absolute rules in this,the standard pattern most software developers use forstoring their own Registry settings is as follows:

• Level 1—Software developer (company name)• Level 2—Application name• Level 3—Version number• Level 4—Individual settings• Level 5—Individual settings

I tend to think that my image as a professional is

Figure 1. The Windows NT Registry has six root keys. Figure 2. Contents of the Software key in the user root of theWindows NT Registry.

Page 21: FoxTalk - dFPUG

http://www.pinpub.com 21FoxTalk November 1999

enhanced when I do things in a standard way, so Imimic this standard in my own applications. Thesample class, AppSettings, was created to maintainRegistry settings using this pattern. It’s included inthe Subscriber Downloads.

The subkey levels are clearly visible in Figure 2. TheAppSettings class treats the Level 1, 2, and 3 subkeys asone big subkey in code. The software developer orcompany name, application name, and version numbersare stored in class properties. Level 4 is generally used tostore individual settings, but it can be split into morelevels if desired. I sometimes split it into one or moresections, which might contain a group of settings forprinting, a custom toolbar, or perhaps window sizes orpositions. Both Levels 4 and 5 are used in this class as theactual storage places for application setting information.

So what are the answers?There was a set of questions at the beginning of the articlethat I promised had some answers. Now seems like agood time to address them.

How can keys to be accessible from anyworkstation on a network?Registry settings are only accessible from the workstationthey’re on. To make settings accessible from any computeron the network, you’ll need to use a separate settingstable—not the Registry—that’s accessible to each machineon the network.

How do the settings get there the first timethe application runs?I have code in my applications to load default values forRegistry keys at the very beginning of the application ifthey’re not found.

How do Registry keys get set during an install?This happens in one of two ways:

1. Distribute either an INI file or a settings table withthe application. Read the settings from the alternatesource, then write it to the Registry.

2. Use an installer that knows how to write to

the Registry.

How are Registry keys removed?I use an uninstaller to remove keys. I don’t remove themfrom within an application, although the AppSettingsclass could certainly be modified to include deletemethods for keys and values.

How are keys upgraded for a new version?Write an upgrade program that reads the key values fromthe old version, and then write these values in the formatrequired for the new version.

How are characters, numbers, and binaryvalues handled?There are Win32 API functions to handle other datatypes in addition to character strings. My approach,though, is to avoid all data types except character strings.Every type of data is easily converted to a characterstring, and I do always know what type of setting I’mretrieving whenever I’m retrieving it in one of myapplications. In the interest of simplicity and reliability,I stick with character strings.

What can I do to keep my Registry settingsfrom disappearing?You can find yourself with the “vanishing Registrysetting syndrome” from time to time in your application.Sometimes a user can move your application to a newcomputer, or clean out your “unnecessary” Registrykeys, or perhaps the settings aren’t there because theapplication is running for the first time. In this case, Ilike to use a back-up approach and write the defaultinformation to an INI file or a settings table.

When distributing an application for the first time,I’ve discovered that a two-tier approach to settings canwork very well. I might distribute a settings table with anapplication that contains defaults. Another option is touse an INI file with default settings.

One reason for taking this approach is that I reallyhate hard-coding defaults into an application. Another isthat the VFP setup wizard doesn’t know how to writeanything to the Registry. I prefer having the settingsavailable at startup, so reading my settings is one of thefirst things one of my applications does.

I use a cascading approach. In this approach, thesettings object is created:

Set ClassLib To Settings.vcxloSettings = CreateObject("appsettings", ; "Movie Manager", ; "McStyles Software, LLC","1.0")

During the Init, the properties for the applicationname, developer name, and version are established. Fromthose values, default INI file and table names arecreated. Then, the GetSettings method is called. This

Figure 3. The main subkeys in the local machine root of theWindows NT Registry.

Page 22: FoxTalk - dFPUG

22 http://www.pinpub.comFoxTalk November 1999

method is a check to see whether a flag is set to use aparticular setting’s function, then it calls each one inturn until a setting is located. The following code is fromthe GetSettings method:

With This * Set to the default value if available. lcValueData = .cValueData

* Now look for the setting. * The search cascades in order * for each OS: * Windows - Registry to INI to table * Mac - Preferences to table If Not llSuccess And _Windows And .lUseRegistry llSuccess = .GetRegistry() EndIf If Not llSuccess And _Windows And .lUseINIFile llSuccess = .GetINI() EndIf If Not llSuccess And _Mac And .lUsePrefs llSuccess = .GetPreference() EndIf If Not llSuccess And .lUseTable llSuccess = .GetTable() EndIf

If llSuccess lcValueData = .cValueData EndIfEndWithReturn lcValueData

In this way, you can retrieve a setting from either anINI file or a table if they don’t yet exist in the Registry,then call the SetSetting method to write the value back tothe Registry. A test program file (settings.prg) is includedin this month’s Subscriber Downloads so you can see anexample of how to use the AppSettings class in your code.Figure 4 shows our settings in an INI file, while Figure 5shows a view of the same settings in a table.

The class library included in the source code for thisarticle was compiled in VFP 6. To use it with an earlierversion, you’ll need to first recompile it. In VFP 5, this isthe statement to use:

compile classlib settings.vcx

To use it with VFP 3, you need to use a differentcommand. There was no compile classlib statement inthat version, so you have to use this command:

compile form settings.vcx

As with any sample code, use it at your own risk,and your mileage may vary. I hope it gets you onyour way!

Macintosh preferencesI handle Mac preferences in the same settings class usedfor Windows. The Macintosh approach to preferencefiles isn’t easy to do with FoxPro. Preference files aresupposed to be double-clickable, bringing up a dialogbox that allows a user to change settings outside of themain application. Some developers actually do this, butothers of us don’t have the resources. I’m one of thosedevelopers, so I fake it. Rather than create an applicationthat the user double-clicks to view, I create a simple textfile. For distribution, I can even change the creator type sothat it will open with the SimpleText editor, depending onthe client.

I create a simple text file that looks and behaves quitea bit like a Windows INI file and store it in the MacPreferences folder inside the System Folder. The resultingtext file looks virtually identical to the INI file shown inFigure 4. The methods in the settings class which do thisare GetPreference and SetPreference. They’reautomatically called by GetSetting and SetSetting whenyou run the class code on a Mac, so there’s no specialhandling required to use them, except to set the lUsePrefsproperty to .T.

Other sources for Registry codeIf you’re using VFP 6, you’re in luck. There’s aregistry.vcx class library located in your FoxPro installdirectory. It’s included as part of the FoxPro FoundationClasses and is located in the (Home()+“FFC”) directory.In VFP 5, there’s a virtually identical class, definedas a PRG file, named registry.prg. It’s located in the(Home()+“Samples\Classes”) directory.

For VFP 3, you have to search a little harder. Therewas no Registry class shipped with VFP 3, but RickStrahl wrote one and placed it on his Web site fordownload. You can still find it on his downloads pageat http://www.west-wind.com. If you do use it, be sureto send him your thanks.

Using application settingsNow that you know where to store application settings,

Figure 4. The same settings as they appear in an INI file. Figure 5. Browsing the contents of a settings table.

Page 23: FoxTalk - dFPUG

http://www.pinpub.com 23FoxTalk November 1999

you’re ready to use these values in some interesting ways.I like storing mundane tidbits like application data pathsand the last location of the application toolbar. But thingsget better when you can provide options to your users.Some basic ideas I’ll cover in future articles will be a list ofuser-defined toolbar buttons, colors and/or bitmaps forthe screen background, and the last used placement andsize for a form. It’s more fun for your users, and not that

much work for you. ▲

11MCDASC.ZIP at www.pinpub.com/foxtalk

Kristyne McDaniel is an independent consultant based in Spring Valley,

CA (near San Diego). Through her company, McStyles Software, she has

been writing custom Windows and Macintosh FoxPro applications since

1992. 619-670-8505, [email protected].

encapsulated the functionality related to a form intothat form alone. We haven’t introduced a new use for aPageFrame to the users of the application.

In this example, we found that we really mightneed two forms in order to properly use the controls’functionality. However, we also wanted to limit thetotal number of forms in the application. By usinga tabless PageFrame, we were able to give the

appearance of multiple forms when there’s actuallyonly one form involved. ▲

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. He is co-author of Effective

Techniques for Application Development and Visual FoxPro 3 Unleashed

and is contributing author for Special Edition Using Visual FoxPro 6.0.

Jim is also the technical editor for Database Design for Mere Mortals.

Visit his Web site at www.jamesbooth.com. 203-758-6942,

[email protected].

Multiple Personalities . . .Continued from page 18

is $100 (or, at least, it was the last time I looked). Thebeta is half off. Hey, $50 is $50.

• You’ll make the exam better. If you take the finalversion of the exam, and you run into a stupidquestion, the only thing you can do to let off steam isdrive rudely on your way home from the test center.During the beta, however, you get to complain—andexpect that your feedback will be listened to. The Foxbetas are held in awe by the rest of Microsoft becauseour community generates more feedback per betatester than any other product in the Redmond stable.And that feedback makes a better product as well.Ever wonder why there was hardly any VFP stuff inthe first two Visual Studio Service Packs? Maybe theyhad to fix Really Bad Bugs in other products—but theVFP team had already nailed the big ones. Here’syour chance to put the beta exam on the top of theheap as well.

• If you don’t take it, they won’t come—back—tomake a VFP 7.0 exam. Yes, that’s right. What if yougave an exam and nobody came? Sounds like a threat,doesn’t it? It does to me, and I believe it, and I think itmakes lots of sense. I can make a guess about howmuch Microsoft spent developing the 6.0 exam—definitely into six figures. If you spent that type ofmoney and ended up with 47 people taking the exam,you wouldn’t do another one either, would you? So

Editorial: Beta Exam . . .Continued from page 2

take it, and tell your friends, your co-workers, youruser group members, the students in your VFPclasses, and the pastor at your church about it as well.And don’t be surprised if you see presenters at thenext Academy Awards wearing little “Fox 6.0 betaexam” buttons instead of those stupid ribbons theyseem to find every year.

And here’s a sixth reason:

• I’ll post the names of the people who complainedabout no VFP 5.0 exam but don’t take the beta. Oncethe beta exam becomes available, I’ll be correlatingthe names of the people who take the exam with thenames of the people who have whined about“Microsoft hates VFP because there’s not an exam”on the various electronic forums. I swear I am notmaking this up.

So when can you take it? It’s a little tricky becausebeta exams are only available for a short period oftime—the typical window for a beta is about two weeks.The VFP 6.0 beta exam should be ready by the end ofOctober; you can find out the exact dates by checkingback at http://www.microsoft.com/mcp/exam/stat/SP70-155.htm.

Just do it.And, by the way, I really was kidding about posting

the names of whiners. I don’t have access to the namesof the people who take the beta exam any more thanyou do. I only have access to social security numbersand bank account balances. Now stop reading and takethe exam! ▲

Page 24: FoxTalk - dFPUG

24 http://www.pinpub.comFoxTalk November 1999

Downloads

User name

Password

vanish

walrus

The Subscriber Downloads portion of the FoxTalk Web site is available to paidsubscribers only. To access the files, go to www.pinpub.com/foxtalk, click on“Subscriber Downloads,” select the file(s) you want from this issue, and enter theuser name and password at right when prompted.

Direct all editorial, advertising, or subscription-related questions to Pinnacle Publishing, Inc.:

1-800-788-1900 or 770-565-1763Fax: 770-565-8232

Pinnacle Publishing, Inc.PO Box 72255

Marietta, GA 30007-2255

E-mail: foxtalk@pinpub .com

Pinnacle Web Site: http://www .pinpub .com

FoxPro technical support:Call Microsoft at 425-635-7191 (Windows)

or 425-635-7192 (Macintosh)

Subscription r ates:United States: One year (12 issues): $179; two years (24 issues): $259

Canada:* One year: $194; two years: $289Other:* One year: $199; two years: $299

Single issue r ate: $17.50 ($20 in Canada; $22.50 outside North America)*

Editor Whil Hentzen; Publisher Robert Williford;

Vice President/General Manager Connie Austin;

Managing Editor Heidi Frost; Copy Editor Farion Grove

European ne wsletter orders :Tomalin Associates, Unit 22, The Bardfield Centre,

Braintree Road, Great Bardfield,Essex CM7 4SL, United Kingdom.

Phone: +44 1371 811299. Fax: +44 1371 811283.E-mail: [email protected].

Australian ne wsletter orders: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]: http://www.ashpoint.com.au

* Funds must be in U.S. currency.

FoxTalk Subscription Information:1-800-788-1900 or http://www.pinpub.com

November Subscriber Downloads

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. POSTMASTER: Sendaddress changes to FoxTalk, PO Box 72255, Marietta, GA 30007-2255.

Copyright © 1999 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. This

publication 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 particularpurpose. 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.

• 11REBURN.ZIP—Source code for Nigel Reburn’s article,

“Using Crystal Reports 7 with VFP 6.” Note: This is a big

file—7M.

• 11DHENSC.ZIP—Source code for Doug Hennig’s article,

“Reusable Tools: Long Live PRGs!”

• 11MCDASC.ZIP—Source code for Kristyne McDaniel’s article,

“Who’s Afraid of the Registry?”

Extended A rticles

• 11COATES.HTM—“Driving the Data Bus: Using the Windows

Shell API to Display a GetFolder Dialog Box,” by Andrew

Coates. This month, Andrew presents a technique for

displaying the familiar Windows shell interface to allow the

user to select a folder.

• 11COATSC.ZIP—Source code for Andrew Coates’s article,

“Driving the Data Bus: Using the Windows Shell API to

Display a GetFolder Dialog Box.”

• 11SCHUMM.HTM—“Deployment: Getting Ready,” by Richard

A. Schummer. Deployment is a big issue that all developers

hopefully face at some point in their careers; otherwise,

there’s not much point to doing the development, and, in

most cases, you won’t make a living in software

craftsmanship. This is the first in a series of articles by

Richard that reviews some of the common issues faced in

deploying applications, from single PC-based apps to large-

scale, worldwide installations.

• 11KITBOX.HTM—“The Kit Box: INCLUDE Me Out!” by Paul

Maskens and Andy Kramek. This month, Paul and Andy

discuss #INCLUDE files, and digress (as always) into compiler

directives in general.

• 11HENTZE.HTM—“VB for Dataheads: What’s This Business

About Visual Basic Classes?” by Whil Hentzen.