FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the...

27
1 Setting Up ODBC Connections for the Client User Robert Abram 2 Editorial: We Built this Project with a Debugging Scaffold Whil Hentzen 8 Reuseable Tools: Zip it, Zip it Good Doug Hennig 12 An Object Factory Charles T. Blankenship 16 The Kit Box: Color Me Surprised! Paul Maskens and Andy Kramek 22 Cool Tool: Beyond Visual Compare: Beyond Compare Whil Hentzen 23 May Subscriber Downloads EA A Few OO Tips Jim Booth May 2000 Volume 12, Number 5 Fox Talk Continues on page 3 Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers Setting Up ODBC Connections for the Client User Robert Abram In this article Robert Abram describes how he’s been working on a large application recently. He’s readied this application to the point of testing on other clean machines as a client test. The application uses an MS SQL Server database as a back end. As Robert started to test on those clean client machines, he was immediately frustrated by having to set up a new ODBC connection on each machine. I’m sure there are many people who can relate to this hindrance. In this article, Robert describes a mechanism he’s created that will automate the setup of ODBC connections. T HE application I’m am working on, when finished, will be set up on hundreds of client machines with different SQL Servers for groups of them. I immediately saw the need for automatic setup of ODBC connections to make the LAN administrator’s life much easier, not to mention my own when testing against different databases and different test machines. I started to wonder if there’s a way to configure ODBC connections programmatically. My question was answered in online user groups, but in fragments of code or registry hacks. I started doing research in the MSDN and found a reference to configure ODBC connections easily, without registry hacks. What I found I could do is check for ODBC drivers, add connections, modify connections, and delete connections. With these tools, I found everything I was looking for to configure ODBC connections programmatically. This article describes those tools and how I used them to automate ODBC connection configuration when the application starts. 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 6.0 6.0

Transcript of FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the...

Page 1: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

1 Setting Up ODBC Connectionsfor the Client UserRobert Abram

2 Editorial: We Built this Projectwith a Debugging ScaffoldWhil Hentzen

8 Reuseable Tools: Zip it,Zip it GoodDoug Hennig

12 An Object FactoryCharles T. Blankenship

16 The Kit Box: ColorMe Surprised!Paul Maskens and Andy Kramek

22 Cool Tool: Beyond VisualCompare: Beyond CompareWhil Hentzen

23 May Subscriber Downloads

EA A Few OO TipsJim Booth

May 2000Volume 12, Number 5

FoxTalk

Continues on page 3

Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers

Setting Up ODBCConnections for theClient UserRobert Abram

In this article Robert Abram describes how he’s been working on a large applicationrecently. He’s readied this application to the point of testing on other cleanmachines as a client test. The application uses an MS SQL Server database as a backend. As Robert started to test on those clean client machines, he was immediatelyfrustrated by having to set up a new ODBC connection on each machine. I’m surethere are many people who can relate to this hindrance. In this article, Robertdescribes a mechanism he’s created that will automate the setup ofODBC connections.

THE application I’m am working on, when finished, will be set up onhundreds of client machines with different SQL Servers for groups ofthem. I immediately saw the need for automatic setup of ODBC

connections to make the LAN administrator’s life much easier, not tomention my own when testing against different databases anddifferent test machines.

I started to wonder if there’s a way to configure ODBC connectionsprogrammatically. My question was answered in online user groups, but infragments of code or registry hacks. I started doing research in the MSDNand found a reference to configure ODBC connections easily, withoutregistry hacks. What I found I could do is check for ODBC drivers, addconnections, modify connections, and delete connections. With thesetools, I found everything I was looking for to configure ODBCconnections programmatically.

This article describes those tools and how I used them to automate ODBCconnection configuration when the application starts.

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

6.06.0

Page 2: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

2 http://www.pinpub.comFoxTalk May 2000

From the Editor FoxTalk

We Built This Project with aDebugging ScaffoldWhil Hentzen

ON one of his first comedy albums, Bill Cosby spentfive or six minutes telling a story and, after thepunch line, explained, “I told you that story so I

could tell you this one.” Same thing here. I’m going todescribe a project that I needed to do. Then I’m going todescribe how I did it—using a debugging scaffold.

But wait! What’s a “debugging scaffold?” This termisn’t one we bandy about much in the Fox community, sobefore I get started on my first story, it’s worth a bit ofbackground. I was cruising through a C++ book a monthago and the author used this term. Although I’ve finishedthat C++ book and still don’t know a pointer from a sharpstick in my eye, I immediately comprehended debuggingscaffolds. So the last few weeks I’ve been thinking aboutthem while I’ve been watching the evening news on Leno,and here’s my first shot at implementing one.

A scaffold is an infrastructure that supports theconstruction of another object, and is built before theobject. In order to build a wall, for example, you’d firstbuild a scaffold on which you’d stand while buildingthe wall.

In programming, a scaffold (a debugging scaffold) is amulti-level construct that supports ongoing programmingdebugging. The construct consists of debuggingcommands and functions that help you debug a chunk ofcode. So what’s the difference between a few cursoryDEBUGO statements (“come on, do you really type outthe entire “DEBUGOUT” command?) and a trace windowtucked in the corner, and a debugging scaffold?

A debugging scaffold has four attributes. It can beturned on and off, much like ASSERTS. You don’t want tohave to wade through a bunch of debugging structureswhen you don’t need them.

Second, it’s designed to be used over and over again,often with the same code segment. How many times haveyou written a long, complex routine, debugged it, andthen deleted all of your debugging code—only to have tocreate it again when a change in the surroundingenvironment or requirements in the routine itself forcechanges—and the insertion of new bugs—into theroutine? Yes, we’ve all been there, but it never seemedworthwhile to formalize this debugging mechanism—surely, this set of modifications to this hairy routine ispositively, absolutely the last set you’ll have to

make, right?Third, it should be multi-level, so that you can turn

on and off just a certain part of the scaffold’s functionality.You don’t want to have to wade through dozens orhundreds of messages or prompts just to debug one smallfunction, for example. What I’m thinking about here isbeing able to “step into” part of the scaffold that’s severallevels deep without having to drudge through all of thestuff in the first few levels.

Finally, it should support anticipated growth.Nothing would be worse—well, few things, at least,would be worse—than building a carefully designedscaffold, only to find that you had to tear it all down andrebuild it to accommodate a few minor items that wereadded late in the game. For example, imagine building a10-story scaffold, only to find out that the owners want adifferent type of window that juts out a bit, and thus youhave to tear down the whole scaffold and rebuild it 12inches farther away from the wall?

All of these attributes point to one more “macro”attribute—it must be carefully thought out and built.Yeah, I know, programming was a lot more fun when youcould just slam code into a text editor and run it, right?This design business, first with databases, then with OOP,and now debugging—it’s enough to make you thinkyou’re becoming a grown-up!

Okay, on to our story.

The requirement for project version numberingYou are probably familiar with the Project Manager’sability to capture a major, minor, and revision versionnumber—and to automatically increment that revisionnumber each time you build the project to an executableor server. See Figure 1 (on page 6).

Unfortunately, while this might superficially seem tobe useful, there are two major problems with the currentimplementation. First, you have no control over how themajor, minor, and revision values are changed. Either youmanually enter values or you settle for having the tertiaryvalue incremented each time you create a new build.What if you want a different scheme?

Furthermore, if you’ve tried to use this capabilityyourself, you’re also aware that’s it was, until 6.0, a real

Continues on page 6

Page 3: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 3FoxTalk May 2000

Helping the user see what's going onI created a startup form that’s shown to the user as asplash screen. On this screen, I put label controls andbitmap check marks to show each step of the process ofverifying and configuring the connection to the SQLServer. For the purposes of showing only the relevantcode, I removed the code that interacts with the splashform in the following code examples.

Testing an ODBC connectionAs I started to play with the ODBC setup, I saw the needfor testing a connection before anything else happens. Forthis, I used the FoxPro SQLConnect function. Because Iknow what the ODBC connection name is, this workedgreat. If the connection succeeds, I start the applicationnormally. If the connection fails, I call a function thatconfigures an ODBC connection. Here’s a bit of code thatshows the SQLConnect command:

PROCEDURE TestConnection

sqlhandle = SQLConnect("xyz_odbc", ; "xuser", "xpassword") IF (sqlhandle > 0) SQLDisconnect(sqlhandle) ELSE IF (ConfigureODBC()) = .F. && create ODBC src. * Code here to handle a failed config attempt. RETURN ENDIF ENDIFENDPROC

PROCEDURE ConfigureODBC ... * Code to configure ODBC.ENDPROC

Configuration fileBecause every SQL Server is going to have a differentserver name, database name, user ID, password, and soon for this application, I decided to have a remoteconfiguration file. The remote configuration file is just atext file that can be set up by the LAN administrator. Theapplication reads this configuration file from a networkserver each time on load to get the ODBC settings.Remote configuration information can be handled indifferent ways, depending on security needsand convenience.

Check to see if the correct driver is availableThe next question is, is the correct ODBC driver available?The SQLGETInstalledDrivers API call can answer thisquestion quickly. Calling SQLGetInstalledDrivers will filla string variable with a character (0) delimited list ofdrivers. Inspecting the lc_driver variable in the debuggerfrom the following code example will show all theavailable ODBC drivers.

Because I need the MS SQL Server ODBC driver, I justneed to get the delimited driver list and issue the ATfunction with “SQL Server” to see if the driver I need hasbeen installed. First, you must issue a DECLAREstatement to reference the API call. The following codeexample shows how I verified that the SQL Serverdriver exists:

DECLARE Integer SQLGetInstalledDrivers in ; odbccp32.dll String @, Short, Short @

PROCEDURE ConfigureODBC ln_size = -1 ln_newsize = 0 lc_drivers = ""

SQLGetInstalledDrivers(@lc_drivers, ln_size, ; @ln_newsize) IF ln_newsize > 0 lc_drivers = SPACE(ln_newsize) ln_size = ln_newsize

IF SQLGetInstalledDrivers(@lc_drivers, ; ln_size, @ln_newsize) = 1 IF AT("SQL Server", lc_drivers) > 1 RETURN ConfigureODBCConnection() ELSE RETURN .F. && No Driver Installed ENDIF ELSE

* Buffer might be too small. RETURN .F. ENDIF ENDIF

ENDPROC

Adding our ODBC DSNThe ODBC DSN can be added by calling theSQLConfigDataSource API function. Calling that functioncan be tricky, but, with some testing, programmaticallycreating an ODBC DSN shouldn’t be a problem.

There are some things to be aware of when creating aDSN. First, not all ODBC drivers support all of thepossible parameters passed in the lc_dsn variable. Forexample, if you pass a user ID and/or password whenconfiguring a SQL Server connection, theSQLConfigDataSource function will fail every time. Thesecond thing to keep in mind is that callingSQLConfigDataSource to add a new DSN will overwrite apre-existing DSN with the same DSN name.

DECLARE Integer SQLConfigDataSource IN odbccp32.dll ; Integer, Short, String @, String @ ODBC_ADD_DSN = 1 && Add data source

PROCEDURE ConfigureODBCConnection

lc_driver = "SQL Server" + CHR(0)

lc_dsn = "dsn=testdsn" + CHR(0) + ; "server=smallserver" + CHR(0) + ; "database=bigdb" + CHR(0) + ; "network=DBMSSOCN" + CHR(0)

IF SQLConfigDataSource(0, ODBC_ADD_DSN, ; @lc_driver, @lc_dsn) = 1 RETURN .T. && Successfully Configured DSN ENDIF

RETURN .F. && failedENDPROC

ODBC Connections . . .Continued from page 1

Page 4: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

4 http://www.pinpub.comFoxTalk May 2000

I also use the “network=DBMSSOCN” to force aTCP/IP connection. Other network drivers besides TCP/IP can be forced. Most computers will default to namedpipes as the default network connection type.

Now that we’ve created the ODBC DSN, I call theSQLConnect once more to verify that we’re connected tothe database.

Informing the userI believe users aren’t as computer illiterate as they seem. Ialso think the Microsoft method of giving the user 10different ways to accomplish something is the rightanswer. Informing the user of what your program isdoing, I believe, is just as important. Of course, I’ve metplenty of programmers (and non-programmers) whodon’t think users can handle multiple-choice options andshouldn’t be told about things that do not concern them.Hmm, that last sentence describes me in most of the jobs Ihave held. Go figure.

Invariably, a user calls to ask about a program thatfailed for some reason, and is unable to explain theproblem. However, the user is very willing to help solvethe error. When I try to walk the user step by step throughthe processes, trying to track down the error, I find thatthe program didn’t try to explain to the user what processit was trying to accomplish. Because the program washaving a verbal deficiency, the program didn’t outline thesteps the user should take when the program failed. So Ispend time playing customer support, because there wasno error. It was a program with social issues. It’s possiblesome of you can relate to this, right?

Keeping the previous two paragraphs in mind, I’d

programmatically create an ODBC DSN and beat you upabout communicating to the user, I’d like to say that youshould take a little extra time (not a lot, because time ismoney, after all) to look at what you’re giving the user.Merely setting up the ODBC connection behind the user ’sback isn’t enough; taking the time to let the program workwith the user is a much better approach.

Stay tunedAs an interesting finishing note to the article, I wasworking out the details for configuring ODBCconnections, and I experienced a problem where theSQLConfigDataSource API call would fail every time on afreshly installed Windows 95 machine, but not a 98 or NTmachine. As a result of trying to solve this problem, Iworked out some handy debugging methods to use whenusing these APIs. I won’t tell you how foolish the answerto the problem was, but if Whil lets me write anotherarticle I’ll tell you about debugging these APIs and someother ODBC API handling routines for FoxPro. ▲

05ABRASC.ZIP at www.pinpub.com/foxtalk

Robert Abram has been a FoxPro developer for just over two years now,

and has been programming for over 10 years in various languages.

Robert is currently working for the Utah State Office of Education. He

doesn’t claim to know everything, but does goes around saying, “I only

know what I know and I know I want to be fishing right now.” 801-775-

8835, [email protected].

Figure 1. A splash screen that shows the steps the program is currently processing.

like to show what I think is a goodexample of how to inform the user ofthe program’s current status andpotential problems that might occur.The following form (see Figure 1)outlines each step of theODBC configuration.

As the program steps thoughtesting and setting up the ODBCDSN, I use a form similar to the formshown in the figure. As each step isfinished, I place a check mark or an“X” to show if the step succeeded orfailed. So, if the SQL Server is down,the user will call the help desk tocomplain. The help desk person willask the user what failed, and the userwill hopefully be able to explain atwhich step the program stalled.

Wrapping it upNow that I’ve shown you how to

Page 5: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 5FoxTalk May 2000

Page 6: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

6 http://www.pinpub.comFoxTalk May 2000

Debugging Scaffold . . .Continued from page 2

pain in the butt to get that version number (as well as anyof the other information in the Version dialog box) whilethe application was running—you needed to access theWinAPI, and, of course, the process for doing so isn’t thesame across all the many Windows platforms. Unless youenjoyed messing with structures and DECLARES, youprobably decided to give up.

Well, for a project I’ve been working on, I needed tobe able to control how the version number is created andincremented, and I needed to get at the version numberwhile my app was running. Because of these issues, Icouldn’t use the native VFP version number, so I decidedto use a different approach.

I created three properties in my global applicationobject that held the information I wanted to use inmy application.

The first property was called cBuildNo, whichcontained a character string of the form v.nnnn.dd whereall of the segments are numeric. The first character, “v,”was simply the version number, and was incrementedonly when a major update of the project was introduced.The second set, nnnn, was the day of the build. Each newday a build was produced, this number was incremented,ignoring days without a build. Thus, if the project wasbuilt on Monday, Tuesday, and Friday, the number wouldbe, successively, 00034, 00035, and 00036. And the last

three characters were the number of the build on that day.So the first time the project was built on a specific day, ddwould be 01, while the third time would be representedby 03. I hope you’ll agree that the limits that I assumed—amaximum of about 10,000 build days, with a maximum of99 builds on a single day—are reasonable. It’s actuallylonger to explain it than to understand it.

The second property, cBuildDateCCYYMMDD, heldthe date when the build was produced, and the thirdproperty, cBuildDateHHMMSS, held the time when thebuild was produced.

Obviously, it was no problem to access theseproperties while the application was running, either fordumping to an error handler or simply to display on anAbout screen. But how did these values get incrementedautomatically? Each time an .EXE was built, the cBuildNoproperty had to be updated according to whether it hadalready been built that day or not, and the cBuildWhenhad to get the current date and time inserted.

Building a debugging scaffoldI decided to use a project hook to run a routine that wouldopen the application object’s VCX, search for thoseproperties in the appropriate memo field in the VCX, andchange their values as needed. But this wasn’t quite assimple as it sounds. Hacking the VCX isn’t necessarily forthe faint-hearted in any case—screwing around with amemo field that contains many properties with anautomated routine is like clipping your toenails whilewearing welding goggles, oven mitts, and standing one-legged on a bed with the Magic Fingers turned up to “11.”

Before I get into the code, I’ll remind you about howproject hooks work—if you want more details, check outDoug Hennig’s article, “The Happy Project Hooker,” inthe September 1998 issue of FoxTalk.

In brief, a project hook is a class (based on a projecthook class) that you attach to a project. When the projectis opened, VFP automatically instantiates a project hookobject from your class. From then on, wheneversomething is done to the project, like adding a file orbuilding an .EXE, project hook events are fired. You canput code in these events or add your own methods thatare called from the events. (If you’re trying this for thefirst time, be sure to attach the project hook class to theproject, then close the project and open it again. Openingthe project again instantiates the project hook object—merely adding the class to the project doesn’t do it.)

To keep things simple, I added code to theBeforeBuild event that would be fired before the user builtthe project. This code would open the application objectclass VCX; increment the cBuildNo,cBuildDateCCYYMMDD, and cBuildDateHHMMSSproperties; close the VCX; and then let the build processgo on.

The p-code for the routine looks like this:Figure 1. Bring forward the Version dialog from the ProjectManager window when the .EXE or Server option is selected.

Page 7: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 7FoxTalk May 2000

* look for the application object class in the list of* all of the files in the current project* open the VCX as a table* spelunk through the Properties memo for the properties* and their values:* - cBuildNo* - cBuildDateCCYYMMDD* - cBuildDateHHMMSS* update these properties* write the properties back to the memo* close the VCX

Fairly straightforward, but in my initial attempts (yes,that’s plural), I’ve found that I made many assumptions—assumptions that I’m sure you too would assume arereasonable—that didn’t play out as expected. Some weremy own fault; others were due to, err, idiosyncrasies inthe tool. As a result, I finally tired of the goofball’sapproach to debugging, and built a scaffold that helpedout immensely. Here’s what I did:

First, I examined “what could go wrong” because,sure as shootin’, Murphy was going to visit. There werebasically five areas of potential discontent:

• Project not open or class not found• Properties not found in the memo• Property values not parsed correctly• Property values not calculated correctly• Property values not written back to the VCX correctly

I didn’t bother with other things that could go wrongif I felt they were generally “one-off” errors that, oncefixed, would stay fixed. Of course, in another situation,where those things were getting mixed up, a scaffold toexamine these values would be a good idea.

First, I wanted to make sure I could turn each discretearea on and off by itself—once I solved the issuesassociated with property values being parsed correctly, Iwanted to ignore that scaffold from then on. Naturally, thescaffold would stay in place so I could use it again when I,or VFP, broke something in the future.

A technique I’ve used for a long time is to create aseries of “debugging flags” at the beginning of a module,then set their values to True or False as needed:

llDebug1 = .t.llDebug2 = .f.llDebug3 = .f.

If you have more than a couple of areas that you needto separate, you might consider (1) more friendly namesfor the flags, or (2) breaking the routine out into separatemethods or procedures.

Once you have flags set up, you can use themas needed:

if m.llDebug1 debugo "The initial amount is: " + tran(m.lnAmount) debugo "The initial name is: " + tran(m.lcName)endif m.llDebug1

The second goal wasn’t as easy as it seemed. You’dthink that all you’d have to do would be to leave the

scaffold in place for future use. But there’s more to it thanthat if you want the scaffold to be useful in the future. Ifyou’re just using a series of DEBUGO statements to echovalues being incremented in a loop, you could do this:

for each loFile in application.activeproject.files if m.llDebug1 debugo loFile.name endif m.llDebug1 <some code>next

When you run your app, you might see a list like so:

HWAPP65.VCXHWCTRL65.VCXHWLIB65.VCXHWLIB65.VCXHWLIB65.VCX

The lack of incrementation after the first three filesindicates where your problem is. But when you run thisdebugging code three months later (or, for me, threehours later), you’ll have no idea what this list means.

Thus, I include documentation in my DEBUGOcommands, like so:

if m.llDebug1 debugo "Rolling through files in current project" m.liHowMany = application.activeproject.files.count m.liCurrent = 0endif m.llDebug1for each loFile in application.activeproject.files if m.llDebug1 debugo "File name " ; + tran(m.liCurrent) + " of " + tran(m.liHowMany) ; + ": " + tran(loFile.name) m.liCurrent = m.liCurrent + 1 endif m.llDebug1 <some code>next

Running this code will display the following in theDebug Output window:

Rolling through files in current projectFilename 1 of 3 HWAPP65.VCXFilename 2 of 3 HWCTRL65.VCXFilename 3 of 3 HWLIB65.VCX

It’s easy to see in the Debug Output window that thissegment of code is going to display the files in the activeproject, and then each file is listed, along with the numberof the file and how many total there will be. Sure, it takesa bit longer to include the extra documentation, but thattime will be saved several times over when you have toexamine this code later on. In this particular case, it’s easyto see that the problem lies somewhere in the fact thatthere are only three files in the project—and it will be easyto spot this again in six months when the same problem,or one like it, crops up again.

As I mentioned, it’s also important to be able to turn apart of the scaffold on or off as desired. Because each ofthese five potential areas of discontent (Hmmm, thatcomes out to “PAD”: another Microsoft TLA?) runs in

Continues on page 21

Page 8: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

8 http://www.pinpub.comFoxTalk May 2000

Reuseable Tools FoxTalk

Zip it, Zip it GoodDoug Hennig 6.06.0

This month’s article presents a class to zip and pack the files ina project, and a class to interface VFP with a zipping utility likeWinZip. Doug Hennig discusses how combining these classeswith a ProjectHook and a project toolbar can help improveyour productivity.

IN my column in the September 1998 issue of FoxTalk(“The Happy Project Hooker,” an article for which Ireceived many more comments about the title than the

article itself <g>), I discussed the ProjectHook class, awelcome addition to VFP 6 that made it easier to buildproject-related tools for developers. I also presentedseveral ProjectHook classes that provided functionalities,like Build buttons in a toolbar, text searching, andrestoring project-specific settings like Intellidrop classesand form templates. I mused that other features would beuseful, like packing the table-based source files (SCX,VCX, FRX, and MNX) in a project or creating a zip file forarchive or transportation purposes. Because I’ve actuallybuilt these functions, it’s time to present these as reusabledeveloper tools.

As a refresher, SFPROJ.VCX contains severalProjectHook classes. SFProjectHook is the baseProjectHook class; it contains core functionalities likechaining to hooked ProjectHook (or other) objects andimplementing a toolbar if desired. SFProjectHookFind is asubclass that implements text search capabilities; itcollaborates with SFFindText, a form that displays wherethe specified text was found. SFProjectHookRegistrysaves and restores project-specific settings, making it easyto switch between projects. SFProjectToolbar,SFProjectToolbarTimer, and several SFToolbarButtonclasses provide a toolbar that works with a ProjectHookobject and automatically releases itself when the projectis closed.

SFProjectUtilsRather than creating another ProjectHook subclass forproject packing and zipping, I decided to create a projectutilities class (SFProjectUtils) based on Custom instead.Although this class is in SFPROJ.VCX like theProjectHook classes, I wanted this class to be used forprojects that aren’t necessarily open in the ProjectManager, so I didn’t need the functionality of aProjectHook. The reason I based it on Custom rather thanSFCustom (our Custom base class in SFCTRLS.VCX) isthat having SFCTRLS.VCX open (which happens when

you instantiate a subclass of one of its classes, even whenthat subclass is in a different VCX) when trying to pack orzip a project that includes this file causes problems. Justbecause it isn’t based on ProjectHook doesn’t mean itcan’t be used from within a ProjectHook; in fact, that’sjust what we’ll do later in this article.

SFProjectUtils has two main methods and severalsupporting methods. The main methods are ZipProject,which creates a zip file of the files in a project, andPackProject, which packs the table-based source files in aproject. The supporting methods, which we won’t look atdue to space limitations, are GetFilesFromProject, whichfills an array with the files in a project; GetDateTime,which combines the date and time values from an arrayfilled with ADIR() into a DateTime value; andGetAssociatedFile, which returns the name of a fileassociated with the specified file (for example, passingTEST.SCX to GetAssociatedFile will return TEST.SCT).SFProjectUtils has several properties; the two importantones are cProject, which contains the name of the projectfile we’re working with, and oZip, which contains areference to an object that’ll actually perform the zippingwork for us.

Let’s look at PackProject first, since it’s the simpler ofthe two main methods. I like to use this method before Ibuild an .EXE or archive a project because, as youprobably know, the memo file of table-based source codefiles (such as VCTs) can get huge with discarded data asyou make changes to it. Although the Project Managerwill compact these files when you build with the“Recompile All Files” option chosen, I don’t use thatoption often. As a result, the .EXE or archive can bebloated with garbage. PackProject provides a fast way totrim the files down to size.

The major code in this method gets a list of files in theproject, opens those that are table-based (the file type is K[screen], V [VCX], R [report], B [label], or M [menu])exclusively, and packs them.

lnFiles = This.GetFilesFromProject(@laFiles, lcProject)for lnI = 1 to lnFiles if laFiles[lnI, 2] $ 'KVRBM' lcName = laFiles[lnI, 1] if file(lcName) wait window 'Processing ' + lcName + ' ...' nowait use (lcName) exclusive pack use endif file(lcName) endif laFiles[lnI, 2] $ 'KVRBM'next lnIwait clear

Page 9: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 9FoxTalk May 2000

PACKPROJ.PRG is a simple utility program you cancall to pack the files in a project. Pass it the name of theproject to pack and it’ll let SFProjectUtils take care ofthe work.

The ZipProject method zips the files in a project. I useit both to archive the project once it’s completed, and fortransportation purposes (taking a copy of the files homeor giving them to another developer). ZipProject acceptstwo parameters: the name of the zip file to create and,because you might not want to zip all the files in a project,an expression to filter the files to compact. We’ll look athow the filter expression is used in a moment. We won’texamine all the code in this method, just the importantstuff. First, we get a list of files in the project, the directoryin which the project is located, and the name of the .APPor .EXE file created from the project (if it exists).

with This lcProject = .cProject if empty(lcProject) return .F. endif empty(lcProject)

* Get a list of files in the project.

lnFiles = .GetFilesFromProject(@laFiles, lcProject)

* Get the directory and name of the .APP file.

lcDirectory = addbs(justpath(lcProject)) lcAppFile = forceext(lcProject, 'APP') if not file(lcAppFile) lcAppFile = forceext(lcProject, 'EXE') endif not file(lcAppFile)

Next, we put information about the project file into anobject and pass that object to the ShouldFileBeZippedmethod, which returns .T. if the filter expression is eitherempty (meaning there is no filter) or evaluates to .T. If itdoes, the AddFile method of the oZip object adds the PJXand PJT files to the list of files to zip. If the .APP or .EXEfile created from the project exists, it’s treated thesame way.

adir(laDir, lcProject) loFile.cFileName = lcProject loFile.cType = 'p' loFile.nFileSize = laDir[2] loFile.tModified = .GetDateTime(laDir[3], laDir[4]) loFile.cAttributes = laDir[5] if .ShouldFileBeZipped(loFile, tcFilterExpr) .oZip.AddFile(lcProject) .oZip.AddFile(forceext(lcProject, 'PJT')) endif .ShouldFileBeZipped(lcProject, ... if file(lcAppFile) adir(laDir, lcAppFile) loFile.cFileName = lcAppFile loFile.cType = '' loFile.nFileSize = laDir[2] loFile.tModified = .GetDateTime(laDir[3], laDir[4]) loFile.cAttributes = laDir[5] if .ShouldFileBeZipped(loFile, tcFilterExpr) .oZip.AddFile(lcAppFile) endif .ShouldFileBeZipped(loFile, tcFilterExpr) endif file(lcAppFile)

Each of the files in the project is also added to the listof files to zip if it passes the filter criteria. Because a filemight have one or two associated files (for example, DCXand DCT files accompany a DBC file), the

GetAssociatedFile method is called to ensure that all ofthe necessary files are included.

for lnI = 1 to lnFiles lcFile = laFiles[lnI, 1] lcType = laFiles[lnI, 2] loFile.cFileName = lcFile loFile.cType = lcType loFile.nFileSize = laFiles[lnI, 3] loFile.tModified = laFiles[lnI, 4] loFile.cAttributes = laFiles[lnI, 5] if .ShouldFileBeZipped(loFile, tcFilterExpr) .oZip.AddFile(lcFile) for lnJ = 1 to 2 lcFile = .GetAssociatedFile(lcFile, lcType, lnJ) if not empty(lcFile) .oZip.AddFile(lcFile) endif not empty(lcFile) next lnJ endif .ShouldFileBeZipped(lcFile, ... next lnI

Finally, if any of the files passed the filter criteria, weclose the project file if necessary (we can’t zip it if it’sopen in the Project Manager), have the oZip object do thezipping work, and reopen the project if we closed it.

if .oZip.nFiles > 0 loProject = iif(type('_vfp.Projects[lcProject].' + ; 'Name') = 'C', _vfp.Projects[lcProject], .NULL.) llOpen = vartype(loProject) = 'O' if llOpen loProject.Close() endif llOpen llReturn = .oZip.ZipFiles(tcZipFile) if llOpen modify project (lcProject) nowait endif llOpen endif .oZip.nFiles > 0endwithreturn llReturn

Before we look at the zipping object, let’s take a lookat an example of how ZipProject might be called. I like tocreate two zip files for each project. One, called SOURCE,contains the project file, .APP or .EXE, and all of theproject-specific source code. The other, called LIBRARY,contains the library (or non-project-specific) source codeused in the project. The reason for zipping LIBRARY isthat sometimes you might need to restore an old project todo maintenance work on it, even though you’ve sinceupdated the library files it uses to a later version. Ratherthan trying to figure out what changes in the library fileswill cause havoc in your old project, it’s easier simply touse the snapshot of the library files as they were when theproject was archived.

ZIPPROJ.PRG (included with this month’s SubscriberDownloads at www.pinpub.com/foxtalk) accomplishesthis goal. Pass it the name of the project to zip, and it’llcreate SOURCE.ZIP and LIBRARY.ZIP. The interestingpart of this program is the following code (loProject is areference to the SFProjectUtil object and lcProject containsthe name of project to process):

pcDirectory = addbs(justpath(lcProject))loProject.ZipProject(pcDirectory + 'source.zip', ; 'ZipSourceFile(toFile)')loProject.ZipProject(pcDirectory + 'library.zip', ;

Page 10: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

10 http://www.pinpub.comFoxTalk May 2000

'ZipLibraryFile(toFile)')

Notice that for the filter expressions, this code passesfunction names. These functions are located inZIPPROJ.PRG, which means that the SFProjectUtil objectin effect calls back to ZIPPROJ to figure out which filesshould be zipped. The ZipSourceFile function will allowthe project file (toFile.cType = “p”), application file(toFile.cType is blank), or any file in the project directoryor a subdirectory of it to be zipped, while ZipLibraryFilewill allow only files that aren’t in the project directory or asubdirectory of it to be zipped:

function ZipSourceFilelparameters toFilereturn toFile.cType = 'p' or empty(toFile.cType) or ; addbs(justpath(toFile.cFileName)) = pcDirectory

function ZipLibraryFilelparameters toFilereturn addbs(justpath(toFile.cFileName)) <> pcDirectory

What if you don’t want this functionality? Easy:Change the filter conditions. Mike Yearwood (to whom Iowe thanks, both for the following code and for helpingmake the classes in this article better) wanted a single zipfile containing only files changed since the last time theproject was zipped, allowing him to send just those filesto someone else. MIKEZIPPER.PRG uses a table thatcontains the date and time the project was last zipped(which is placed into the ptLastTimeZipWasMadevariable), and uses this as the filter function:

function ZipSourceFilelparameters toFilereturn toFile.tModified > ptLastTimeZipWasMade

SFZipUtils and SFZipUtilsWinZipNow, let’s look at the classes that handle the zippingprocess for us. Because the exact process of how to zipfiles depends on which tool you use (several tools areavailable, including WinZip, PKZip, DynaZip, and severalfree or shareware utilities), I created an abstract classcalled SFZipUtils (based on Custom rather thanSFCustom for the same reason I outlined earlier) that hasthe framework for a zipping class and leaves theimplementation to a subclass.

SFZipUtils has an AddFile method that adds afilename to the protected aFiles array. Call this method,passing it the name and path of the file, for each file to bezipped. The nFiles property contains the number of filesto be zipped; it has an assign method that makes it read-only and an access method that calculates the value forthe property. The Clear method simply clears the aFilesarray; call this method after zipping some files when youwant to zip a different set of files. The cZipFile propertycontains the name of the zip file to create; it has an assignmethod to ensure a valid filename is entered. ThelUseRelativePaths property indicates whether relative

paths should be used in the zip file; set it to .F. to useabsolute paths.

The ZipFiles method uses a template design patternto create the zip file. It doesn’t do a lot of work itself;rather, it directs other methods to do the work. Thefollowing is a stripped-down version of the code (see thesource code for the complete version):

lparameters tcZipFilewith This

* Hook method before the list of files to zip has been* created.

.BeforeAddFilesToZip()

* Add each file to the zip file.

lnFiles = .nFiles for lnI = 1 to lnFiles lcFile = .aFiles[lnI] if .lUseRelativePaths lcFile = sys(2014, lcFile, lcZipFile) endif .lUseRelativePaths .AddFileToZip(lcFile) next lnI

* Hook method after the list of files to zip has been* created.

.AfterAddFilesToZip()

* Hook method before the zip file has been created.

.BeforeCreateZipFile()

* Ensure we're in the directory where the zip file* should be created (it works better if we're using* relative file paths) and zip the files.

lcCurDir = sys(5) + curdir() cd (justpath(lcZipFile)) llReturn = .CreateZipFile(lcZipFile) cd (lcCurDir)

* Hook method after the zip file has been created.

.AfterCreateZipFile()endwithreturn llReturn

The BeforeAddFilesToZip, AfterAddFilesToZip,BeforeCreateZipFile, and AfterCreateZipFile methodscalled from ZipFiles are hook methods; they don’t haveany code in this class, but can be used to place additionalcode at the appropriate place in the process in a subclass.AddFileToZip and CreateZipFile are also empty in thisclass, but they’re not hook methods; they’re abstractmethods that must be coded in a subclass to provide thebehavior for the zipping mechanism used.

Because I have WinZip (from Nico Mak ComputingInc, www.winzip.com), I created a subclass of SFZipUtilscalled SFZipUtilsWinZip that uses WinZip to create thezip file. This subclass creates a file containing the list offiles to be zipped (the name of this file is passed toWinZip, as we’ll see later), so the BeforeAddFilesToZipand AfterAddFilesToZip methods were overridden tocreate and close a textmerge file, respectively.AddFileToZip was overridden to output the name of thespecified file to the textmerge file:

Page 11: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 11FoxTalk May 2000

lparameters tcFile\\<<tcFile + chr(13) + chr(10)>>

The CreateZipFile method, which actually creates thezip file, calls WINZIP.EXE (the actual name and path tothe program are stored in the cZipProgram property) todo the work. WinZip is passed the following parameters:“-min” to run minimized, “-a” to add files, “-p” to savedirectory information (rather than just the filename itself),the name of the zip file, and “@<filename>,” where<filename> is the name of the textmerge file (stored in thecZipList property) containing the list of files to zip.

Initially, I had this code call WINZIP.EXE using theVFP RUN command. However, the problem with this isthat RUN returns immediately even though WinZiphasn’t finished executing. I wanted to wait until it wasdone to be sure the zip file was created, so I used a greatpublic domain class called Process (included in thismonth’s Subscriber Downloads), written by Ed Rauh ofeSolutions Services, LLC. Set the icCommandLineproperty of this class to the command line to execute andthe icWindowMode property to the mode for theapplication (“HID” means hidden; see the Process classfor other choices), then call the LaunchAppAndWaitmethod to execute the application and wait until itterminates before continuing. The Init method ofSFZipUtilsWinZip instantiates Process into the oProcessproperty. Okay, now that this description took much morespace than the actual code for CreateZipFile <g>, here’sthe code:

lparameters tcZipFilewith This .oProcess.icCommandLine = .cZipProgram + ; ' -min -a -p ' + tcZipFile + ' @' + .cZipList .oProcess.icWindowMode = 'HID' .oProcess.LaunchAppAndWait()endwith

Before using SFZipUtilsWinZip, don’t forget to set thecZipProgram property to the path to WINZIP.EXE onyour system.

Adding to a ProjectHookTo make it easier to access the methods in SFProjectUtils, Icreated a subclass of SFProjectHookFind (I used this classbecause I also want text search capability) calledMyProjectHook (in SFPROJECT.VCX) that instantiatesSFProjectUtils into its oUtils property and has PackProjectand ZipFiles methods that call oUtils.PackProject andoUtils.ZipProject, respectively. It also has ZipSourceFile

and ZipLibraryFile methods with the same code as thefunctions in ZIPPROJ.PRG; thus, file filtering creates thedesired zip files. I created a subclass of SFProjectToolbarcalled MyProjectToolbar (also in SFPROJECT.VCX) thathas buttons for building an .APP or .EXE, checkboxesfor various build options, and buttons to perform a textsearch or zip or pack the project (see Figure 1). Thesebuttons call the appropriate methods in MyProjectHook,which instantiates MyProjectToolbar because itscToolbarClass property contains “MyProjectToolbar.”

ConclusionProject utilities like those I presented in my September1998 article and this article make working with projectfiles a lot easier. I use these utilities all the time, andhope you find them as useful as I do. ▲

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

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

Saskatchewan, Canada. He’s the author or co-author of Stonefield’s

add-on tools for FoxPro developers, including Stonefield Database

Toolkit, Stonefield Query, and Stonefield Reports. 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 last three Microsoft

FoxPro Developers Conferences (DevCon), as well as to user groups

and at regional conferences all over North America. He’s a Microsoft

MVP and Certified Professional. [email protected].

Figure 1. MyProjectToolbar provides fast access toproject functions.

Page 12: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

12 http://www.pinpub.comFoxTalk May 2000

FoxTalk

An Object FactoryCharles T. Blankenship

Creating objects is a normal day-to-day task for a VFPdeveloper, but there’s a much more robust way to go about itthan simply using the CREATEOBJ() command directly. CharlesT. Blankenship illustrates how and why you should use anAbstract Factory to create the objects you use in your day-to-day programming.

IF you’ve been using Visual FoxPro for any period oftime, you’re undoubtedly very familiar with theCREATEOBJECT() function. Many developers use this

function directly and liberally throughout the source codeof their application. In many situations, this is perfectlyacceptable; in many other situations, though, thisapproach is far too inflexible.

In order to get the CREATEOBJECT() function towork, you must provide several pieces of information.First, you must pass the name of the class definition, viaparameter, to the CREATEOBJECT() function. Second, theenvironment must be set up correctly, in the sense that theprogram or the class library containing the class definitionmust be opened by the SET PROG or SET CLASSLIBcommands. Furthermore, if the object you want toinstantiate is an aggregate whose parts are included indifferent class libraries or programs, each of these must beopened in the environment as well. If they aren’t, you endup seeing the following annoying message: “Classdefinition ‘name’ is not found (Error 1733).” I’m sureyou’ve seen it from time to time.

So, there are three parts that must be present before

you can manufacture an object: the class name (classdefinition), the class library that contains the class name,and a properly set-up environment. Hmmm, a little morecomplex than it appears at first glance. There must be away to encapsulate this complexity and make themanufacture of objects in VFP much simpler. Luckily,there is, and the creational pattern that makes thispossible is known as a factory.

In the business world, factories bring together rawmaterials, add processes and, in the end, create a product.The analogy for a software factory is the same except thatthe raw materials are files (programs and class libraries)and Visual FoxPro commands; the process is the class I’mgoing to describe below, and the product is a brand-newbaby VFP object. Put them together and you have afactory that brings together all of the raw materialsrequired to manufacture objects.

The raw materialsThe first problem to tackle is determining where to storethe raw materials required to manufacture the object. Theonly logical conclusion is a table. The table, which Inamed FACTORY.DBF, has the structure shownin Figure 1.

Each row in this table defines the informationrequired to manufacture an object. Notice that the tablecontains columns to store the class name (cClass), theclass library (cLibrary) where the class name is defined,and the additional class libraries (mClasses) and

6.06.0

Figure 1. The FACTORY.DBF table.

programs (mProcs) required tosupport the object if it’s an aggregate.You should also take note that theFACTORY.DBF contains several othercolumns as well: cKey, cFile,mPreProc, and finally mPostProc.Together, these columns contain all ofthe raw materials required tomanufacture an object.

The class definition (cClass)This column contains the name of theclass definition to use to create theobject. At the time of object creation,the value contained in this columnbecomes the parameter passed to theCREATEOBJECT() function.

Page 13: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 13FoxTalk May 2000

The class library (cLibrary)This column contains the name of the class library orprocedure that stores the class definition. Thisinformation is used to make sure the proper class libraryor procedure has been opened up to support the creationof the object.

The supporting classes and procedures (mClassesand mProcs)Each of these columns contains a comma-delimited list ofclass library or procedure files that must be opened tosupport the creation and functionality of the object. Notethat tests are conducted to make sure that a class libraryor procedure isn’t opened more than once.

The object key (cKey)The information you store in cKey is one of the mostpowerful features of the Object Factory. This key allowsyou to assign a plain English description, or an “alias,” toan object that you want to create. For example, I’ll assumethat you’re creating an accounting application that allowsyour customers to create invoices. In that application is adefault invoice object provided by your company, whoseclass definition is “boInvoice.” If you’re not using theObject Factory or an equivalent, the code you use toinstantiate your invoice could look like this:

loInvoice = CREATEOBJ('boInvoice')

Notice that you’ve limited your applicationsignificantly by hard-coding the name of the classdefinition into the source. A better approach is to associatethe class definition “boInvoice” with an alias, such as“InvoiceObject.” You can now use the Object Factory tomanufacture the object associated with the alias, using awrapper function I’ll illustrate later. The factory objectthen looks up the object definition in the FACTORY.DBFusing the “InvoiceObject” key, determines the class nameto pass into the CREATEOBJECT() function, and theproduct is the object of your or your user ’s choice.

Why is this better? For many reasons, and the mostimportant of all is extensibility. Assume for an instant thatyou have a very large client and that particular client can’tuse your invoice form as is. The client needs an enhancedversion. Wouldn’t it be a nice sales technique to tell it tosubclass yours, thereby inheriting all of your functionality,and make the modifications it desires? Then, to use theirown newly created invoice object, all the client wouldhave to do is simply change the class name and classlibrary of the “InvoiceObject” in the FACTORY.DBF from“boInvoice” to “boCustomInvoice.” Pretty cool, huh? Alsonotice that your initial source code is preserved via thesubclass. This means that the client can take advantage offuture updates from you without overwriting its owncustom work.

The file (cFile)The contents of this column provides enhanced supportfor the SET CLASSLIB command. The format for thiscommand, straight from the VFP Help file, is as follows:

SET CLASSLIB TO ClassLibraryName ; [IN APPFileName | EXEFileName] [ADDITIVE]

Notice that class libraries can also be located in otherfiles such as applications (.APP), and executables (.EXE).When the class library containing the object to bemanufactured is contained in a file of this type, you mustidentify the name of that file. When a value is present inthe cFile, the SET CLASSLIB command is expanded byinserting the value contained in that column into the INclause of the command.Now the CREATEOBJECT()function can find a class library that is hidden in an .APPor .EXE.

Pre-processing (mPreProc) and post-processing(mPostProc)As a final spit and polish for the Object Factory, I decidedto throw in another feature—that of pre- and post-processing. These memo fields provide a place for you towrite an entire program (not recommended) or place acall to an external function or program (recommended)that must execute before and/or after the object itselfis created.

Okay, the preceding section describes all of the parts.I’ll now discuss the process of putting those parts togetherto manufacture an object.

The processThe controlling method for the Object Factory is anexample of a behavioral pattern called the TemplateMethod. Technically stated, Template Methods “define theskeleton of an algorithm in an operation, deferring somesteps to subclasses. Template Methods let subclassesredefine certain steps of an algorithm without changingthe algorithm’s structure.” The process used tomanufacture objects in the Object Factory is as follows:

*-------- Location Section ------------- *} Program: CFactory.PRG *} Class: CAbstractFactory *} Method: ManufactureObject() *--------------------------------------- LPARAMETERS tcKey, ; roObject, ; tlSkipEnvironmentSetup

LOCAL loObject , ; lcClassName , ; lcClassLibrary , ; lcClassFile , ; lcClassLibList , ; lcProcList , ; lcPreProc , ; lcPostProc , ; llRetVal

roObject = .NULL.IF tlSkipEnvironmentSetup

Page 14: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

14 http://www.pinpub.comFoxTalk May 2000

lcClassName = tcKeyELSE This.GetClassInformation( tcKey , ; @lcClassName , ; @lcClassLibrary , ; @lcClassFile , ; @lcClassLibList , ; @lcProcList , ; @lcPreProc , ; @lcPostProc )ENDIF

IF .NOT. tlSkipEnvironmentSetup This.SetClassLibraries( lcClassLibrary , ; lcClassFile , ; lcClassLibList )

This.SetProcedures( lcProcList ) This.ExecuteProcedure( lcPreProc )ENDIF

roObject = This.InstantiateObject( lcClassName )

IF .NOT. tlSkipEnvironmentSetup This.ExecuteProcedure( lcPostProc )ENDIF

llRetVal = TYPE('roObject.Name') == "C"

RETURN llRetVal

The benefits of a Template MethodI want you to notice several things about this particularuse of the Template Method pattern. The first and mostimportant is that this method is rather English-like inappearance. It’s quite apparent, if you ignore thetlSkipEnvironmentSetup switch, that the following thingsmust occur, in this order, to manufacture an object. First,the Template Method must GetClassInformation from theFACTORY table, next it must SetClassLibraries andSetProcedures that are required to support the newlycreated object, then it must ExecuteProcedures that aredesignated as pre-processing, and execute theInstantiateObject process. Finally, it ExecutesProceduresthat are designated as post-processing. The point here isthat it would take a novice programmer less than 15seconds to determine what was being accomplished here.Template Methods make maintaining code much easierfor junior programmers.

Second, notice how the Template Method allowssubclasses to redefine certain steps of an algorithm.Assume for a moment that you subclass the FactoryObject into a new subclass named DocumentFactory. Inthis new subclass, it’s possible to override any one or allof these processes and replace them with implementation-specific processing as needed. As long as you don’toverride the primary method, ManufactureObject(), theorder of processing defined in the template methodremains intact.

Using the Object Factory, part 1: the starterprogram, Factory.PRGWhen I first started using the Object Factory in theframework, I had to create the Object Factory itself to useit. That got old after, oh, about the second time. Therefore,I created a program that wraps up the complexity of using

the Object Factory and named it FACTORY.PRG. The coderequired to use the Factory Object changes from this:

loInvoice = .NULL.loFactory = CREATEOBJECT('cAbstractFactory')IF loFactory.ManufactureObject('InvoiceObject',; @loInvoice) = .T. <do some processing here>ENDIF

to this:

loInvoice = .NULL.IF Factory('InvoiceObject',@loInvoice ) <do some processing here>ENDIF

This method of invoking the Object Factory is quitehandy when executing it from the Command Window.Previously, in the framework, I’d have to launch the entireapplication just to test a form. The simple reason for thisis that the form wouldn’t work unless the environmentwas properly set up. Now, using the Object Factory, I cantest forms from the Command Window without thearduous task of starting the application first.

The listing for the FACTORY.PRG follows:

*-- Factory.PRGLPARAMETER tcKey, roObject, tlSkipEnvironmentSetup

LOCAL lcProcList, llRetVal, loFactory

lcProcList = UPPER(SET('PROC'))

IF .NOT. ( "CFACTORY.FXP" $ lcProcList ) SET PROC TO ..\..\COMMON\LIBS\CFACTORY.FXP ADDITIVEENDIF

IF .NOT. ( "IFACTORY.FXP" $ lcProcList ) SET PROC TO ..\..\COMMON\ILIBS\IFACTORY.FXP ADDITIVEENDIF

loFactory = CREATEOBJ( 'cAbstractFactory' )

IF TYPE('loFactory.Name') == "C" loFactory.ManufactureObject( tcKey, ; @roObject, ; tlSkipEnvironmentSetup ) llRetVal = TYPE( 'roObject.Name' ) == "C"ELSE llRetVal = .F.ENDIF

RETURN llRetVal

Using the Object Factory, part 2: keeping a publicreference aroundThe other option you have is to keep a reference to theObject Factory present at all times. The previous exampleforces you to endure the object creation process two timesfor each object you create. The first is the creation of theObject Factory itself, while the second is the object thatthe factory eventually creates. Some of you might thinkthis is inefficient. If so, simply add a property to yourglobal application called .oFactory and, during theinstantiation process, create the Object Factory and store areference to it there. Then, each time you want tomanufacture an object, all you have to do is use thefollowing signature:

Page 15: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 15FoxTalk May 2000

<goApp>.oFactory.ManufactureObject( ; <cKey> , ; @<oObject> , ; [<lSkipEnvironmentSetup>] )

This method of use is preferred within afunctioning application.

The signatureAh, finally, how to use the thing. The first step in thisprocess is to populate the FACTORY.DBF with all of anobject’s required information. Next, create a variable intowhich the newly manufactured object is to be stored.Finally, invoke the Object Factory by calling itsManufactureObject method. Pass to that method the keyassociated with the object that you want to create in thefirst parameter and the variable into which the objectshould be stored in the second parameter (by reference).For example:

PRIVATE loInvoiceLoInvoice = .NULL.IF <goApp>.oFactory.ManufactureObject( "InvoiceObject", ; @loInvoice ) <work with the object as usual>ELSE <perform some exception processing>ENDIF

If the method returns a .T., it means the ObjectFactory successfully created the desired object. An .F.means the object couldn’t be created for some reason.

Remember, no matter which method you use toinvoke the Object Factory (FACTORY.PRG or a persistingobject reference), the signature remains the same. The firstparameter is the key associated with the object in theFACTORY.DBF. The second parameter is the variable intowhich the newly created object is to be stored.

Simplifying the object manufacturing processThe final modification to make to the Object Factory is tobe able to use it without having to populate theFACTORY.DBF. To do this, simply pass a .T. into theManufactureObject() method. This skips the processes inwhich the class information is extracted out of the tableand uses the value passed into the first parameter as theclass name instead of the alias. For example, this codecreates an object from the “boInvoice” class definition,effectively bypassing the FACTORY.DBF altogether:

LOCAL loInvoiceloInvoice = .NULL.<goApp>.oFactory.ManufactureObject("boInvoice", ; @loInvoice, .T. )

SummaryHopefully, I’ve convinced you that employing an ObjectFactory to create your objects provides your applicationwith increased flexibility and extensibility. In general,follow these rules. Rule 1: Use the FACTORY.DBF to

create objects that a user might want to substitute with hisown. Rule 2: Use the simplified signature of the ObjectFactory to completely encapsulate the object-creationprocessing of your application. Notice that when you usethis method, you never have to test whether or not theobject was successfully created. You know this by thereturn value from the ManufactureObject() method.

As always, as more and more people adopt amethodology, the many and varied implementations ofthat methodology glaringly highlight any shortcomings ofthe initial design. If a reader into a situation where thisdesign falls short and should be modified, please feel freeto contact me and we’ll begin the redesign process. Finallycomes Rule 3: In object-oriented development, you shouldalways embrace iteration. ▲

05BLANSC.ZIP at www.pinpub.com/foxtalk

C. T. Blankenship is a senior consultant for Flash Creative Management,

Inc., a consulting firm headquartered in Hackensack, New Joisey (spelled

using a colloquial pronunciation, of course). He’s an MCP and the proud

owner of a 1998 Harley Davidson Road King. Any persons wishing to ride

to the Y2K Florida DevCon or discuss this article should contact him.

757.853.4465, 757.853.6862, [email protected],

[email protected].

Page 16: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

16 http://www.pinpub.comFoxTalk May 2000

The Kit Box FoxTalk

Color Me Surprised!Paul Maskens and Andy Kramek

Using the Windows API, Andy Kramek and Paul Maskens areon the trail of colors that are usable in many display modes.

Andy: I’m having problems with colors on the screen.

Paul: What kind of problems?

Andy: Well, I have a cool tool for picking colors, but thecolors I choose don’t always display properly in differentcolor modes. For example, if I switch to 256-color mode,then some of my formerly solid colors are shown asdithered patterns and they are unusable!

Paul: Well, if you chose colors that can’t be displayed inboth modes I’d expect that. Particularly if you start in65,536 (64K)-color mode, high-color (16 million) or, evenworse, true color.

Andy: Yes, I gathered that much, but how do I find outwhich colors are reliable in all modes?

Paul: You mean, apart from trial and error?

Andy: Yes, I don’t want to have to try 64K RGB colorsettings in 256-color mode to see which ones workthe same.

Paul: Okay, then, first I want to see your cool color tool.(See Figure 1.)

Andy: Here it is. You can either type in the values for Red,Green, and Blue or use the spinners. You see the color

change immediately, and if you really like it, you can giveit a name and save it to a table. (See Figure 2.)

Paul: Neat.

Andy: Not only that, but once you save your colors, youcan search through your colors and load them into thebuilder, which changes the color in the window.

Paul: I like that. What about the “Export” button?

Andy: I thought you’d never ask <g>. (See Figure 3.)

Paul: Ah, I see. This is a neat feature. You can now useMURKY_MAUVE when you want to set the textforeground color. Provided you used the .H file in an#INCLUDE directive, of course.

6.06.0

Figure 1. Color builder tool: main screen.

Figure 2. Color builder tool: searching colors.

Figure 3. Color builder tool: exporting colors.

Page 17: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 17FoxTalk May 2000

Andy: Exactly. Now what happens if I save these colors in64K color mode and then try to display them in 256-color mode?

Paul: Most of them—I’d guess at least 65,088—don’tdisplay properly.

Andy: How do you get 65,088?

Paul: Oh, come on Andy, I used a calculator. 65,536 – 256 =65,088.

Andy: And the 65,536?

Paul: Ah, well, from my assembler background I know afew useful values gained by incestuously multiplying 2sfor common purposes. When I see 64K, I immediatelytranslate that to meaning a 16-bit value, which I know is65,536, holding values 0-65,535 unsigned.

Andy: Oh, right. But what I want to know is which colorswill display properly in which modes. It would be nice toshow them in the window, as they’d appear in eachlower-color mode, but I think that’s impossible. Even awarning that a color wouldn’t display as shown in anymode with fewer colors than the current display settingswould be useful, though.

Paul: Well, apart from trying all 65,536, I can’t see anobvious solution. I can make a few guesses that mightwork, but I don’t know if they will work. RGB() valueslike (0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0),(255, 0, 255), (0, 255, 255), and (255, 255, 255) ought towork in any mode. Except monochrome, of course <g>.I’d guess that using 128 for each of the non-zero RGBvalues would also work. That gives you 15 colors I’dexpect to work in all modes.

Andy: How about a Windows API function?

Paul: Sure, do you have one in mind? There are so manythat knowing where to start is a problem. I guess thatWindows uses a palette of available colors, because VFPhas the SET PALETTE command that affects how graphicsare displayed. So I’d start by looking at all of the API callsthat include the word PALETTE in the function name.

Andy: Okay, there aren’t many. This is from the MSDNLibrary:

The following functions are used with color.AnimatePalette CreateHalftonePaletteCreatePalette GetColorAdjustmentGetNearestColor GetNearestPaletteIndexGetPaletteEntries GetSystemPalettteEntriesGetSystemPaletteUse HTUI_Coloradjustment

RealizePalette ResizePaletteSelectPalette SetColorAdjustmentSetPaletteEntries SetSystemPaletteEntriesUnrealizeObject UpdateColors

Paul: GetSystemPaletteEntries looks like a goodcandidate. I’m guessing that the system colors are goingto be available in all modes, from 256-color mode up totrue-color.

Andy: Seems a reasonable assumption, though we aredealing with Windows. Logic doesn’t always apply.

Paul: Okay, so what parameters doesGetSystemPaletteEntries require, and what does it return?

Andy: Ah, that’s not all in the MSDN information. Youneed to have installed a product that includes the relevantfiles to find out those things. They’re usually .H files, fullof #DEFINED constants.

Paul: Well, I just happen to have Visual C++ installed aspart of Visual Studio 6. Admittedly, I can’t get beyond“Hello World” in Visual C++, but it does mean that Ihave the necessary INCLUDE files on my disk. What haveyou got, and what’s missing?

Andy: Well, here’s the definition of theGetSystemPaletteEntries API function:

UINT GetSystemPaletteEntries( HDC hdc, // handle to DC UINT iStartIndex, // first entry to be retrieved UINT nEntries, // #of entries to be retrieved LPPALETTEENTRY lppe // array that receives entries);

Paul: That’s a good start. We can translate that into FoxPropretty easily.

DECLARE INTEGER GetSystemPaletteEntries IN WIN32API ; INTEGER nHDC, ; INTEGER nStartIndex, ; INTEGER nEntries, ; STRING cBuffer

Parameters in API CallsVisual FoxPro doesn’t actually support all of the datatypes used with the API. However, it does offeralternatives and hides some of the complexity. In thepreceding example, Paul has substituted the VFP genericINTEGER data type for both the HDC and the lessspecific UINT, and a STRING for the LPPE. The functionspecifies that LPPE is a long pointer to an array of paletteentries. A palette entry is defined (in WINDGI.H) as a“structure” consisting of four consecutive bytes inmemory. Because a FoxPro string is also made up ofconsecutive bytes in memory, we can simply use a FoxProstring to store the "array" without worrying about thepointer attribute; FoxPro takes care of that for us.

Page 18: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

18 http://www.pinpub.comFoxTalk May 2000

Andy: There’s some more documentation, too.

ParametersHdc [in] Handle to the device context.

IStartIndex [in] Specifies the first entry to be retrieved from thesystem palette.

nEntries [in] Specifies the number of entries to be retrievedfrom the system palette.

lppe [out] Pointer to an array of PALETTEENTRY structures toreceive the palette entries. The array must containat least as many structures as specified by thenEntries parameter. If this parameter is NULL, thefunction returns the total number of entries inthe palette.

Return ValuesIf the function succeeds, the return value is the number of entries

retrieved from the palette.If the function fails, the return value is zero.

Paul: Good. That means we can find out how many colorsare in the palette by passing NULL as the last parameterbefore we allocate the string for receiving thepalette entries.

Andy: But you need to know what’s returned in that stringto make sense of it. Here’s the definition of PaletteEntryfrom MSDN:

typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags;} PALETTEENTRY;

The associated documentation that follows lacks onlyone thing: the actual values of the PC_EXPLICIT,PC_NOCOLLAPSE and PC_RESERVED flags. It’s obviousthat peRed, peGreen and peBlue correspond to our VFPRGB() function parameters and, being bytes, accept values0-255.

MembersPeRed Specifies a red intensity value for the palette entry.PeGreen Specifies a green intensity value for the palette entry.PeBlue Specifies a blue intensity value for the palette entry.PeFlags Specifies how the palette entry is to be used; peFlags may

be NULL or one of the following values:

Value Meaning

PC_EXPLICIT Specifies that the low-order word of the logicalpalette entry designates a hardware palette index.This flag allows the application to show thecontents of the display device palette.

PC_NOCOLLAPSE Specifies that the color be placed in an unusedentry in the system palette instead of beingmatched to an existing color in the system palette.If there are no unused entries in the system palette,the color is matched normally. Once this color is inthe system palette, colors in other logical palettescan be matched to this color.

PC_RESERVED Specifies that the logical palette entry be used forpalette animation. This flag prevents other windows

from matching colors to the palette entry since thecolor frequently changes. If an unused system-palette entry is available, the color is placed inthat entry. Otherwise, the color isn’t availablefor animation.

Paul: It’s likely that those “values” are defined in a headerfile, and they’re in fact 1, 2, and 4. But which is whichis impossible to tell from the documentation inMSDN, right?

Andy: Right, so can you look up those values in your files?

Paul: Hmm…there are 931 files! I’ll have to make a guessat which file it’s actually in. Or search all files in theINCLUDE directory for “PC_RESERVED.” Got it—foundin WINGDI.H:

/* palette entry flags */

/* palette index used for animation */#define PC_RESERVED 0x01/* palette index is explicit to device */#define PC_EXPLICIT 0x02/* do not match color to system palette */#define PC_NOCOLLAPSE 0x04

Andy: Now we’re getting somewhere. Microsoftapparently documented them in random order, but atleast we can interpret the flags if we need to.

Paul: Yes, but there’s one little problem remaining.Remember the function definition? That hDC (the“Device Context”) parameter needs to be derivedfrom a window. Looks like we’re going to needanother API function.

Andy: Two, actually. First you have to get a handle to thewindow; then from that you get the handle to the devicecontext. We’ll need both of these functions:

DECLARE INTEGER GetActiveWindow IN WIN32APIDECLARE INTEGER GetDC IN Win32Api INTEGER nWHnd

Paul: Good! Looks like we can actually get a workingprogram at last and print out the palette. How about this:

LOCAL lnHWND, lnDC, lcBuffer*** Declare necessary DLLsDECLARE INTEGER GetActiveWindow IN WIN32APIDECLARE INTEGER GetDC IN Win32Api ; INTEGER nWHndDECLARE INTEGER GetSystemPaletteEntries IN WIN32API ; INTEGER nHDC, ; INTEGER nStartIndex, ; INTEGER nEntries, ; STRING cBuffer

*** Get the Windows Handle for the Active WindowlnHWND = GetActiveWindow()

*** Now get the device context for the current windowlnDC= GetDC( lnHWND )

*** Set Buffer to appropriate size, try 10 colors*** (Need 3 Chars for RGB value + 1 for the flag)

Page 19: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 19FoxTalk May 2000

lcBuff = SPACE( 40 )

= GetSystemPaletteEntries( lnDC, 0, 10, @lcBuff )

FOR lnCnt = 1 TO LEN(lcBuff) IF lnCnt%4 = 0 *** End of a color string, this is the flag character ? "RGB( " + lcStr + " ) : F = " ; + PADL( ASC( SUBSTR(lcBuff, lnCnt, 1) ), 3, "0") ; + CHR(13)+ CHR(10) lcStr = "" ELSE *** Extract the color numbers as RGB values lcStr = lcStr + IIF(! EMPTY( lcStr ), ", ", "") ; + PADL( ASC( SUBSTR(lcBuff, lnCnt, 1) ), 3, "0") ENDIFNEXT

Andy: Great, but we ought to point out that there’s abetter version of this program (with more comments andexplanation too) in the Subscriber Downloads file for thisarticle available at www.pinpub.com/foxtalk. Anyway,here’s what we get from 256-color mode.

RGB( 000, 000, 000 ) : F = 000RGB( 128, 000, 000 ) : F = 000RGB( 000, 128, 000 ) : F = 000RGB( 128, 128, 000 ) : F = 000RGB( 000, 000, 128 ) : F = 000RGB( 128, 000, 128 ) : F = 000RGB( 000, 128, 128 ) : F = 000RGB( 192, 192, 192 ) : F = 000RGB( 192, 220, 192 ) : F = 000RGB( 166, 202, 240 ) : F = 000

Paul: And are we right? Do those all appear assolid colors?

Andy: Yes! I’ve tested every one and they all appear asundithered colors.

Paul: Great. Now we can try to find out how many colorsthere are in the palette. There’s another useful-lookingAPI function, GetSystemPaletteUse, that should tell ushow many used system colors there are. We can then usethat number to fetch only the used colors. In the .H file (Ididn’t read the documentation), we have:

UINT WINAPI GetSystemPaletteUse(HDC);

Or, in VFP terms:

DECLARE INTEGER GetSystemPaletteUse IN WIN32API ; INTEGER nHDC

Andy: So if we add that to the program and define thebuffer dynamically, we can get the list of colors used inthe system palette without having to worry about howlong the list is.

Paul: Cool, and here are the results.

20 Color Entries in System PaletteRGB( 000, 000, 000 ) : F = 000RGB( 128, 000, 000 ) : F = 000RGB( 000, 128, 000 ) : F = 000RGB( 128, 128, 000 ) : F = 000RGB( 000, 000, 128 ) : F = 000

RGB( 128, 000, 128 ) : F = 000RGB( 000, 128, 128 ) : F = 000RGB( 192, 192, 192 ) : F = 000RGB( 192, 220, 192 ) : F = 000RGB( 166, 202, 240 ) : F = 000RGB( 004, 004, 004 ) : F = 004RGB( 008, 008, 008 ) : F = 004RGB( 012, 012, 012 ) : F = 004RGB( 017, 017, 017 ) : F = 004RGB( 022, 022, 022 ) : F = 004RGB( 028, 028, 028 ) : F = 004RGB( 034, 034, 034 ) : F = 004RGB( 041, 041, 041 ) : F = 004RGB( 085, 085, 085 ) : F = 004RGB( 077, 077, 077 ) : F = 004

Andy: Bizarre. We get 20 colors, of which the first 10 arethe same as before. The other 10 are apparently shades ofgray (equal parts of red, blue, and green) that defy anyobvious logic, and are all dithered in 256-color mode. Thisdoesn’t work!

Paul: Ah, well, err… I don’t understand it either. The last10 colors are also flagged as PC_NOCOLLAPSE, whichmakes little sense to me. Trying it in other modes givesthe same result, except that in 64K color mode on mymachine I get an error result (maybe I should have readthat documentation!). I vote we abandon this and try itthe hard way.

Andy: What do you mean, the hard way?

Paul: Well, there’s another API function we can try, onethat takes the RGB definition of a color and returns thenearest color that matches. If the two colors aren’t thesame, then we can’t display that color properly.

Andy: So you propose calling that in 256-color mode, withits 16 million possible colors (24 bits = 224 = 16777216, tobe precise), then seeing which ones can be displayed?

Paul: Exactly. Here’s the documentation:

The GetNearestColor function returns a color value identifying a colorfrom the system palette that will be displayed when the specified colorvalue is used. From the MSDN CD again:

COLORREF GetNearestColor( HDC hdc, // handle to DC COLORREF crColor // color to be matched);

ParametersHdc [in] Handle to the device context.crColor [in] Specifies a color value that identifies a requested color.

To create a COLORREF color value, use the RGB macro.

Return ValuesIf the function succeeds, the return value identifies a color from thesystem palette that corresponds to the given color value. If the functionfails, the return value is CLR_INVALID.

And we can get, from the INCLUDE file:

#define CLR_INVALID 0xFFFFFFFF

Page 20: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

20 http://www.pinpub.comFoxTalk May 2000

Andy: What, pray tell, is that COLORREF? We don’t havethe RGB macro in FoxPro, so how do we do it?

Paul: Ah, the good news is that it’s just like a VFP colorreturned by the RGB() function. Which shouldn’t come asmuch of a surprise. Since we’re mixing colors in exactlythe same way, it would be natural to use the R,G,B bytesin the same order that Windows expects. Here’sthe documentation:

The COLORREF value is used to specify an RGB color.

typedef DWORD COLORREF;typedef DWORD *LPCOLORREF;

RemarksWhen specifying an explicit RGB color, the COLORREF value has thefollowing hexadecimal form:

0x00bbggrrThe low-order byte contains a value for the relative intensity of red; thesecond byte contains a value for green; and the third byte contains avalue for blue. The high-order byte must be zero. The maximum value fora single byte is 0xFF.

The key, then, is that 0x00bbggrr, which is how wepack and unpack a color number.

Andy: Okay, I have a function to do that already—it’s areally easy bit of code, too.

FUNCTION NumToRGBLPARAMETERS tnColNumLOCAL lnredval, lngrnval, lnbluval, lnLeftSTORE 0 TO lcredval, lcgrnval, lcbluval*** Get Blue ComponentlnBluVal = INT(tnColNum/65536)*** Get Green ComponentlnLeft = MOD(tnColNum, 65536)IF lnLeft > 256 lnGrnVal = INT(lnLeft/256) lnLeft = MOD(lnLeft, 256)ENDIF*** Get Red ComponentlnRedVal = lnLeftlcRGB = “RGB( ” + PADL(lnRedVal, 3, “0”) ; +“, ”+ PADL(lnGrnVal, 3, “0”) ; +“, ”+ PADL(lnBluVal, 3, “0”) + “ )”RETURN lcRGB

Paul: As opposed to my version <g>.

FUNCTION NumToRGBLPARAMETERS tnColorLOCAL lnRed, lnGrn, lnBlulnRed = BITAND( tnColor, RGB( 255, 0, 0 ))lnGrn = BITRSHIFT( BITAND( tnCol, RGB( 0, 255, 0 )), 8)lnBlu = BITRSHIFT( BITAND( tnCol, RGB( 0, 0, 255 )), 16)lcRGB = “RGB( ” + PADL(lnRed, 3, “0”) ; +“, ”+ PADL(lnGrn, 3, “0”) ; +“, ”+ PADL(lnBlu, 3, “0”) + “ )”RETURN lcRGB

Andy: That shows that you still think about hardware.Shift indeed!

Paul: Well, Calvin put it in, so I thought it would be politeto use it. Plus, it matches the way I think about it—“mask

and shift” instead of “divide and truncate.”

Andy: So, dragging you back to the point, what arethe results?

Paul: Well, it’ll take a while. Eight minutes or so later (youneed a faster processor, Andy), we’ve got this file:

000,000,000 000,000,128 000,128,000 000,128,128 128,000,000128,000,128 128,128,000 128,128,128 000,000,255 000,255,000000,255,255 255,000,000 255,000,255 255,255,000 255,255,255160,160,164 166,202,240 192,192,192 192,220,192 255,251,240

Andy: Right, now I can test those in the color builder andsee if they really are pure (nondithered) colors or not.

Paul: Well, all those with 128 or 0 in are the low-intensityprimary and secondary colors. There should be noproblem with them, nor with the high intensity colors(255 and 0). Of course, black and white should work. Ithink that Windows standard gray is (192, 192, 192) andwould expect that to be there too. That just leaves a fewoddball colors: (160,160,164), (166,202,240), (192,220,192),and (255,251,240).

Andy: Well I tested all of them, and they really are all non-dithered colors in the color builder in 256-color mode. Atlast, a solution!

Paul: Right, the triumph of brute force over science. I justwish that I really understood this.

Andy: Me, too. I’ve had a thought, though. We shouldrepeat these tests under Windows NT Terminal Serverwith both a PC client and one of those NCD boxes. Just incase there’s a surprise.

Paul: You mean that running a Windows CE-basedNetwork Computing Devices terminal might not producethe same results as running the test under NT? Goodpoint—there are so many variables there that it mightwell not do what I expect.

The program is running on the terminal server, whichis currently set to 65,536 colors. Then the terminalconnects to the server but can display only 256 colors.I bet there’s a clever translation going on somewhere,which we could eliminate if we set both to 256-color mode.

Andy: Sounds reasonable; having both in the same colormode would make things simpler. Any surprises?

Paul: Yes, I’m surprised it produced the same resultsunder NT4TS as it did when I ran the program on myNT4 laptop.

Page 21: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 21FoxTalk May 2000

Andy: How about higher color modes?

Paul: I’ll stick to laptop testing, because we aren’t going touse more than 256 colors under Terminal Server. There Igot a surprise. It’s different on my NT laptop and yourWin98 desktop! I don’t know why, but NT reports thatthere are no fixed palette colors (SYSPAL_NOSTATIC).Then, when I test using brute force, I do indeed get 65,536different colors returned as being displayable.

Andy: I’m not going to check all of those by hand. I thinkI’ll believe the program.

Paul: Well, I thought I’d better check by searching yourfile for the 20 colors from the previous test in 256-colormode. Once I did, I found a problem. Only one of the four“oddball” colors works (192,220,192). The others—

(160,160,164), (166,202,240), and (255,251,240)—are not inthe list of 64K mode colors.

Andy: Oh dear, I suppose that means we’re back to trialand error and that we’ve totally failed to figure this oneout. Can anyone out there help us? ▲

05KITBOX.ZIP at www.pinpub.com/foxtalk

Andy Kramek is a longtime FoxPro developer, FoxPro MVP, independent

contractor, and occasional author, based in Reading, England. He’s

currently working at Euphony Communications Ltd.

[email protected].

Paul Maskens is a VFP specialist and FoxPro MVP, working as

development manager for Euphony Communications Ltd, based in

Oxford, England. [email protected].

Debugging Scaffold . . .Continued from page 7

serial, it seems that this particular example doesn’t havemuch in the way of demonstration. Butyou’re wrong!

The fourth area, property values not incrementedcorrectly, provides two potential levels of scaffolding. Thefirst level of the scaffold would be a “wrapper” (youdidn’t think I’d be able to cover a topic that required“design” without using the term “wrapper” as well, didyou?) for the area. For example, you could test the inputand output of the incrementing function or functions, tosee if indeed this area was causing a problem at all. If itwas, you could turn the first level off but turn the secondlevel on, to examine “jes’ whut in tarnation wuz goin’ onin thar.”

Depending on the complexity of what you’re testing,you could even provide a series of nested levels thatcould be used independently or with each other.

Finally, the scaffold should be designed andconstructed to be able to handle anticipated growth. Ofcourse, since growth usually occurs in the future, thiscomes down to reading the mind of the future, and thatcan sometimes be difficult. What I do to compensate forthis difficulty is to ask myself what types of things are

likely to change in the foreseeable future. This won’tcover all possible avenues of change, but many times afuture modification will be evolutionary, rather thanrevolutionary, and these can be anticipated tosome extent.

In this example, one possible source of change wouldbe the numbering scheme itself. Perhaps the three-segment version number will be changed to foursegments. Or maybe the number of digits will change—once this project goes global, we’ll have seven teamsworking on this project, in Milwaukee, L.A., Honolulu,Melbourne, Cairo, Lisbon, and Rio, and thus it will bevery possible to have more than 99 builds in a singlecalendar day.

If you write your code modularly, it will be easier tomake the scaffolding modular as well, and that lendsitself to being able to be changed easier. The rest of thistopic—modular code—is material for another article.

ConclusionFormal debugging scaffolds are a new concept to ourcommunity, but I hope it’s one that catches on with you. Itshould—it’s helped me write better code, and do it moreefficiently—and (this is your mother/editor talking) it’llbe good for you too! ▲

Whil Hentzen is the editor of FoxTalk.

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 22: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

22 http://www.pinpub.comFoxTalk May 2000

Cool Tool FoxTalk

Beyond Visual Compare:Beyond CompareWhil Hentzen

Editor Whil Hentzen continues his series on Cool Tools with aprofile of Scooter Software’s visual directory/file comparisonutility, Beyond Compare.

ABOUT a hundred years ago (well, actually, it was inlate 1993), I wrote about a Cool Tool that displayeda visual comparison of two ASCII files. That was

back in the days of FoxPro 2.0, so the types of files wewere interested in comparing had extensions like .TXT,.PRG, and, occasionally, .SPR. Yes, most everything wewere interested in was an ASCII file of one sort or another,and so this tool, Visual Compare, was most handy.

One ironic feature was its name—Visual Compare ranin a DOS box, and the results appeared in a 25x80 display.While acceptable in the days of Windows 3.1, this doesn’tquite cut it anymore.

Fast forward a century or so, and we find our needsto be pretty much the same. We’re interested incomparing files that have extensions like .TXT, .PRG, and,occasionally, .VCX or ,SCX. Additionally, we’re interestedin comparing the contents of two directories. We wereinterested in that feature in 1993, but our requirementsweren’t as rigorous as they are today.

Scooter Software in Madison, Wisconsin, hasdeveloped a tool that has completely replaced VisualCompare in my toolbox: Beyond Compare. It’s got a

expect—the files that are identical, those that are missingon one side or another, and those that have the samenames but different attributes. For example, you’ll see thatW21A.PRG is the same on both sides, W21B.bak is foundonly in the Spare directory, and the versions of W21B.PRGare different in both directories.

If you right-click on a file, as shown in Figure 2 (onpage 23), you’ll get a great context menu that allows youto perform all sorts of functions that you’d ordinarily doin Explorer. But that’s not all. If you select the Compare Tomenu option and then right-click on a second file, you’llget the screen shown in Figure 3 (on page 23).

The dialog box shows you the two files you arecomparing. (You can compare binary files, but thecontents usually aren’t very interesting.) Again, asexpected, you see the two files lined up side by side, withmatching lines across from each other, and missing anddifferent lines displayed in contrasting colors. So far, sogood. But the best part is the controls—a little slider barand a scroll bar—on the far left side of the dialog box.

You can use the scroll bar to move through the file,naturally. The slider bar to its left represents the pair offiles you’re comparing. The vertical read-only text box hastwo halves: The left half and right half may be the samecolor, gray, or different colors. These represent “bird’s-eye” views of the two files. The rectangular box/arrow

Figure 1. The directory comparison screen of Beyond Compare displays the contents oftwo directories.

schizoid personality—it providesrobust and flexible directorycomparisons functionality and allowsyou to configure the comparisons sixways from Sunday, but it alsocompares two individual files, muchlike Visual Compare did, but withseveral additional features that I love.

Figure 1 shows the main screenof Beyond Compare, where I’mlooking at a pair of test directories.Because this screen shot is in blackand white, you can’t see that thevarious types of comparisons areidentified via colored boxes (in thelegend at the bottom of the dialogbox), but they are.

You’ll see everything that you’d

Page 23: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 23FoxTalk May 2000

slider on top of the text box showsyou what part of the files you’relooking at. Not only can you use thescroll bar’s thumb, but you can grabthe slider to reposition your locationin the file. The slider bar in the screenshot in Figure 3 shows that there area lot of different sections in these twofiles.

There are oodles more features,such as a startup screen that allowsyou to “save” a session where you’vedefined the two directories you wantto compare, as well as settings thatdefine how those two directories arecompared. There’s also aconfiguration screen that has justabout every imaginable comparisonsetting. For example, you can tell thecomparison to ignore files that areexactly one hour apart (hmmmm!),that are a couple of seconds apart, toflag files that are older than 1/1/1980, and so on.

Beyond Compare is shareware,which means you get to play with itfor 30 days, and if you like it, sendthem a few bucks. It’s definitelyworth every penny. A copy isincluded in this month’s SubscriberDownloads, and you can find outmore at their Web site atwww.scootersoftware.com. ▲

05COOLSC.ZIP

at www.pinpub.com/foxtalk

Whil Hentzen is the editor of FoxTalk.Figure 3. The dialog box that compares two files displays the contents of the file as wellas a legend of the file on the left side of the screen.

Downloads• 05ABRASC.ZIP—Source code for Robert Abram’s article,

“Setting up ODBC Connections for the Client User.”

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

“Zip it, Zip it Good.”

• 05BLANSC.ZIP—Source code for Charles Blankenship’s

article, “An Object Factory.”

May Subscriber Downloads• 05KITBOX.ZIP—Source code for Paul Maskens and Andy

Kramek’s article, “Color Me Surprised!”

• 05COOLSC.ZIP—Source code for Whil Hentzen’s article,

“Beyond Visual Compare: Beyond Compare.”

Extended A rticle

• 05BOOTH.HTM—“A Few OO Tips” by Jim Booth. Jim shares

some handy advice for object-oriented programming.

Figure 2. Right-click on a file in the directory listing to display a context menu thatallows you to manipulate and compare files a number of ways.

Page 24: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

24 http://www.pinpub.comFoxTalk May 2000

User name

Password

incubate

journal

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.

FoxTalk (ISSN 1042-6302) is published monthly (12 times per year)by Pinnacle Publishing, Inc., 1000 Holcomb Woods Pkwy, Bldg 200,Suite 280, Roswell, GA 30076. The subscription price of domesticsubscriptions is: 12 issues, $129; 24 issues, $232. POSTMASTER: Sendaddress changes to FoxTalk, PO Box 769389, Roswell, GA 30076.

Copyright © 2000 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.

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

1-800-788-1900 or 770-992-9401Fax: 770-993-4323

Pinnacle Publishing, Inc.PO Box 769389

Roswell, GA 30076-8220

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)

Subscr iption r ates:United States: One year (12 issues): $129; two years (24 issues): $232

Canada:* One year: $144; two years: $247Other:* One year: $149; two years: $252

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;

Executive Editor Heidi Frost; Managing Editor Robert Hatch;

Copy Editor Andrew McMillan

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

Page 25: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 1FoxTalk Extended Article: May 2000

FoxTalkSolutions for Microsoft® FoxPro® and Visual FoxPro® Developers

This is an exclusive supplement forFoxTalk subscribers. For more

information about FoxTalk, call us at1-800-788-1900 or visit our Web

site at www.pinpub .com/foxtalk.

Extended Article

A Few OO TipsJim Booth

Visual FoxPro is a language rich with useful objects. It seemsthat there are always three or more ways to do any task. Thisfact causes some confusion for folks as they try to figure outwhich is the best way to approach their problems. This month,Jim Booth will share a few “rules of thumb” about theOO approach.

I’LL start out by telling you that a “rule of thumb” isexactly that and nothing more. These are guidelines,not laws, and guidelines are supposed to be helpful in

nature, not mandatory.

Object identity and responsibilityWhen designing a composite object like a form, there aremany objects involved. In considering the location forspecific code elements, you need to ask yourself, “What isresponsible for this behavior?” You need to view theobjects as actual “living” things that have behaviors,properties and responsibilities.

It’s not a good idea to separate the objects from theirresponsibilities by putting code in the wrong object. Whenwe do this, we cause the application to be more difficult todebug and maintain.

Show me!Assume a form with a set of navigation buttons. One ofthose buttons will move to the next logical record.Obviously, we’ll be using that button’s Click event toinitiate the action, but exactly what should the button’scode do?

When I ask myself what the button is responsible for,the answer I get is, “To receive a message from the userand then to pass on that message.” Which object shouldbe responsible for moving the record pointer? Whichobject “owns” the data? The form! So the form shouldhave a method for moving the record pointer. Thebutton’s code should do no more than react to the user’sclick, perhaps something like this:

* Next Button ClickThisForm.GoNext()

The form should have a method named GoNext thatactually moves the record pointer and refreshes thedisplay. Why? Because the form, not the button, owns thedata. The button’s purpose is to give the user amechanism for communicating with the form.

What do we gain?The biggest thing we gain is the reusability of the button.Because the button only calls a method of the form, it canbe placed in any form that has a GoNext method, then doits job without any code modifications from one form toanother. This same idea can be carried to cover almost allof the buttons.

A second benefit is that the code that provides acertain behavior is in a logical location and not scatteredall over the place.

Parent, child, and sibling objectsIn VFP’s containership model we find certain objects thatcontain other objects—these are generally referred to asparent objects. The objects that the parent has inside it arethe child objects. Two or more objects that are inside thesame container object are called siblings.

It’s okay for a parent object to be aware of itschildren. It’s okay for a child object to be aware of itsparent. It’s not okay for siblings to be aware of each other.

For example, suppose you have a form with three textboxes (Text1, Text2, and Text3) in it. If Text3 has to react tothe value in Text1, it wouldn’t be a good idea to writecode in Text1 that changes something about Text3. Thebetter approach is to provide a method of the commonparent (the form) that can be called by Text1 and let theform’s method make the changes to Text3. Why? Becausethe rule above says that it’s okay for Text1 to know aboutthe form (its parent object). It’s also okay for the form toknow about Text3 (its child object). But it isn’t okay for

6.06.0

Page 26: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

2 http://www.pinpub.comFoxTalk Extended Article: May 2000

Text1 to know about Text3.

What do we gain?We gain freedom from adverse effects caused by adding,removing, or renaming objects inside the form. If weknow that any code affecting the contained controls iscontained only in the form’s methods, then adding,removing, or renaming a control gives us only one placeto make code adjustments, the form. We don’t need tocheck every control to see if it refers to the one we justremoved, because the siblings don’t know about eachother. The siblings know the form and the form knowseach of them.

Many small methodsA good goal to work toward is to have very short methodcode. Trying to pare down the code in a method to one ortwo pages of source is beneficial in many ways. When youencounter a method that starts growing into page afterpage of source code, you should consider breaking it upinto multiple methods that are called from each other.Breaking up the code, if done logically, can provide foreasier enhancement and greater flexibility.

The flexibility is gained through the fact that the codethat was once one big method is now broken into smallermethods that can be called independently from eachother. For example, writing a generic update method in aform class might require:

• Get a list of cursors that are open and buffered.• Begin a transaction.• Issue a TableUpdate on each cursor.• Attempt to resolve conflicts on a field-level basis.• Either end or rollback the transaction.

We could write one method named SaveChanges thataccomplishes all of this, or we could create a SaveChangesmethod, a GetCursors method, and a Resolve method. Bybreaking these up this way, when we go to implement theRevert method of the form we could call the sameGetCursors method to get a list of the cursors.

Put the object into object-oriented programmingThe name of this type of programming is object-oriented(OO) programming. It uses objects. Objects have data(properties) and behavior (methods). When you need aroutine to accomplish some specific task, think method,not procedure. When you need some transient data foryour code, think property, not variable.

Sounds simple enough, but it always engendersquestions. Questions like, “Do you mean that thereshould never be a variable in use?” or “I should neverwrite any more user-defined functions?” I think of it morelike this: If I’m tempted to write a user-defined function(UDF) to solve a problem, I’ll spend some time thinking ofhow I could solve that same problem without a UDF and

use an object’s method instead. I even consider creating aspecial object for that very purpose. It’s the same withvariables—if I think I need a memory variable at somepoint in my code, before I create that variable I investigatethe possibility that a property of another object is abetter solution.

Why should I spend the time considering thesealternatives? Because a well-designed object with theproper methods and properties is much better integratedthan a group of UDFs that share memory variables. Thisdifference will often render the object-based solution amore flexible one that can be used in many more placesthan the equivalent procedural approach could.

Also, incorporating properties into objects results inthe data storage mechanism being encapsulated and thedata being protected from errant changes. If there’s avariable named MyImportantValue in memory, then anyroutine from the past, the present, or the future can andvery well might alter the value of that variable. However,if the value is stored in a property namedMyImportantValue of an object named MyValues, thenthe only way any routine can alter it is by referring to itby its complete name, MyValues.MyImportantValue. Ifanother routine needs the same data, then the new routinecan have its own copy of the object, thus protecting theoriginal from being altered in unexpected ways.

I use the following question to decide if a routine Ineed should be a method of an object or a UDF: “Is thiswhat I would consider an extension of the underlyinglanguage?” If the answer is yes, I write a UDF. If theanswer is no, then I figure out which object should havethe method and have at it.

Last but not least, program to interface, notimplementationThe interface of an object is its set of public properties andmethods. All we should need to know in order to use anobject is the documentation of those properties andmethods. We need to know what arguments the methodsrequire and what values they return. For the properties,we need to know the allowable values and the effect thesevalues have on the object’s behavior.

We should not need to know anything at all abouthow the object does whatever it is it does. If it’s necessaryto understand how the object functions, then there’s aserious design problem, and it’s wise to fix that problemas soon as it’s noticed.

In the example of the form’s GoNext method Idescribed previously, the command button that calls theGoNext method should have no code in it that dependson exactly how the form will move the record pointer.Imagine a situation in which the data source is originallyVisual FoxPro tables. The GoNext method of the formmight simply issue a SKIP command and do someboundary checking to avoid hitting the end of the file.

Page 27: FoxTalk - dFPUG-Portalportal.dfpug.de/dFPUG/Dokumente/FoxTalk/PDF2000/FT0500.pdfBecause I need the MS SQL Server ODBC driver, I just need to get the delimited driver list and issue

http://www.pinpub.com 3FoxTalk Extended Article: May 2000

However, if a future change moves the data source toMicrosoft SQL Server, the form’s GoNext method mightbe issuing a new query to the server to produce a newcursor with the desired record in it.

If the button were somehow dependent on the SKIPcommand being used, then it would need to be changedwhen the data source changed. Programming to interfaceputs us in a situation wherein the button has no idea, nordoes it care, how the form does what it does. In this case,the change of data source is simply implemented in theform’s code and, as long as the interface for the formdoesn’t change, the button can skip happily along inblissful ignorance of the details. This provides a very high

level of reusability, because the objects aren’t dependenton each other’s implementation. We can change behaviorsof an object with little or no concern for the other objectsthat use it. ▲

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 and is a Microsoft Certified

Professional (MCP). He’s co-author of Effective Techniques for Application

Development and Visual FoxPro 3 Unleashed and is a contributing author

for Special Edition Using Visual FoxPro 6.0. Jim is also the technical editor

for Database Design for Mere Mortals. 203-758-6942,

www.jamesbooth.com, [email protected].