FoxSolutions for Microsoft® FoxPro®Talk and Visual FoxPro® Developers

May 1998 Volume 10, Number 5 1 Teaching OOP to FPW with Stored Procedures Dave Jinkerson Teaching OOP and Scott Malinowski 2 Editorial: No Good Deed Goes Unpunished to FPW with Whil Hentzen 6 Best Practices: Development Checklist: Object-Oriented Stored Procedures Design, Part 2 Jefferey A. Donnici 10 Reusable Tools: Dave Jinkerson Build Your Own Wizards and Scott Malinowski Doug Hennig 14 ActiveX and Automation Review: Automation from a VFP Perspective Many FoxPro developers would like to make the transition between FPW and VFP, John V. Petersen but they don’t know where to start. Dave and Scott will show you how to use your 18 Using AddObject current knowledge of FPW to build the bridge every programmer needs to cross Richard A. Schummer from procedural programming into OOP. In this article, they’ll look at an example 23 The Cutting Edge: that will help you move your procedural programming style one step closer to OOP It’s Never Too Late and, in so doing, begin the journey from FPW to VFP. Les Pinter 24 May Subscriber Downloads BJECT-ORIENTED programming (OOP) is a data-centered view of EA What’s Really Inside: programming in which data and behavior are strongly linked. The TableUpdate() Oproper way to view OOP is as a programming style that captures the in Visual FoxPro 5.0 behavior of the real world in a way that hides implementation details. With a Jim Booth language that supports OOP, data and behavior are conceived as classes, and Extended Article available at www.pinpub.com/foxtalk Instances of classes become objects. What many FPW programmers don’t realize is that, in a language that doesn’t support OOP, you can rely on EA Cool Tool: Clean Off Your Monitor with StickyNotes programming style and still create an OOP-like result. Whil Hentzen Extended Article available at ADTs with style www.pinpub.com/foxtalk A key factor in OOP is the creation of user-defined data types from the EA The Kit Box: Saving and language’s native data types. In languages that support OOP, user-defined Restoring Objects extensions to the native types are called Abstract Data Types (ADT). To create Barbara Peisch and Paul Maskens Extended Article available at an ADT, you must define a set of values and a collection of operations that can www.pinpub.com/foxtalk act on those values. But stop and realize that FPW programmers can create sets EA How to Sell Part of values (Tables) and collections of operations (Procedures/Functions) that of an Application can act on those values. Keep this thought in your mind; it will become a key Paul Russell piece in teaching FPW, OOP-style. Extended Article available at Continues on page 3 www.pinpub.com/foxtalk From the Editor FoxTalk

No Good Deed Goes Unpunished Whil Hentzen

EEPERS. Remember the opening scene in the TV With this onslaught of information and limited space, version of The Odd Couple where Tony Randall I’ve faced some tough choices. Should we cut articles? Jattempts to help an old lady across the street, and Well, I’d like to cover as many topics as possible each he’s greeted with a swinging purse-in-the-face by the little month, so that didn’t seem right. In fact, I have an ever- old lady, followed by a punch (albeit a rather effeminate growing list of topics, articles, and even potential effort) by the Boy Scout who then took Randall’s place columns—trying to slow down the horses just as we’re as the escort? starting down a long incline is at best impractical, and at Kinda feel like Tony Randall myself, these days. worst, foolhardy. Should we edit out material to reduce You’ve heard it said enough that you’re probably the length of articles? Well, we can cover a topic in less getting tired of it, but we’re not in Kansas anymore. space, but often people want more of the specifics. There’s an overwhelming amount of information needed We wanted to find a way to deliver more material to keep current with Visual FoxPro development—not within budget. FoxTalk is comparably priced to the just Fox, but all of Visual Studio, as well as bigger and competition, once you compare apples to apples. You more complex operating systems and delivery have access to the Subscriber Downloads/companion mechanisms. You need as much high-end, quality disks free with your paid subscription—not as an added information as you can get. It’s vital to your career. And charge, as some publishers do. More to the point, we offer for years now, FoxTalk has been the best place to get it. As the kind of high-end information that you just can’t get editor of this publication, I feel obligated to deliver as elsewhere. Still, we were committed to figuring out a way much in-depth information—on the widest variety of to give our subscribers even more of the information you topics—as possible. need while maintaining costs. Continues on page 22

Editor Whil Hentzen, Editorial Advisory Board Scott Malinowski, Walter Loughney, Les Pinter, Ken Levy, Publisher Robert Williford, Editorial and Fax Vice President/General Manager Connie Austin, Managing Editor Heidi Frost, Copy Editor Farion Grove Subscription 770-565-8232 Information FoxTalk (ISSN 1042-6302) is published monthly (12 times per year) purpose. Pinnacle Publishing, Inc., shall not be liable to the 770-565-1763 Mail by Pinnacle Publishing, Inc., 1503 Johnson Ferry Road, Suite 100, purchaser or any other person or entity with respect to any liability, 800-788-1900 PO Box 72255 Marietta, GA 30062. The subscription price of domestic loss, or damage caused or alleged to be caused directly or indirectly Marietta, GA subscriptions is: 12 issues, $179; 24 issues, $259. Periodical postage by this publication. Articles published in FoxTalk reflect the views of E-mail 30007-2255 paid at Marietta, GA and at additional mailing offices. USPS#005373. their authors; they may or may not reflect the view of Pinnacle [email protected] POSTMASTER: Send address changes to FoxTalk, PO Box 72255, Publishing, Inc. Inclusion of advertising inserts does not constitute Marietta, GA 30007-2255. an endorsement by Pinnacle Publishing, Inc. or FoxTalk. Pinnacle Web Site Copyright © 1998 by Pinnacle Publishing, Inc. All rights reserved. No Subscription information: To order, call Pinnacle Customer Service http://www.pinpub.com part of this periodical may be used or reproduced in any fashion at 800-788-1900, or 770-565-1763. Cost of domestic subscriptions: whatsoever (except in the case of brief quotations embodied in 12 issues, $179; 24 issues, $259. Canada: 12 issues, $194; 24 issues, critical articles and reviews) without the prior written consent of $289. Outside North America: 12 issues, $199; 24 issues, $299. Pinnacle Publishing, Inc. Printed in the United States of America. Individual issues cost $17.50 ($20 in Canada, $22.50 outside North America). All funds must be in U.S. currency. Brand and product names are trademarks or registered trademarks of their respective holders. Microsoft is a registered trademark of For European newsletter orders, contact: Tomalin Associates, Microsoft Corporation. The Fox Head logo, FoxBASE+, FoxPro, and Unit 22, The Bardfield Centre, Braintree Road, Great Bardfield, Essex Applies to VFP v5.0 Applies to VFP v3.0 Applies to FoxPro v2.x Visual FoxPro are registered trademarks of Microsoft Corporation. CM7 4SL, United Kingdom. E-mail: [email protected]. FoxTalk is an independent publication not affiliated with Microsoft Tel: +44 (0)1371 811299. Fax: +44 (0)1371 811283. 12 issues: £179 Corporation. Microsoft Corporation is not responsible in any way for the editorial policy or other contents of the publication. For Australian newsletter orders, contact: Ashpoint Pty., Ltd., 9 Arthur Street, Dover Heights, N.S.W. 2030, Australia. Accompanying files available online This publication is intended as a general guide. It covers a highly Phone: +61 2-9371-7399. Fax: +61 2-9371-0180. at http://www.pinpub.com/foxtalk technical and complex subject and should not be used for making E-mail: [email protected]. Web: http://www.ashpoint.com.au. decisions concerning specific products or applications. This publication is sold as is, without warranty of any kind, either express FoxPro technical support: Call Microsoft at 206-635-7191 or implied, respecting the contents of this publication, including (Windows) or 206-635-7192 (). Applies specifically to one of these platforms. but not limited to implied warranties for the publication, performance, quality, merchantability, or fitness for any particular Send all other questions or requests via the options at right.

2 http://www.pinpub.com If you look at an ADT within VFP, you’ll find an Stored Procedures . . . abstract memory relationship (that is, class definition) Continued from page 1 between data types (properties) and operations and stored procedures (methods). Conceptually, our example is no different: What is a stored procedure? VFP defines a stored The abstract memory relationship is created between procedure as: “A procedure stored in a . The our runtime cursors. The properties between the data procedure can contain any commands and functions types are the data items and their corresponding values allowed in a user-defined function.” I don’t see anything within the survey. Finally, our stored procedures that here that can’t be done in FPW. will operate on these data items represent our methods. However, some VFP developers will dispute the fact Here’s the query from the survey definition table, that FPW has anything remotely similar to a “Database.” relevant to our example:

Let’s not limit our understanding of the word “Database” select field_name, field_type, field_len, field_dec ; to imply a Visual FoxPro Database Container (DBC). The from surveydef ; where clientid = cClient ; word “Database” is in such common use, we’ll take a And surveyid = cSurvey ; moment to define the word and its context. order by order ; into array Struct A “Database” is a collection of related data. “Data” refers to a logical collection of known facts that can be The “Struct” array represents the definition of recorded, organized for a specific purpose, and have our value set. SELECT field_name, field_type, field_len, implicit meaning when viewed collectively. Admittedly, field_dec... gives us an abstract template from which to VFP is a true relational DBMS; however, this doesn’t create a specific value set (from the collection of all mean that FPW, although lacking a DBC, can’t be used to possible value sets—that is, our survey flat file) that will manage a “Database.” Don’t confuse VFP’s DBC with a represent the complete field structure for each type in the Database Management System (DBMS). When dealing ADT. The fields used in the structure description, for each with VFP’s DBC, most of the relational management can data item, are identical to FPW’s structure extended types be achieved implicitly through the DBC. With FPW, those (see the COPY STRUCTURE EXTENDED command in the same relations can be realized and managed, although FPW online help). Now we need to take this definition you’re explicitly responsible for their implementation. In one step further and actually produce our value set. fact, the FPW stored procedures just mentioned will Remember, all we have so far is the definition; what we become the stored procedures that will act upon our need is an ADT. We mustn’t lose track of how ADTs are database. Now we’re on our way to defining an ADT. created in FPW: They come from the creation of a cursor (or table), the existence of relationships between cursors, Creating ADTs and a set of procedures that act on that value set. We can First, let’s look at some relationships that can be use FPW’s (or VFP’s) CREATE CURSOR command to established between tables (FPW or VFP). We’ve taken create the value set for this example: these relationships from an FPW application that was CREATE CURSOR ValueSet FROM ARRAY Struct written to manage ad hoc customer surveys. Surveys are defined in a flat file for ease of definition, training, and Now we have the value set created—and the management; moreover, for these same reasons, the table important thing to remember is that our value set looks isn’t normalized. The real important parts of this flat file exactly like all the other cursors that you’ve been working are the survey questions, the choices to those questions, with. We haven’t redefined the FPW CURSOR-data-type; and the data type that collects each choice. In our survey we’ve just shown you how to look at this data type in definition file, you can have choices that are any data another way—as the basis for describing an ADT. By type supported in FPW. Collectively these data types looking at a cursor definition in this manner, our hope is create a set of values, and the stored procedures represent to help you understand that ADTs, in languages that the operations that can act on those values (sounds support OOP, aren’t built any differently. We’ve taken a suspiciously like an ADT). base type from the language (Cursor) and built a custom Specific surveys are rendered at runtime by querying type from it (Survey value set). the survey definition table (our flat file) with specific You’re probably thinking, “Where are the stored customer and survey IDs. Once all the survey questions, procedures?” The other query in our example takes care choices, and data structures are collected, the resulting of this missing item. Remember where this example came cursors are then related together to form a value set. from. The Survey definition flat file holds many surveys What’s a value set? The individual data types don’t carry that are logically grouped together by client and survey much meaning, but as a relational data set (for instance, IDs. The stored procedures for each data item also carry value set), they form a survey. Moreover, the survey this client-survey identification. So the query for our establishes our fundamental ADT. stored procedures is as follows: http://www.pinpub.com FoxTalk May 1998 3 SELECT field_name, script, run, when, valid, force ; = FCLOSE(nFxpHandle) FROM Udf ; DELETE FILE 'run.fxp' WHERE clientid = cClient AND surveyid = cSurvey ; ENDIF INTO CURSOR StoredProcs = FCLOSE(nPrgHandle) DELETE FILE 'run.prg' ENDIF Now we have another cursor that belongs to our ENDSCAN ADT—one that holds only the stored procedures relevant to our example. Let’s clarify the structure of this cursor. To answer the question “How do we get FoxPro SELECT field_name, script, run, when, valid, force... gives us to compile at runtime, outside of the development the results shown in Table 1. environment?”—well, we really don’t. After writing procedure code for our “script” memos, we run that code through the preceding Code-to-Binary converter. Then, Table 1. The structure of the cursor created for stored procedures. at runtime, instead of processing the code itself, we make Parameter Description the decision to load whichever file type is needed: code “Field_name”, The name of each data item in our value set. (.prg) or binary (.fxp). The following function shows “Script”, The xBASE-script (procedure code) for each field. how to do it: “Run”, The compiled binary equivalent. FUNCTION SvUdf “When”, A logical flag to tell us to fire this code during the PRIVATE cFile, nHandle IF SET('DEVELOPMENT') = 'ON' “when” event. cFile = FULLPATH('RUN.PRG') nHandle = FCREATE(cFile) “Valid”, A logical flag to tell us to fire this code during the = FPUTS(nHandle, StoredProcs.script) “valid” event. = FCLOSE(nHandle) DO (cFile) “Force”, A logical flag that tell us to always fire the valid code DELETE FILE (cFile) during the “valid” event, regardless of whether an DELETE FILE (STRTRAN(cFile,'.PRG','.FXP')) error has occurred. ELSE cFile = FULLPATH('RUN.FXP') nHandle = FCREATE(cFile) = FWRITE(nHandle, StoredProcs.run) If you’re wondering, there’s a way to implement = FCLOSE(nHandle) both “when” and “valid” scripts for a single data item, DO (cFile) DELETE FILE (cFile) but for this example we’ll make them mutually exclusive. ENDIF So, we’ll make available the “when” event, the “valid” RETURN .T. event, but for now, not both. How did we put the Of course, if you’re thinking about taking this compiled binary equivalent of the stored procedure in example and implementing a production environment, a memo field? Another question you’re probably asking you’ll want to add error checking and possibly return is, how do we get FoxPro to compile at runtime, outside something more valuable than “True” to the caller. But the of the development environment? We have answers to nuts and bolts are simple enough that, with a few more both questions. lines of code, you too could start telling everyone that you can compile FoxPro stored procedures at runtime— Storing Xbase code as a binary memo without the aid of the development platform. Also worth The following code block handles the conversion from noting here is that the stored procedures in this example Xbase to binary and stuffs the compiled code into a memo can be anything. Any Xbase code will work. field. This code also assumes that you’re calling your So now we have the pieces to create our ADT. We stored procedure table UDF.dbf, and that both the “script” have the value set, which represents our collection of and “run” fields are memos. Some bounds checking that’s data types, and we have our stored procedures that will hinted at (but not literally enforced) is the maximum operate on those data types. We’re almost there—all we bytes written to any given “run” memo. We’ve limited need is the event-driven relationship between the value the number of bytes to 4096 to emphasize two things: set and the stored procedures. 1) always think about code optimization; and 2) anything much over 4096 bytes and you’ll start to pay a price for Creating relationships speed at runtime. It’s our hope that after we’ve outlined the relationships * EXTERNAL: Public table 'Udf', that drive our stored procedures, you’ll have ideas of * part of the Survey system module. #DEFINE _READ 10 your own for not only other relationships that drive the #DEFINE _MAX_BYTES 4096 method code, but also for enhancing this example so it PRIVATE nPrgHandle, nFxpHandle SELECT Udf applies to other implementations. The relationships that SCAN ALL nPrgHandle = FCREATE('run.prg') we’ll use aren’t necessarily the ones that exist in our IF nPrgHandle > 0 Survey definition application, but this isn’t important = FWRITE(nPrgHandle, Udf.script) COMPILE 'run.prg' NODEBUG to our example. What’s important is that your indexes nFxpHandle = FOPEN('run.fxp', _READ) be character based. That way, you’ll be able to take IF nFxpHandle > 0 REPLACE Udf.run WITH FREAD(nFxpHandle, _MAX_BYTES) advantage of FoxPro’s string concatenation operations

4 FoxTalk May 1998 http://www.pinpub.com and not get involved with translations from one data most misunderstood and consequently overlooked type to another. This will also give you the ability to commands. Look in the FoxPro Help for a complete concatenate one of FoxPro’s built-in functions to achieve listing of all the available switches for the Browse the power of event-driven code. (Which function? Read command. Also look into its aliases—Edit and Change. on.) It’s also important that you completely understand And remember: The Browse command works the same the inner workings of the BROWSE command and the in VFP as it does in FPW. VARREAD() function. Let’s see why. When establishing our relationships between our Table 2. Examples of event hooks in the BROWSE command. value set and the operations that act on those values, the question becomes, how do we get the power of event- Parameter Description :W = lExpression2: This event determines driven code to take place in FPW? Well, the answer whether the cursor can be comes from taking a look at where events happen in that moved to a field. platform. In this example, let’s use the events from the :V = lExpression1 [:F] [:E = cMessageText]: This event lets you perform Browser window or, more accurately, the Edit command field-level data validation equivalent of the Browser window. We use the Browse within the Browse window. window for two reasons: First, it works the same from :F Forces the VALID clause to FPW to VFP, and second, the Browse command is a execute before the user moves relatively simple example of an event-driven runtime the cursor to the next record. environment that works from within FPW. :E Forces your error message text In both FPW and VFP, if a Browse, Change, or Edit to appear instead of the system error message. window is active, VARREAD() returns the name of the current field. In FPW, the field name is returned with the Conclusion first letter of the field name capitalized (proper). In VFP, To put OOP style into your programming, abstract data the field name comes back in all caps (thank Microsoft for types (ADT) must be available to the language. But an that little “gotcha”). At any rate, this means we can tap ADT is just a set of values and a collection of operations into code that’s executed when an event occurs or code that can act on those values. In languages that support that can also be called programmatically. An event is just OOP, ADT is a user-defined extension to the native types an action recognized by an object. Moreover, code can be available in the language. However, in a language that written that provides the object’s response. This event can doesn’t support OOP, we can still realize fundamental be triggered by a user’s action on the object or by a ADTs. This example has shown you how to implement system action on the same object. stored procedures in FPW. VFP defines a “Stored Procedure” as “A procedure stored in a database. The Putting it all together procedure can contain any commands and functions We’ve fundamentally defined a collection of data types allowed in a user-defined function.” Our example defined and a set of operations that operate on those data types. a custom set of values and at runtime relates those values Now let’s put it all together and see how VARREAD() can with the operations that are allowed to act upon those act as the relationship that drives our events. First, index values. Therefore, we defined an ADT, and by doing so, the “StoredProcs” cursor on field_name, then create the added a little OOP style to our programming. ▲ VARREAD() relationship between the “ValueSet” cursor and the “StoredProcs” cursor. Here’s how to do it: 05MALIN.ZIP at www.pinpub.com/foxtalk SELECT StoredProcs INDEX ON field_name tag EventName Dave Jinkerson has been developing database solutions and SELECT ValueSet SET RELATION TO LOWER(VARREAD()) ; programming in FoxPro for five years, and he’s a Microsoft Certified INTO StoredProcs ADDITIVE Product Specialist in the Visual FoxPro platform. Dave has also been researching and developing in the object-oriented paradigm for seven Now when the user is in the Edit mode of the Browse years. He is currently a principal consultant and technical advisor for window, the corresponding event code will be available to Desert Fox Software, Inc. in Scottsdale, AZ. [email protected]. the environment. But you’re probably asking, “What good is that, if I can’t respond to the event with code?” But you Scott Malinowski is the owner of Desert Fox Software, Inc., a Scottsdale, can! Look in depth at the Browse command, and you’ll AZ-based software development firm that specializes in providing find all the event hooks that you’ll ever need to respond complete Internet/intranet database solutions. He has over 23 years of to your event-driven model. Some examples are shown in RDBMS application development experience, including 12 years with Fox Table 2. products. He is a Microsoft Certified Product Specialist in VFP. He’s in his The Browse command is one of the most powerful fourth year as a member of FoxTalk’s Editorial Advisory Board. 602-905- commands in FoxPro and, in our opinion, one of the 8125, fax 602-905-1257, [email protected].

http://www.pinpub.com FoxTalk May 1998 5 Best Practices FoxTalk

Development Checklist: Object-Oriented Design, Part 2 Jefferey A. Donnici

Continuing with last month’s discussion of object-oriented who need to use it in their applications. I also think it’s design, this month’s column is the latest in an ongoing series important to not create subclasses that differ from their of developer-related checklists intended to provide things to superclass only in their visual attributes. For example, a watch out for during the lifecycle of a software development “standard text box class” shouldn’t be subclassed to create project. As mentioned last month, the design process is one a “standard BOLD text box class.” The difference between of the most important steps in an object-oriented software the two is purely visual, and there isn’t any “functional” project. The attention and care taken during this process will difference between the way the two would be used. ease the way for future maintenance and flexibility. If, on the other hand, the “standard text box class” is subclassed to create a “standard SUBTOTAL text AST month, I prepared to write the column with box class,” then there’s a functional, and organizational, a list of items to watch for when working on the difference between them—even if the only external Ldesign phase of an object-oriented application. I difference is that the subclass is bold. Whenever you need also felt it was important to give some background to represent a subtotal value on a form, it’s obvious which information on what the design phase consists of and text box class definition to use. Furthermore, if a client why it’s so important. Once that explanation was out of changes his or her mind with regard to the way subtotals the way, I started down the list of “gotchas” that I’ve seen are displayed, you have a single point in the class in my experience with object-oriented design and read hierarchy to be changed. about in a variety of references. Before I knew it, though, I had filled up a substantial amount of article space and Is every subclass of a given class definition a was still only halfway down the complete list that I’d legitimate instance of the parent class? prepared. Rather than irritate the editor, the publisher, Before creating a new subclass, ask yourself whether or and probably the reader, I opted to split the topic into two not the need for the new class will always be partially pieces and cover the design checklist in two parts. You filled by an instance of the superclass. In other words, now hold in your hands the second half of the checklist does every instance of the child class definition always for object-oriented designs. require the functionality and information provided by its Before going on, you might find it helpful to re-read superclass? Only when the subclass can legitimately be the first portion of last month’s Best Practices column. identified as a specialization of its superclass does an This will provide a brief refresher on the definition, inheritance relationship between them make sense. concepts, and terms involved with an object-oriented If you can’t answer the above question with a “yes,” design. With that information behind us, let’s jump right you might look at creating an abstract base class from into the remaining checklist items and their explanations. which the existing class definition and its intended subclass both inherit. This allows them both to inherit Does each new class definition introduce some what’s common between them, but it also provides the new and substantial functionality? layer of specialization that both need to provide the One of the quickest ways to build an unmanageable class specific functionality required of them. hierarchy is to create new subclass definitions for every little need and whim that arises during development. I’m Do classes with common behavior and of the opinion that each class definition should provide information, but different jobs within an overall some new and significant functionality or level of solution, inherit from the same “base” class? organization so that the overall class hierarchy doesn’t This checklist item is somewhat similar to the last one, become bloated and intimidating to the developers but it applies specifically to the role that each class plays

6 FoxTalk May 1998 http://www.pinpub.com within the overall system design. On occasion, two classes This approach also has two added benefits that are will be using similar data (properties) and functionality important to note. The first is that there’s probably a lot of (methods) to complete very different tasks. In this case, common behavior between the various subclasses of the using an inheritance relationship between the two classes abstract superclass. Using the exports example, most doesn’t make sense, because the one class that ends up as export types would need to do things like validate paths, the subclass needs to completely redefine its role from its confirm that sufficient write privileges exist, and verify superclass—instead of the preferred specialization of the that an export file of the same name doesn’t already exist. superclass’s role within the system. Because these requirements are common to all export Instead, create a common “base” class that provides types, they can be implemented in the abstract superclass the methods and properties that are common to the so that each subclass has them available. The other benefit needs that both classes must fulfill. Inheriting from that is that the subclass for each export type can use or do base class, create the two new class definitions that meet anything else it needs to get its own job done without the the specific needs of the system. This approach allows exporting mechanism (the instantiating component) the common superclass to be modified when the having to know that the subclass is doing something shared functionality must change, but still provides a different from the others. One example of this might be layer of specialization for the subclasses to perform the HTML export class. In truth, there’s not a lot of their own tasks. difference between an HTML document and a standard text file. It might be most efficient for the HTML export Do interchangeable classes within a component class to use an instance of the text file export class to do have the same public interface and inherit from a portion of its job. Once the initial text file class has the same abstract class? completed, the HTML export might then process the In a well-designed application, it’s common for some class resulting text file to create the HTML document. There definitions to be used interchangeably at runtime to meet are, of course, a multitude of other possibilities, but the a specific need of the application. Typically, this involves point here is to remove the specialized needs from the some component that must dynamically decide what mechanism that needs them so that the mechanism can functionality is required and then instantiate the specific call on them each in the same fashion. class definition that meets that need. In cases like this, it’s unwieldy and difficult to maintain if the component that Is the public interface for a class definition as instantiates the specialized classes also has to know a high as needed in the class hierarchy so that different public interface for each of the resulting objects. inheritance can enforce consistency? To avoid this, provide a single “abstract” class One question that often comes up in an object-oriented definition (that is, a class definition that’s never meant to class design is “at what level of my class hierarchy be instantiated) from which all the specialized classes should I put this functionality?” Of course, the coy (those that are being instantiated by the component at (yet true!) answer to this is “as high in the hierarchy as runtime) can inherit. This allows the component that uses necessary, but not any higher.” The point here is to these classes to know a single interface and speak to each provide common requirements (both functionality and instantiated class without having to know anything about data) at a sufficiently high level so that it can be reused the nature of that class’s specialization. A good example of as much as possible. this is a “file export” mechanism that’s probably common Similar to the preceding example, the functionality of to many applications. The exporting mechanism doesn’t the individual export classes is different enough that each know until runtime whether the user wants to export should be its own class definition. However, because there data as an Excel workbook, a text file, or an HTML Web are a number of common requirements between these page. Given that, it makes sense to have a different class classes, it makes sense for those requirements to be definition for each of these export types and provide the defined further up the class hierarchy (in that case, they exporting mechanism with the ability to instantiate each belong in the “abstract export class”). Once again, this as needed. The point of this checklist item, however, is means that the classes inheriting from the superclass are to suggest that the Excel class, the text file class, and forced to have the same public interface as their parents the HTML class all inherit from the same “generic (with some specialization as required). This provides export” superclass. This way, the exporting mechanism interface consistency to other components and classes instantiates the one required and then deals with the within the system and also maximizes the level of resulting object through a common programmatic reusability within the system. As one moves down the interface. Aside from determining which class class hierarchy, each new class is consistent with its definition to use, the exporting mechanism doesn’t superclass while also providing the specific behavior need to know anything else about the internals of and information required as part of its role. creating the export file. http://www.pinpub.com FoxTalk May 1998 7 Do parent classes in the hierarchy avoid any the class definition—the chances for reusability are assumptions or extra considerations for their greatly reduced. eventual subclass(es)? When a given class is being designed, the considerations Are container classes maintaining responsibility for for that class should be specific to the role that the class their members, but avoiding dependency on the plays within the overall system. Aside from using good classes that they will themselves be members of? design practices for reusability, the superclass shouldn’t Moving through the different types of relationships be making any assumptions about the class(es) that might between objects at runtime, this checklist item refers to eventually inherit from it. In other words, any specialized the “has a” relationship between two classes. When a behavior or information that might be needed by a container class (such as a form) has member objects on subclass, but isn’t required by the role of the superclass, it (such as buttons, text boxes, and labels), there’s a doesn’t belong in the superclass’s class definition. Instead, “has a” relationship between that class and its members. that specialized functionality and/or data should be One of the tenets of object-oriented programming is implemented at the subclass level in the class hierarchy. “encapsulation,” which says that objects should The reasoning behind this checklist item is similar to communicate only though a defined interface and that the most: flexibility and reusability. When a superclass has internals of any given object or component shouldn’t be been implemented with assumptions or considerations for exposed to the objects it collaborates with. Aside from an expected subclass, the reusability of the class is hiding complexity, this also means that a new object or typically limited to the subclass role that’s been planned component—one with the same interface—can be in advance. If the superclass has been designed without substituted into the system in the future without regard for the types of subclasses that might inherit from disruption to the other elements that use it. What this also it, then the types of subclasses—and the roles they means, however, is that those external objects that use a perform within the system—are no longer limited by the component shouldn’t have to deal directly with the assumptions made further up in the hierarchy. member objects within the component. Instead, the container component (called a “composite”) should Does a class definition avoid assumptions or provide the mechanisms on its own interface for external dependencies on the objects that will eventually elements to affect and get information from its members. be using an instance of it? While it’s important for a container to maintain This checklist item is somewhat similar to the previous responsibility of the member objects is contains, it’s one, but it refers to a different type of relationship also important that the composite not be written with between objects. While the previous discussion was dependencies or assumptions about the objects that might specific to the “is a” (inheritance) relationship, this item eventually contain it. In other words, the composite discusses the “uses a” relationship that can exist between container might at some point need to perform its role as two objects. Just as a class definition should avoid making a member object within another container. As long as the assumptions about the possible subclasses it might composite doesn’t have any dependencies on the other eventually beget, it should also be designed without composites that could use it, it can be used throughout a assumptions about the future “collaborators” it might system without putting unnecessary constraints on its interact with. implementation. While it sounds a little odd to think of it this way, no object exists in a vacuum, right? Eventually, two (and Do container classes provide sufficient interface usually more) objects will need to exchange information to the external system to handle the messaging and behavior in order to complete some larger task that’s required to and from the objects they contain? beyond the scope of either object. In order to do this, the As in the preceding checklist item, it’s important that a objects must have a defined public interface through composite container contain enough functionality on its which they can communicate with each other. Problems interface for other, external components to fully utilize its can arise, however, when the internals of the object and its members’ capabilities. An example of this would (“inside” the black box) make assumptions about the be an “address” container that has a series of text boxes to other objects it might eventually interact with. Once gather name, address, city, state, and ZIP. A composite of again, the point behind this checklist item is to ensure the this type could have a high level of reusability in a system highest level of reusability for each class definition in the and would ultimately be interacting with a wide variety system design. When a single class makes assumptions of other components. Those other components, however, about the object(s) it will interact with at runtime, its shouldn’t have to know how, for example, to address the reusability is limited to interaction with objects that meet “ZIP” text box. To do so, they’d need to know the name of that assumption. Should an object come along that doesn’t the text box within the composite container, as well as its meet the assumption—but could still use the services of position in the overall containership hierarchy.

8 FoxTalk May 1998 http://www.pinpub.com Instead, the other components should only interface display that text, then you have to know to go into the with the composite container itself, while it has button within the container and change its Click event. responsibility for maintaining that member text box’s Even worse, they could decide that they’d rather have a information. This can often be accomplished through the check box performing that button’s role. When you use of “accessor” methods, which provide the ability to remove the button from the container, the logic associated “get” and “set” values within the composite container. with it is gone as well. In a more complex example, it’d be Using methods like SetZipcode and GetZipcode, it’s tough to remember all the code that was dealing directly possible for external mechanisms to manipulate the with the sibling. internals of the “address container” without having to The solution to this dilemma is to provide an know the specifics of how the container was “OnClick” method, for example, at the composite implemented. container level. This method would contain all the logic that needs to occur when the user presses the button and Do “sibling” member classes within a container/ would be called from the Click event of the button. In composite communicate only through their addition to making sure that the container keeps common parent container? responsibility for its member objects, this also ensures The final checklist item refers to the communications that any change within a certain member object has a between member objects of the same container. These very limited effect on its siblings. objects, called “siblings,” are all at the same level of what’s called “lexical scope” because they’re all at the Whew! same place within the containership hierarchy. What’s That concludes my list of issues to watch out for when important to remember, however, is that these “siblings” you’re working on an object-oriented design. Next month shouldn’t actually have communications directly with will bring about a completely new checklist topic, but one another. Instead, it’s far more flexible for all the don’t think that’s the end of the design discussions. I’m communications to go through the common “parent looking forward to hearing your thoughts on some of the container” that the two member objects share. design-related items that I’ve mentioned, as well as your There are a number of things to be gained by using suggestions for other items to bring up in the future. If this sort of mechanism, not the least of which is that all you’re interested in these types of design-related the code for a complex composite class is handled at one discussions, I’d highly recommend the book Object- central point (on the composite itself). Also, having the Oriented Design Heuristics by Arthur J. Riel (published by code reside at the composite level allows it to act as a Addison-Wesley, ISBN 020163385X). Many of the issues “mediator” to the sibling objects it contains. This allows found on this checklist are things I first read about in this different siblings to be added and removed to and from book and then, despite that reading, experienced later in the composite without disrupting the functionality of the all their glory (meaning the hard way). After working other siblings. The only changes that must be made would with a couple of different object-oriented languages now, exist at the composite level, and, even then, those changes I’ve repeatedly found it to be an excellent resource. Once would typically consist of pointing to a new sibling. again, I’ll be following this series of checklists with some Consider the case of a button that must change a label’s of the feedback and suggestions I get from readers, so caption while both of them live within a parent container. don’t hesitate to send me your thoughts and opinions! ▲ The easy option is to put code in the button’s Click event that looks like this: Jefferey A. Donnici is an applications developer who specializes in reusable components and developers’ tools with Resource Data THIS.Parent.lblLabel.Caption = "Foo" International, Inc. in Boulder, CO. Jeff is a Microsoft Certified Professional In this small example, the problem is solved and the in Visual FoxPro and a three-time Microsoft Developer Most Valuable label gets updated as needed. If, however, the clients Professional. 303-444-7788, fax 303-444-1286, [email protected], decide that they’d rather have a read-only text box [email protected].

Go ahead, add a line to your résumé—“Published Articles.” Earn Money! If your tip shows up in the pages of FoxTalk, we’ll send you $25. See page 2 for the address . . . and see your name in print where you can send your tips.

http://www.pinpub.com FoxTalk May 1998 9 Reusable Tools FoxTalk

Build Your Own Wizards Doug Hennig

Wizards provide an ideal way of holding your users’ hands (text file, Excel, Word, e-mail, HTML, and so forth) through complex, multi-step tasks. This month, Doug presents and filename. The Finish step would do the a set of classes you can use to build your own wizards. actual exporting.

E’VE all used wizards for several years now. • An Import Wizard might be complicated to build, FoxPro has included wizards since version but it would be a handy way for users to get existing W2.x (the Setup Wizard was one of the first), data into your application. GoldMine (a Windows Windows 95 includes all kinds of wizards for things like contact manager we use at Stonefield) has a slick setting up printers and dial-up connections, and even import function, although it’s not exactly laid out like end-user applications like Word and Publisher include a wizard in the version we use. You specify the data wizards. Why all the emphasis on wizards? source type (for example, DBF or text file) and A wizard is the ideal user interface for: filename, then define a mapping between the fields in the data source and the contact tables. You can • tasks that have a lot of steps either view data source field names or the data content (you can even move to the next and previous • complex tasks where it’s easy to make a mistake records, which may help identify the data). Mappings or forget a step can be saved in case you regularly import the same file structures. • non-trivial tasks that aren’t performed very often • I’m currently working on an application for a client in • a user base that’s somewhat inexperienced which several tasks don’t easily fit into the concept of with computers forms. For example, to schedule a photo shoot, the user has to identify which magazine and issue the This is because a wizard takes an overall task and shoot is for, select the items to be shot, enter details divides it into a set of logical steps. The user can choose on how to shoot them, and print several reports—all the order in which to perform the steps, or even return to this is considered to be a single task. The Photo Shoot an earlier step to correct an error. It provides a way to Wizard will ensure that the user doesn’t skip any hand-hold the user through an operation, making it easy steps in this complex task. for him or her to understand, while at the same time ensuring the task is properly completed. Although they do vastly different things, wizards This doesn’t mean you’ll replace all your data entry have a common interface. They have a series of steps forms with wizards. Forms, even those with page frames for the user to perform, each step having its own set of and lots of controls, are still often the best user interface instructions and controls in the same form (usually done for viewing and editing records in tables. However, as a page in a tabless page frame). Back and Next buttons consider using a wizard when you have a clearly defined take the user from one step to the next, and a combo box sequence of steps that make up a task. Here are some allows the user to choose any step in any order (assuming examples of where a wizard might be appropriate: the wizard’s rules permit this). A Cancel button cancels the task, and the Finish button completes it. Sounds like • An Export Wizard can be a generic tool you include the place for a class definition, doesn’t it? Figure 1 shows a in your applications to allow the users to get data wizard we’ll build in this article, the Order Wizard. out in a format other than reports. Step 1 might be to select the table to export from, Step 2 to select Wizard forms the fields to export, and Step 3 to choose the format SFWizardForm (defined in SFWIZARD.VCX, available in

10 FoxTalk May 1998 http://www.pinpub.com .SelectStep(1) the Subscriber Downloads at www.pinpub.com/foxtalk) .RefreshSteps() is the base class for wizard forms. It’s subclassed from dodefault() SFForm, our Form base class defined in SFCTRLS.VCX. endwith It has a private datasession, so it doesn’t interfere with SetupSteps anything else, and optimistic buffering, so changes made is an abstract to tables can easily be reverted. method (you fill Since a wizard’s tasks typically consist of several in the code that steps, the form has an SFWizardPageFrame object (I’ll defines the discuss this later) with one page per step. To maintain the contents of the same interface as Microsoft’s wizards, the page frame has aSteps array for its Tabs property set to .F. so the users can’t select a step the wizard Figure 1. The Order Wizard. by clicking on a tab; they have to select a step from a you’re creating), combo box or by using the Next and Back buttons. Also, although it has a single line to requery the cboStep combo all controls on the page frame appear on the right half of box (which is bound to aSteps); at the end of the custom each page, leaving room for graphics representing each code, you just use DODEFAULT() to update the combo step on the left half. box. Here’s an example of what might go in this method SFWizardForm has the custom public properties (taken from the Order Wizard): shown in Table 1.

with This .aSteps[1, 1] = 'Step 1 - Order Information' Table 1. Custom properties of SFWizardForm. .aSteps[1, 2] = 'not empty(ORDERS.ORDER_DATE) ' + ; 'and not empty(ORDERS.EMP_ID)' .aSteps[2, 1] = 'Step 2 - Select Customer' Property Description .aSteps[2, 2] = 'not empty(ORDERS.CUST_ID)' aSteps Array of steps; one row per step and three columns: .aSteps[3, 1] = 'Step 3 - Select Products' 1 = step displayed to the user .aSteps[3, 2] = 'ORDERS.ORDER_AMT <> 0' 2 = “step complete” rule expression .aSteps[4, 1] = 'Step 4 - Finish' 3 = .T. if the step is complete .aSteps[4, 2] = 'not empty(ORDERS.SHIP_VIA)' dodefault() endwith nCurrentStep Current step nMaxSteps Number of steps in the wizard The expression that goes in the second column can test the value of controls on the step’s page, if The aSteps array drives a couple of things: a combo desired. To make it easier to reference them, the page is box (cboSteps) from which the user can select a step (its automatically referenced via a WITH statement before RowSource property is set to Thisform.aSteps), and the trying to evaluate the expression. This means you can use determination of when to enable the Next and Finish something like “not empty(.txtDate.Value)” rather than buttons. Since the user can’t go on to a particular step having to specify the complete object hierarchy (such as until all tasks up to that step are complete, the second “not empty(Thisform.pgfWizard.Page3.txtDate.Value)”). column of aSteps contains an expression for each step Note that this code doesn’t populate the third column of that, if evaluated to .T., indicates the step is done. The aSteps; that column is taken care of by other methods. expression could check for certain controls on that step’s The SelectStep method is called to select a particular page having valid values, or it could call a custom step; Init calls it to select step 1, the Next and Back method for more complex validation. The third column of buttons call it to select the next or previous step, and the the array is set to .T. if this step and all preceding steps are cboSteps combo box calls it to select the specified step. If done; only then can the user move to the next or last step. SelectStep returns .F., the specified step can’t be selected The Init method dimensions the aSteps array to as because prior steps aren’t finished. SelectStep calls the many rows as there are steps (the nMaxSteps property) abstract method StepDone, which allows you to perform and three columns, then calls the SetupSteps method to any code prior to leaving a step or prevent the step from populate this array. It selects the first step by calling the being left under certain conditions (returning .F. does SelectStep method and passing it 1 for the step number. It this). After setting the nCurrentStep property to the then calls the RefreshSteps method to refresh all controls specified step, the abstract StepSelected method is called, based on the selected step. Here’s the code for Init: where you can put any code that needs to occur when a page is selected. with This assert .nMaxSteps > 0 ; message 'Set nMaxSteps to a valid value' lparameters tnStep assert .pgfWizard.PageCount ; local llReturn = .nMaxSteps message "nMaxSteps doesn't ; with This match pgfWizard.PageCount" assert between(tnStep, 1, .nMaxSteps) ; dimension .aSteps[.nMaxSteps, 3] message 'Invalid step passed to SelectStep' .SetupSteps() if (tnStep <= .nCurrentStep or ; http://www.pinpub.com FoxTalk May 1998 11 .aSteps[tnStep - 1, 3]) and ; .StepDone(.nCurrentStep) last step and the current step is completed .nCurrentStep = tnStep (Thisform.aSteps[Thisform.nCurrentStep, 3] is .T.). .StepSelected(.nCurrentStep) .Refresh() The Finish button is only enabled when the last step llReturn = .T. is done (Thisform.aSteps[Thisform.nMaxSteps, 3] is .T.). else llReturn = .F. Its Click method calls the Finish method of the wizard endif tnStep <= .nCurrentStep ... form; this is an abstract method (except it releases the endwith return llReturn form in this class) because the action to take when the user clicks on this button varies from wizard to wizard. The RefreshSteps method is called when the value of The Cancel method of the form is called from the any control changes (you’ll see how that’s done later). It Click method of the Cancel button and from the evaluates the second column of each row in the aSteps QueryUnload method of the form (so clicking on the array, and sets the third column to .T. only if each step form’s Close box cancels the wizard). This method and all prior ones are complete. If a step can’t be selected reverts all changes in all tables, so you might not need because prior steps aren’t complete, RefreshSteps adds a to customize this method in a particular wizard. Here’s backslash prefix to the step description in the first column the code: of aSteps so it’ll be disabled in the cboSteps combo box. local laCursors[1], ; lnI, ; local llOK, ; lcCursor lnSteps, ; for lnI = 1 to aused(laCursors) lnI, ; lcCursor = laCursors[lnI, 1] lcPrompt, ; if cursorgetprop('Buffering', lcCursor) > 1 lcRule, ; tablerevert(.T., lcCursor) loControl endif cursorgetprop('Buffering', lcCursor) > 1 with This next lnI if not empty(.aSteps[1, 1]) llOK = .T. lnSteps = alen(.aSteps, 1) + 1 The form’s KeyPreview property is set to .T. so it can for lnI = 2 to lnSteps lcPrompt = iif(lnI = lnSteps, '', ; process keystrokes before any other control. The reason .aSteps[lnI, 1]) lcRule = .aSteps[lnI - 1, 2] for this is so the form’s KeyPress method can treat PgUp with .pgfWizard.Pages[lnI - 1] as if the user clicked the Back button and PgDn as if the llOK = llOK and evaluate(lcRule) endwith user clicked Next. Here’s the code: .aSteps[lnI - 1, 3] = llOK do case LPARAMETERS nKeyCode, nShiftAltCtrl case lnI = lnSteps with This case left(lcPrompt, 1) = '\' and llOK do case .aSteps[lnI, 1] = substr(lcPrompt, 2) case nKeyCode = 18 and .cmdBack.Enabled case left(lcPrompt, 1) <> '\' and not llOK .SelectStep(.nCurrentStep - 1) .aSteps[lnI, 1] = '\' + lcPrompt case nKeyCode = 3 and .cmdNext.Enabled endcase .SelectStep(.nCurrentStep + 1) next lnI endcase endwith After testing each step, RefreshSteps refresh all controls that need refreshing. It calls the RefreshSteps Wizard controls method of the page frame (which I’ll discuss later in the SFWIZARD.VCX contains several classes that can be used article) and the Refresh method of any control that has an as controls in wizard forms. They’re subclasses of the lRefreshSteps property set to .T. appropriate class in SFCTRLS.VCX. SFWizardPageFrame, which is used for the page for each loControl in .Controls frame in SFWizardForm, has a RefreshSteps method used do case case lower(loControl.Class) = 'sfwizardpageframe' to refresh only those controls that need refreshing; it’s loControl.RefreshSteps() called from the RefreshSteps method of the form. It has case type('loControl.lRefreshSteps') = 'L' and ; loControl.lRefreshSteps the following code: loControl.Refresh() endcase next loControl local loPage, ; endif not empty(.aSteps[1, 1]) loControl endwith for each loPage in This.Pages for each loControl in loPage.Controls if type('loControl.lRefreshSteps') = 'L' and ; loControl.lRefreshSteps The Click methods of the Back and Next buttons loControl.Refresh() call the SelectStep method with the previous or endif type('loControl.lRefreshSteps') = 'L' ... next loControl next step number (Thisform.nCurrentStep - 1 or next loPage Thisform.nCurrentStep + 1). The Back button is only enabled when the user is on step 2 or later, and the The Init method makes the page frame the same size Next button is only enabled if the user isn’t on the

12 FoxTalk May 1998 http://www.pinpub.com as the form, and the Refresh method sets This.ActivePage existing order number. It then uses DODEFAULT() to do to Thisform.nCurrentStep so the page for the current step the rest of the SFWizardForm behavior. is selected. The nMaxSteps property is set to 4, and the SFWizardCommandButton has an lRefreshSteps PageCount property of the page frame is set to 4 because property, which defaults to .T. and indicates whether the that’s how many steps there are in this wizard. The aSteps object should be refreshed when the wizard form’s array is populated with the information about these steps RefreshSteps method is called. All buttons on in the SetupSteps method (I showed the code for this SFWizardForm are SFWizardCommandButton buttons; method earlier). this way, the Next, Back, and Finish buttons can be Step 1 is for order information, so Page 1 of the refreshed as soon as the user changes something. page frame contains a text box for the order date SFWizardCheckBox, SFWizardComboBox, (bound to ORDERS.ORDER_DATE) and a combo box SFWizardOptionGroup, SFWizardSpinner, and for the employee who filled the order (populated with SFWizardTextBox also have an lRefreshSteps property names from the EMPLOYEE table and bound to and all have the following code in their InteractiveChange ORDERS.EMP_ID). This step isn’t complete until the and ProgrammaticChange methods: user enters values in both controls. Step 2 is to select the customer who placed the local lnPos, ; lcAlias, ; order, so Page 2 of the page frame contains a combo lcField box populated with names from the CUSTOMER table with This lnPos = at('.', .ControlSource) and bound to ORDERS.CUST_ID, and text boxes bound if .lRefreshSteps and lnPos > 0 and ; to address fields from the CUSTOMER table. The not .Value == evaluate(.ControlSource) lcAlias = left(.ControlSource, lnPos - 1) AnyChange method of this combo box positions the lcField = substr(.ControlSource, lnPos + 1) CUSTOMER table to the selected customer’s record so the replace (lcField) with .Value in (lcAlias) endif .lRefreshSteps ... customer text boxes will show the address for the selected .AnyChange() customer. These text boxes are read-only, so the address endwith Thisform.RefreshSteps() can’t be changed in this form, but it would be a simple change to give the wizard that capability. This step isn’t This code ensures the Value of the control is written complete until a customer has been chosen. The form’s to its ControlSource (if there is one) before calling any StepDone method, which is automatically called when the other method; failing to do so might cause step user leaves a step, has code that populates some fields in completion expressions that test the value of a field to the ORDERS table from the current CUSTOMER record fail, since the field’s value hasn’t been updated from the when the user leaves Step 2. control yet. The code then calls the custom AnyChange The user selects the products ordered in Step 3 method (which our base classes have for code that’s (shown in Figure 2). Page 3 of the page frame contains needed when a control’s Value changes) and the form’s a grid bound to the ORDITEMS table, an Add command RefreshSteps method. As a result, whenever any change button, and a text box showing the order total. Clicking (programmatic or interactive) is made to a control, on the Add button adds a new record to the ORDITEMS the form and all necessary controls are immediately table and sets ORDITEMS.LINE_NO to the next line refreshed, enabling or disabling the buttons and cboSteps number for this order. The first column of the grid is combo box as necessary. bound to ORDITEMS.LINE_NO and is read-only. The second column is bound to ORDITEMS.QUANTITY. The Building a wizard third column, which is bound to ORDITEMS.PRODUCT_ Okay, now that I’ve discussed the components of a ID, contains a combo box populated with product names wizard, let’s actually build one (if I were Ken Levy, I’d from the PRODUCTS table, making it easy to select a create a wizard for building wizards, the Wizard Wizard, Continues on page 22 but I digress). The ORDERWIZ form accompanying the source code for this article is an Order Wizard. This wizard makes it easy for users to enter an order for Figure 2. Step 3 in products sold to a customer. The data files for this wizard the Order Wizard. come from the VFP TESTDATA database, so copy the contents of the SAMPLES\DATA subdirectory of your VFP directory to the same directory where you install this source code before running this form. The Init method of ORDERWIZ adds a new record to the ORDERS table, defaulting the order date to today’s date and the order number to one more than the highest http://www.pinpub.com FoxTalk May 1998 13 ActiveX and Automation Review FoxTalk

Automation from a VFP Perspective John V. Petersen

Last month, John introduced you to the basics of ActiveX simple example using Word 97: Automation. This month, he expands on the topic by discussing the various issues that face you—the Visual 1. Open a Word document. FoxPro developer who wishes to include ActiveX Automation 2. Click the Tools menu. in your applications. 3. Select the Macro menu. 4. Select the Record New Macro option. HERE are many examples of ActiveX Automation available today in books and on the Web. The dialog box should look similar to Figure 1. TUnfortunately, these examples are written from the When creating a macro, it’s important to understand perspective of the Visual Basic developer. This probably the scope of documents to which your macro will apply. shouldn’t come as a big surprise, since ActiveX By default, the macro dialog box will point to the Automation is predicated on the Visual Basic for template upon which your document was created. This Applications (VBA) language. If you’re developing in might or might not be what you want. Alternatively, you Visual Basic, your work is relatively painless in that you can assign your macro to the current document by can copy and paste VBA code directly in your VB changing the selection in the Store Macro In drop-down application. In VFP, however, the effort involved in list box. In the example shown, I’ve specified that my getting VBA code to work is a bit more extensive. But print macro will be stored with the current document. take heart—while initially frustrating, VBA code samples This will be important, as I’ll need to know where to find can be made to work in VFP. You just need to understand how to make the modifications to the code.

A quick word on the macro recorder . . . Before going further, it’s important to mention the macro recorder that’s present in the applications that make up Microsoft Office. Whether you use Word, Excel, or PowerPoint, code can be automatically generated by recording tasks that you’d perform with the mouse and keyboard. If you’re currently employing automation and aren’t using the macro recorder, you’re missing out on a wonderfully productive tool that can seriously reduce your workload. In addition to generating code, the macro recorder is a great teaching tool. Last month, I mentioned that understanding the object model of an automation server application is the key to making your automation efforts successful. After all, knowing where to find something is often more complicated than understanding how to do something. The macro recorder can quickly Figure 1. In the Record Macro dialog box, you can specify the de-mystify a server’s object model. name of your macro, where it will be stored, and a description of If you haven’t explored the macro recorder, try this what the macro does.

14 FoxTalk May 1998 http://www.pinpub.com the macro in the Visual Basic Editor. receive, it’s usually of little or no help Once you press the OK button in the Record Macro in debugging the situation. This is dialog box, a toolbar will appear in Word with two why an understanding of how VFP buttons. Figure 2 shows the toolbar that will appear in treats automation servers differently your document. The first button will stop macro than VB is important. recording, and the second button will pause it. The Here’s a good example. If you Figure 2. The following steps will complete the macro: attempt to copy and paste this code Macro Recorder in VFP, it will bomb. For one thing, toolbar can both 1. Select the File menu. the macro recorder assembled the stop and pause 2. Select Print. arguments that are passed to the macro recording. 3. Select a printer and press OK. PrintOut Method in a non-default 4. Select the Stop Recording Macro button in the order. In FoxPro, you can’t simply toss arguments to a Macro Recorder toolbar. function in any old order, but in the VB environment, this is okay. The reason is that VB supports a mechanism That’s it! You’ve generated VBA code. Now, you need called “named arguments.” to look at what’s been generated. There are two ways to do this. The first is to go back to the Tools menu, choose The VBA code—up close and personal Macro, and then select the Macros option. The available The following is the code that was generated by the macros are displayed in a dialog box, as illustrated in macro recorder: Figure 3. Sub myprintmacro() It’s important to note that if you have multiple ' documents open, you might not see your macro listed. In ' myprintmacro Macro ' Macro recorded 03/10/98 by John Viktor Petersen this example, the name of the document is Document4. ' So, to be sure I see the new macro, I need to select the ActivePrinter = "HP DeskJet 550C" Document4 window, making it the active document in Application.PrintOut FileName:="", _ Range:=wdPrintAllDocument, Item:= _ Word. Once I’ve done this and displayed the Macros wdPrintDocumentContent, Copies:=1, _ dialog box, I can then press the Edit button. Pressing the Pages:="", PageType:=wdPrintAllPages, _ Collate:=True, Background:=True, PrintToFile:=False Edit button brings forth the Visual Basic Editor. This is End Sub illustrated in Figure 4. One thing that makes automation a challenge is The first line of code is pretty straightforward in that the lack of feedback the developer gets when invalid the ActivePrinter property is being assigned the name of a arguments are passed. Usually, the error consists of an printer. In the second line, the PrintOut method of the error message stating that some sort of data type Application object is being invoked. As I mentioned, one mismatch occurred. Other times, it might be a generic of the nice features of VBA—and VB, for that matter—is OLE error message. Regardless of which message you that named arguments are supported. In other words,

Figure 3. The Macros dialog box lists the macros associated with Figure 4. The Visual Basic Editor in VBA is identical to the code the document selected in the drop-down combo box. window in the full Visual Basic Development System. http://www.pinpub.com FoxTalk May 1998 15 arguments in VBA can be recognized regardless of the arguments should be the first and second arguments order in which they’re passed. The benefit to this is that passed, respectively. Upon observing the code, the macro some arguments can be omitted, and there’s no preset recorder had something different in mind—instead, it order in which the arguments have to be passed. You can made the filename first. The problem is that the filename accomplish the same thing in VFP by parsing strings, but argument is a string, but the default argument— it’s a great deal more work. background—is Boolean. Because VFP must pass Of course, you can omit the naming of parameters, arguments in the default order, a data type mismatch but in doing so, you must pass the arguments in their error will occur. default order, and you must provide placeholders for Another feature of the VB environment is the ability those arguments that separate arguments you wish to to intrinsically work with constants. While you can define pass. For example, suppose you wish to specify a filename your own constants in VB, many are already predefined. and the number of copies to be printed. You must specify The Object Browser can help here as well, since these the defaults for the range and item parameters as well. constant definitions are contained in the server type In VFP, however, knowing the order and quantity of library. This capability is illustrated in Figure 6. arguments is necessary to get things working properly. In this example, all of the constants— wdPrintAllDocument, wdPrintDocumentContent, Don’t judge a book by its cover . . . and wdPrintAllPages—have a value of 0. Thus, before porting the VBA code to VFP, three things must be ascertained: Porting the code to Visual FoxPro The following code is a VFP program that illustrates 1. The number and order of arguments the how the Word-produced VBA code can be made method expects VFP-compatible:

* printinword.prg 2. The data type of the arguments #Define True .T. #Define False .F. #Define wdPrintAllDocument 0 3. Values of constants, if any #Define wdPrintDocumentContent 0 #Define wdPrintAllPages 0 At first glance, it would appear the PrintOut method oWord = CreateObject("word.application") oWord.Documents.Add takes nine arguments. In fact, this isn’t true. In last oWord.ActivePrinter = "HP DeskJet 550C" month’s column, I introduced you to the Object Browser * This attempt would bomb because the macro in VBA. Figure 5 shows the Object Browser with the * recorder did not assemble the arguments PrintOut method highlighted. * in the default order. In this case, * the macro recorder made the filename Are you sitting down? Incredibly, the PrintOut * parameter first. By default, the method expects up to 15 arguments. Also, according to * background argument is first. the Object Browser, by default, the default and append *oword.PrintOut("",wdPrintAllDocument,;

Figure 5. For methods, the Object Browser can indicate the Figure 6. The Object Browser can also indicate the value of arguments a method can accept. intrinsic constants used in VB/VBA.

16 FoxTalk May 1998 http://www.pinpub.com wdPrintDocumentContent,; 1,"",wdPrintAllPages, ; knowledge of a server’s object model—such as Word— True,True,False) can be greatly accelerated. * The printout method with Next month, I’ll extend things by discussing scenarios * no arguments will invoke the defaults. in which to include your automation code and why you oword.printout might want to extend your applications with automation. Finally, many of you might remember the old Dr. FoxPro’s * Placeholders must be used since VFP does not support * named argument passing. The number of copies is the Answer Clinic column and its Q&A format. I’d like to * eighth parameter. In VB, the syntax is simplified with * the use of named arguments: oword.printout Copies:=1 print one or two of your questions each month regarding automation and VFP. So, if you have a nagging oWord.PrintOut(True,False,wdPrintAllDocument,; "","1","999",wdPrintdocumentcontent,2) automation problem you’d like to see addressed, send me your question and it might show up in a future * Or, if the arguments before the number ▲ * of copies argument can be kept at their default, issue of FoxTalk! * the following will work as well. oWord.PrintOut(,,,,,,,2) 05PETER1.ZIP at www.pinpub.com/foxtalk

One tip that I’ve found to save work is to define the John V. Petersen, MBA, is vice president of IDT Marketing Systems and word True as .T. and False as .F.. If you don’t do this, Services, a Philadelphia-based marketing database consulting firm. John you’ll find yourself doing lots of searching and replacing. has presented at numerous developer conferences, including DevCon 97, Tech-Ed, and the Southwest Visual FoxPro Conference. John is a Microsoft Also, I define any constants in VFP exactly as they appear Most Valuable Professional (MVP) and a co-author of Developing Visual in VB. This helps in matching up the VFP calls with the FoxPro 5.0 Enterprise Applications and Hands-On Visual Basic 5—Web VB-based documentation. Development, both from Prima Publishing. [email protected]. In this last example, I elected to print two copies of the document. This requires that placeholders for each preceding argument must be passed as well. The last call in the VFP code will work, but it’s much less readable. Do yourself (and anybody else who needs to work with your code) a favor—explicitly pass the defaults. It will make copying and pasting VBA code much easier. One other subtle difference in the VFP version of the code deals with parentheses. In VB, method calls can be made and arguments can be passed without the use of parentheses. In fact, unless the method is returning a value, VB doesn’t allow you to use parentheses. In VFP, on the other hand, if arguments are being passed to a method, parentheses must be used. Finally, inside the VBA environment, properties— which are members of the application—can be referred to directly. In the VBA code, the ActivePrinter property could have been referred to as Application.ActivePrinter. The same can’t be said for methods, as they must be fully qualified. While the macro recorder is nice, it can be inconsistent. Whenever possible, fully qualify the properties in your VBA code. It will make the copying and pasting operations that much more simple.

Summary VBA code in VFP works! It just takes an understanding of both the VBA code itself and how VFP can make use of the code. With an understanding of the subtle differences between the language constructs of VB and VFP, and an understanding of the server object model, VFP can make use of the code with a few modifications. Also, by making use of the macro recorder and the Object Browser, your

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

Using AddObject Richard A. Schummer

AddObject is a method that can be called by each of the VFP click on the container object in the Class or Form container objects to add other objects to them. This month, Designer (for example, adding a spinner or check box Rick explores how to use this powerful method, demonstrates control to a grid). how it’s done with some examples, and offers some further • Select one or more fields in a table in the suggestions where developers can use it. DataEnvironment and drag them into a container object. EVELOPERS who have worked with Visual FoxPro have come to understand that there’s usually • Select one field at a time from a table in the Database Dmore than one way to accomplish just about every Designer or Project Manager and drag them into a task within the VFP development environment or within container object. an application. There are a number of ways to add objects • Drag a class from the Project Manager or Class to a container object—most developers I’ve talked to add Browser into a container object. objects to these classes at design time using the Class and Form Designer, but some prefer to write classes in • Run a builder that’s designed to add objects—for program code. This article will focus, in detail, on the example, the Grid Builder, which adds columns, programmatic way to add objects via the AddObject headers, and controls. This is actually done method at runtime, and I’ll address some of the benefits programmatically, but through a visual interface. and drawbacks. AddObject syntax Container classes Each container class has an AddObject method. The VFP has several container classes available for developers native VFP behavior of this method is to take the to leverage. These classes are called container classes requested object and perform an implicit CREATEOBJECT because they can contain other objects. Table 1 contains a within the container object. Developers can add code to list of the VFP container classes (sorry, I couldn’t resist). the AddObject method. This code is executed when you programmatically add a new object, but before the new object is instantiated via the implicit CREATEOBJECT. Table 1. The VFP container classes. The syntax for the AddObject method is as follows:

Column Object.AddObject(cName, cClass [, cOLEClass] ; Command Group [, aInit1, aInit2 ...]) Container Control The cName parameter is the name assigned to the DataEnvironment object when it’s instantiated. This is identical to the Name Form property you enter in the Property Sheet when using the Form Set visual designers. The cClass parameter is the class name Grid OLE Container Control of the object as it’s stored in a Visual Class Library, Option Group defined in code in a program or procedure file, or in a Page compiled app/exe file. The cOLEClass is the name of a PageFrame registered OLE class. Any other parameters (aInit1 and so Toolbar forth) are parameters that are passed along to the Init method of the newly added object. One key point to There are a number of ways to add an object to an remember when using this method is that the object just object container visually: added is invisible by default. You need to explicitly set the • Select an object on the Form Control toolbar, then Visible property to True for this object if it needs to be

18 FoxTalk May 1998 http://www.pinpub.com seen by the end-user. Here’s a basic example that can be wanted to be able to sort the data in a Grid based on added to the Init method of a Form class to add a label clicking the Header. Each time I wanted this feature in a object and place it where it belongs on a form. The Grid, I ended up customizing each Click method in each FRMAO.SCX form and cFoxTalk class library for this Header that I wanted to sort on. This becomes a pain if example are contained in this month’s Subscriber you have several dozen Grids within an application. The Downloads at www.pinpub.com/foxtalk. following code is a custom class that I’ve created to get around this limitation (it’s available in this month’s THIS.AddObject("lblCompany", "label") Subscriber Downloads). This class must sit in a program, WITH THIS.lblCompany since the Header object can’t be created in the Class .Caption = "Company:" .Alignment = 1 Designer. To use this class, the following code must be .Top = THIS.txtCompany.Top + 5 executed before the class can be instantiated: .Left = THIS.txtCompany.Left - ; THIS.lblCompany.Width - 3 .Visible = .T. SET PROCEDURE TO Header.prg ADDITIVE ENDWITH * Header.prg This method gives developers an opportunity to DEFINE CLASS PSHeader AS Header cOrder = "" control or override whether the object gets instantiated at nOrderColor = RGB(255,0,0) runtime. You can accomplish this by placing logic in the nNotOrderColor = 0 method that determines whether the object should be PROCEDURE Init LPARAMETERS tcOldCaption, tnOldBackColor instantiated and issuing a NODEFAULT if you don’t want DODEFAULT() the object created. THIS.Caption = tcOldCaption THIS.BackColor = tnOldBackColor THIS.nNotOrderColor = tnOldBackColor LPARAMETERS cName, cClass ENDPROC

IF cClass = "txtProfitMargin " PROCEDURE Click IF "CustRep " $ goApp.oUser.mSecurity lcOrder = THIS.cOrder NODEFAULT IF !EMPTY(lcOrder) ENDIF SET ORDER TO (lcOrder) ENDIF THIS.Parent.Parent.SetAll("BackColor", ; THIS.nNotOrderColor, ; The AddObject method can be handy in saving "PSHeader") memory and Windows’ resources. It can also provide a THIS.BackColor = THIS.nOrderColor means of controlling what the end-user sees or has access THIS.Parent.Parent.Refresh() to within any container. Setting the container’s Visible ELSE * Leave as is property to False only makes it invisible to the user, but ENDIF ENDPROC the object still resides in memory and takes up resources. ENDDEFINE There are some major drawbacks to this method of *: EOF :* adding objects to a container instead of using the visual designers. These drawbacks aren’t reasons to avoid the I’ve created a sample form called frmHeader.scx AddObject, but rather issues that developers need to be (included in the Subscriber Downloads) with a Grid that’s aware of when using this method. The first one is the based on the VFP sample data customer table. (Copy the biggest disadvantage: If you do issue a NODEFAULT in files in ..VFP\SAMPLES\DATA to the directory that the AddObject method, you have to start checking for contains the code for this article.) The following code object references using TYPE() and ISNULL() anywhere resides in the Init method of a Grid Object: you use this object in code. VFP doesn’t catch these types of errors at compile time. If your testing isn’t complete, * Replace default headers with custom one you might be getting support calls regarding “Object does FOR EACH loColumn IN THIS.Columns WITH loColumn not exist” errors. Another disadvantage is that the object IF !EMPTY(loColumn.ControlSource) won’t appear in the VFP Editor Object List that’s available * Save default caption and color lcOldCaption = loColumn.Header1.Caption when editing method code. The final drawback is that it lnOldBackcolor = loColumn.Header1.BackColor won’t be immediately obvious to other developers on * Name Header Obj after ControlSource your team where the object came from, since it’s not lcControlSrc = loColumn.ControlSource lnControlSrc = RAT(".", lcControlSrc) + 1 visible when looking in the designer. lcControlSrc = SUBSTR(lcControlSrc, lnControlSrc)

* Remove old and create new header Headers in a Grid .RemoveObject("Header1") How many times have you wanted to customize the code .AddObject("grh"+lcControlSrc, ; "PSHeader", ; in the Header object? Never? All the time? Me too. I lcOldCaption, lnOldBackcolor)

http://www.pinpub.com FoxTalk May 1998 19 ENDIF *Set the initial order ENDWITH THIS.Column2.grhCompany.Click() ENDFOR * Set columns with sort feature The FOR EACH...ENDFOR code can reside in your THIS.Column1.grhCust_id.cOrder = "cust_id" THIS.Column2.grhCompany.cOrder = "company" custom Grid base class. Just place a DODEFAULT() along THIS.Column3.grhContact.cOrder = "contact" with setting the cOrder property code in the Init. You

Adding ActiveX Controls on the Fly

HE integration of ActiveX controls with VFP has opened a no customized code, and you can’t add code on the fly. A Tnew world of component development to FoxPro developers. workaround is to build another custom object that can contain At the same time, these powerful development components the code needed at runtime, depending on the control. You can add a bit of complexity, especially if the ActiveX controls are call this class to perform the needed actions, or make calls to the upgraded. The following scenario demonstrates the issues: generic methods in the ActiveX control. This might not work in all cases, depending on the design and purpose of the ActiveX • Modify or create a new Form. control. Here’s a scenario . . . At runtime you can add a TreeView control dynamically by • Add an ActiveX control. placing code like this in the forms Init method:

• Save the form, compile, test, implement—customer very THISFORM.AddObject("oleTreeView", ; "oleControl", ; happy, as usual. "COMCTL.TreeCtrl.1") THISFORM.AddObject("cusTreeHandler1", ,; "cusTreeHandler") • Upgrade the control. For example, implement a Service Pack for Visual Studio (after all, the new version must be better The custom tree handler object can have methods to than the old version—tongue planted firmly in cheek). initialize the TreeView ActiveX control, position it on the form, connect an ImageList for icons, add nodes, delete nodes, refresh • New controls installed. it, and so forth. These methods can be called from the custom handler’s Init method or other objects on the form, depending • Customer requests changes. on the requirements. Then, when the developer updates the development environment or the client updates the production • Install application at customer site after the “upgrade,” but machines (which never happens ), with new controls the do not include updated controls. forms continue to work.

• Run the application, bring up new form for the customer. Problems The code can’t be added to respond to the events of the What happened? “OLE class not registered error” is triggered. control, and this is clearly a huge drawback. The code can be ActiveX controls aren’t always backward compatible. Information added at design time through a builder via WriteMethod. This is about the control from the development machine is stored in nice if you’ve built a third-party product that uses ActiveX the form/class in the OLE and OLE2 fields of the metadata file controls. Distribute the changes, run the ActiveX builder/updater (SCX or VCX). This information is used during the OLE object’s program, and have the developer rebuild the app. Stonefield instantiation. If the information isn’t the same as the control Database Toolkit uses this concept when updating from one registered on the production machine, the error is triggered. version to another. One note of caution: This methodology doesn’t help if the Solution company that creates the control modifies the public interface of You can install the upgraded control, but this can negatively the control (for instance, if the manufacturer changes the name impact other applications loaded on the system that use of the method used to update the tree nodes or takes away the these controls. The better option might be to use AddObject property that connects the control to the ImageList control for to dynamically add the control at runtime. This way, the displaying icons). This behavior isn’t typically found, but it’s Class ID (CLSID) isn’t stored in the form or class and is added definitely bitten developers in the past. with no problem. The drawback is that the ActiveX control will have —RAS

20 FoxTalk May 1998 hhttp://www.pinpub.comttp://www.pinpub.com can also build logic into the Header class to check for Errors happen! index tags automatically and set them as you add the If you’re still new to working with objects, you’re likely to new Headers. run into a number of typical errors. Following are three of The same concepts can be applied to adding the most common ones. Columns in a Grid, Cursors in DataEnvironment, Pages in a PageFrame, Option Buttons in an Option Group, and Class definition “name” is not found (Error 1733) so on. This is a great way to customize and extend the This error occurs when the class definition isn’t accessible. behavior of the VFP classes that can’t be subclassed in the Either the class doesn’t exist, or the class library isn’t Class Designer. listed in SET CLASSLIB TO or the SET PROCEDURE TO, if it’s defined in code. You need to add the location of the Other uses class to the “path” via a SET CLASSLIB TO or SET Builders can add objects to a class during development PROCEDURE TO, depending on where the class resides. time. This is an excellent way to extend the objects. One You might also want to check your spelling . case might be the sample I designed for replacing the Header objects in a Grid. This can be done at design time Object class is invalid for this container (Error 1744) instead of runtime by implementing a Grid builder that This error is triggered when the object being added to the replaces the Header objects. container is invalid as a member of the parent container. AddObject can be used to delay the instantiation For instance, you can’t have a Page object be a member of of objects until they’re needed. One example is used a Control object, or a Grid Column be added as a member often by developers who work with VFP 3.0, since that of a PageFrame. version instantiates objects more slowly than 5.0 does. You can easily add objects that sit on a Page in a Property “name” is not found (Error 1734) PageFrame. First, create a Container of the interface This error is generated when the AddObject is executed objects that will be displayed on a Page. In the Activate for non-container objects. This error might seem obvious, method of the Page, you check to see if the container but you can’t execute AddObject for TextBoxes or other object exists; if it doesn’t, you can call AddObject to add non-container objects. it. This adds a container level to the object hierarchy and adds another class to be modified during RemoveObject development, but it gives your users a faster form The RemoveObject method provides the exact opposite instantiation. However, there’s a trade-off here. If the effect as AddObject: It removes the selected object from user rarely selects the Page, then he or she will get full the container. Obviously, it must already exist within benefit of the speedier form and less memory resource the container. The developer’s custom code included in usage. If the user uses the Page all the time, he or she the method is executed before the object is removed will get a faster form start up but slower Page selection, from the container, just as is done with the AddObject which might not be ideal. custom code. AddObject can be used to customize objects based on user security or site implementation. This Conclusion is based on the Strategy Design Pattern. You might This powerful control over object instantiation is an design different classes for different types of users— important addition to any VFP developer’s toolbox. maybe the Manager gets a view of the entire customer There are a number of situations in which it can be list with some CommandButtons that perform managerial implemented. Hopefully, this article will inspire you to functions, and the Clerk gets clerical functionality via find places where it can fit into your own developer different CommandButtons. Add the appropriate toolbox. ▲ CommandButtons at runtime based on the user’s security level. If the application is used in different countries, each 05SCHUMM.ZIP at www.pinpub.com/foxtalk of which has different tax rates, you can build a variety of tax calculation classes that can be implemented based on Rick Schummer has been developing computer-based solutions for a the location of the system. This implementation can be Fortune 1 company for the past eight years using FoxPro. After hours, he enjoys writing developer tools that improve his team’s productivity based on the Strategy Design Pattern. Steven Black and generally hopes to make his life easier. Rick is a founding member discussed the Strategy Design Pattern that implements and secretary of the Detroit Area Fox User Group (DAFUG) and president this for discount and tax calculations (see “OOP Design of the Sterling Heights Computer Club. He’s also a regular presenter Patterns: Add Design Flexibility with a Strategy Pattern,” for these organizations and other user groups and is available January 1997). for conferences.

http://www.pinpub.com FoxTalk May 1998 21 got our ISDN lines (at the office and at home), I’d put Editorial: No Good Deed . . . together a list of things I needed online and go shopping Continued from page 2 during one session. Download a driver, grab an article off That’s how the Extended Articles were conceived. the Knowledge Base, get my e-mail, check out what’s new As you’ve seen, we chose the idea of continuing to at www.GreenBayPackers.com, and so on. It was actually include a variety of articles in each issue, while putting pretty efficient. more in-depth coverage of some of the articles in But you’re going to get 24/7 access one of these days. electronic format up on the Web for your access. As a Get used to accessing things online in real-time. result, we’ve been able to raise the number of pages To make accessing our supplemental material faster of content by 50 percent or more the past couple of for everyone, as of this issue, the Extended Articles will months—delivering even more high-end, exclusive now be posted in HTML format, so you won’t have to information at absolutely no cost to you. download the file. (Since this is only an option on the Never did we expect negative feedback. Web, those who receive the companion disks will Yes, I understand, it’s nice to plop down in front continue to receive the material in PDF format.) And of the tube, keeping one eye on reruns of Wackiest Ship check out all the added free material you’ll be able to in the Navy while you page through magazines. But access this way. The Table of Contents on the front page more and more, you’re finding material available is practically bursting at the seams. electronically—not in printed format. When was the last Whether we continue offering the Extended Articles time you saw a 300-page Windows manual? The Help is, of course, up to you, our subscribers. We’re here to files that ship with Visual FoxPro (and Visual Studio, in serve your needs, and that was our intention in general) get more and more robust. I’ve not looked at an introducing this feature. So, gentle reader, here’s my Office book in years—just ask the dancing paper a request. Please let me know—[email protected]— question, and voila! This trend is going to continue to whether you prefer to keep the Extended Articles, or accelerate. whether you’d prefer the content of your subscription on Yes, I know, it’s a pain if you don’t have 24/7 access paper only. You decide. ▲ to the Web. Dial-up access is a nuisance. But before we

SetupSteps method of each wizard: the text to display for Build Your Own Wizards . . . each step and the expression that, when evaluated to .T., Continued from page 13 indicates the step is complete. If you used such a table, product. Column 4 is bound to ORDITEMS.UNIT_PRICE, the SetupSteps method of SFWizardForm would then which is set by the AnyChange method of the product open the table, grab all the records for the current wizard, combo box to the unit price of the selected product. The and populate the aSteps array (probably using a SQL custom UpdateTotal method of the form is called when SELECT statement). the user changes the quantity, product, or unit price; this Whether data-driven or not, the SFWizardForm method calculates and displays the order total. Step 3 is and accompanying classes in SFWIZARD.VCX allow complete once the order total is non-zero. The form’s you to easily add wizards to applications. I expect to be StepDone method has code that eliminates partially using these classes more and more in the applications entered records in ORDITEMS (for example, if the user I develop. ▲ clicks on the Add button but doesn’t enter the quantity or product for the new line). 05DHENSC.ZIP at www.pinpub.com/foxtalk In Step 4, the user enters the shipping method (using a combo box showing shippers) and freight amount. This Doug Hennig is a partner with Stonefield Systems Group Inc. in Regina, step is complete once a shipper has been chosen, and Saskatchewan, Canada. He is the author of Stonefield’s add-on tools for since this is the last step, the Finish button is enabled. The FoxPro developers, including Stonefield Database Toolkit for Visual FoxPro and Stonefield Data Dictionary for FoxPro 2.x. He is also the Finish method of the form uses TABLEUPDATE() to write author of The Visual FoxPro Data Dictionary in Pinnacle Publishing’s out all changes in all tables. The Pros Talk Visual FoxPro series. Doug has spoken at user groups and regional conferences all over North America; he spoke at the 1997 Conclusion Microsoft FoxPro Developers Conference (DevCon), and he will be One improvement that could be made to SFWizardForm speaking at the 1998 DevCon. He is a Microsoft Most Valuable is to make it data-driven. A Wizards table could contain Professional (MVP). [email protected], the information that currently must be coded into the [email protected].

22 FoxTalk May 1998 http://www.pinpub.com The Cutting Edge FoxTalk

It’s Never Too Late

Les Pinter

friend of mine has a daughter who’s 10 years old. mean that in the figurative sense. Did you hear that Bank The other day, she asked me if I thought it would of America has offered a signing bonus of over $50,000 to Abe possible for her to learn to program computer those of their employees who promise not to leave and games. “Sure,” I told her. She skipped off, delighted at become Year 2000 consultants? No kidding. It was in the the idea. Examiner last week. As Whil Hentzen pointed out recently, Her dad wasn’t impressed. “She gets these whims, the excess demand for programmers has left the people and two days later they’re forgotten. Don’t waste your who have to buy our services a little bent out of shape. time.” But I’m a sucker for anyone who wants to do But every company out there seems to need to have some something with their life—especially kids. (BTW, if you software developed. have a spare computer, the Club de Informatica de San But quite beyond the monetary rewards, it’s fun. I Mateo, which gives computers and computer skill classes discovered a new technique for doing keyword searches to Mexican kids in my neighborhood [I’m the entire staff], using only conditional indexes the other day, and I called would be happy to see that they get into the right hands.) everyone I knew to tell them about it. They probably So I got her a book called Game Programming in 21 Days. thought I was nuts, but I was so excited I couldn’t help It’s in C++. She’s a week into it, and she’s already myself. I’ll bet that folks in the ball bearing business don’t modifying one of the games. have many days like that. Willy, the mechanic at my main client’s office, is When is it too late to enjoy the thrill of new fascinated with computers. He’s been a “wrench” for discovery? Never. If it weren’t for Mac Rubel, I’d be the 20 years, but he comes in red-eyed every morning from old geezer of this profession, and I can’t wait to get another late night spent in front of a monitor. He’s the started in the morning. I’m slogging away at my VB/VFP one who stuck the “size matters” label on my 21-inch book, and I’m developing a grudging admiration for VB. ViewSonic. I guess he’s got monitor envy. But his goal is It’s not as good as FoxPro, but it ain’t bad. And it’s really to be a database programmer. He’s already concluded that fun to figure out how to do stuff in only three or four lines FoxPro is the language for him. I like this guy’s taste. of code that only takes one command in FoxPro. Really! Dr. Robert Hemmes is a retired professor from It’s like discovering FoxPro all over again. With slightly Stanford, and one of my best friends. He’s something of a less admiration. Luddite, in spite of having developed some of the more It’s certainly not too late for Sofia to learn C++. It’s important avionics instrumentation of the century, and he not too late for Willy to change careers. It’s not too late for doesn’t have much faith in the ability of technology to Robert to add another operating system to his ramshackle solve the real problems of the world. But he’s built a little network. It’s not too late for me to learn VB. And it’s not network in his house across the creek from the Stanford too late for you, gentle reader, to go out and start learning campus, and he’s fascinated with it. Why he has a about DAO, COM, and Active Server Pages. We’re all network, when it’s just him, is a mystery. But he loves riding whitewater into the future, but at least we get to his network, and he calls me periodically to tell me have fun while we learn. ▲ something I didn’t know about the mysteries of packets and protocols. Les Pinter is a database developer in the San Francisco Bay area. I seem to be surrounded by people whose lives have 414-344-3969, fax 413-344-6026, [email protected], been enriched by the thrill of computing. And I don’t just [email protected].

http://www.pinpub.com FoxTalk May 1998 23 Downloads May Subscriber Downloads

• 05MALIN.ZIP—Source code and sample data files described Post-Its adorning your monitor, mini-tower, and every other in Dave Jinkerson and Scott Malinowski’s article, “Teaching flat surface within eyesight? StickyNotes allows you to OOP to FPW with Stored Procedures.” PrcExmpl.PRG quickly jot and keep notes on your monitor, instead of demonstrates how abstract data types are created in OOP. around it. Also mentioned this month: a utility to restore SURVYDEF contains a sample data table, and UDF.DBF is a your Win95 desktop settings to the way they used to be sample table containing stored procedures. before some gremlin messed with them. • 05DHENSC.ZIP—Source code described in Doug Hennig’s • 05COOL.ZIP—The complete set of files for StickyNotes, a article, “Build Your Own Wizards.” SFCTRLS.VCX and freeware utility described in this month’s Cool Tool column. SFWIZARD.VCX are class libraries used to support • 05KITBOX.HTM—Extended Article. This month, Barbara ORDERWIZ.SCX, a sample wizard that makes it easy for Peisch and Paul Maskens discuss a use for the Memento users to enter an order for products sold to a customer. pattern—saving and restoring the state of an object—and • 05PETER.ZIP—Source code for the PrintInWord program delve into the best way to represent the data on disk. They described in John Petersen’s article, “Automation from a VFP also discuss how they worked around limitations of VFP and Perspective.” the Memento pattern and talk about a commercial product—ObjectTalk from IAS—that does the same thing. • 05SCHUMM.ZIP—Source code for Rick Schummer’s article, “Using AddObject.” FrmAO.SCX and cFoxTalk.VCX are used in • 05RUSSEL.HTM—Extended Article. In this article, Paul Russell the first example and demonstrate how AddObject works. presents a way to let an application market itself. Simply sell HEADER.PRG and the form frmHeader.SCX are used in the the customer the features of an application that they “want,” second example to display a Grid with a customized header. even though there’s much more in the application. When the Be sure to copy VFP’s sample data into the directory with customer’s whistle has been whet for more, simply sell them these files first. the extra features. Paul shows all the benefits of creating a single code base for all of your customers, as well as ways Extended Articles you can capitalize on this while protecting your content. • 05BOOTH.HTM—Extended Article. In this article, Jim Booth • 05RUSSEL.ZIP—Source code for two routines described in takes a look at the improvements and enhancements to the Paul Russell’s article, “How to Sell Part of an Application.” The TableUpdate() function available in version 5.0 of VFP. first routine converts a string of 0s and 1s into a number. The • 05COOL.HTM—Extended Article. Annoyed by the zillions of second converts the string back to a bit value.

Help is a Mouse-Click Away: Introducing Developer Solutions–Online at www.pinpub.com When a programming crisis or dilemma comes up, you need choice to access any of the material we’ve published on dependable solutions fast. Technical support calls are eating your this topic. Alternatively, you can click on “Contents” on the upper- time and costing hundreds of dollars—even when you don’t get left side of the screen to list any of the published material by solid answers from them. In response to your needs, we’re proud issue date. to announce a new online search mechanism to help you find the Possible uses: information you need when you need it. • Use the search function to find an article you can’t locate Check out the continually updated Pinnacle Publishing in your back issues. index of expert-written articles at www.pinpub.com (tell your • Find all of our published articles on a given topic. colleagues!). You’ll be able to access each monthly issue of • Use the pay-per-view option to access articles published FoxTalk, SQL Server Professional, Smart Access, and Visual Basic before your subscription started by following the online Developer from 1996 through the present. You can view the entire sign-on instructions. (You’ll be able to access the download online Table of Contents of a newsletter or enter a search term to file as well.) pinpoint all of the available related articles. The search is free, tips • Browse the “Contents” portion for summaries of articles and are free, and articles (including any corresponding Subscriber free tips that have been published in FoxTalk and our other Download files) can be downloaded for a nominal fee. publications. Here’s how it works: Go to www.pinpub.com and click on Developer Solutions—Online. Enter the search term of your Coming soon: Online subscription options!

Portions of the FoxTalk Web site are available only to paid User name iguana subscribers of FoxTalk. Subscribers have access to additional resources to give you an edge in FoxPro development. Password jungle

24 FoxTalk May 1998 http://www.pinpub.com