39
1 Manipulating Your Outlook Contacts Della Martin 2 Editorial: The Computer Says We Have Seven Copies... Whil Hentzen 6 Transactions in a VFP Client/Server Application Hector J. Correa 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating an Application Over the Internet Doug Henning 18 Data Warehouses, OLAP, and You Leslie Koorhan 24 September 2000 Source Code EA Beyond the Class Designer—The VCX Editor Kevin McNeish EA VFP Data on Your Handheld: The SatForms IDE Whil Hentzen EA Cool Tool: SearchString Jim Booth Fox Talk Solutions for Microsoft® FoxPro® and Visual FoxPro® Developers Accompanying files available online at http://www.pinnaclepublishing.com/ft 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 6.0 6.0 Manipulating Your Outlook Contacts Della Martin Generating custom lists from your Outlook 2000 contacts isn’t difficult (if you know where the few quirks are), and gives you flexibility that extends Outlook’s functionality. In this article, Della Martin shows you the way to create your own custom list. I T all came about when I wanted to print out a list of certain people in my address book, identified by a particular keyword in the Notes field. Outlook’s choices of output are convenient, but they don’t let me filter the list, nor do they let me select the fields I want to print. I wanted only their name, phone, e-mail address, and Web pages—it should fit nicely on one page (and Outlook’s output rambled on for pages!). Automation to the rescue! I knew I could read the information from the Contacts object, read in just the fields I need to create a cursor, and use the Report Writer to print out what I needed (or send it to Word, Excel, or a myriad of other possibilities). The plan of action The plan is to create a cursor containing only the required contacts, and only the desired fields. To populate the cursor, just grab a reference to Outlook, and then walk through the contact list. If it has the proper keyword in the notes, plop it into the cursor. Once the cursor is populated, then you can use the Report Writer, write it back out to Word, or whatever tickles your fancy… this last step is up to you (I’m assuming you know what to do once you build a cursor!). Continues on page 2 September 2000 Volume 12, Number 9 6.0 6.0

FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

1 Manipulating YourOutlook ContactsDella Martin

2 Editorial: The Computer SaysWe Have Seven Copies...Whil Hentzen

6 Transactions in a VFPClient/Server ApplicationHector J. Correa

9 The Kit Box:COM-monsense DesignPaul Maskins and Andy Kramek

13 Reusable Tools:Updating an ApplicationOver the InternetDoug Henning

18 Data Warehouses, OLAP,and YouLeslie Koorhan

24 September 2000 Source Code

EA Beyond the ClassDesigner—The VCX EditorKevin McNeish

EA VFP Data on Your Handheld:The SatForms IDEWhil Hentzen

EA Cool Tool: SearchStringJim Booth

FoxTalkSolutions for Microsoft® FoxPro® and Visual FoxPro® Developers

Accompanying files available onlineat http://www.pinnaclepublishing.com/ft

Applies specifically to one of these platforms.

Applies toFoxPro v2.x

Applies toVFP v5.0

Applies toVFP v3.0

Applies toVFP v6.0

6.06.0

Manipulating YourOutlook ContactsDella Martin

Generating custom lists from your Outlook 2000 contacts isn’t difficult (ifyou know where the few quirks are), and gives you flexibility that extendsOutlook’s functionality. In this article, Della Martin shows you the way tocreate your own custom list.

IT all came about when I wanted to print out a list of certain people in myaddress book, identified by a particular keyword in the Notes field.Outlook’s choices of output are convenient, but they don’t let me filter the

list, nor do they let me select the fields I want to print. I wanted only theirname, phone, e-mail address, and Web pages—it should fit nicely on one page(and Outlook’s output rambled on for pages!).

Automation to the rescue! I knew I could read the information from theContacts object, read in just the fields I need to create a cursor, and use theReport Writer to print out what I needed (or send it to Word, Excel, or amyriad of other possibilities).

The plan of actionThe plan is to create a cursor containing only the required contacts, and onlythe desired fields. To populate the cursor, just grab a reference to Outlook,and then walk through the contact list. If it has the proper keyword in thenotes, plop it into the cursor. Once the cursor is populated, then you can usethe Report Writer, write it back out to Word, or whatever tickles your fancy…this last step is up to you (I’m assuming you know what to do once you builda cursor!).

Continues on page 2

September 2000Volume 12, Number 9

6.06.0

Page 2: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

2 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

From the Editor FoxTalk

The Computer Says We HaveSeven Copies…Whil Hentzen

AS the popular press is stuffing itself with thenewest proposal for developing distributedapplications, I’ve been thinking about the past.

I remember when “how to” magazines coveredtechniques and processes for using products that wereactually available for purchase and use—not simplyarticle after article praising technologies that won’t beavailable for years while spreading FUD about thecompetition. But enough about IBM’s marketingapproach in the 60’s and 70’s—what I’ve really beenthinking about is how we’ve learned to developapplications in the past, and how we’re going to doit in the future.

Learning to develop applications on the PCplatform (standalone machines as well as LANs) was aninteresting process. I learned to program using FortranIV on a DEC PDP using 80 column cards submitted inbatch to the gods behind the glass wall in the computercenter. We wrote simulations for engineering problems.For example, one program I wrote calculated the optimalconfiguration of components in an automatic feedbacksystem of some sort of mechanical device like anautomobile’s suspension system.

The program consisted of little more than a routineto define inputs, a huge section of terse mathematical

calculations, and a routine to produce output. Themajority of the time was spent writing code thatmapped to the engineering calculations we‘d alreadyfigured out by hand. And then, of course, debuggingthat code.

By comparison, programming business applicationson the PC required very little in the way of math—figuring a running percentage is about as complexas the calculations get for most business applications.

On the other hand, the interface through whichusers enter data, the business rules, and the resultingoutput can become fairly complex. It’s not because themath is hard, but because you have to define the userinterface. And, of course, there are human interactionsthat you can’t define in black and white. There areeven human interactions that you simply might notthink of.

I remember walking into a bookstore to ask for abook by 60’s radical Abbie Hoffman not that long ago.There weren’t any on the shelf, so I went to the counter.The clerk tapped on the keyboard, and then wentback to the stacks to look with me. “That’s odd,”she said, after not finding any on the shelf herselfeither. “The computer says we have seven copies ofSteal This Book.” ▲

The Outlook object modelAt the top of the Application object is the object thatrepresents the server itself. This is the object youinstantiate with CreateObject(), as in:

oOutlook = CreateObject("Outlook.Application")

In Outlook’s case, CreateObject() opens an instance ofOutlook if one isn’t already running. However, if Outlookis already running, CreateObject() attaches to the runninginstance rather than starting a new one. It’s as if you used

GetObject() instead of CreateObject().When developing Office Automation apps, generally

the next thing to do is to manipulate a new or existingdocument. However, Outlook doesn’t have a maindocument object like Word’s Document object orPowerPoint’s Presentation object. The closestapproximation is a NameSpace object. According to theOutlook VBA Help file, a NameSpace “represents anabstract root object for any data source.” Not very helpful,is it? Basically, it’s the object that gives access to theOutlook data, so you must create one.

You create a NameSpace object with theGetNameSpace() method. It takes a single parameter,“MAPI,” which is the only supported parameter. It’s amystery why you have to actively call GetNameSpace

Outlook Contacts. . .Continued from page 1

Page 3: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 3FoxTalk September 2000

with a parameter that must be “MAPI” to load the datainstead of having it loaded automatically when Outlookstarts. But if you want your app to work, it’s somethingthat must be done. The code looks like this:

oNameSpace = oOutlook.GetNameSpace("MAPI")

Once you get a NameSpace object, things begin tomake more sense. The NameSpace object has a Folderscollection that contains one or more items, depending onyour configuration. For a stand-alone user, the only itemin the collection is “Personal Folders.” With ExchangeServer available, the collection may also contain “PublicFolders” and “Mailbox” items. Those folders each havetheir own Folders collections that contain a folder for eachof the individual applets within Outlook, such asCalendar, Tasks, Inbox, Outbox, Contacts, and so forth.You can access them, like this, if you’ve saved thereference to the NameSpace object in oNameSpace, asshown previously:

oContacts = oNameSpace.Folders[1].Folders["Contacts"]

Another way to get to the individual folders is to usethe GetDefaultFolder and pass the appropriate constant. Itreturns a reference to the specified folder:

#DEFINE olFolderContacts 10oContacts = oNameSpace.GetDefaultFolder( ; olFolderContacts)

Examining the Contacts folderThe Contacts folder contains a collection of ContactItem

Table 1. The most commonly used properties of the ContactItem object.

Property Type DescriptionFullName Character The person’s full name, unparsed. The name is also available in a variety of configurations,

like LastNameAndFirstName, LastFirstAndSuffix, and so forth. See Help for a complete list.FirstName Character The person’s first name.LastName Character The person’s last name.HomeAddress Character The person’s home address, unparsed. As with name, the components are available individually.

Outlook is also capable of doing sophisticated parsing of an address it’s given to break itinto components.

HomeAddressStreet Character The street portion (first line) of the home address.HomeAddressCity Character The city portion of the home address.HomeAddressState Character The state portion of the home address.HomeAddressCountry Character The country of the home address.HomeAddressPostalCode Character The postal/ZIP code of the home address.BusinessAddress Character The person’s business address, unparsed. The same individual components are available as

for home address.SelectedMailingAddress Numeric Indicates which address is the primary address for this person.

Uses these constants: olNone (0), olBusiness (2), olHome (1), olOther (3).HomeTelephoneNumber Character The person’s home telephone number.BusinessTelephoneNumber Character The person’s business telephone number.HomeFaxNumber Character The person’s home fax number.BusinessFaxNumber Character The person’s business fax number.PagerNumber Character The person’s pager number.CarTelephoneNumber Character The person’s car phone number.PrimaryTelephoneNumber Character The primary phone number to use for this person.Email1Address Character The first e-mail address for this person.Birthday Datetime The person’s birthday.

objects. Each ContactItem object has a number ofproperties (oddly, Outlook doesn’t use collections tomanage the various addresses, phone numbers, ande-mail addresses). Table 1 lists the most commonly usedproperties, and the Help file (VBAOUTL9.CHM) listsmany more under the ContactItem Object topic.

The other Office applications generally offer a specificproperty name (in this case, I’d expect a Contactsproperty, but Outlook uses it for something completelydifferent) to reference each item in the collection, as wellas a generic Item method. In Outlook, only the Itemmethod is available to access each contact, as in:

oContacts.Item[1]

You can also access the collection through the displayname, as in:

oContacts.Item["Martin, Della"]

Note that you must format the string exactly as it’sshown in the Display field. Matching what’s there is noeasy feat, because Outlook offers four different options(the middle two differing in that one contains a comma):

• FirstName MiddleName LastName• LastName FirstName MiddleName• LastName, FirstName MiddleName• NickName

Thankfully, for this application, you don’t need todirectly access specific contacts, so don’t worry abouthow to format that name. If you do need to access specific

Page 4: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

4 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

contacts, there doesn’t seem to be a direct way to do it,except through knowing the exact Display string.

Preparing to collect the contact infoThe next step is to build a cursor to hold the contactinformation. Your cursor will vary, depending upon theproperties you need; in this case, I’ll gather the first andlast name, phone number, all e-mail addresses I can find,along with all Web site addresses. In my scenario, I’m notpicky about which phone number I want, but I do want avalid one. If the PrimaryTelephoneNumber property isblank, I’ll take the BusinessTelephoneNumber, and finallythe HomeTelephoneNumber (even if it’s blank).

Outlook allows the user to enter around 255characters per character field, so there aren’t any hard andfast field lengths to reference in building your cursor. I’veused field lengths that work well for the data I have; yourmileage may vary. Here’s the cursor code:

* Create a cursor to hold contact informationCREATE CURSOR ContactInfo ; (cFirstName C(15), cLastName C(20), ; cPhoneNum C(30), ; cEmail1 C(30), cEmail2 C(30), cEmail3 C(30), ; cWebsite1 C(50), cWebsite2 C(50))

Note that there are fields for three e-mail addresses.The three e-mail addresses correspond to theEmail1Address, Email2Address, and Email3Addressproperties. Outlook will let you store a number of e-mailaddresses (I stopped testing after 12); however, there areonly properties for three. Why this isn’t a collection, Idon’t know, but I have a hunch it could be changed inthe next major release. Be sure to isolate this code witha wrapper to minimize potential changes in the nextmajor version.

So you can store a bunch of e-mail addresses… whichthree do you get? The Help file indicates that theEmail1Address property contains “the first e-mail entryfor the contact.” That’s not quite how it works. It appearsthat the Email1Address property contains the defaultaddress, regardless of whether it’s the first, third, or 12thin the list. The Email2Address and Email3Addressproperties contain the first two addresses in the list, otherthan the default. So if the default is the first address in thelist, Email2Address contains the second address andEmail3Address contains the third. If the default is thefourth address in the list, Email1Address contains thedefault, while Email2Address contains the first address,and Email3Address contains the second.

Walking through the contactsNow all that’s left is to loop through each contact in thefolder. When you need to process all members of acollection, VFP’s FOR EACH loop is your best bet. FOREACH lets you go through a collection (or array) without

using a counter or worrying about how many membersthere are. I’ll show code where the FOR construct walksthrough each item in oContacts.Item and sets the objectreference to the oContact variable. The properties shownin the Table 1 are accessed directly from the oContactobject reference.

One last little detail: How do you determine thecontacts to include in the cursor? In my case, I wanted justthose that have a particular keyword in the Notes section.For this example, I’ll use the “RAMS” keyword, whichrepresents my current project. While my contacts for thisproject span many companies and locations, I know I’vekeyed in “RAMS” for each of them in Outlook’s Notessection. So, I’ll just look through that list in the Help fileand verify that Notes is the property I’m looking for….Hey! There’s no Notes property! After rummagingthrough the list for quite a while, I finally figured out thatthe Notes are held in the Body property. So much forconsistency in naming properties to match the UI.

* Go through contacts#DEFINE NotesPhrase "RAMS"FOR EACH oContact IN oContacts.Items WITH oContact * Is this contact one we want to keep? We know * that by finding the defined NotesPhrase in the * .Notes property IsOK = NotesPhrase $ UPPER(.Body)

IF IsOK * Obtain the basic info cFirst = .FirstName cLast = .LastName

cEmail1Address = .Email1Address cEmail2Address = .Email2Address cEmail3Address = .Email3Address

cWebPage1 = .BusinessHomePage cWebPage2 = .PersonalHomePage * Find a telephone number

IF EMPTY(.PrimaryTelephoneNumber)

IF EMPTY(.BusinessTelephoneNumber) cPhone = .HomeTelephoneNumber ELSE cPhone = .BusinessTelephoneNumber ENDIF ELSE cPhone = .PrimaryTelephoneNumber ENDIF

* Put it into the cursor INSERT INTO ContactInfo ; (cFirstName, cLastName, cPhoneNum, ; cEmail1, cEmail2, cEmail3, ; cWebSite1, cWebSite2) ; VALUES (cFirst, cLast, cPhone, ; cEmail1Address, cEmail2Address, ; cEmail3Address, ; cWebPage1, cWebPage2) ENDIF && IsOK

ENDWITHENDFOR

After running the code (the complete example isavailable in this month’s Source Code files), the cursor

Continues on page 17

Page 5: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 5FoxTalk September 2000

Page 6: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

6 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

FoxTalk

Transactions in a VFPClient/Server ApplicationHector J. Correa

We all know how important transactions are in the databaseworld. We also know that VFP provides an excellent supportfor transactions. However, what happens when your VFPapplication uses another DBMS, like SQL Server, to store itsinformation? Do you still need transactions? Yes, you do. Doyou need VFP transactions or SQL Server transactions? Youneed both of them! Hector Correa shows why.

GENERALLY speaking, a transaction is a programunit that accesses a database. During execution,the transaction can retrieve and possibly update

data. A database management system, like VFP, has theresponsibility of executing a transaction so that it’s bothatomic and correct. To be atomic, a transaction must eitherexecute to completion or not execute at all.

The use of transactions allows you to transform yourdatabase from one consistent state to another. A databaseis in a consistent state if it satisfies all of its integrityconstraints. During a transaction, the database might beinconsistent. However, if the integrity constraints aren’tsatisfied at the end of a transaction, VFP must abort thetransaction and leave the database in a state as if thetransaction had not been executed.

Typical examples of transactions include banktransfers (withdraw $500 from checking, deposit $500to savings) and invoicing systems (add five items to aninvoice, remove five items from inventory).

The advantages of using transactions areuncountable, but the basic idea is that transactionsmake your life much easier because you now have apowerful, VFP built-in mechanism to help keep yourdatabase in a consistent state.

Now, let’s talk about a typical scenario in real-worldapplications. What happens when your VFP applicationuses another database management system, like SQLServer, to store its data? SQL Server supports transactionsas well. Do you need SQL Server transactions? Theanswer is yes. Should you use SQL Server transactionsinstead of VFP transactions? No, you need to use both ofthem. Let’s see why.

Testing scenarioIn this article, I’ll use VFP remote views to access andupdate a SQL Server database. For the sake of simplicity,

I’ll use two imaginary VFP remote views (rv_MyViewand rv_MyOtherView) to access two imaginary tables(MyTable and MyOtherTable) in SQL Server. Figure 1shows how this scenario is organized.

I tested all of the samples shown in this article withSQL Server and MSDE. However, if you plan to useanother DBMS as your back end, you might need toperform a few adjustments to it.

Included in this month’s Source Code files (found atwww.pinnaclepublishing.com/ft), you’ll find moreexamples that use remote views to access and update theNorthwind database that comes with SQL Server 7.

The world without transactionsFor the most part, transactions are a mechanism to ensurecorrectness in a database. In a perfect world, youwouldn’t need transactions. The following code updatesdata in the back end and doesn’t use transactions:

* Update data in VFP cursors.replace balance with balance - 100 in rv_MyViewreplace balance with balance + 100 in rv_MyOtherView

* Update changes to MyTable.lEverythingOK = tableupdate( 2, .F., 'rv_MyView')if lEverythingOK * Update changes to MyOtherTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyOtherView')endif

Now, because we don’t live in a perfect world, let’ssee what will happen if the update to rv_MyViewsucceeds, but the update to rv_MyOtherView fails.

The first TableUpdate() will instruct VFP to issue anINSERT/UPDATE command to SQL Server (for example,

6.06.0

Figure 1. My sample scenario.

Page 7: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 7FoxTalk September 2000

UPDATE myTable SET balance = 400 WHERE myTablePK= 'abc'). Once that command has been submitted to theback end, the back end processes it and tells VFP thecommand was successfully executed. The secondTableUpdate() failed—one reasoning for this is that theback end couldn’t process requested commands becausethe record that I’m trying to update has been deleted byanother user.

How should I handle this? I’d already updatedMyTable and I don’t want to keep that change if theMyOtherTable doesn’t get updated. Of course, I couldwrite code to save original state of MyTable and thenrestore it if something goes wrong. But hey, that’s why wehave transaction capabilities in any decent databasemanagement system. Let’s use them!

Transaction commandsBoth VFP and SQL Server provide similar capabilities fortransaction management. Table 1 summarizes theequivalent commands.

Table 1. The VFP and SQL Server transaction commands.

VFP command/function SQL Server equivalent (T-SQL)Begin Transaction Begin Transaction

Set Implicit_Transactions OnEnd Transaction CommitRollback RollbackTnxLevel() @@TranCount

Although both DBMS provide similar capabilitiesfor transaction management, there are some subtledifferences of which you need to be aware.

The first difference is that VFP provides an EndTransaction command, while SQL Server provides aCommit command.

Another discrepancy is found with the Rollbackcommand when using nested transactions. In VFP,Rollback reverts changes made to the current transactiononly (that is, you need one command for each nestedtransaction). Whereas in SQL Server, Rollback revertschanges in all nested transactions up to the outertransaction (so, you need just one Rollback commandregardless of how many nested transactions youmight have).

There’s also a difference in the number of nestedtransactions allowed. VFP allows nested transactions upto five levels deep, while SQL Server doesn’t have a levellimit of the “deepness” of nested transactions.

Using VFP transactionsThe use of VFP transactions when working with remoteviews is straightforward. The following code shows youhow to do it:

* Update data in VFP cursors.

replace balance with balance - 100 in rv_MyViewreplace balance with balance + 100 in rv_MyOtherView* Start a VFP transaction.begin transaction * Update changes to MyTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyView' ) if lEverythingOK * Update changes to MyOtherTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyOtherView') endif* End VFP transaction.if lEverythingOK end transactionelse rollbackendif

When I issue a VFP begin transaction command, I’masking VFP to start logging every change to VFP cursors.At the end, I’ll accept all of those changes by issuing aVFP end transaction command, or I’ll reject them all byissuing a VFP rollback command.

Let’s say that I have the same scenario here as in myprevious example: update to MyTable succeeds, but theupdate to MyOtherTable fails. When this happens,lEverythingOK will be false, and thus, I’ll rollback mychanges to revert everything to its original state. This is alltrue, but let’s take a look at what I mean by original state.

My two views had some changes pending on thembefore I issued VFP begin transaction. When I issuedbegin transaction, I asked VFP to keep an eye oneverything that happens. Then, when I updatedrv_MyView, VFP sent those changes to MyTable in theback end and marked rv_MyView as updated. Next, mycall to update rv_MyOtherView failed because the backend couldn’t process changes to MyOtherTable. At theend, I rolled back the transaction, and thus, VFP markedrv_MyView as not updated (that is, its original state.)

Now, let’s take a closer look at what TableUpdate()does when updating a remote view. TableUpdate() sendsthe appropriate SQL statement (INSERT, UPDATE orDELETE) to the back end and, upon success, marks theVFP cursor as updated. That’s right, TableUpdate()actually sends the update to the back end.

You might be wondering what happened to thechanges to rv_MyView. We know for sure that they weresent to the back end and that the back end accepted them.Were those changes reverted when I rolled back the VFPtransaction? No, they weren’t! VFP marked rv_MyView asnot updated, but it never told the back end that it needs toforget about the changes in MyTable.

In other words, with the use of VFP transactions, VFPsaves and restores original state in VFP cursors, but not inback-end tables!

Using SQL Server transactionsAs you can see, when you’re using another databasemanagement system, like SQL Server, you need amechanism to handle transactions in the back end, inaddition to the transaction capabilities that VFP provides.

Page 8: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

8 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

Fortunately, most DBMSs build in this capability.When you want to start a transaction on the back end,

all you have to do is send a command to the server to dothis job. There are basically two ways to accomplish this,and both of them involve SQL pass-through functions.

The first method to start a transaction on the server isto use VFP SQLSetProp() functions. The following codeshows how:

* Start a transaction on the server.nOldTransMode = dbgetprop( 'MyConnection', ; 'Connection', 'Transactions' )SQLSetProp( nConnection, 'Transactions', DB_TRANSMANUAL ) * Update changes to MyTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyView' ) if lEverythingOK * Update changes to MyOtherTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyOtherView' ) endif* End the transaction on the server.If lEverythingOK SQLCommit( nConnection )else SQLRollback( nConnection )Endif* Restore original transaction mode.SQLSetProp( nConnection, 'Transactions', nOldTransMode )

Although it isn’t very intuitive, the call to theSQLSetProp() function is the one that actually starts atransaction on SQL Server. Setting the ‘Transactions’property to DB_TRANSMANUAL indicates to the SQLServer that transactions will be handled manually. That is,the programmer will issue an explicit rollback or committo indicate the end of a transaction. When you issue thiscall, VFP sends a SET IMPLICIT_TRANSACTIONS ONcommand to SQL Server. This command is sort of asmarter version of BEGIN TRANSACTION that delaysthe beginning of the transaction until it’s really required(for example, when an UPDATE command is detected).

There’s another non-intuitive call that you mustdo when using VFP built-in functions to handletransactions on the server. You should ensure that youset the transactions mode back to its original state (mostlikely DB_MANUAL) once you’re finished with yourtransaction. Neglecting this step could producemajor problems in your applications (for example,locking issues).

Instead of using those non-intuitive calls andguessing what VFP is asking the server to do, I prefersending the transaction commands to the back endmyself. The following code shows this approach:

* Start a transaction on the server.SQLExec( nConnection, 'BEGIN TRANSACTION' ) * Update changes to MyTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyView' ) if lEverythingOK * Update changes to MyOtherTable. lEverythingOK = tableupdate( 2, .F., 'rv_MyOtherView' ) endif* End the transaction on the server.If lEverythingOK SQLExec( nConnection, 'IF @@TRANCOUNT > 0 COMMIT' )else SQLExec( nConnection, 'IF @@TRANCOUNT > 0 ROLLBACK' )Endif

The preceding code goes down to the metal andexplicitly issues T-SQL commands to start and end thetransaction on the back end. I’ve found this approacheasier to read than using VFP built-in functions.

Now, let’s see what happens with this code if rv_Viewis successfully updated and rv_MyOtherView fails. Myfirst call to SQLExec() starts a transaction in the back end.That is, I’m now asking the back end to keep track of allthat happens to the back-end tables. Then, TableUpdate()sends changes to rv_MyView to the back end and marksthe cursor as updated. Next, my second update failsbecause the back end couldn’t process changes toMyOtherTable. At the end, I ask the back end to roll backany changes performed to the back-end tables.

The best of both worldsThe following example shows how my code to processtransactions looks when using both VFP and SQLServer transactions:

* Start a VFP transaction and* a transaction on the server.begin transactionSQLExec( nConnection, 'BEGIN TRANSACTION' )* Update changes to MyTable.lEverythingOK = tableupdate( 2, .F., 'rv_MyView' )if lEverythingOK

Continues on page 12

Page 9: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 9FoxTalk September 2000

COM-monsense DesignPaul Maskens and Andy Kramek

This month, Paul Maskens and Andy Kramek discuss theissues that arise when designing a class that must be capableof being instantiated directly in Visual FoxPro and also as aCOM object.

Andy: I need some help with a rather tricky designproblem that I have right now. Here’s the situation: I needto design a class that can be used within a Visual FoxProapplication to consolidate data that comes from multipledatabases. This data can be manipulated in theapplication and can then be output in a variety of formats(for example, either as Visual FoxPro data, XML or as anADO RecordSet). The final complication is that the sameclass needs to be capable of being instantiated as a COMobject, too. Figure 1 shows a high-level diagram of thedesign requirement for our “Data Controller Class.”

Paul: The “Back Office System” that you have there can, Iassume, be anything at all?

Andy: Exactly. It might be a Visual FoxPro applicationlike “AccountMate,” or a SQL-based system like“Oracle Financials.” As far as this class is concerned,it’s simply a data source. The Extension data is going tobe a local Visual FoxPro database (and so is the“Configuration” data).

Paul: I see. So the “Core Functionality” is to combine datafrom the back office system with data that’s held locally inthe “Extension Data.” I presume, from the name, that theextension data provides additional information forspecific functionality that’s either not present in the back-office system or isn’t standardized between differentsystems. The Visual FoxPro front end is then going to beused to manipulate this data and export the combinedresults through the Input/Output manager. And becauseyou’ve named this an Input/Output manager, I’d guessthat there’s going to be data coming the other way, too,or is this just an export process?

Andy: Oh, yes, data will be coming the other way, too.We could be receiving data in as many different formatsas we need to export it. The only limitation is that wewon’t be attempting to update back office data directly—the only thing that we need to import updates for willbe the extension data. An additional wrinkle is that thisdata needs to be configurable, and so the Configurationdatabase is going to be used to keep track of whatfields are being used and how they’re mapped to theinterface tables.

Paul: But if this is all a Visual FoxPro application, I can’tsee the need for instantiating the thing as a COM

The Kit Box FoxTalk

6.06.0

Figure 1. High-level design.

Page 10: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

10 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

component at all, unless you’re proposing that some otherinterface be used to manipulate the extension data. Oh, Ibet you want the ability to put this up on a corporateintranet at some point.

Andy: Exactly right. Although the initial intention is thatthis should be a Visual FoxPro application, there couldcome a point when the data would have to be madeavailable to a wider community. Because the possibility isknown to exist, we shouldn’t be closing any doors in ourdesign. Now, the public interface consists of only fourmethods as follows:

• OpenDataSet()• CloseDataSet()• FetchData()• SendData()

The functions are pretty self-explanatory I think. The“Open” and “Close” data set methods establish aconnection to a back end and (using the configurationdata) link in the extension data, while the “Fetch” and“Send” methods are used to handle the link to corefunctionality and the Input/Output functions. All of thevarious parts of the core functionality and the differentInput/Output options will be handled by separateclasses so that for a given request I can just instantiatethe relevant “functional object.” Now here’s myspecific problem.

The class uses Visual FoxPro cursors in its internaldata environment. So when we’re connected to a VisualFoxPro front end, there’s actually no need for us to re-format data for the interface (after all, Visual FoxPro canuse these cursors directly). In this situation, all I need todo is return a reference to the data. But when this class isinstantiated as a COM component, there’s no guaranteethat the interface will be able to use VFP cursors; besidesthe object should be stateless and shouldn’t be holding onto properties or data between calls to its functions, and soit must return the actual data, not just a reference to it.How can I reconcile these two requirements?

Paul: First, you have to ensure that the interface canprovide sufficient information to enable the class,however it’s instantiated, to carry out its function. Unlessyou know some magic that will tell you whether the classwas instantiated by VFP’s CREATEOBJECT() or as a COMobject using VB’s CREATEOBJECT(), you must definehow the data is to be returned. Now, I’d do that by justpassing a parameter that specifies the type of data that thecalling object is expecting. It’s then as simple as passingeither “VFP” or “VB?”.

Of course, you’ll also need to pass something thatidentifies what the request is actually for. Presumably,you’ll also need parameters to tell you what configurationdata to use and where to return the results.

Andy: We can do it all with parameters, then—thatworks for me! As you said, that means all I’ll need isthe following:

• The specific data that’s required. This defines whatfunctional object that will be instantiated.

• The configuration data set to use. This defines wherewe’ll get the data.

• The format that we’re using. This defines the specificI/O format we need.

• A backward reference to the calling object so that wecan return the results.

Paul: To me this seems an ideal place to use a Strategypattern. Incidentally, I don’t see why your Input/Outputmanager has to be a single object. In the same way thatyour data manager uses multiple connection managers (Iread your data classes article <g>), then surely you’dwant to avoid an “itdoesabsolutelyeverything” I/Omanager? I think that you’d need that even if it’s only tosimplify the maintenance. So when, six months down thetrack, the clients ask for an additional type of output,you’ll only need to update the control table.

Andy: What control table? I wasn’t actually planning ondata driving this part—although I can see why you mightwant to. My intention was to name the methods in theI/O manager after their output type. For example, theVFP method would be named “IOVFP,” and we’d have“IOVB” and “IOXML” for VB and XML output,respectively. Then, when called with the format, thecontroller simply appends that format code to “IO” andlooks for the appropriate method.

Paul: That would work, though I’d rather instantiatedifferent classes than use methods. When you need to addanother output type (and you surely will <g>), there’s noneed to change the actual code—merely add anotherclass. The only objection to that approach is that you thenhave to handle the VFP errors that arise when thespecified method (or class) doesn’t exist. Whereas if youuse the table-based approach, you’ll only need to handle aSEEK() failure. However, it’s just a personal preference.

Andy: Seems that either way will work just fine. But I takeyour point on table-driving it—I’ll do it that way. My nextquestion is where, in this I/O manager, is thefunctionality going to be?

Paul: In the IOVB class or IOVFP class is my first thought.But that’s not how your data classes work. You have thefunctionality split between the data manager, theConnection objects, and the Behavior objects.

Andy: Right, so what’s your answer?

Page 11: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 11FoxTalk September 2000

Paul: Well, I’d say that the data classes have the betterdesign, not just because it’s yours <g>. Of course, it’smore complex and takes more time and effort to get thedesign right, but it’s worth it in the long run. I can see atfirst there’s a temptation to put all of the functionalityinto the IOVB and IOVFP classes, particularly for thosewho insist on “Design At The Keyboard.” Hmm, have Ijust condemned myself as a DATKer?

I’m going to stick my neck out here and say that youshould apply your “must, should, could” rules that youkeep quoting to me.

Andy: Very well. For a start, the I/O manager must definethe public interface (used by the configuration manager).It should handle the common behaviors necessary tohandle the data and present it to the I/O formatter. Itshould create the appropriate I/O formatter instance. Itcould handle things like transferring files.

Paul: I definitely think that last one ought to be a separateclass, or in fact set of classes. Your data transfer is likely torange from a simple copy in the same computer to FTPtransfer to a remote host. They might not even be in thesame platform (Unix, Linux, NT, or even Mac). Unlikeyour data classes, though, I think you only need twolayers, not three.

Andy: Yes, there’s no requirement for a component

analogous to the connection object in the data classes. Ithink we can now draw an implementation diagram (seeFigure 2). As you can see, the Interface object determineswhat functional component to instantiate when a requestis received, based on the type of request. The functionalcomponent communicates with the data source andpasses the results back to the interface as a VFP cursor.The Interface object then hands this cursor off to the I/Omanager for formatting, and that object instantiates theappropriate formatter for the requested data type. Thanks,Paul, that’s helped clarify things for me.

Paul: No problem. Now, I have a follow-up question foryou. Earlier you said, “The object should be stateless.”That’s something that I really don’t understand wellenough. State has to be stored somewhere, after all. Forexample, in the interface we’ve been discussing, the sameparameters are passed every time and must, therefore, becoming from (and hence stored by) the client. Some ofthese values could be stored by the object, but I supposethat would mean that only one client can ever use theobject. So is the object “stateless” because the state isbeing stored by the client, or is it because the object isstateless that the client has to store the state?

Andy: I’m going to quote Rick Strahl from his bookInternet Applications with Visual FoxPro 6.0 (HentzenwerkePublishing, 1999). He defines a stateless server as one that

Figure 2. The implementation of the high-level design.

Page 12: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

12 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

“does not retain any context for the client. The serverdoes not know what action the previous requestperformed and does not make any assumptions aboutthe current environment.”

So I guess that this is a stateless object because itdoesn’t store anything about the client, and doesn’t makeany assumptions about the current environment. Anyclient that wants to interact with this object must,therefore, maintain its own state information.

Paul: Well, that’s what Microsoft (elsewhere) refers to as“client-managed state,” and it looks as though, for once,we can agree with their use of terminology.

Andy: That’s a novelty <g>. To my way of thinking,the key to all of this is the concept of “persistence.” Astateless object “does it once, then throws it all away,”while a stateful object keeps information in case it’suseful next time.

Paul: Ah, but as you’ve described it, your object keeps theconfiguration data internally. Now isn’t that “state”information in some sense?

Andy: Only in the context of the object itself. Theconfiguration data has nothing to do with any individualclient. It’s only used to support the internal operations of

our object. Therefore, it’s created when the object is firstinstantiated and shares the lifetime of the object.

Paul: If the object doesn’t change that data, and in itsoperation doesn’t rely on index orders, record pointervalues, and other states that it gets into while processing arequest from a client—I can see your point.

Andy: It definitely doesn’t rely on any of those things. Itwill always look up the values it’s been passed asparameters. We’re only holding this data as anoptimization to avoid the necessity of reopening the sametables in the same workspace for each request the objectreceives. This data is, in the context of the lifetime of theobject, static.

Paul: Thanks! It’s always good when writing this columnreduces my ignorance level. ▲

Paul Maskens is a VFP specialist and FoxPro MVP, who works as manager

of international technologies for Euphony Communications Ltd. He’s

based in Oxford, England. [email protected].

Andy Kramek is a long-time FoxPro developer, FoxPro MVP, independent

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

currently working in Holland. [email protected].

Transactions . . .Continued from page 8

* Update changes to MyOtherTable. lEverythingOK = tableupdate( 2,.F.,'rv_MyOtherView')endif

* End the transaction on the server and VFP.If lEverythingOK SQLExec( nConnection, 'IF @@TRANCOUNT > 0 COMMIT' ) end transactionelse SQLExec( nConnection, 'IF @@TRANCOUNT > 0 ROLLBACK' ) rollbackEndif

As you can see in this last code example, processingtransactions in a VFP client/server application is prettystraightforward. As a golden rule you should keep inmind that you’re dealing with two (not one) databasemanagement systems, and thus your code must ensurethat both DBMSs are aware of your actions with theunderlying database. Once you’ve done that, ensuringcorrectness in your database is a piece of cake… even ifyou live in an imperfect world. ▲

09CORRSC.ZIP at www.pinnaclepublishing.com/ft

Hector J. Correa is a senior developer with Vision Data Solutions in

Independence, MO. Hector is a Microsoft Certified Professional. He’s been

working with xBase languages since 1990. [email protected].

Page 13: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 13FoxTalk September 2000

Updating an ApplicationOver the InternetDoug Hennig

Ever wanted to update your client’s applications with minimaleffort? In this month’s article, the first of two on this topic,Doug Hennig presents a set of classes that download anapplication’s files from an FTP site, automating the process ofdistributing application updates.

BASED on the e-mails I’ve received and the messagesI’ve seen in various FoxPro forums, my October1997 column, “Reusable Tools: STARTAPP: The Best

of Both Worlds,” which presented a utility I calledSTARTAPP, hit a nerve. To refresh your memory,STARTAPP is an application “loader”— rather thanrunning the application’s EXE from a server, the userruns STARTAPP.EXE (which could be renamed tosomething like MYAPP.EXE) on their workstation.STARTAPP compares the application’s files (such asEXEs and DLLs) on the workstation with those in adefined directory on a server, and copies newer filesfrom the server to the workstation. It then runs the mainprogram (APP or EXE) for your application.

There are several advantages to using STARTAPP.Most important are the abilities to run the application ona workstation rather than a server (for performance butalso portability reasons), while providing an easy way toupdate the workstations (avoiding going to each one toupdate the files manually), and an easy way to install anew version of an application without kicking all of theusers out first (which would be required if they wererunning the application off the server). I’ve usedSTARTAPP with nearly every application I’ve deliveredsince I wrote it.

Since writing STARTAPP, I’ve encountered a similar,yet different need—the ability to update an application’sfiles remotely. For clients too far away to visit on-site, Iusually ZIP the files making up a new revision of anapplication, and e-mail the ZIP file to the application’s“administrator” at the client site. However, there areseveral common problems that I’ve encountered:

• File size: Even zipped, the files making up anapplication can be several megabytes. Some clientshave their e-mail systems configured to rejectmessages larger than a certain size.

• Too many complicated steps: The user has to save theZIP file attachment to a directory, open Windows

Explorer, navigate to that directory, find the savedfile, double-click on it to run an unzipping programlike WinZip (assuming they have such a program),then extract the files. Believe it or not, in this day andage, there are still many people who have no ideahow to do any of these steps. And if the user makesone mistake in this process, the update won’t beproperly installed.

• Too busy, too lazy, or too scared: You send the update, goover all of the installation steps with the user, and aweek later, they report bugs you know you’ve fixed.When you have them check the file size, date, orversion number, it turns out they never got around toinstalling the update.

A different approach is to burn a CD and ship it to theclient. Except for the media (CD vs. floppy), this is howwe’ve done it since the 1980’s, and it has all of the frailtiesyou’d expect: The CD wasn’t burned properly or isincompatible with the client’s drive, the CD was damagedin shipping, the CD took forever to get there, the CDnever arrived…

A better way to install an updated application wouldbe to have an Internet version of STARTAPP. That is, anapplication loader would automatically check an FTP siteto see whether newer versions of the application’s files areavailable, download them if so, and then run the mainprogram for the application. The beauty of this approachis that when you want to ship a new version of anapplication, you simply install some files on an FTPserver. The next time your client runs the application,they’ll get the new version automatically.

That’s what this and next month’s articles are about.This month, I’ll cover some classes that handle the processof copying files from a server (LAN/WAN or FTP); nextmonth, I’ll cover several strategies for driving theseclasses and updating your application’s files. Beforedigging into this month’s code, I’d like to thank Willie vanSchalkwyk for suggesting this idea and helping flesh outthe basic concepts.

SFUpdateSFUpdate, in SFUPDATE.VCX, is based on the VFPCustom base class rather than SFCustom in

6.06.0

Reusable Tools FoxTalk

Page 14: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

14 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

SFCTRLS.VCX (as would normally be the case) to avoidproblems with several instances of SFCTRLS floatingaround (one in the loader program and another in themain program). SFUpdate is an abstract class; I’ll show acouple of subclasses that implement updates from a LANor WAN server and from an FTP site. Table 1 shows thecustom properties of SFUpdate.

UpdateLocal is the main method in SFUpdate. Itchecks each file in the aFiles property and if necessary,copies it to the workstation. It’s mostly a templatemethod; that is, it directs other methods about how thisprocess should work. Let’s look at it in logical sections.First, if the cScriptFile property contains the name of aprocedure file (this property should include the path andan FXP extension), that file is opened with SETPROCEDURE TO. This allows you to “script” certainparts of the update process; in other words, withouthaving to create a subclass, you can tell an instance ofSFUpdate how to behave at certain stages by simplyproviding a procedure file with the appropriate code.This can be used for client-specific code, for example.You could even generate the PRG and compile it on thefly if desired:

local lcScriptFile, ; llScriptFile, ; llReturn, ; lnFiles, ; lnI, ; lcFile, ; llOKwith This if not empty(.cScriptFile) and file(.cScriptFile) if '\' $ .cScriptFile lcScriptFile = upper(.cScriptFile) else lcScriptFile = '\' + upper(.cScriptFile) endif '\' $ .cScriptFile lcScriptFile = forceext(lcScriptFile, '.') if not lcScriptFile $ upper(set('PROCEDURE')) llScriptFile = .T. set procedure to (.cScriptFile) additive endif not lcScriptFile $ upper(set('PROCEDURE')) endif not empty(.cScriptFile) ...

Next, the BeforeUpdate method is called.BeforeUpdate, AfterUpdate, BeforeCopyFile, andAfterCopyFile are hook methods that allow you toperform additional processing in the script file or in asubclass of SFUpdate (you’ll see how this might be usedin the “SFInternetUpdate” section later in the article). InSFUpdate, these methods run the function of the samename in the script file if one is being used, and return .T.if everything went okay. The Before methods also allowyou to terminate the process under some conditions(such as, if an error occurs); SFUpdate won’t continueprocessing if the BeforeUpdate method returns .F., and itwill stop if the BeforeCopyFile method returns .F. andthe lTerminateOnError property is .T.

If BeforeUpdate returns .T., GetServerFileInfo is calledto put information about the files in the server directory(specified in cServerDirectory) into the aServerFiles arrayproperty. If that method (which is empty in this class)returns .T., each file in the aFiles array is processed in aloop. If the lQuiet property is .F., which means you shoulddisplay status messages, the DisplayStatus method iscalled to display information about the process.

llReturn = .BeforeUpdate() if llReturn llReturn = .GetServerFileInfo() if llReturn lnFiles = alen(.aFiles) for lnI = 1 to lnFiles lcFile = .aFiles[lnI] if not empty(lcFile) if not .lQuiet .DisplayStatus('Checking server and ' + ; 'workstation versions of ' + lcFile + ; '...') endif not .lQuiet

Next, CheckVersion is called to determine whetheryou’re supposed to copy the file from the server to theworkstation; I’ll discuss that method later. If it returns.F., meaning the workstation file isn’t current, and theserver copy of the file exists (in which case, the

Table 1. The custom properties of SFUpdate.

Property DescriptionaFiles An array of files to update from the server.aServerFiles An array of information about files on the server (Protected).cErrorMessage The error message if something went wrong.cPassword The password to access the server.cScriptFile The name of an optional VFP program that “scripts” the behavior of various stages of the updating process.cServerDirectory The directory on the server where the files can be found.cServerName The name of the server.cTitle The caption for any status window.cUserName The user name to access the server.lCheckVersion .T. (the default) to compare the workstation and server versions of a file;

.F. to force the files to be copied from the server.lPrompted .T. if the user has been prompted for the download (Protected).lPromptForDownload .T. (the default is .F.) to prompt the user if they want to proceed with the file transfer.lQuiet .T. (the default is .F.) to suppress status display.lTerminateOnError .T. (the default is .F.) to stop the update process if an error occurs.nCurrentServerFile An index into aServerFiles for the current server file (Protected).

Page 15: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 15FoxTalk September 2000

nCurrentServerFile property will contain the index to thefile in the aServerFiles array), then you copy the file.DisplayStatus is called to update the statusdisplay—if you’re supposed to prompt the user(lPromptForDownload is .T.) and you haven’t alreadydone so (lPrompted is .F.), the PromptForDownloadmethod is called. If that method returns .F., the userdecided not to update to the latest version, so terminatethe process. Otherwise, the BeforeCopyFile hook methodis called (which allows you to cancel the process for anyfile-specific reason or perform any other pre-copyprocessing), and then the CopyFile method (which isempty in this class) is used to perform the actual file copy.

if not .CheckVersion(lcFile) and ; .nCurrentServerFile <> 0 if not .lQuiet .DisplayStatus('Updating workstation ' + ; 'version of ' + lcFile + '...') endif not .lQuiet if .lPromptForDownload and not .lPrompted .lPrompted = .T. llReturn = .PromptForDownload() if not llReturn exit endif not llReturn endif .lPromptForDownload ... llOK = .BeforeCopyFile(lcFile) and ; .CopyFile(lcFile) llReturn = llReturn and llOK do case

If the file was successfully copied, the AfterCopyFilehook method is called. This method (or the AfterCopyFileprocedure in a script file that it calls) could, for example,unzip a ZIP file that was copied or update a statusdisplay such as a progress thermometer. If the file copyfailed and the lTerminateOnError property is .T., theprocess is terminated.

case llOK .AfterCopyFile(lcFile) case .lTerminateOnError exit endcase endif not .CheckVersion(lcFile) ... endif not empty(lcFile) next lnI

After all of the files have been processed successfully,the AfterUpdate hook method is called, and if lQuiet is .F.,ClearStatus is used to clear any status display. Finally, if ascript file was opened at the beginning of UpdateLocal,it’s closed with RELEASE PROCEDURE.

if llReturn .AfterUpdate() endif llReturn endif llReturn endif llReturn if not .lQuiet .ClearStatus() endif not .lQuiet if llScriptFile release procedure (.cScriptFile) endif llScriptFileendwithreturn llReturn

The CheckVersion method is called by UpdateLocal to

determine whether we should copy the file from theserver to the workstation. If lCheckVersion is .T. (set it to.F. to force all workstation files to be updated, regardlessof whether they need it or not), it calls GetLocalFileInfo toget information about the local copy of the file, andcompares the file sizes (which must be the same) and lastmodified DateTimes (the workstation version must be thesame or newer than the server, because installing a file onthe workstation sets the file to the current DateTime). Ifthe files are the same, it returns .T., indicating not to copythe file from the server. This method also sets thenCurrentServerFile property to the index for the currentfile into the aServerFiles array so other methods canretrieve information about the file from the array.

lparameters tcFilelocal laLocal[1], ; lnLocalFiles, ; llLocalFileExists, ; lnServerFile, ; llServerFileExists, ; llReturnwith This

* Get the version info for the server and workstation* copies of the file (the server file information is in* This.aServerFiles).

lnServerFile = ascan(.aServerFiles, ; upper(tcFile)) llServerFileExists = lnServerFile > 0 .nCurrentServerFile = iif(llServerFileExists, ; asubscript(.aServerFiles, lnServerFile, 1), 0) if .lCheckVersion lnLocalFiles = .GetLocalFileInfo(tcFile, ; @laLocal) llLocalFileExists = lnLocalFiles > 0 endif .lCheckVersion

* Return .T. if the file exists on both the server and* workstation, they are the same size, and the* workstation version is the same or a later DateTime* than the server version.

llReturn = llServerFileExists and ; llLocalFileExists and ; .aServerFiles[.nCurrentServerFile, 2] = ; laLocal[1, 2] and ; .aServerFiles[.nCurrentServerFile, 3] <= ; laLocal[1, 3]endwithreturn llReturn

DisplayStatus and ClearStatus are abstract methodsthat, in a subclass, can show status information about theprocess. DisplayStatus is called by UpdateLocal at variousplaces in the processing, and is passed a message aboutwhat process is about to occur. ClearStatus is called at theend of UpdateLocal to clear any message displayed byDisplayStatus.

GetLocalFileInfo is called by CheckVersion. Itspurpose is to fill the passed array with information aboutthe specified file. It uses ADIR() to get the file’s name,size, date, and time, and calls GetDateTime to convert thedate and time into a DateTime value.

lparameters tcFile, ; taFileslocal laFiles[1], ;

Page 16: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

16 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

lnFiles, ; lnIlnFiles = adir(laFiles, tcFile)if lnFiles > 0 dimension taFiles[lnFiles, 3] for lnI = 1 to lnFiles taFiles[lnI,1] = laFiles[lnI, 1] taFiles[lnI,2] = laFiles[lnI, 2] taFiles[lnI,3] = This.GetDateTime(laFiles[lnI,3],; laFiles[lnI, 4]) next lnIendif lnFiles > 0return lnFiles

The final method, PromptForDownload, simply asksthe user whether they want to download the updated filesfor the application, and returns .T. if they choose Yes.

That’s it for SFUpdate. As I mentioned, it’s an abstractclass, so I created a couple of subclasses. SFLANUpdate(which I won’t cover here) is for updating a workstationfrom a LAN or WAN server (similar to the purpose ofSTARTAPP), and SFInternetUpdate is for updating froman FTP site over the Internet.

SFInternetUpdateSFInternetUpdate only overrides a few methods ofSFUpdate, but that’s because it collaborates with anotherclass to do the actual work of downloading informationand files from an FTP site. I originally intended to use theMicrosoft Internet Transfer ActiveX control; however,after fussing with it for a while, I decided it wasn’t worththe effort, given that an inexpensive third-party utility Iwas already using was much easier to implement.wwIPStuff, from West Wind Technologies (www.west-wind.com), is a shareware ($99 to register) set of classesthat provide a wide variety of Internet utilities. Idiscussed the e-mail services in my March 2000 article(“Spam, Wonderful Spam”); this month, I’ll address theFTP services.

Before looking at SFInternetUpdate, let’s take a peekat a subclass I created of the wwFTP class that comes withwwIPStuff. SFFTP, in SFIPSTUFF.VCX, overrides a fewmethods. aFTPDir, which is the FTP equivalent of ADIR(),returns 0 rather than an error code if the method failed.This makes it consistent with the success return value,which is the number of files matching the desired filespecification. FTPGetFileEx, which retrieves a file from anFTP site, accepts both source and target filenames. InSFFTP, if the target name isn’t specified, it defaults to thesame name as the source file.

Also, although wwFTP has a method (UnzipFiles) tounzip a ZIP file (using DynaZip from Inner Media,www.innermedia.com) and a property, lUseZip, thatindicates whether that method should be used, it doesn’tactually call UnzipFiles after downloading a file. InSFFTP, FTPFileGetEx calls UnzipFiles if the file wassuccessfully downloaded, has a ZIP extension, andlUseZip is .T., then erases the ZIP file if it was successfullyunzipped. Finally, OnFTPBufferUpdate, which is calledperiodically as a file is uploaded or downloaded, displays

a WAIT WINDOW NOWAIT message showing thenumber of bytes transferred so far, and a percentage of thetotal. This method uses two new properties, lQuiet (.T. tosuppress the status display) and nFileSize (the size of thefile being transferred).

Back to SFInternetUpdate. BeforeUpdate, which iscalled before the update process begins, instantiates anobject from the class specified in the cFTPClass andcFTPLibrary properties (which contain “SFFTP” and“SFIPSTUFF.VCX” by default) into the oFTP property, setsseveral properties for the object, and tries to connect tothe FTP server. It returns .T. if it succeeded or .F. if not; thecErrorMessage property contains information about whatwent wrong if it failed.

local llReturn, ; lnResultwith This if vartype(.oFTP) = 'O' llReturn = dodefault() else if not '\WWUTILS.FXP' $ upper(set('PROCEDURE')) set procedure to wwUtils additive endif not '\WWUTILS.FXP' $ upper(set('PROCEDURE')) .oFTP = newobject(.cFTPClass, .cFTPLibrary) .oFTP.cUserName = .cUserName .oFTP.cPassword = .cPassword .oFTP.cTitle = .cTitle lnResult = .oFTP.FTPConnect(.cServerName) llReturn = lnResult = 0 if llReturn llReturn = dodefault() else .cErrorMessage = .cErrorMessage + ; iif(empty(.cErrorMessage), '', chr(13)) + ; 'BeforeUpdate: ' + iif(empty(.oFTP.cErrorMsg), ; 'Cannot connect to Internet update site', ; .oFTP.cErrorMsg) endif llReturn endif vartype(.oFTP) = 'O'endwithreturn llReturn

GetServerFileInfo uses the aFTPDir method of theoFTP object to fill the aServerFiles property with the fileson the FTP site and returns .T. if it succeeded.

local laFiles[1], ; lnFiles, ; llReturn, ; lnIwith This if empty(.aServerFiles[1, 1]) lnFiles = .oFTP.aFTPDir(@laFiles, ; .cServerDirectory + '*.*') llReturn = lnFiles > 0 if llReturn dimension .aServerFiles[lnFiles, 3] for lnI = 1 to lnFiles .aServerFiles[lnI, 1] = upper(laFiles[lnI, 1]) .aServerFiles[lnI, 2] = laFiles[lnI, 2] .aServerFiles[lnI, 3] = laFiles[lnI, 4] next lnI else .cErrorMessage = .cErrorMessage + ; iif(empty(.cErrorMessage), '', chr(13)) + ; 'GetServerFileInfo: ' + .oFTP.cErrorMsg endif llReturn else llReturn = .T. endif empty(.aServerFiles[1, 1])endwithreturn llReturn

Page 17: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 17FoxTalk September 2000

CopyFile first sets the nFileSize property of the oFTPobject to the size of the file to be downloaded (which itgets from aServerFiles), erases the workstation copy of thefile (because wwIPStuff gives an error if the file alreadyexists), and then uses the FTPGetFileEx method of theoFTP object to download the file.

lparameters tcFilelocal lnResult, ; llReturnwith This .oFTP.lQuiet = .lQuiet .oFTP.nFileSize = .aServerFiles[.nCurrentServerFile, 2] erase (tcFile) lnResult = .oFTP.FTPGetFileEx(.cServerDirectory + ; justfname(tcFile)) llReturn = lnResult = 0 do case case not llReturn .cErrorMessage = .cErrorMessage + ; iif(empty(.cErrorMessage), '', chr(13)) + ; 'CopyFile: ' + .oFTP.cErrorMsg case .oFTP.lCancelDownload llReturn = .F. endcaseendwithreturn llReturn

Trying it outTESTUPDATE.PRG demonstrates how theSFInternetUpdate class works. It downloads theDIRMAP.TXT file from Microsoft’s FTP site; running it asecond time won’t retrieve the file again because italready exists on the workstation and hasn’t beenupdated on the server. Notice that some properties, suchas cServerDirectory, cUser, and cPassword, aren’t setbecause they aren’t needed in this case. However, they

might be required for your FTP site. Before running thisprogram, copy the following wwIPStuff files to thedirectory where TESTUPDATE.PRG is located:WWIPSTUFF.VCX, WWIPSTUFF.VCT, WUTILS.PRG,and WCONNECT.H.

ConclusionSo far, you have the basics for an Internet update process.Using code similar to that in TESTUPDATE.PRG, youcould implement an application update utility right now.However, next month, I’ll show at a few strategies fordriving SFInternetUpdate and downloading anapplication’s files. One will involve using a loaderprogram similar to STARTAPP, while another willimplement the majority of the functionality in the mainprogram itself, delegating only the final step to a separateEXE (because we can’t overwrite the running EXE). ▲

09DHENSC.ZIP at www.pinnaclepublishing.com/ft

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

Saskatchewan, Canada. He’s the author and/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 1997, 1998, and 1999

Microsoft FoxPro Developers Conferences (DevCon), as well as at user

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

Most Valuable Professional (MVP) and Microsoft Certified Professional

(MCP). [email protected].

contains the necessary information. All that’s left is towrite a report to print them out. Or you might automateExcel to build a spreadsheet, or automate Word togenerate a fancy document. Or even automate Outlook tosend e-mail to everyone in your list, or automate Explorerto visit their websites. Once you’ve got the information,the possibilities are endless! ▲

09MARTSC.ZIP at www.pinnaclepublishing.com/ft

Della Martin is a Senior Research Associate for the University of

Tennessee, where she specializes in application modules that transform

VFP data into awesome PowerPoint presentations, Excel spreadsheets

and charts, and Word documents. She’s the co-author of Microsoft Office

Automation with Visual FoxPro (Hentzenwerke Publishing, 2000).

[email protected].

Outlook Contacts. . .Continued from page 4

XML • Web D evelopmen t • SQL Server • Visual B asic • MS Access • O racle • Visual C++ •Delphi • FoxPro • XML • Web Development • SQL S erver • Visual Basic • MS A ccess • Oracle

Sign up now for Pinnacle’s FREE eNewsletters!Get tips, tutorials, and news from gurus in the field

delivered straight to your Inbox.

http://www.FREEeNewsletters.com

XML • Web D evelopmen t • SQL Server • Visual B asic • MS Access • O racle • Visual C++ •Delphi • FoxPro • XML • Web Development • SQL S erver • Visual Basic • MS A ccess • Oracle

Page 18: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

18 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

FoxTalk

Data Warehouses, OLAP, and YouLeslie Koorhan

In this final installment of a series, Leslie Koorhan explains thetwin pillars of Microsoft SQL Server OLAP Services’ tools forclient access, MDX and ADOMD. MDX is the SQL-like languagefor querying OLAP cubes, and ADOMD is the object modelexposed for accessing those cubes. Together, they provide arelatively simple way to incorporate cube access from anyprogram that can access objects.

IN the first article of this series, I explained what a datawarehouse is, as well as the various terms used in thisbranch of data storage and access. Then I built a data

mart from the sample TasTrade Visual FoxPro database,transferred that database into a Microsoft SQL Server 7.0database using Data Transformation Services (DTS), andbuilt a cube using the Microsoft SQL Server OLAPServices administrative tool, OLAP Manager. In the mostrecent article, I demonstrated the server-side object modelof OLAP Services, Decision Support Objects (DSO).Finally, this month, I get to explain what I started out todo in the first place: getting the data out of OLAP cubesfor the users.

Don’t get me wrong; I’m not explaining a userinterface. Rather, I’m demonstrating the methods used forthe programs that will get the data and then present it inwhatever format is desired. This is much easier than mostpresentations of data, because cube data is read-only.So it can be very easy to choose a display tool. Onemethod that I‘ll show is an add-in called the MicrosoftFlex Grid.

There will be one difference in this article that willseparate it from the rest. In the sample code, I’ll be usingthe sample OLAP cube that ships with OLAP Services,rather than the sample cube that was built several monthsago in these pages. That way, anyone can use the samplecode with only slight modifications, if OLAP Servicesis available.

There are two tools that OLAP Services supplies forclient-side access, MDX (Multi-Dimensional Expressions)and ADOMD (ActiveX Data Objects—Multidimensional).MDX appears at first glance to be standard SQL, butit’s actually a totally separate language. The second toolis simply ADO, with just a few extra objects forhandling cubes.

MDXAs I mentioned, MDX at first glance seems to be just like aSQL SELECT command because it has SELECT, FROM,

and WHERE clauses. But with a closer look, it’s obviousthat there’s something much different going on. BetweenSELECT and FROM are very strange things, not at all theexpressions that are expected in a normal SQL query.Instead, a typical statement looks like this:

SELECT [Store].[USA].Children ON COLUMNS, Product.[ProductFamily].Members ON ROWSFROM Sales

And there are other items, just as strange, in theWHERE clause, resulting in an MDX statement that lookslike this:

SELECT {[Education Level].[Bachelors Degree]} ON COLUMNS, {[Marital Status].[M], [Marital Status].[S]} ON ROWS, {[Product].[Food].[Frozen Foods].Children} ON PAGESFROM SalesWHERE ([Gender].[F], [Promotion Media].[Radio], Measures.[Store Sales])

In order to understand how MDX works, I’ll breakdown the clauses into their defined roles. The SELECTclause is used to define the axes of the result set. TheFROM clause lists the name of the cube that’s beingqueried. And the WHERE clause contains what’s calledthe slicer dimensions and/or alternate measures. In thepreceding example, the WHERE clause contains bothslicer dimensions and an alternate measure.

AxesSince cubes are multidimensional, the results from a cubecan also be multidimensional. The axes define the numberof dimensions as well as the members that will appear onthose axes. Each axis represents one dimension that thewriter visualizes when creating the MDX query. Each axishas an index number identifying its position within thecellset. Axes are numbered starting from zero, just like thedefault in arrays in Visual Basic. The first three axes canbe thought of, respectively, as the x-axis, y-axis, and z-axisfrom geometry and 3-D graphs. And the first five axes(0-4) have aliases that can be used: COLUMNS, ROWS,PAGES, SECTIONS, and CHAPTERS.

In a MDX statement, axes should always be listed inascending numeric order, for ease of reading andmaintenance. The syntax of an axis specification is the setdesired on that axis followed by ON and then the axisitself, using either Axis(n) or an alias for that axis. Veryoften, MDX queries only specify two axes, COLUMNS

6.06.0

Page 19: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 19FoxTalk September 2000

and ROWS, but put multiple dimensions on one or bothaxes. This is known as a flattened data set. No axis needs tobe specified here at all. In that case, the slicer dimensions(see the next section) will be used to filter the result andone item will appear in the results, unless there’s no dataat all.

If there’s to be more than one member on an axis,braces (‘{}’) must be used to enclose all of the members ofthat set. If there are multiple words in a dimension orlevel name, or numbers or reserved words, then thatmember name should be enclosed in brackets (‘[ ]’).Sometimes it just makes good sense to use brackets all ofthe time just to avoid any complications. If a dimension orlevel name is unique within the cube, then it does need tobe qualified; otherwise, the complete hierarchy must bespecified with dots in between each level name.

In the first of the previous examples, there are twoaxes. The COLUMNS axis contains all of the store statesfrom the USA, and the ROWS axis contains the top-mostproduct categories. In the second example, there are threeaxes, with COLUMNS having one position, for Bachelor’sDegrees, ROWS having two positions, for Married andSingle, and PAGES (the z-axis) having a position for eachtype of frozen food.

Slicer dimensions/alternate measuresThe WHERE clause contains filters for the resulting set ofdata, as well as the option to display alternate measures.This clause acts very much the same as the WHERE clausein a normal SQL statement. The difference is that data isfiltered along dimensions, causing the result data to besliced. Only the data from the slicer dimensions will endup in the result.

In the second code example previously shown, thereare two slicer dimensions, female gender and radiopromotions. Only the values from those two dimensionswill be included in the result set.

The WHERE clause can also specify an alternatemeasure. Without specifying such, the result set will havethe default measure. The default measure is the firstmeasure defined in a cube. To display a different measurein the result set, that measure must be specified in theWHERE clause. It’s presented as if it were a dimension,where the name of the dimension is Measures. (Basically,Measures is a dimension to OLAP Services. When creatingdimensions for a cube, you’re allowed a maximum of 63dimensions, because Measures is the 64th.) If there arealso slicer dimensions in the WHERE clause, simplyspecify them along with the alternate measure.

The syntax here is simpler than the axis specification.If there’s more than one, then enclose all slices andalternate measures in parentheses, comma delimited.Each dimension is specified in the same way as theaxis dimensions.

In the second preceding code example, the measure is

store sales. In the first example, the measure is unit salesbecause that’s the default measure.

Defining setsAs mentioned earlier, the syntax of an axis specification isthe desired set followed by ON and the axis itself. The setis the list of members that appear on an axis. There arevarious ways of defining that set.

The first way is simply to list a comma delimited listof members, such as {[Customer].[Canada].[Toronto],[Customer].[USA].[New York], [Customer].[France].[Paris]}. This set would have three positions on an axiswith the appropriate resulting values. If there’s aninherent order to a set’s naming, then an inclusive rangecould be specified by using a colon, as in {[2000].[Q1:Q4]}.This set would have four positions, one for each quarterin the year 2000.

Another very common method is to use functions.Although there are more than 100 built-in functions inOLAP Services, I’ll only illustrate several of these. Thefirst and most used is MEMBERS. Unlike functions inVisual FoxPro and many other languages, MEMBERS isapplied after the dimension specification, as in{[Store].Members} or {[Year].Members}. The differencebetween these two examples is important. In the former,the axis would contain positions for all stores, broken bycountry, city, and name, because [Store] is the name of thedimension. In the latter, there would only be positions foreach year in the cube because [Year] is the name of a levelwithin the Time dimension.

CHILDREN is another function that works in thesame way as MEMBERS. With this function, only the setof values one level below the level specified would bereturned. Examples are {[Store].Children} and{[1999].Children}. In the former, only positions for thecountries would be used because the immediate levelbelow the Store dimension is the top level, which is theCountry level. In the latter example, if years were brokendown into quarters, then there would be four positionsspecified. In the second example, the set could havebeen specified as {[1999].[Q1],[1999].[Q2],[1999].[Q3],[1999].[Q4]} or {[1999].[Q1:Q4]}.

Another function is DESCENDANTS, which is usedin the normal way—as the function name followed byparentheses with arguments. The first argument is themember name; the second argument is the level namefrom which sets will be returned. An optional thirdargument allows specification of other levels insteadof the one specified, or in addition to, that level. Thenext example illustrates the use of this function:DESCENDANTS([Food],[Brand Name]). This wouldresult in a position for each brand name within the Foodlevel of the Product dimension. If the third argument ofBEFORE was used, then it would include a position foreach member of each level between Food and Brand

Page 20: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

20 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

Name. In this case, that would be the ProductDepartment, Category, and Subcategory levels.

As mentioned earlier, multiple dimensions can beplaced on one axis. One way of doing this is by usingtuples. A tuple is a multiple dimension point expressed asan intersection between the desired dimensions. Anexample of a tuple is ([Promotion Media].[Radio],[Promotions].[Best Savings]). Notice the comma delimitedlist of dimensions within parentheses. One tuplerepresents one position on an axis, but with multiplemembers. Given that, the following code is an example ofa set of four tuples that could be used on an axis:

{([Gender].[M],[Marital Status].[S]), ([Gender].[M],[Marital Status].[M]), ([Gender].[F],[Marital Status].[S]), ([Gender].[F],[Marital Status].[M])}

There’s an easier way to define tuples—by using theCROSSJOIN function. This function creates tuples fromtwo other sets by creating positions for every possiblecombination of the two sets. A better way to express theprevious example would be:

{CROSSJOIN([Gender].Children,[Marital Status].Children)}

PivotTable ServiceMicrosoft SQL Server OLAP Services includes server-sidecomponents and client-side components. The server sideis referred to as OLAP Server and exposes its functionalitythrough Decision Support Objects (DSO), which wasillustrated in the previous article of this series. On theclient side, OLAP Services has something calledPivotTable Service. This is the component that makesrequests of the OLAP Server to get the data. This servicealso has an OLE DB provider so that OLE DB applicationscan request data from the OLAPcubes. This provider exposes itsfunctionality through a specialActiveX Data Object interface,subclassed as Multidimensional(ADOMD). ADOMD has manyobjects for getting to the metadata aswell as the data.

One of the features of thePivotTable Service is that it providescache management on the client forcube data. This means that once datahas been transferred to a client, thatdata remains in memory, allowingfor faster response times whenrequerying. This can also aid indrilling up through data. If a userrequested data at a low level of adimension and then requests thesame data but at a higher level, thenthe PivotTable Service doesn’t need

to send the request to the OLAP Server. Instead, it willcalculate the data by rolling up the cached values. It canalso filter the data if the user requests a subset of the datathat’s already in cache. Finally, if the user wantsadditional data as well as what was already requested,then the PivotTable Service will only request data fromOLAP Server that isn’t in the local cache and combinewith the values that are there.

Another feature is that the PivotTable Service cancreate local cubes that can be stored on the clientcomputer for later analysis, and then act as the server forthat data. This means that it’s easy to create a local cube(through the MDX commands CREATE CUBE andINSERT INTO) on a laptop that’s attached to the network,and then later query that cube when the computer is nolonger online. If possible, this would be a nice feature toexplore in a future article.

The ADOMD object modelADOMD’s object model (see Figure 1) consists of aCatalog object that provides for the establishing of aconnection to a multidimensional data store, a CubeDefscollection with a reference to every cube in the database,and a Cellset object, which is the result of amultidimensional query, the MDX statement. Within theCellset object there’s the cellset metadata as well as theCells collection itself that has the data.

The Catalog object has two properties:ActiveConnection and Name. In order to connectto an OLAP server, just set the ActiveConnection propertyto a connection string. The connection string consists ofthree parts: the name of the OLE DB provider, the nameof the server, and the name of the database to use. Thethree parts are labeled “Provider,” “Data Source,” and

Figure 1. The ADOMD object model.

Page 21: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 21FoxTalk September 2000

“Initial Catalog.” The provider for OLAP Servicesis always “MSOLAP.” If you’re working on theserver itself, you can use “LocalHost” as the DataSource name. The whole string looks like this:“Provider=MSOLAP;DataSource=LocalHost;InitialCatalog=Foodmart.” Of course, you can use a differentserver name and database name.

In the sample form mdxGrid.scx (available with theSource Code files at www.pinnaclepublishing.com/ft),the connection string is specified in a text box.

Once the ActiveConnection property is set, it can thenbe used with any of the other objects, such as the Cellsetobject in order to make a connection. But rather than do itthat way, you can use the standard ADO Connectionobject. This is better because if you’re using theActiveConnection property of the Catalog object, then theconnection is temporary and has to be reestablished withevery MDX query. By using a Connection object, theconnection to the server can remain open as long asneeded so that the MDX queries are handled faster. Inthe sample form, a Connection is used rather than theCatalog object.

The CubeDef object has a Properties collection thatconsists of Property objects, each of which has a Nameproperty and a Value property. The Property objectsattached to a CubeDef include CatalogName, CubeName,CubeType, CubeGUID, and Description.

Within the CubeDef object are the objects that definethe structure of the cube. There’s a Dimensions collectiondirectly within the CubeDef object, and each Dimensionobject has Name, UniqueName, and Descriptionproperties. The Dimension object has a Hierarchiescollection, and each Hierarchy object has Name,UniqueName, and Description properties. And this goeson and on. Also worth mentioning is the UniqueNameproperty, which is an unambiguous name for that object.Within Hierarchy objects is a Levels collection. Each Levelobject has the usual properties, but also a Depth propertyindicating how deep the level is within that dimension orhierarchy. (If a dimension has only one hierarchy, then forall practical purposes the Hierarchy object is the same asthe Dimension object.) A Level object also has a Captionproperty. Within a Level object, there’s a collection ofMembers, each of which has many properties indicatingthe actual dimension values. Each Member object has aName property that’s the actual dimension value (“USA”would be the name of a member in the country level).Member objects can also be used to reference other levelmembers. For example, the Children collection of acountry member would be the members of the state level.

All of the collections previously mentioned are zero-based, so if the Count property indicates two objects inthe Levels collection, they can then be referenced asLevels(0) and Levels(1).

By using the CubeDef object and its contained objects,

you can discover all of the information about the cubestructure. By using these objects, you can query a cubeand then offer choices to the users choices based onwhat’s there, especially if you design general-purposeclasses or programs to work with any OLAP cube.

Submitting MDX queries with ADOMDThe Cellset object is the place where the MDX query goesand where the results end up. There are three propertiesand two methods that are commonly used with thisobject. The ActiveConnection property is where theconnection string goes, or where a reference to aConnection object is linked. The State property indicatesthe state of the cellset. If it’s open, there’s data in thecellset. The Source property is the MDX query statementto be executed. The Open method sends the query to thePivotTable Service or execution, and the Close methodreleases the resources of the Cellset object.

Within the Cellset object there’s more metadata aboutthe cellset itself, as well as the data values. Every Cellsethas an Axes collection, with each axis from the querypresented as one object in this collection. Each Axis objecthas a Positions collection. Each Position object representsone spot along the axis where a value will appear in thecellset. The position is a point along the axis. This objectcontains a Members collection, where each Member objectrepresents the dimension members that make up theposition. This way, whether a position on the axis is a one-dimensional item or a combination of items, the memberscontain that information.

Figure 2 is an illustration of a dataset with eachobject labeled. In this diagram, the axis objecthighlighted would be Axes(1), the position object shownwould be Positions(5), and the member objects wouldbe Members(1) and Members(3). On the columns axis,there are eight positions, where the first, second, fifth,and sixth positions have four members each, the thirdand seventh positions have three members, and thefourth and eighth have only two members.

Finally, the data itself is in the cells of the Cellsetobject. To get at a Cell object, you use the Item method ofthe Cellset. There are three ways to reference a cell withinthe cellset:

• List the names of the members for each position.• Use axis coordinates (zero-based on each axis).• Use the ordinal position within the cellset, where 0

represents the first position of the array.

Examples if these three ways, using Figure 2 and thecell with a value of 54, are as follows:

oCellset("White","USA","USA_North","Boston","1991,Qtr 3")oCellset(5,4)oCellset(37)

Notice that when using the first method with

Page 22: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

22 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

multiple members per position, all of the member namesmust be specified to identify a position. Members arelisted in ascending order according to dimension nestingwith the outermost dimension first, and axes are listed inascending order. In the second method, axes are specifiedin increasing order. Ordinal positions can also bediscovered through the Ordinal property of a Cell object.

The two ways to see the contents of a cell are throughthe Value and FormattedValue properties. The latter is theactual data presented in its internal data type. TheFormattedValue is character data.

Given all of this information, how might this be used?In this month’s Source Code files, I’ve included a VisualFoxPro form, mdxGrid.scx, and I’ve taken a very simpleapproach. This example deals with a hard-coded MDXquery that can be modified for drilling up or down withinthe sample Foodmart database’s Sales cube. The firstquery that’s executed is the first example shown earlier inthis article. But the column axis value and the row axisvalue are stored as properties of the form. The cellset itselfis also stored as a property of the form. So is theConnection object used, and even the FROM and WHEREclause values are stored as properties. This makes thisform very easy to modify.

The form has only three controls: a text box for theconnection string, a command button to make theconnection and retrieve the initial cellset, and a MicrosoftFlexGrid control to display the results. The advantage ofthe FlexGrid control is that it displays row headers as wellas column headers.

The command button’s Click event creates an ADOConnection object and opens the connection with the

string found in the text box. This way, even though thestring is hard-coded, you could modify it before using thecommand button. The Click event also populates thecolumn, row, and from properties of the form, and thencalls the DisplayData method of the form.

This method doesn’t actually display any data; all itdoes is build the MDX query, create a cellset object andthen run the cellset Open method, passing the MDX queryand a reference to the Connection object. Then it calls theDisplayCellset method, passing in the cellset object.

This method clears the grid and sets the number ofcolumns and rows according to the count of positions ofeach axis. The TextMatrix is an array representing allpositions within the FlexGrid, including the row andcolumn headers. In a FlexGrid, the zero column is whererow headers go, and the zero row is where columnheaders go. The big problem to adjust to is that theTextMatrix lists the rows number first, then the columnsnumber, whereas the cellset is the reverse.

The next step is to set the column headers:

FOR lnColumn = 1 TO THISFORM.grdResults.Cols - 1 THISFORM.grdResults.TextMatrix(0, lnColumn) = ; toAdomdCellset.Axes(0).Positions(lnColumn - 1). ; Members(0).CaptionENDFOR

When the row headers are displayed, it would be niceto make it pretty by indenting the headers in the grid toindicate sublevels. (This doesn’t work for column headersbecause they’re side by side.) So the next step is to findout the “base” level of the row axis. This is the top level inthe original MDX query. What it means is that after fixingthat level, this sample program is unable to drill up above

Figure 2. A visual representation of a dataset returned from an MDX query with the objects marked.

Page 23: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 23FoxTalk September 2000

that level. For example, if [Store States] is the leveldefined as the axis, then this program would be unable toshow [Store Country] data, but it would be able to drilldown to [Store City] data, and below. After finding outthe “base” level, that number is used to put spaces infront of levels below that.

* get the depth of the first level on rowslnBaseLevelDepth = toAdomdCellset.Axes(1). ; Positions(0).Members(0).LevelDepth

* display row labelsFOR lnRow = 1 TO THISFORM.grdResults.Rows - 1 lcIndent = ; SPACE((toAdomdCellset.Axes(1).; Positions(lnRow - 1).Members(0).LevelDepth ; - lnBaseLevelDepth) * 3) THISFORM.grdResults.TextMatrix(lnRow, 0) ; = lcIndent + toAdomdCellset.Axes(1). ; Positions(lnRow - 1).Members(0).CaptionENDFOR

Finally, the cell data is displayed in the FlexGridusing the FormattedValue property. Notice thediscrepancy between the row and column numbers of thegrid and the axes numbers of the cellset. This is becausethe FlexGrid used the zero positions for the headers.

* display the dataFOR lnColumn = 1 TO THISFORM.grdResults.Cols - 1 FOR lnRow = 1 TO THISFORM.grdResults.Rows - 1 THISFORM.grdResults.TextMatrix( lnRow, lnColumn) = ; toAdomdCellset.Item( lnColumn - 1, ; lnRow - 1).FormattedValue ENDFORENDFOR

The last thing to do is to save the cellset as a propertyof the form.

The only other action on this form is to allow the userto drill down into finer details of the cube, and then backup as far as the starting MDX query. This is done throughthe double click event of the FlexGrid. The example willonly work for double clicks in row or column zero of thegrid. It works by using the TOGGLEDRILLSTATEfunction of MDX. This function encompasses twoother functions, DRILLDOWNMEMBER andDRILLUPMEMBER. The code takes the UniqueNameproperty of the member that’s being clicked andcombines that with the previous values from the row orcolumn axis to create a new line for the MDX query.Then it simply runs the DisplayData method.

DO CASE CASE THIS.MouseCol = 0 loMember = ; THISFORM.adomdCellset.Axes(1).Positions( ; THIS.MouseRow - 1).Members(0) THISFORM.cMDXRows = "ToggleDrillState(" + ; THISFORM.cMDXRows + ", {" + ; loMember.UniqueName + "})" THISFORM.DisplayData CASE THIS.MouseRow = 0 loMember = ; THISFORM.adomdCellset.Axes(0).Positions( ; THIS.MouseCol - 1).Members(0) THISFORM.cMDXColumns ; = "ToggleDrillState(" + ; THISFORM.cMDXColumns + ", {" + ; loMember.UniqueName + "})" THISFORM.DisplayDataENDCASE

ConclusionWith MDX and ADOMD, you can query a cube and doanything with the resulting data that you’d like: display itfor the user to see, print it in a report, or even do furtherdata manipulation with the figures. There’s much morethan just what was presented in this article. I’d suggestthat if you’re interested in using these tools, you availyourself of the excellent Help in the MSDN library forADOMD. The Help that comes with Microsoft SQL ServerOLAP Services covers the MDX syntax only.

By the time you read this article, there’s a strongpossibility that SQL Server 2000 will have been released.Everything I’ve mentioned here and much, much morewill be in the new release. The only change is that OLAPServices will be called Analytical Services.

I hope that through this series, you’ve learned morethan enough about data warehouses and OLAP, and haveseen the many uses for it within any organization. ▲

09KOORHN.ZIP at www.pinnaclepublishing.com/ft

Leslie Koorhan is an independent contractor and trainer from New Jersey.

He’s written numerous articles as well as training manuals for FoxPro 2.x

and Visual FoxPro. Leslie is a Microsoft Certified Trainer in Microsoft SQL

Server, as well as an MCDBA and MCSE. Leslie also does training for

Application Developers Training. 973-670-6668, fax 973-770-7011,

[email protected] or [email protected].

Page 24: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

24 http://www.pinnaclepublishing.com/ftFoxTalk September 2000

User name

Password

reflect

season

The Source Code portion of the FoxTalk Web site is available to paid subscribers only. Goto www.pinnaclepublishing.com/ft, click on “Source Code,” select the file(s) you wantfrom this issue, and enter the User name and Password at right when prompted.Preferred subscribers can log in on the home page to gain full access.

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 .pinnaclepublishing .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): $169 Preferred ($129 Standard);

Two years (24 issues): $319 Preferred ($247 Standard)Canada:* One year: $184 Preferred ($144 Standard); two years: $319 Preferred ($247 Standard)Other:* One year: $189 Preferred ($149 Standard); two years: $324 Preferred ($252 Standard)

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 of IT Farion Grove;

Managing Editors Kate Burchfield, Andrew McMillan

Australian ne wslett er or ders: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.pinnaclepublishing.com/ft

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.

Download this month’s subscriber source code files at

www.pinnaclepublishing.com/ft. If you have a Preferred

subscription, click on the “Login” button, enter your unique User

name and Password, then click on “Source Code.” If you have a

Standard subscription, click on the “Source Code” button, select

the appropriate file, and then enter the User name and Password

printed in the lower right corner of this page.

• 09MARTSC.ZIP—Source code to accompany Della Martin’s

article, “Manipulating Your Outlook Contacts.”

• 09CORRSC.ZIP—Source code to accompany Hector Correa’s

article, “Transactions in a VFP Client/Server Application.”

September 2000 Source Code

The FoxTalk Web site has a brand-new face!Pinnacle Publishing is pleased to announce a new look and feel for the FoxTalk Web site. Take a look at www.pinnaclepublishing.com/ft

to see what’s new and improved. Explore back issue content, use the expanded search capability, get information about user groups,

check out hot FoxPro links, explore the free content, and even sign up for a free eNewsletter. If you have any feedback or suggestions

for improvement, please contact us at [email protected]. In the meantime, though, please be sure to bookmark the new URL—

www.pinnaclepublishing.com/ft—and tell your friends and colleagues!

Downloads• 09DHENSC.ZIP—Source code to accompany Doug Hennig’s

article, “Updating an Application Over the Internet.”

• 09KOORHN.ZIP—Source code to accompany Leslie

Koorhan’s article, “Data Warehouses, OLAP and You.”

Extended A rticles

• 09MCNESC.HTM—Source code to accompany Kevin

McNeish’s article, “Beyond the Class Designer—The

VCX Editor.”

• 09BOOTH.HTM—Source code to accompany Jim Booth’s

article, “Cool Tool: SearchString.”

Page 25: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 1FoxTalk Extended Article: September 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 siteat www.pinnaclepublishing.com/ft.

Extended Ar ticle

Beyond the ClassDesigner—The VCX EditorKevin McNeish

There are times when the “out-of-the-box” Visual FoxProediting tools simply don’t provide the full power you need toedit your classes. This usually leads to hacking the visual classlibrary to get the results you desire. However, this process isfraught with disaster and not for the faint of heart—enter theVCX Editor. In this article, Kevin McNeish provides you with apowerful visual tool for safely editing your VCX files. As anadded benefit (consider it the Ginsu knives of the article),the design of the VCX Editor also helps you better understandthe use of business objects in a multi-tier architecture.

HAVE you ever needed to change the class or parentclass of an object that you’ve dropped on a form?Have you ever needed to search all of the classes

in a library for a specific line of code, command, or objectreference? Have you ever wanted to better understand thestructure of Visual FoxPro’s class libraries? The VCXEditor allows you to do these things and more; it alsohelps you understand how you can use business objects inyour applications to achieve maximum scalability.

Launching the VCX EditorThe VCX Editor originates from the Mere MortalsFramework, but it’s now been released into the publicdomain. The full source code for the VCX Editor isavailable in this month’s Source Code files atwww.pinnaclepublishing.com/ft. The files consist of theVCXEdit project, two forms (a main form and a “find”form), a class library, an include file, some samplelibraries, and several image files. After downloading andunzipping the files into a directory on your computer, youcan launch the VCX Editor by entering DO FORMVCXEDIT in the Visual FoxPro Command Window.

The VCX Editor has a page frame with seven tabscontaining controls that are used to edit the various VCX

fields (see Figure 1). It also has command buttons thatallow you to Save, Find, and navigate between recordsin a VCX. To open a class library to be edited, just clickthe Open button and select the class to be edited in theOpen dialog box.

When the VCX Editor opens a class library, it makes atemporary copy of the VCX rather than working on theVCX file directly. As you navigate between records in theVCX, the Editor automatically saves each record to thetemporary VCX file. It’s not until you actually click theSave button that the Editor copies the temporary VCX fileover the original VCX file.

The fields that can be viewed and modified in the

6.06.0

Figure 1. The VCX Editor provides visual tools for editing yourVCX files.

Page 26: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

2 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

VCX Editor are listed in Table 1. There are three VCXfields that aren’t listed in the table: ObjCode, Ole, andOle2. These fields contain binary data that can’t beedited manually. As you navigate between fields in theVCX Editor, a description of the information containedin each field is displayed at the bottom of the form.This is especially useful when viewing the contentsof the “Reserved” fields whose names aren’t veryself-explanatory!

With the VCX Editor, you can set certain fields thatare too “dangerous” (or unnecessary) to edit directly toread-only. These Read Only fields are italicized in Table 1.In addition, other VCX Editor controls are dynamically setto Read Only if the value of its corresponding fieldshouldn’t be changed for a given record. As you movethrough the VCX using the navigation buttons, the VCXEditor controls enable/disable accordingly. For example,the Class combo box is only enabled when the record

Table 1. The VCX file structure (shown in order of the VCX Editor) Read Only fields are italicized.

Field name DefinitionObjName The name of the class or object member.

Class The parent class of the class or object member.

ClassLoc The relative path and filename of the parent class library. If the parent class is a VFP built-in baseclass, then the ClassLoc field is empty.

BaseClass The name of the VFP base class of the class or object member.

Parent References the immediate parent container object of which the object is a member. If an object isn’ta member of a container, the Parent field is empty.

Platform Associates a specific platform for an object in VFP3. In VFP5 and higher, Platform containsthe text “WINDOWS.” It contains the text “COMMENT” in the VCX header record and all “class end”records.

TimeStamp The TimeStamp field contains a number that represents the date and time a class or member objectwas created or last edited. VCX header records and “class end” records have their TimeStamp fieldset to 0.

UniqueID Specifies a unique ID code for each class and member object. VCX header records’ UniqueID is setto “Class,” and “class end” records’ UniqueID is set to “RESERVED.”

Properties Properties and values that override the values defined in the parent class.

Protected Lists the names of properties and methods that are marked as protected or hidden. Hidden propertiesand methods are followed by a caret (^).

Methods Contains all method code of a class or object member.

Reserved1 Indicates the start of a class definition (a “class start” record) by storing the string “Class.” If arecord isn’t the start of a class definition, the Reserved1 field is empty. In the VCX header record,Reserved 1 is set to “VERSION = 3.00.”

Reserved2 Stores the number of records associated with a class or container record. This field only contains avalue for the first record of a class definition. If a class doesn’t contain any object members, the“class start” record contains a 1. For records that aren’t the start of a class definition, theReserved2 field is empty. If a class is marked as OLEPublic, the “class end” record is set to “OLEPublic.”

Reserved3 Stores a list of all user-defined properties and methods of a class. It lists one property/method perline with the member description separated by a single space character. Standard property namesare listed without a preceding character, array properties are preceded by a caret (^), and methodnames are preceded by an asterisk.

Reserved4 Stores the relative path and filename of the class’s toolbar icon. This icon is displayed in the FormControls toolbar, the Form Designer, the Class Designer, and the Project container when the class ispart of a project.

Reserved5 Stores the relative path and filename of the class’s container icon. The container icon is onlydisplayed in the Class Browser.

Reserved6 Specifies Pixels or Foxels scale mode of a class or object.

Reserved7 Stores a class description.

Reserved8 Stores the relative path and filename of a class’s include file.

User Open for user-defined purposes.

Page 27: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 3FoxTalk Extended Article: September 2000

pointer is on a “class start” record, since this is theonly type of record for which the Class field shouldbe specified.

The Main page of the VCX Editor contains severalimportant fields that you might need to edit. Instructionsfor editing these fields are outlined later in this article.Notice, also, from Table 1 that the VCX Editor convertsthe TimeStamp field into a readable date/time format.The Properties tab lists the properties and their valuesthat override the properties of the parent class. TheProtected tab lists properties and methods that aremarked as protected or hidden members for a class.Although the VCX Editor allows you to do so, there’sreally no compelling reason to edit values in theProperties and Protected tabs. You can more easily (andsafely) edit these in VFP’s Property Sheet or the EditProperty/Method dialog box. The Methods tab containsall snippet code of a class or object member—again,instructions for editing are found later in this article.

The Reserved1-3 and Reserved4-8 tabs allow you toedit the miscellaneous information stored in the VCX’s“Reserved” fields such as class bitmap/icon, scale mode,description, and include file. To help avoid typos (whereapplicable), an ellipses button and corresponding dialogbox have been provided for selecting information to bestored in the Reserved fields. The User tab allows you toedit information in the VCX User field. This field is openfor user-defined (this means you) purposes.

The structure of the VFP class libraryBefore using the VCX Editor, it’s important to understandthe structure of Visual FoxPro’s class libraries. As shownin Table 1, the VCX file is a specialized FoxPro table thatyou can use like any other table. Each VCX contains asingle header record at the start of the file. This recordcontains the text “COMMENT” in the Platform field andthe text “Class” in the UniqueID field. All other fields inthe header record are empty. To see this, launch the VCXEditor and open the Sample1.vcx file. Sample1.vcx is acompletely empty class library, and thus contains only asingle header record.

As you add classes to a library, each class in thelibrary contains at least one “class start” and one “classend” record. For example, the Sample2.vcx file contains asingle container class called MyContainer; therefore, theVCX file contains only three records: the VCX header, aclass start, and a class end record.

Open the Sample2.vcx in the VCX Editor and you’llsee that the first record is the VCX header record. If youclick the Next button, you’ll go to the “class start” recordfor the MyContainer class. The Main tab contains anObject box that displays the name of the class—in thiscase “MyContainer.” The Class, ClassLoc, and BaseClassfields display the lineage of the MyContainer class. TheClass field specifies that the name of MyContainer’s

parent class is VFP’s container base class. The ClassLocfield specifies the relative path and filename of the parentclass library. This box is empty because MyContainer is adirect subclass of Visual FoxPro’s Container class. TheBaseclass control contains, obviously, “container.” If youclick on the Properties tab, you’ll see a list of all theproperties that have been overridden in the MyContainersubclass—in this case, Width, Height, and Name. If youclick on the Reserved1-3 tab, you’ll see that the Reserved1field contains the string “Class.” The Reserved2 fieldcontains the number 1, which indicates the MyContainerclass doesn’t contain any members. If MyContainer werea composite class containing object members, Reserved2would be set to the number of records associated with theclass, including the container class itself.

If you click the Next button one more time, you’llnavigate to the “class end” record. As you can see, the“class end” record’s fields are empty except for threefields on the Main tab. The ObjName field of the “classend” record is always set to the same value as theObjName field of the “class start” record—in this case,“MyContainer.” The Platform field is set to “COMMENT”and the UniqueID field is set to “RESERVED.”

How does Visual FoxPro store composite classes suchas forms that contain user interface controls? Compositeclasses begin with a “class start” record that’s followed bya record for each member object. The class ends with—you guessed it—a “class end” record. The Sample3.vcxfile contains a form class that has three user interfacecontrols that you can peruse with the VCX Editor.

Searching for records in the VCXOften, you need to locate a particular record, relatedrecords, or code fragments in a VCX. If you click the Findbutton on the VCX Editor, it launches a Find form (seeFigure 2) that allows you to search for text in the followingVCX fields:

• Baseclass• Class• ClassLoc• Methods

Figure 2. The Find form allows you to locate records in a VCXbased on virtually any criteria.

Page 28: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

4 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

• ObjName• Parent• Platform• Properties• Protected• Reserved1-8• TimeStamp• UniqueID• User

For example, to find a specific class, launch the Findform, select “ObjName” in the Search box, enter the nameof the class in the For box, and click the Search button. Tofind a specific code fragment, select “Methods” in theSearch box and enter the code you’re searching for in theFor box. Note that the search isn’t case sensitive.

If the VCX Editor finds a record that meets yoursearch criteria, it automatically places focus on the controlon which you performed the search. If you want to searchfor the next instance of particular search criteria, simplyclick the Find Again button.

For example, if you want the VCX Editor to skip tothe starting class record for all classes in a VCX, in theSearch box select “Reserved1.” In the For box, enter thetext “Class.” Click the Search button to jump to the firstclass in the VCX, then click the Find Again button to jumpto each subsequent class.

Redefining classesThe Visual FoxPro Class Browser allows you to Redefinethe parent class of a class. This is extremely useful whenyou need to rearrange the hierarchy of your classes.However, the Class Browser doesn’t allow you to redefinethe parent class of member objects that are part of acomposite class. This is where the VCX Editor can fill inthe gap.

When you drop a control on a form class, VisualFoxPro sets the values of the following properties thatspecify the lineage of the control:

• Class—Specifies the name of the class used to createthe object member.

• ClassLoc—Specifies the library of the class used tocreate the object member.

• BaseClass—Specifies the base class of the class used tocreate the object member.

For example, if you drop a class named CTextBoxstored in a library named CContrls.vcx onto a form class,the lineage fields might contain the following values:

Field ContentsClass ctextboxClassLoc ..\libs\ccontrls.vcxBaseClass textbox

If you want to change the ctextbox class to a class

named CTextBoxl stored in a library named KContrls.vcx,you’d do the following:

1. Launch the VCX Editor and open the class librarycontaining the form control that you want to change.

2. Find the CTextBox record (see the above sectionSearching for VCX Records).

3. Click on the ellipses button next to the Class box. Inthe Open dialog, select the CTextBoxl class located inKContrls.vcx, then click OK.

This changes the lineage fields to:

Field ContentsClass ctextboxlClassLoc ..\libs\kcontrls.vcxBaseClass textbox

Since the ClassLoc and BaseClass fields arecompletely dependent on the setting of the selected class,these fields are read-only in the VCX Editor.

Although it’s not recommended, the VCX Editor alsoallows you to redefine a class’s parent to be one of VisualFoxPro’s base classes. To do this, simply select the desiredbase class from the Class combo box in the VCX Editor.Using the previous example, if you redefine theCTextBoxl’s parent class to the Visual FoxPro baseTextbox class, this changes the lineage fields to:

Field ContentsClass textboxClassLocBaseClass textbox

If you try to redefine the parent class to a class thathas a different base class, the VCX Editor warns you that“some intrinsic properties and methods may be lost.” Forexample, if you try to change the textbox class to a combobox class, the VCX Editor will display the warning dialogbox but will allow you to continue with the redefinition.

Searching, viewing, and changing method codeOne of the coolest features of the VCX Editor is the abilityto view, search, and edit all of the method code in a classlibrary. For example, the MyForm class located inSample3.vcx contains method code at the form level andin the command button. If you open Sample3.vcx in theVCX Editor, you can search for any class or member objectthat contains code by searching the Methods field for thetext “procedure.” This works because each separatemethod in a class is bracketed within PROCEDURE andENDPROC statements. The first record the editor finds isthe MyForm class. If you click on the Modify button, itlaunches an edit window from which you can searchfor and edit code. In closing the editing window,you’re prompted to save any changes you made tothe method code.

If you click the Find Again button, the editor will

Page 29: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 5FoxTalk Extended Article: September 2000

find the method code that’s stored in the commandbutton. As you can see, method code is stored with theclass or member object to which it belongs. All of theform-level code is stored in a single VCX record, andall of the command button code is stored in its ownseparate record.

Separation of user interface and business logic inthe VCX EditorMany Visual FoxPro developers are aware of the conceptof n-tier architecture. One of the rules of n-tierarchitecture is to separate your user interface logicfrom your business logic. What does this mean inpractical terms? How can you accomplish this in yourown applications?

The VCX Editor provides a good example of how tokeep the presentation tier and the business tier separate.For example, as mentioned earlier in this article, many ofthe fields in the VCX Editor are dynamically set to ReadOnly based on the type of record at which the editor iscurrently pointed. Where’s the logic that determineswhich fields should be set to Read Only for each type ofrecord? Many developers would be tempted to create aform-level method that determines these rules. However,it’s the wrong place for this logic—it belongs in thebusiness tier. The VCX Editor relies on the VCXBizObjbusiness class to tell it the rules regarding enabling anddisabling the appropriate controls.

The VCXBizObj class is located in the VCXBiz libraryand is dynamically added to the VCXEdit form atruntime. The VCXBizObj class has a method calledCanEditField. This method accepts a single parameter—the name of a VCX field. It returns a logical value thatindicates whether the field is Read Only for the currentVCX record. The VCXEdit form’s ResetControls method isthe place where calls are made to the VCXBizObjCanEditField method. The ReadOnly property of each UIcontrol is set to True or False based on the value returnedfrom CanEditField.

If you examine the code in both the VCXEdit formand the VCXBizObj class, you’ll get a good feel for whatlogic should be in the user interface and what logicshould be in the business tier. What do you gain fromsegregating your application logic this way? Scalability.The structure of the VCX Editor allows you to easilyreplace the existing user interface with a different userinterface—HTML, for example. You could create anHTMLVCX Editor front end, create an Automation Serverthat includes VCXBizObj, then, using Active Server Pages,instantiate the VCXBizObj class and access all of its publicproperties and methods. If you follow a similararchitecture for your own applications, it provides asmooth transition from the desktop to the Internet.

A true three-tier architecture calls for data access logic

to be broken into a layer separate from the business tier.This allows you to create applications that are scalablebetween different data sources, such as Visual FoxPro,SQL Server, and Oracle. The VCX Editor makes noattempt to break this data logic out from the business tier.The VCXBizObj class is designed to work specifically withVisual FoxPro class libraries and, therefore, doesn’t needthe scalability that a separate data layer provides.

Segregating your application logicYou can use the VCX Editor to help you determinewhether your application’s user interface logic is separatefrom its business logic. Here’s how it works: First of all,your business object libraries shouldn’t contain anyclasses that possess user interface elements; they shouldbe “pure” business tier libraries. This allows you to createa separate component project that only contains yourbusiness-related class libraries, which, in turn, compilesinto a much leaner DLL than would otherwise be possible.If you include user interface elements in your businessclass libraries, Visual FoxPro will pull all of the userinterface class hierarchy into your project when compilingyour Automation Server, making for a very large .DLL.

If you follow this rule of segregating your businessclasses from your user interface classes, you should beable to open your business object libraries in theVCX Editor and search the Methods field for any ofthe following:

• ThisForm• MESSAGEBOX• MsgSvc() && Steven Black’s public domain

messaging service

If you find any of these commands or references,you’ve got user interface logic in your business tier!

Extending the VCX EditorIn the future (when I have a bit more time on my hands!),I’ll enhance the VCX Editor and make that available in thepublic domain. One of the changes I’d like to make isadding “search and replace” capabilities. For example, itwould be nice to set the Reserved8 field to the sameinclude file for all classes in a library, or perform a searchand replace on a class library’s method code. For now, Ihope you enjoy using the VCX Editor as both a practicaland educational tool. ▲

09MCNESC.ZIP at www.pinnaclepublishing.com/ft

Kevin McNeish is president of Oak Leaf Enterprises—a company that

specializes in object-oriented custom software, training, and developer

tools. He’s the creator of The Mere Mortals Framework, and has spoken at

many user groups as well as conferences in North America and Europe.

He’s also written articles for FoxPro Advisor and FoxTalk. Kevin mentors

Page 30: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

6 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

and trains many software companies in building flexible, component-

based applications that scale from the desktop to the Internet. He’s a

Microsoft Certified Developer and has created many enterprise-wide

applications for a wide variety of vertical markets using Visual FoxPro as

the primary development tool. 804-979-2417, www.oakleafsd.com,

[email protected].

Page 31: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 1FoxTalk Extended Article: September 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 siteat www.pinnaclepublishing.com/ft.

Extended Ar ticle

VFP Data on Your Handheld:The SatForms IDEWhil Hentzen

In the July issue, I walked you through the structure of asimple Palm application that you can synchronize with amatching desktop VFP app. This month, it’s time to get upclose and personal with the Satellite Forms IDE—how to builddata structures and forms that you deploy on your Palm.

THE Satellite Forms development package is used tocreate applications that run on your Palm handheld.In this article, I’m going to show how to use this

rich and flexible environment.The Satellite Forms IDE, shown in Figure 1, is used for

three purposes: to create tables that your Satellite Formsapplication will access on your PC; to create the actualapplication (you know, a UI and some business logic) thatresides on your handheld; and a mechanism to deploy

that application and the related tables to your handheld.There are actually additional functions, like the ability tomanage extensions (the Satellite Forms version of ActiveXcontrols), but I won’t get into those here.

Before you begin, you’ll want to set a couple ofapplication-wide properties. Right-click on the Propertiesnode in the Contents of Application tree view and select“Properties” from the resulting context menu, or justdouble-click on the Properties node. The ApplicationProperties screen will be displayed, as shown in Figure 2.

The one property you’ll really want to set is the“Name of Application” property—if you don’t, yourapplication will be named “Untitled,” and that’s what willshow up on your handheld. You don’t have to specify anInitial Form if you only have one form in your

application; otherwise, come back tothis dialog box once you’re done withcreating the initial form. The otherproperties can wait until later.

The SatForms Table DesignerThe Table Designer in Satellite Formsis extremely easy to work with,nearly to the point of being trivial.Firing up the tool and playingaround for a few minutes is allyou need to completely coverits capabilities.

As you might remember,SatForms relies on an intermediatedata store on your PC when movingdata back and forth between yourhandheld and your desktop. You usethe Table Designer to create thisintermediate data store. When you

Figure 1. The Satellite Forms IDE before adding tables or forms.

Page 32: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

2 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

deploy the SatForms application to your handheld, thisintermediate data store will be converted to a proprietary“.PDB” field that’s loaded onto your handheld.

By the way, this intermediate data store that resideson your PC takes the form of a dBASE V .DBF table—pretty darn convenient, eh?

In order to create a table, right- click on the Tablesnode in the Contents of Application tree view in the leftside of the SatForms IDE. You’ll have three choices in thecontext menu: Insert Table, Import Table, and either Showor Hide Tables, depending on whether the Tables node isexpanded to show the tables below the node.

If you click on Insert Table, the Table Designer dialogbox will appear, as shown in Figure 3. The first thing todo, obviously, is name the table. The value you enter inthe Name of Table text box will be used both internally bythe Satellite Forms engine as well as for the name of thefile on disk. Note that you can later change this value. Butdoing so only changes the value used internally bySatForms; the name of the file on disk stays the same.

As you can see in Figure 3, there are two tabs in thedesigner. The Layout tab is used to create and edit thetable structure—much like MODIFY STRUCTURE does inVisual FoxPro. You click the New button to create a newfield, the Edit button to modify the highlighted field, andthe Delete button to remove a row. Up and Dn (“Down”)are used to rearrange the order of the fields, which, as allgood theorists know, is relationally meaningless.

When you click the New or Edit buttons, you’ll getthe Edit Column dialog box as shown in Figure 4. You canenter the name of the column, select the data type(available choices are Character, Numeric, True/False,Date, Time, Ink, and Time Stamp; I’ll cover “Ink” and“Time Stamp” in future columns), and then choose thewidth and number of decimals when appropriate. Forexample, True/False fields are automatically defined ashaving a width of 1, and you don’t get to change it, justlike in VFP.

You might be cringing at having to call up a separatedialog box to edit a field’s definition, but there’s a reason.Looking at Figure 3 again, you’ll notice that there’s noOK button in the Table Designer dialog box. Yes, closingthe dialog box saves your changes; thus, instead ofconfirming your changes at the table level, you do sowith each field.

The real excitement starts when you click on theEditor tab of the Table Designer, as shown in Figure 5. Ohboy! It’s a real, honest-to-goodness Browse window foryou to enter and edit data!

Figure 2. Setting the application-wide properties.

Figure 3. Creating a new table with the SatForms Table Designer.

Figure 4. Using theEdit Column dialogbox to changeattributes of afield definition.

Page 33: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 3FoxTalk Extended Article: September 2000

You insert and delete rows via the appropriatecommand buttons in the bottom of the dialog box, andtype in data in the appropriate rows and columns as youdesire. As soon as you type data in a field, the informationis committed to the disk, and I’ve found that you don’teven have to leave the field (much less the row) in orderto save your changes. This is okay, really, because theEditor tab is supposed to be used for entering sample datafor testing—it’s not for your users, nor even for you to putlarge amounts of data into the table.

That’s about all there is to creating a table. In thisexample, I’ve created a table for a simple To Do list—withfields for the name of the task, who’s responsible for thetask, how long it should take, and a logical flag forwhether the task is done or not.

Now let’s build a data entry form for this table thatyou can use on your handheld.

The SatForms Forms DesignerJust like with a table, you can create a new form in theSatForms IDE by right-clicking on the Forms node andselecting the Insert Form menu option in the resultingcontext menu. Doing so will display an empty form, asshown in Figure 6.

The first thing you’ll notice is the size of the emptyform—it’s 160 pixels square. You can’t resize this form—by definition, all SatForms forms are this size because it’sthe size of the Palm “desktop.” You don’t have to use theentire form, of course, but you can’t “tile” windows likeyou can in GUIs like Windows or the Mac OS.

Manipulating a formThere’s a bunch of stuff you can do with a form evenbefore you start to put controls on it. First of all, there’sthe toolbar in the Form Designer window. The left-most

button allows you to turn the “grid” (the dots on theempty form) on or off. The combo box (that shows200 percent in Figure 6) allows you to change themagnification of the form displayed in the IDE. Ifyou have a control selected, the next two read-only textboxes show the position of the upper left corner of theselected control, and the width and height, respectively, ofthe selected control.

Right-clicking in the empty form displays a contextmenu with two available choices: Form Properties andScripts. We’ll tackle the Scripts option later—it allows youto attach code to various form-level events. Selecting theForm Properties menu option opens the Properties ofForm dialog box as shown in Figure 7.

You can name the form—this name isn’t displayed tothe user, but rather is used by the developer. For example,it’s what is displayed under the Forms node in theContents of Application tree view.

SatForms doesn’t support a “tabbed dialog” interface,but its substitute is a multiple-page form. If you’ve usedMicrosoft Access, you’re probably familiar with theconcept of “multi-page forms.” Think of Leslie Nielsenin “The Naked Gun,” when he pulls out his police IDand an accordion of credit cards unfolds from his wallet.The SatForms multiple-page form works sort of likethat—you can specify events or write code that movesfrom page to page, much like you move from tab to tab ina page frame in VFP. You can specify how many pagesyour form has in this dialog box.

Figure 6. An empty form in the SatForms Form Designer.Figure 5. You can enter data into a Satellite Forms table via theEditor tab of the Table Designer.

Page 34: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

4 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

The Linked Table combo box performs the samefunction as the VFP data environment, with the differencethat you can only have one primary table linked to aform. (This isn’t as big a restriction as it might seem atfirst; more in a future column.) The Linked Table combobox is populated with all of the tables in the Tables nodein the Contents of Application tree view.

The last part of the Form properties dialog box allowsyou to set what’s called “User Permissions” or, in otherwords, what functions a user is allowed to do. Thisfunctionality is extensible, as these are defaults for theform. As you can see in Figure 7, you can allow the user toadd, delete and modify records, navigate betweenrecords, and even delete (or not delete) the last record inthe table.

Placing controls on a formPlacing controls on a form in SatForms works much likeother visual tools you might be familiar with. You have apalette or toolbar of available controls, and you dropthose controls onto your form. With the SatForms IDE, allyou have to do is click on the toolbar button and thecorresponding control will be placed on the form.

Note that the control is always placed in the upperleft corner of the form—you then need to drag the controlto where you want it to reside.

The SatForms controls inventoryMany of the native SatForms controls are similar to

VFP controls:• Title• Text• Edit• Paragraph• Check box• Radio button• Command button• List box• Droplist (combo box)• Lookup• Ink Control• Bitmap• Graffiti Shift Indicator• Auto Stamp• Custom Control

Most of these need no explanation, but a few don’thave direct counterparts to VFP or VB or aren’timmediately obvious.

The Title control is essentially a caption for the form.An example is shown in Figure 8, where its value is set to“Dumb Stuff I Gotta Do.”

The Ink Control allows you to capture a signature orother freehand drawing on the Palm—you can think of itas a Palm-specific General control.

The Graffiti Shift Indicator control places an“indicator” graphic on the form that shows the shift statusof the Graffiti handwriting recognizer. This graphic willshow different symbols depending on whether Graffiti isin lowercase, shifted, or caps-lock mode.

The Auto Stamp control automatically enters a date or

Figure 7. The Satellite Forms Properties of Form dialog box. Figure 8. A sample Satellite Forms application form.

Page 35: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 5FoxTalk Extended Article: September 2000

time stamp in a table column.The Custom Control allows you to specify a custom

control you’ve built or acquired from another source,much like you’d specify an ActiveX control in Windows.

Control propertiesYou can set properties of every control by right-clickingon a control of interest and selecting Control Propertiesfrom the context menu. A sample of the ControlProperties dialog is shown in Figure 9. As with othervisual environments, some properties are shared by more

than one control, while other properties are unique to aspecific control.

All controls have Name of Control, Dimensions,and Visible properties. Most controls have Data Source,Font, Read-Only, and Don’t Modify Table properties.Most controls also allow you to specify an ActionWhen Clicked.

The combo box in the Data Source property ispopulated with the appropriate fields for the table that’slinked to this form. For a check box control, only logicalfields in the linked table are displayed in the combo box,and for text box controls, only character and numericfields are displayed in the combo box.

Command buttons have special properties becausethey’re never attached to data—they’re always used toinitiate an action. (Well, I suppose you could use one as afancy label by just changing the caption, but that’s not theintended purpose of the control.) Figure 10 shows theButton control’s Properties dialog box.

As you can see, it has common properties like Name,Text (what you and I think of as “caption”), Font, andDimensions. There are also some obvious button-specificproperties like “Visible” and “Border.” “Auto Repeat”changes the nature of how the control fires its action onthe handheld. Normally, a control fires at the end of a tapwhen the stylus is lifted from the handheld screen. (That’sour “mouse-up” event.) When Auto Repeat is checked,the control’s action fires as soon as contact is made, and ifyou keep the stylus pressed against the screen (lightly,folks, lightly—you aren’t drilling for oil!), the action willbe repeated at a periodic (but undocumented) interval.

The real magic is contained in the Action whenClicked property. Click on the Edit button, and the dialogbox shown in Figure 11 will appear.

Figure 9. The properties dialog for the Check Box control.

Figure 10. The Button control’s property dialog. Figure 11. Selecting an action to attach to a button.

Page 36: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

6 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

As you can see, there are a wealth—or, as KellyBundy would say, a “veritable cornucopia“—of pre-definedactions, including record and form navigation, and recordmaintenance. Note the last two options in the combo: RunScript and Launch App. Run Script allows you to runcode that you’ve written yourself in case one of theseoptions isn’t suitable, and Launch App allows you to runanother SatForms application.

There’s even more power under the hood, though.Selecting the Filters tab and then clicking the Add button,as shown in Figure 12, allows you to create a specializedtype of action that filters a table. This interface is a bitawkward at first. You can think of the Filters tab as beingjust another type of action—a “Filter Table” action in theAction combo box.

Thus, you could create a button named “Filter” thatwould set a filter on the table based on criteria youspecify in the Create New Filter dialog box. Note that youcan use either a hard-coded expression or the currentvalue of a control on the form.

The form for the table created in Figure 5 might looklike that shown in Figure 8.

Saving your projectWhen you create your tables and forms, SatForms will try,like many other brain-dead tools, to save your work in asubdirectory under the directory where you installedSatellite Forms itself—yes, buried deep on drive C.

Jeesh. Hasn’t anyone at Puma ever had to reformat theirC drive?

Obviously, don’t accept the defaults—create yourown “SatFormsProjects” directory on your developmentdrive, and create separate directories for each SatFormsproject you create. Once you start saving components fora project there, SatForms will be good about savingsubsequent components in the same directory as well.

When you’re done creating your tables and forms andstuff, you’ll press Save (if you haven’t done it already)and be greeted with a zillion (a zillion is a fraction of abazillion) dialog boxes about naming your files. Well,okay, two dialog boxes.

The first one will ask you to save the SatFormsapplication itself. The resulting file will have an “SFA”extension. You’ll next be asked to save your table, andthat file will have a “DBF” extension. However, if youlook in your directory, you’ll see several more files thanjust those two. Let’s take a look at what these are.

First, suppose that you named the project“SFDEMOAPP” and the table “SFDEMO1.” You’ll see thefollowing files in your project directory:

• SFDEMO1.DBF• SFDEMOAPP.PDA• SFDEMOAPP.SFA• SF-SE_SFDEMOAPP.PDB• SFT-SE_SFDEMO1.PDB

Figure 12. Adding a filter to a button.

Page 37: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 7FoxTalk Extended Article: September 2000

The DBF and SFA files are pretty easy to identify,since I just told you what they are. The SFDEMOAPP.PDAfile is created by SatForms and is the file that getstransferred to your handheld. In other words, it’s theapplication that runs on your Palm. The SFT-SE_SFDEMO1.PDB file is the proprietary data file onthe handheld where data is saved. I believe that the SF-SE_SFDEMOAPP.PDB file is associated with the PDAfile, in that it contains part of your application—not yourdata—but the documentation is less than clear aboutthis point.

Deploying your SatForms applicationon your PalmOnce you’ve finished your application—and I recommendyou start out really simple, like these articles have (just toget all the steps down)—it’s time to put it on yourhandheld and run it there.

First, put your handheld in the cradle, and make surethe connection to your PC is tight. Next, in SatForms,open your project. Select the Handheld | DownloadApp and Tables menu option. The dialog box shown inFigure 13 will appear on your PC.

At this point, press the Sync button on the cradle.Both your PC and the handheld will display the usualmessages about which files and applications are beingsynchronized—if you watch the messages on your PCclosely, you’ll see Satellite Forms in the list.

Finally, the messages will finish. On your PC, you’llbe returned to the Download Application to Handhelddialog box shown in Figure 13. On your handheld, you’llprobably see a message about “Cleaning up” for a fewseconds longer. Once that message goes away, the sync isdone and you’re ready to try out your new handheldapplication.

Take your handheld out of the cradle, turn it on (ifit’s not already on), and tap the SatForms icon on themain application screen. You’ll see your sample app

(along with others that you might have downloaded) ina list. Tap the name of your sample (hopefully it’s notcalled “Untitled”), and the initial form will display. Tapthe Next and Back buttons, change some data, add a newrecord—whatever you like. Open a different applicationon your Palm, turn your Palm off, and then turn it on andopen your sample SatForms app again—the changes youmade will still be there. That’s all there is to it.

Transferring back to your PCFinally, let’s move those changes back to your PC.

Repeat the steps in the previous section, with the oneexception of selecting the Handheld | Upload Tablesmenu option instead of “Download App and Tables.”Once you’re done synchronizing, open the table in theSatForms Table Designer, click on the Editor tab, andyou’ll see the changes you made on your handheld backon your PC. Don’t believe me? Take your handheld out ofits cradle, wrap it in tin foil, and put it in a lead-shieldedbox in the basement—then look at the table on your PCagain. Yes, you’ve really moved your data from thehandheld back to your PC!

Next monthThe first time I did this, I experienced the same sort ofrush as when I ran a HELLOWLD.EXE of my own makingback in the early 1980’s. However, after the glow wearsoff, there’s one important thing to realize: Thesynchronization of the DBF on your PC and the data onyour handheld isn’t intelligent at all. In fact, it’s actuallyjust a wholesale replacement of the entire table.

This can cause all sorts of problems. Next month, I’llexplain each of these problems, and then direct yourattention to Visual FoxPro—and show how to build a VFPapplication that will handle all of these issues. ▲

Whil Hentzen is the editor of FoxTalk. [email protected].

Figure 13. Press the Sync button on your handheld cradle whenready to deploy your application and tables from the SatFormsIDE to your handheld.

Page 38: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

http://www.pinnaclepublishing.com/ft 1FoxTalk Extended Article: September 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 siteat www.pinnaclepublishing.com/ft.

Extended Ar ticle

Cool Tool: SearchStringJim Booth

Have you ever had to find all the places that a variable is usedin a project? Ever wanted to know all the places that a certainstring appears in your forms? If you answered yes, then readon—Jim Booth shows why SearchString is a truly Cool Tool.

SEARCHSTRING is a freeware program written by ErikMoore that allows you to search for the occurrenceof any string in a project. You can select all objects in

a project or selectively search in programs, forms, classes,and so on.

The UIFigure 1 shows the user interface for the SearchStringapplication. The first tab shows the file list. The twocontrols at the top allow you to enter the string to searchfor and select the project to search in, respectively. The liston the pageframe will display the results of the search.But before you can do a search, you must tell SearchStringto which files you want the search applied. The controls

6.06.0

Figure 1. The File List tab of the user interface. Figure 2. The search options.

you use in order to configure how SearchString willwork are contained in the Search Options tab, shownin Figure 2.

On this page, you can set the options for what filesand what areas in those files to apply the search.

Let’s get to itThe tool is used to search for a string in the componentfiles of a Visual FoxPro project. For the example, I selecteda project on my computer and chose to search for thestring llRet, as seen in Figure 3.

Once the Search button is clicked, the searchingoccurs; the result is shown in Figure 4.

The result listing tells you the file that contains thestring, the object within that file, and the type of objectthat it is. The first item in the list is for a file namedForms.vcx, and within that file the frmBase object has thesearch string in one of its methods.

This alone would make the tool a real benefit for

Page 39: FoxTalk - portal.dfpug.deportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT0900.pdf · 9 The Kit Box: COM-monsense Design Paul Maskins and Andy Kramek 13 Reusable Tools: Updating

2 http://www.pinnaclepublishing.com/ftFoxTalk Extended Article: September 2000

Figure 3. The search is set up for the string llRet in the projectcdstest.pjx.

locating the strings; however, the functionality of this tooldoesn’t end here. Double-clicking on this entry in the listopens the frmBase class in the Visual FoxPro classdesigner. From there, you can do whatever you chooseregarding the string.

In addition, by using the “Save List as…” button, youcan save a copy of the results in a file for future use.

ConclusionWith SearchString, Erik Moore has given us a valuabletool to help in our development work. He’s even made itavailable for free. I know this tool will save me a gooddeal of time searching for those strings that I need tomodify or those code sections I need to visit.

Erik’s SearchString is available for downloadwith this month’s Source Code files atwww.pinnaclepublishing.com/ft, as well as fromwww.universalthread.com, in the files library. ▲

09BOOTH1.ZIP at ww.pinnaclepublishing.com/ft

Jim Booth is a Visual FoxPro developer and trainer. He’s spoken at FoxPro

conferences in North America and Europe. Jim has been a recipient of

the Microsoft “Most Valuable Professional Award” every year since its

inception in 1993. He is coauthor of Effective Techniques for Application

Development and Visual FoxPro 3 Unleashed, and is contributing

author of Special Edition Using Visual FoxPro 6.0. 203-758-6942,

www.jamesbooth.com, [email protected].

Figure 4. The results of our search.