FoxTalk. SECURITY is always an issue anytime you deploy an enterprise-wide. User-Defined. Field-Level Security. Steve Zimmelman. February

Similar documents
Splitting Up is Hard to Do Doug Hennig

Manage Your Applications Doug Hennig

May I See Your License? Doug Hennig

Mining for Gold in the FFC

Taking Control Doug Hennig

The Mother of All TreeViews, Part 2 Doug Hennig

uilding Your Own Builders with BuilderB Doug Hennig and Yuanitta Morhart

A New Beginning Doug Hennig

IntelliSense at Runtime Doug Hennig

Data-Drive Your Applications Doug Hennig

Extending the VFP 9 IDE Doug Hennig

Christmas Stocking Stuffers Doug Hennig

A File Open Dialog Doug Hennig

A New IDE Add-on: FoxTabs Doug Hennig

Zip it, Zip it Good Doug Hennig

CATCH Me if You Can Doug Hennig

FoxTalk. GOING through the classes provided in the FoxPro. More Gems in the FFC. Doug Hennig 6.0

A Generic Import Utility, Part 2

Web Page Components Doug Hennig

Base Classes Revisited Doug Hennig

Role-Based Security, Part III Doug Hennig

I Got Rendered Where? Part II Doug Hennig

pdating an Application over the Internet, Part II Doug Hennig

Data Handling Issues, Part I Doug Hennig

Putting Parameters in Perspective Doug Hennig

Session V-STON Stonefield Query: The Next Generation of Reporting

## Version: FoxPro 7.0 ## Figures: ## File for Subscriber Downloads: Publishing Your First Web Service Whil Hentzen

Try Thor s Terrific Tools, Part 2

The Best of Both Worlds

The Ultimate Grid. Visual FoxPro 6 DORON FARBER. Structure of the Solution. Design Issues

This chapter is intended to take you through the basic steps of using the Visual Basic

Speed in Object Creation and. Destruction. March 2016 Number 49. Tamar E. Granor, Ph.D.

Report Objects Doug Hennig

More Flexible Reporting With XFRX Doug Hennig

Chapter 18 Outputting Data

Outlook Web Access. In the next step, enter your address and password to gain access to your Outlook Web Access account.

XP: Backup Your Important Files for Safety

Robert Ragan s TOP 3

NCMail: Microsoft Outlook User s Guide

A File Open Dialog Box Doug Hennig

This is a book about using Visual Basic for Applications (VBA), which is a

Making the Most of the Toolbox

Taking Advantage of Idle Cycles. Make Your Application Work When the User Isn't. The Idea. The Strategy. December, 2003

Microsoft Access Database How to Import/Link Data

Sisulizer Three simple steps to localize

Part I: Programming Access Applications. Chapter 1: Overview of Programming for Access. Chapter 2: Extending Applications Using the Windows API

Data Handling Issues, Part II Doug Hennig

Multi-User and Data Buffering Issues

Advisor Answers. January, Visual FoxPro 3.0 and 5.0

Chapter 2 The SAS Environment

A Document Created By Lisa Diner Table of Contents Western Quebec School Board October, 2007

Advanced Uses for Dynamic Form

Give users a control that makes entering dates as easy as it is in Intuit Quicken.

Windows Event Binding Made Easy Doug Hennig

Working with the Registry. The Registry class makes it easy. The Registry Structure. January, By Tamar E. Granor

Arduino IDE Friday, 26 October 2018

Much ADO About Something Doug Hennig

CheckBook Pro 2 Help

MAPLOGIC CORPORATION. GIS Software Solutions. Getting Started. With MapLogic Layout Manager

anguage Enhancements in VFP 7, Part I Doug Hennig

COSC 2P91. Bringing it all together... Week 4b. Brock University. Brock University (Week 4b) Bringing it all together... 1 / 22

Taskbar: Working with Several Windows at Once

Clean & Speed Up Windows with AWO

1: Introduction to Object (1)

Taking Advantage of ADSI

CHAPTER 1 COPYRIGHTED MATERIAL. Getting to Know AutoCAD. Opening a new drawing. Getting familiar with the AutoCAD and AutoCAD LT Graphics windows

As a programmer, you know how easy it can be to get lost in the details

Intro. Scheme Basics. scm> 5 5. scm>

This book is about using Visual Basic for Applications (VBA), which is a

It Might Be Valid, But It's Still Wrong Paul Maskens and Andy Kramek

Advisor Discovery. Use BindEvent() to keep things in synch. BindEvent() Refresher. June, By Tamar E. Granor, technical editor

Oracle Cloud. Content and Experience Cloud ios Mobile Help E

Centura is Dynamic Gianluca Pivato F

Part 1: Understanding Windows XP Basics

In this chapter, I m going to show you how to create a working

Financial Statements Using Crystal Reports

Integrating Visual FoxPro and MailChimp

10 Tips For Effective Content

Furl Furled Furling. Social on-line book marking for the masses. Jim Wenzloff Blog:

SharePoint 2010 Site Owner s Manual by Yvonne M. Harryman

One of Excel 2000 s distinguishing new features relates to sharing information both

Customizing DAZ Studio

Crash Course in Modernization. A whitepaper from mrc

Learn Dreamweaver CS6

NCMail: Microsoft Outlook User s Guide

Word: Print Address Labels Using Mail Merge

Setting up ODBC, Part 2 Robert Abram

Handling crosstabs and other wide data in VFP reports

One of the fundamental kinds of websites that SharePoint 2010 allows

Without further ado, let s go over and have a look at what I ve come up with.

Getting started 7. Setting properties 23

the NXT-G programming environment

1-Step Appraisals Personal Property Appraisal Software

Navigating and Managing Files and Folders in Windows XP

Cool Tools by Craig Boyd, Part II Doug Hennig

Testing is a very big and important topic when it comes to software development. Testing has a number of aspects that need to be considered.

Download Free Pictures & Wallpaper from the Internet

MAPLOGIC CORPORATION. GIS Software Solutions. Getting Started. With MapLogic Layout Manager

Splitting a Procedure File

Hello World! Computer Programming for Kids and Other Beginners. Chapter 1. by Warren Sande and Carter Sande. Copyright 2009 Manning Publications

Transcription:

FoxTalk Solutions for Microsoft FoxPro and Visual FoxPro Developers User-Defined Field-Level Security Steve Zimmelman Just because your new development is all done in Visual FoxPro 6.0 doesn t mean that your legacy 2.x applications have disappeared. However, you don t want to get stuck with a lot of ongoing maintenance. In this article, Steve discusses how to add a feature in an old DOS application in a way that won t require substantial amounts of follow-up work. SECURITY is always an issue anytime you deploy an enterprise-wide application, and it can easily become one of the many black holes in application maintenance. A client recently asked me if they could have field-level security in their FoxPro 2.6 DOS application. They wanted to block users from specific fields depending on their access rights. After careful evaluation and realizing that this request could easily turn into one of those black holes I wanted to give them something they could maintain without my intervention. The application already had some basic security when users log in, they re assigned an access level from MASTER to LEVEL9. So I incorporated the access levels and used a table to store the screen, field names, and access levels that could be used to gain entry to specific fields. This way, it can store security for any object on any screen in the application. Structure for database: SECURITY.DBF Field Field Name Type Width 1 SCREEN Character 15 2 VARNAME Character 21 3 MASTER Logical 1 4 LEVEL1 Logical 1 5 LEVEL2 Logical 1 6 LEVEL3 Logical 1 7 LEVEL4 Logical 1 8 LEVEL5 Logical 1 Continues on page 3 1 User-Defined Field-Level Security Steve Zimmelman 5 Best Practices: Seeing Patterns: The Strategy Jefferey A. Donnici 10 Reusable Tools: A Last Look at the FFC Doug Hennig 14 What s Really Inside: When Does it Happen? Jim Booth 16 Creating a Class Help Generator Koriana Kent 20 Comparing VFP and SQL Server 7.0 Datatypes Michael Levy 23 February Subscriber Downloads EA EA EA EA 6.0 Applies to VFP v6.0 (Tahoe) February 1999 Volume 11, Number 2 Editorial: A Recent Compilation of Cool Tools Whil Hentzen ActiveX and Automation Review: Outlook One More Time! John V. Petersen The Kit Box: View from the Top Paul Maskens and Andy Kramek Visual Basic for Dataheads: The Basics of Writing Code Whil Hentzen Applies to VFP v5.0 Applies to VFP v3.0 Accompanying files available online at http://www.pinpub.com/foxtalk Applies specifically to one of these platforms. Applies to FoxPro v2.x

2 FoxTalk February 1999 http://www.pinpub.com

Field-Level Security... Continued from page 1 9 LEVEL6 Logical 1 10 LEVEL7 Logical 1 11 LEVEL8 Logical 1 12 LEVEL9 Logical 1 Here s how it works: If a user has an access level of MASTER, the application initializes a hot-key sequence to the security maintenance routine and passes with it two parameters: _CurObj, the current screen object number, and WOUTPUT(), the current screen s name: If (Upper(m.cUserLevel) = 'MASTER') On Key Label ALT+U Do F_Security ; With _CurObj, WOutPut() The security maintenance routine (F_Security) uses the value of _CurObj to find the field s name. It then looks in SECURITY.DBF for a record containing a match for the Screen and Var name. If it doesn t find one, it sets the flag m.lnew to.t. so the save routine will know whether it needs to add a record or save the changes back to the current record (see Figure 1). Procedure F_Security Parameter nobject, cscreen Push Key Clear Private narea,cvar,lnew narea = Select() If Empty(m.cScreen) cscreen = 'SCREEN' If! Used('Security') Use Security In 0 Order Security Select Security *-- Declare all field memvars private --* For I = 1 To FCount() Private (Field(i)) EndFor *-- Pad memvars to same length as fields --* cscreen = Padr(Upper(m.cScreen),Len(Security.Screen)) cvar = Padr(ObjVar(m.nObject),Len(Security.VarName)) *-- Look for corresponding record in Security.dbf. *-- If one doesn't exist, add it. * If! Seek(m.cScreen+m.cVar) Scatter Memvar Blank Screen = m.cscreen VarName = m.cvar lnew =.T. Else Scatter Memvar lnew =.F. Function SecSave *-- Called from OK button in Security.SPR --* Private lsave,s,i lsave = m.master *-- If m.master isn't checked, check for other *-- levels. If no other levels are checked, don't *-- save the record. If! m.lsave For i = 1 To 9 s = 'm.level'+str(i,1) If Eval(m.s) lsave =.T. Exit EndFor Do Case Case! m.lsave And! m.lnew *-- Nothing was checked, --* *-- reset record to be reused. --* m.screen = '' m.varname = '' Gather Memvar Case m.lsave If m.lnew If Seek(Space(15),'Security') Gather Memvar Else Insert Into Security From Memvar Else Gather Memvar EndCase Return.T. When a user goes to edit a screen, the file SECURITY.DBF is queried and any objects that don t pass security are disabled for the current user. Putting it all together The final step is to add a function to the screen s When event and pass the name of the screen to the function ScrnWhen() (see Figure 2). The function ScrnWhen() spins through every object on the screen, passing the object number and the screen name to the function FldRights(), which, in turn, verifies whether the user has the proper access level to gain entry to the field (see Figure 3). If not, then the field is disabled. Function ScrnWhen Parameter cscrn Private i Do Security.SPR Pop Key Select (m.narea) Return The screen file Security.spr calls the function SecSave() to save or discard any changes: Figure 1. MASTER users can specify what security levels are necessary to gain access to the current field. Figure 2. Add the function ScrnWhen() to the When event of the screen. http://www.pinpub.com FoxTalk February 1999 3

i=1 DO WHILE (Type('ObjVar(i)')='C') If! FldRights(m.i,m.cScrn) Show Object (m.i) Disabled i=i+1 ENDDO Return.T. Function FldRights Parameter nobject,cscreen Private Rtn,cEval,lCheck,s,i,cVar Rtn =.T. * *-- Pad memvars to same length as fields --* cscreen = Padr(Upper(m.cScreen),Len(Security.Screen)) cvar = Padr(ObjVar(m.nObject),Len(Security.VarName)) * If Empty(m.cVar) Return.T. If! (Upper(m.cUserLevel) = 'MASTER') If Seek(m.cScreen+m.cVar,'Security') lcheck = Security.Master * *-- At least one level must be true * to be a valid entry. * If! m.lcheck For i = 1 To 9 s = 'Security.LEVEL'+Str(i,1) If Eval(m.s) lcheck =.T. Exit EndFor If m.lcheck ceval = 'Security.'+m.cUserLevel Rtn = Eval(m.cEval) Return (m.rtn) Figure 3. The user doesn t have access to the Name and Phone fields or the Delete button. This concept works on almost every editable screen object, with only one small caveat: If you use Button Sets, it will only recognize the first button in the set because they all use the same MemVar. So it s best to keep the buttons as individual objects with their own MemVar pointer. 02ZIMMEL.ZIP at www.pinpub.com/foxtalk Steve Zimmelman is a lead systems analyst for Professional Support, Inc., in Wilmington, MA, and a contributor to FoxTalk, Delphi Developer, and InfoWorld magazine. He also plays a pretty good blues guitar. skz@bicnet.net. 4 FoxTalk February 1999 http://www.pinpub.com

Best Practices Seeing Patterns: The Strategy Jefferey A. Donnici 6.0 FoxTalk This month s Best Practices column is the fourth installment in a continuing a series that discusses some common objectoriented design patterns. The primary focus of the series is to briefly describe each pattern and then demonstrate how it can be identified and implemented in Visual FoxPro applications. The design pattern discussed this month is the Strategy pattern, which allows a set of similar algorithms to be used interchangeably at runtime, as needed by the state of the system. THIS fourth installment in the Seeing Patterns series discusses the Strategy pattern. For those who are just tuning in, the purpose of this series is to discuss the real-world use of several object-oriented design patterns, using Visual FoxPro examples for illustration. As I ve mentioned in the past, I encourage you to look through the last few Best Practices columns for a brief introduction to the concepts involved with design patterns. And just to repeat myself again, make sure to have a copy of Design Patterns: Elements of Reusable Object-Oriented Software by E. Gamma, R. Helm, R. Johnson and J. Vlissides (Addison-Wesley, ISBN 0-201-63361-2) handy for this series. I know I ve mentioned and recommended this book several times, so I apologize in advance to those who have heard me repeat myself each time. I just want to make sure that new readers are able to follow along with our patternsrelated discussions. Before we jump into the Strategy pattern, remember that object-oriented design patterns aren t a languagespecific concept. While there are patterns that are specific to certain environments or types of applications, the term design patterns generally applies to patterns that can solve a design problem in any object-oriented development environment. Naturally, this series discusses patterns in a Visual FoxPro-centric fashion, but it s important to realize that these patterns are just as valuable and relevant in other languages. Again, I ve mentioned this in the past, but I want to make sure my discussion and examples here, and in previous columns, don t lead you to believe that these are the only possible implementations of a patterns-based design. Dissecting the Strategy Like some of the previous patterns mentioned in this series, the Strategy pattern has been discussed in FoxTalk in the past. In the January 1997 issue (see OOP Design Patterns: Add Design Flexibility with a Stretegy Pattern ), Steven Black introduced the Strategy pattern to FoxTalk readers, explaining that the Strategy preserves flexible behavior by decoupling a process from the one or more potential behaviors (or algorithms) that implement it. In other words, the Strategy pattern prescribes removing specific functionality from the various places in an application that use it. The idea is that there are often behaviors that occur in several places throughout a system and are only slightly different in their internal logic. By abstracting that behavior into an independent algorithm class, the various behaviors can be reused in the system without the clients that implement them requiring modification. As it turns out, the Strategy also happens to be my personal favorite of all the patterns mentioned in Design Patterns (only true geeks have favorite design patterns, right?). I think this is because a design incorporating the Strategy pattern usually includes variations on several other patterns. Most often, the Bridge pattern is used, as in the first example that follows. The Strategy pattern can be described as a type of Bridge pattern where one side of the Bridge is determined on the fly and is unknown at design time. Also, the Strategy pattern is the first pattern I felt I really understood, and it provided a solution in a design I happened to be working on while reading the book (which always helps when deciding on your favorite pattern). One example of the Strategy pattern is a set of classes that save and restore user preferences for an application. While the uses for the preference values vary, the way that the application sets and gets those values is largely the same. On the front end of this design requirement, each of these preference settings needs an interface through which the user can modify the setting. If the application has several options for specifying which font the user prefers (for a report, grid, and so forth), then applying the Strategy pattern to the design can greatly reduce the amount of code and maintenance involved. By providing a single container class that contains the user interface (the client) and then using several behavior classes (the algorithms) to deal with setting/restoring the different preference values, the Strategy pattern provides an extremely high level of reuse. http://www.pinpub.com FoxTalk February 1999 5

Putting Strategy to use and reuse When considering the Strategy pattern in your design, there are some advantages and drawbacks to remember that might affect how you approach the actual design: The Strategy pattern provides for a very high level of reuse, but this reuse comes with a cost. That cost is complexity. By abstracting similar behaviors out of the components that use them, the diagram of the relationships between classes becomes significantly more complex. It s important to document the design decisions so that future maintenance and updates are made with the original design considerations in mind. The individual behavior classes that are dynamically used at runtime will often have common functionality between them. Look for similarities between these behaviors, and consider using abstract classes so that inheritance can provide added reuse in those common areas. Carefully plan the mechanism that will decide which behavior to use at runtime. While a CASE statement can be used for small component designs, a more dynamic mechanism will provide greater scalability. After all, the monolithic CASE structure is one of the things that the Strategy helps to do away with. By using a separate Strategy object to make the decision about which behavior to instantiate, the client that benefits from these behaviors can remain static as behaviors are added to, changed, and deleted from the system. Allow for a common interface among all of the behavior classes within the pattern. Because the Strategy object and/or client won t know until runtime which behavior is being used, it s important that the programmatic interface be consistent. This lets the Strategy object not worry about what to pass to the behavior class, as well as what will be returned. appear inefficient, it s another cost of using a common interface between all behaviors and dynamically choosing a behavior at runtime. Revisiting an old example The first column in this series discussed the Bridge pattern and mentioned that the Strategy pattern should be considered when a portion of the Bridge relationship can t be determined at design time. In this example, which was a part of that column, the mechanism that creates an export file uses a Strategy pattern to determine which file type to create. Figure 1 is a UML (Unified Modeling Language) diagram of the export component, with the Strategy object, cstexporter, and the individual behaviors labeled in the diagram. In this example, the cstexportinterface class uses an array property to determine which file export types are available, as well as which behavior classes provide the type-specific export mechanism. In a production environment, this array might be populated dynamically, based on a cursor or table, so that the application s metadata drives the provided functionality. By using the cstexporter abstract class, the common behavior of checking drives, directories, and files is written only once and is accessible via inheritance to the typespecific subclasses. DEFINE CLASS cstexportinterface AS Custom cfilename = "" cfiletype = "" oexportprocess =.NULL. DIMENSION aexporttypes[3,2] aexporttypes[1,1] = "XLS" aexporttypes[1,2] = "cstexcelexport" aexporttypes[2,1] = "TXT" aexporttypes[2,2] = "csttextexport" aexporttypes[3,1] = "DBF" aexporttypes[3,2] = "cstdbfexport" FUNCTION Export LPARAMETERS tcfilename, tcfiletype LOCAL llretval THIS.cFilename = UPPER(ALLTRIM( tcfilename )) Because the interface for all behavior classes is the same, there also exists the potential for overloading of the communication between the Strategy object/client and the behavior classes. Some of the information passed to the behavior classes might be unnecessary for the behavior to perform its role. While this might Figure 1. The Strategy pattern decouples the behavior classes, which create different types of export files, from the application s client components that allow exporting. 6 FoxTalk February 1999 http://www.pinpub.com

THIS.cFileType = UPPER(ALLTRIM( tcfiletype )) THIS.PreProcess() llretval = THIS.CreateExport() THIS.PostProcess() RETURN llretval ENDFUNC FUNCTION PreProcess *-- Any pre-processing of the current cursor, *-- prior to export, occurs here. ENDFUNC FUNCTION CreateExport LOCAL lncounter, llretval *-- Loop through the array of supported file *-- types to find the class to use for the *-- export type. FOR lncounter = 1 TO ; ALEN(THIS.aExportTypes, 1) IF THIS.aExportTypes[lnCounter,1] = ; THIS.cFileType THIS.oExportProcess = CREATEOBJECT( ; THIS.aExportTypes[lnCounter,2]) llretval = THIS.oExportProcess.Export( ; THIS.cFilename) ENDIF ENDFOR && lncounter = 1 TO ALEN(THIS.aExportTypes, 1) RETURN llretval ENDFUNC FUNCTION PostProcess *-- Post-processing of the current cursor, *-- prior to export, occurs here. ENDFUNC ENDDEFINE DEFINE CLASS cstexporter AS Custom cfileclause = "" cfilename = "" FUNCTION Export LPARAMETERS tcfilename THIS.cFilename = tcfilename THIS.CheckPath() THIS.CheckFile() THIS.CreateFile() RETURN FILE(tcFilename) ENDFUNC FUNCTION CheckPath *-- The purpose of this method is to verify *-- that the drive/dir indicated is valid, *-- that the user has rights to write to that *-- location, etc. Code omitted for clarity. ENDFUNC FUNCTION CheckFile *-- The purpose of this method is to verify *-- that the filename indicated is valid, *-- that it's not too long for the current *-- OS or network drive, that there are no *-- invalid characters, etc. This method *-- might also check to see whether the file *-- already exists and provide the user the *-- opportunity to replace it. Again, the *-- code for this is omitted for clarity. ENDFUNC FUNCTION CreateFile LOCAL lcfilename, lcfileclause lcfilename = UPPER(ALLTRIM(THIS.cFilename)) lcfileclause = THIS.cFileClause *-- Create the file using the clause indicated *-- by this filetype. COPY TO (lcfilename) TYPE &lcfileclause ENDFUNC ENDDEFINE DEFINE CLASS cstexcelexport AS cstexporter cfileclause = "XLS" FUNCTION CreateFile cstexporter::createfile() *-- At this point, it might be desirable to *-- make some Excel-specific changes to the *-- exported file. These might include opening *-- it via OLE Automation for formatting the *-- worksheet, changing field names for each *-- column to an English name from a data *-- dictionary, etc. ENDFUNC ENDDEFINE DEFINE CLASS csttextexport AS cstexporter cfileclause = "DELIMITED" ENDDEFINE DEFINE CLASS cstdbfexport AS cstexporter cfileclause = "FOX2X" ENDDEFINE Revisiting Codebook In the Visual FoxPro 3 Codebook, by Y. Alan Griver (published by Sybex), the accompanying framework provides a number of examples of design patterns at work. One of the features provided by the framework is the ability to read and write system-wide settings. These settings can be user preferences, data directory locations, or internal settings that determine the system s capabilities at runtime. Because VFP 3.0 could be run in both 32-bit and 16-bit environments, it wasn t possible to determine at design time whether to use Registry values or an application INI file. After all, a well-behaved 32-bit program uses the Registry for system settings, but 16-bit operating systems don t have a Registry to use. To work around this Catch-22, the application object determines which mechanism to use at runtime and instantiates the correct object accordingly. In the GetSystemSettingClass() method, properties of the application object are referenced to see whether the developer has specified one method over another. If not, then the operating system is checked to see whether it s a 32-bit system that will support Registry use. lcsystemsettingclass = "cinifile" THIS.cRegistryKey = "" THIS.cINIFile = THIS.GetINIFile() IF NOT THIS.lUseINIFile DO CASE CASE UPPER(OS()) = "WINDOWS NT" ; OR LEFT(UPPER(OS()),9) = "WINDOWS 4" lcsystemsettingclass = "cregistry" THIS.cRegistryKey = THIS.GetRegistryKey() THIS.cINIFile = "" ENDCASE ENDIF RETURN lcsystemsettingclass The return value of this method is used in the Init() event method to add the.osystemsetting member object http://www.pinpub.com FoxTalk February 1999 7

to the application. After the application is instantiated, all needs to read and write system settings are handled by the application s GetSetting() and SetSetting() methods. The classes that provide the underlying functionality, cregistry and cinifile, both contain Get() and Set() methods with an identical interface. This is provided because both classes inherit from the same abstract parent class, csystemsetting. Only the mechanism-specific subclasses contain the implementation details of reading/ writing the Registry or the INI file. To read a system setting, the GetSetting() method on the application receives three parameters: the section of the system s preferences where the setting is, the name of the system setting entry to retrieve, and a default value to return if no entry is found. Those parameters are massaged slightly and then passed to the Get() method of the.osystemsetting member object on the application. If the INI file mechanism is being used, then the name of the INI file is passed as the fourth parameter. Otherwise, the fourth parameter is a logical False. Here s a portion of the code from GetSetting(): LPARAMETERS tcsection, tcentryname, tudefault *-- Some code omitted for clarity lcsection = THIS.cRegistryKey + tcsection luinifile = IIF(EMPTY(THIS.cINIFile),.F., THIS.cINIFile) RETURN THIS.oSystemSetting.Get(lcSection, tcentryname, ; ludefault, luinifile) Because the interface for the cinifile and cregistry Get() methods are the same, this method works for either mechanism. When the Registry mechanism is being used, the False value is ignored by the cregistry.get() method. When the INI file mechanism is used, it tells the cinifile class which INI file contains the application s settings. A similar mechanism is used to provide for writing system settings to either an INI file or the system Registry. In this case, both classes have a Set() method with identical interfaces. Because there isn t a need to deal with a default return value, there are only three parameters passed to the application object s SetSetting() method. These parameters are the section of the preferences to write the value into, the name of the key to write, and then the value to store to that key. Once again, the parameters are passed through to the Set() method of the.osystemsetting member object: LPARAMETERS tcsection, tcentryname, tuvalue *-- Some code omitted for clarity lcsection = THIS.cRegistryKey + tcsection luinifile = IIF(EMPTY(THIS.cINIFile),.F., THIS.cINIFile) RETURN THIS.oSystemSetting.Set(lcSection, tcentryname, ; tuvalue, luinifile) In this case, notice that there are still four parameters being passed to the Set() method of the behavior classes. That s because the INI file class still needs to know which INI file to write the value to. If the Registry mechanism is being used at runtime, the fourth parameter is again set to a logical False so that it will be ignored by the cregistry class s Set() method. While this design uses only two different types of behavior classes to handle the functionality at runtime, it could easily be extended to store preferences in a table. To do this, a new class would be created as a subclass of csystemsetting, which provides the interface, and the Get() and Set() methods would be implemented in a manner that dealt only with tables. The only change to the design then would be to provide the application object, which is serving as the Strategy object as discussed previously, with a way of determining which of the three mechanisms to use. Figure 2 shows a UML diagram of this type of design with a new, table-specific settings class outlined with a dashed line. And finally... In this, the last example of the Strategy pattern, I m using a series of different classes to determine what sort of discount to apply to a product. The discount varies according to the type of customer requesting the price Figure 2. By abstracting the pieces that save and restore application settings, the system can dynamically determine which mechanism to use. Using the csystemsetting class allows common requirements to be inherited by each mechanism. 8 FoxTalk February 1999 http://www.pinpub.com

quote. When a new product or customer type is selected from the form s combo boxes, the text boxes are updated to reflect the new price to give to the customer (see Figure 3). The form, QUOTES.SCX, along with all the other files required for this example, are included in this month s Subscriber Downloads at www.pinpub.com/foxtalk. For each customer type listed in the CUSTTYPE.DBF table, a class has been designated to calculate that customer type s product discount. For the sake of simplicity, this table is put into an array when the form is instantiated. The layout of this table looks like this: Customer type New/Existing Customer VIP Customer Corporate Customer Non-Profit Organization Discount class cstnewdiscount cstvipdiscount cstcorpdiscount cstnpodiscount Each of the class definitions listed here is subclassed from cstabstractdiscount, which provides the programmatic interface for the subclasses. All five class definitions can be found in the QUOTES.VCX class library. When the dealer cost and retail price values are sent to the CalcDiscount() method in the discount class, it returns the discounted price that the customer receives. The discount classes also have an ndiscountfactor property, which is used differently by each class s CalcDiscount() method. The point is that the implementation of each discount Strategy in this example is very different, though the form that uses it (specifically, the form s ReCalc() method) doesn t know any of the implementation details. In fact, because the decision over which discount class to use is made on the fly based on the rows in a table, even the decision of which class to use is removed from the form s responsibilities. For this example, the discount class is instantiated only for as long as it takes to calculate the discounted price to quote to the customer. This is handled by the form s Recalc() method, which also updates the form s other controls to reflect the current price, product, and discounting information. Here are the contents of that method: LOCAL lcdiscountclass, lodiscount, lncost, ; lnmsrp, lnquote lcdiscountclass = THISFORM.GetDiscountClass() *-- If we got a class definition, use it to calculate *-- the discounted price to quote to the customer. IF NOT EMPTY(lcDiscountClass) SET CLASSLIB TO CURDIR() + "quotes.vcx" ADDITIVE lodiscount = CREATEOBJECT(lcDiscountClass) lncost = THISFORM.aProducts ; [THISFORM.cboProducts.ListIndex, 3] lnmsrp = THISFORM.aProducts ; [THISFORM.cboProducts.ListIndex, 4] lnquote = lodiscount.calcdiscount(lncost, lnmsrp) ELSE lncost = 0 lnmsrp = 0 lnquote = 0 ENDIF THISFORM.txtCost.Value = lncost THISFORM.txtMSRP.Value = lnmsrp THISFORM.txtQuote.Value = lnquote THISFORM.Refresh() Obviously, there s not a lot of fancy calculation or black magic happening here. Remember, the Strategy pattern isn t a complex pattern to see or implement in your own designs the bulk of the work in understanding the pattern or using it in a design is in how to determine which Strategy to use at runtime. So now you re thinking that the contents of that GetDiscountClass() method called previously are pretty complex, right? Here s that method in its entirety: LOCAL lcretval *-- Return the third element in the customer types *-- array - using the listindex from the combo to *-- determine the row. IF THISFORM.cboCustTypes.ListIndex > 0 lcretval = ALLTRIM(THISFORM.aCustTypes ; [THISFORM.cboCustTypes.ListIndex, 3]) ELSE lcretval = "" ENDIF RETURN lcretval Pretty simple, right? The discounting class to use for each customer type is specified in the table itself, which was queried into an array by the form. To get the correct class name, I just have to determine the current row of the Customer Type combo box and reference that row in the acusttypes array. There are, of course, a lot of things about this example that would change if it were to be used in a real application. For example, I d expect there to be a lot more error handling or trapping of exceptions in the form. You Figure 3. A different class definition is used to determine the can also imagine that the CalcDiscount() methods for each discounting mechanism for each customer type. As the customer of those discounting classes would be quite a bit more type or product changes, the discounting class is used to complex than they are here. At any rate, the purpose is to calculate the information for the form s text box controls. Continues on page 23 http://www.pinpub.com FoxTalk February 1999 9

A Last Look at the FFC Reusable Tools Doug Hennig 6.0 FoxTalk This month, Doug takes a last look at the FFC, digging into classes in a couple more categories of the FoxPro Foundation Classes: Menus and Utilities. THE Menus subfolder of the Foundation Classes folder in the Visual FoxPro Catalog of the Component Gallery (from now on, I ll just refer to the subfolder without mentioning where it can be found, since they re all in the Foundation Classes folder) has two classes: Shortcut Menu Class (_ShortcutMenu in _MENU.VCX) and Navigation Shortcut Menu (_NavMenu in _TABLE2.VCX). _ShortcutMenu is an object-oriented wrapper for VFP s non-object-oriented shortcut menu system. It s one of the most useful classes in the FFC because it provides a simple way to programmatically (and therefore dynamically) create shortcut menus. The sample that demonstrates it is pretty good; you can run it from the Component Gallery by right-clicking on the class, choosing View Sample, and then Run. This class has the following methods: AddMenuBar: Adds a new bar to the menu. You can specify the prompt and optionally the code to execute when the bar is selected (if nothing is specified, the code in the conselection property is executed) or another menu object to display as a submenu, other clauses for the bar (such as a SKIP FOR clause), the location of the bar in the menu, whether the bar is marked or not, whether the bar is disabled or not, and whether the bar appears in bold or not. AddMenuSeparator: Adds a separator bar to the menu. ShowMenu: Displays the menu, executes the appropriate action for the selected bar, and then hides the menu. ClearMenu: Removes all bars from the menu. NewMenu: Instantiates another _ShortcutMenu object without having to use CREATEOBJECT() or NEWOBJECT(). This is frequently used when a menu bar has a submenu. For example, here s code from the _NavMenu class that defines a submenu (ogomenu) first, then the main menu (This.oMenu), with the submenu used as the action to execute when the first bar in the main menu is selected (gomenu is passed as the action in the AddMenuBar method): ogomenu = THIS.oMenu.NewMenu() WITH ogomenu.addmenubar(menu_top_loc,"othis.onav.gotop()").addmenubar(menu_bottom_loc,"othis.onav.gobottom()").addmenubar(menu_next_loc,"othis.onav.gonext()").addmenubar(menu_prev_loc,"othis.onav.goprevious()").addmenubar(menu_record_loc,"othis.dogoto") ENDWITH WITH THIS.oMenu.AddMenuBar(MENU_GOTO_LOC,oGoMenu).AddMenuSeparator.AddMenuBar(MENU_ADD_LOC,"oTHIS.AddRecord").AddMenuBar(MENU_DELETE_LOC,"oTHIS.DeleteRecord").AddMenuSeparator.AddMenuBar(MENU_SORT_LOC,"oTHIS.DoSort").AddMenuBar(MENU_FILTER_LOC,"oTHIS.DoFilter").AddMenuBar(MENU_FILTER2_LOC,"oTHIS.DoFilter2") ENDWITH _ShortcutMenu is pretty complete, except for one thing: You have to know the name of the array containing the menu choices (it s called amenu) and do ALEN() on it to know how many bars have been defined. Wouldn t you already know how many bars there are since you populated the menu yourself? Not necessarily; you might want to populate the menu, then allow a hooked object to add its choices to the menu. So, I subclassed _ShortCutMenu into SFShortcutMenu (in SFFFC.VCX, like the other FFC subclasses I ve created) and added an nbarcount property, with access and assign methods. The assign method has this single line of code: error 1743, 'nbarcount' This makes the property read-only. The access method has the following code: return iif(empty(this.amenu[1]) or ; isnull(this.amenu[1]), 0, alen(this.amenu, 1)) This returns 0 if there are no bars defined or the number of rows in the amenu array if there are any bars defined. Notice that this code doesn t actually assign a value to nbarcount; it always remains 0, but the access method calculates and returns a value when anything asks for it, so it s not important that the property actually contain any value. I like _ShortcutMenu (and SFShortcutMenu) so much that I implemented it in every class in SFCTRLS.VCX (our base class library). I removed the cshortcutmenuname property (which was used to hold the name of a popup 10 FoxTalk February 1999 http://www.pinpub.com

menu that would be created on the fly) and added an omenu property to contain an object reference to an SFShortcutMenu object and an luseformshortcutmenu property, which contains.t. if the menu of the form should be included in the menu for an object on that form. I also made the Destroy method set omenu to NULL to avoid dangling object references. I changed the custom ShowMenu method (which is called from RightClick); rather than creating a shortcut menu using DEFINE POPUP, it instantiates an SFShortcutMenu object, calls the custom ShortcutMenu method to populate it, calls the ShortcutMenu method of a hooked object (if there is one) to add to it, and then displays the menu if it has any bars. Here s the code: private loobject, ; lohook, ; loform with This loobject = This lohook =.ohook loform = Thisform * Define the menu if it hasn't already been defined. if vartype(.omenu) <> 'O'.oMenu = newobject('sfshortcutmenu', 'SFFFC.VCX') if vartype(.omenu) = 'O' * Populate it using a custom method..shortcutmenu(.omenu, 'loobject') * Use the hook object (if there is one) to do any * further population of the menu. if vartype(lohook) = 'O' and ; pemstatus(lohook, 'ShortcutMenu', 5) lohook.shortcutmenu(.omenu, 'lohook') endif vartype(lohook) = 'O'... * If desired, use the form's shortcut menu as well. if.luseformshortcutmenu and ; type('thisform.name') = 'C' and ; pemstatus(loform, 'ShortcutMenu', 5) loform.shortcutmenu(.omenu, 'loform') endif.luseformshortcutmenu... endif vartype(.omenu) = 'O' endif vartype(.omenu) <> 'O' * Activate the menu if necessary. if vartype(.omenu) = 'O' and.omenu.nbarcount > 0.oMenu.ShowMenu() endif vartype(.omenu) = 'O'... endwith Note the use of PRIVATE, rather than LOCAL, variables. They define references to objects in case the action for a bar is to call a method of the object. You can t do this using code like This.Method() because This isn t applicable in a menu. Instead, the object reference is put into a variable and that variable is referenced; for example, loobject.method(). The ShortcutMenu method is used to actually fill the shortcut menu object with menu bars. It accepts two parameters: an object reference to the menu object to populate and the name of the variable containing the object reference to this object. Why pass the menu reference when it s stored in the omenu property? Because we might want to hook several classes together, populating the menu object of the first one. For example, you might want to have the right-click menu for a text box include not only items for the text box, but for the form it s on as well. To do that, set the luseformshortcutmenu property of the text box to.t. The ShowMenu method of the text box will call the ShortcutMenu method of the form, passing a reference to the text box s menu object and the name of the variable the text box defined that references the form (loform). That way, the form can add its items to the text box s menu and everything will work. In most classes, ShortcutMenu is an abstract method since I couldn t think of any useful menu choices that would be used in every instance and subclass. Those that have a text component (SFComboBox, SFEditBox, SFSpinner, and SFTextBox), however, have menu bars for cut, copy, paste, clear, and select all. Here s the code from SFTextBox.ShortcutMenu: lparameters tomenu, ; tcobject with tomenu.addmenubar('cu\<t', ; "sys(1500, '_MED_CUT', '_MEDIT')").AddMenuBar('\<Copy', ; "sys(1500, '_MED_COPY', '_MEDIT')").AddMenuBar('\<Paste', ; "sys(1500, '_MED_PASTE', '_MEDIT')").AddMenuBar('Cle\<ar', ; "sys(1500, '_MED_CLEAR', '_MEDIT')").AddMenuSeparator().AddMenuBar('Se\<lect All', ; "sys(1500, '_MED_SLCTA', '_MEDIT')") endwith Other obvious choices for menu items are add, delete, save, cancel, and other methods for data entry form classes, dial this number for a phone number class, and send email for an e-mail address class. _NavClass, the other class in the Menus folder, is more of a demo of how to use the _TableNav FFC class, which provides table navigation methods (like GoTop, GoNext, GoPrevious, and so forth) than a useful class on its own, mostly because it lacks the flexibility you d likely need in a real-life application. However, it might be worth looking at to get ideas for how you might create your own, more flexible version. Utilities We ve already looked at some of the classes in the Utilities folder; Application Registry, INI Access, ODBC Registry, and Registry Access were discussed in my December 1998 column (see Mining for Gold in the FFC ). Array Handler (the _ArrayLib class in _UTILITY.VCX) has three array-handling methods, which are very useful if you don t already have routines that perform these functions. AColScan looks for a value in a particular column of an array (as opposed to ASCAN, http://www.pinpub.com FoxTalk February 1999 11

which will look in every element of the array) and can return the row number it was found in (as opposed to ASCAN, which returns the element number on which you almost always have to then use ASUBSCRIPT to get the row number). InsAItem inserts an element into an array and sets that element to the specified value; in the case of a two-dimensional array, it adds an entire row and can set either the first element or each element in the array to the specified value. DelAItem deletes an element (an entire row in the case of a two-dimensional array) from an array. File Version (_FileVerson in _UTILITY.VCX) is a wrapper for the VFP 6 AGETFILEVERSION() function, which puts information about a file (such as copyright and version information) into an array. Its GetVersion method ensures that the specified file exists and puts the results of AGETFILEVERSION() into its aversion property. DisplayVersion() displays the version information using MESSAGEBOX(). This class is somewhat useful, but I think I ll just use AGETFILEVERSION() directly when I need it. Find Files (_Filer in _UTILITY.VCX) is a wrapper class for FILER.DLL, a cool text search tool found in the TOOLS\FILER subdirectory of the VFP home directory. Although you can use FILER.DLL directly, _Filer provides an instantiation wrapper for this COM object: If it isn t registered on the system, it offers to register it for you (assuming the DLL can be found, of course). It has properties that map to the COM object s properties; for example, _Filer.cFileExpression maps to the FileExpression property of the COM object. It can automatically handle empty properties in some cases; for example, if cfileexpression is blank, it puts *.* into the FileExpression property of the COM object, and if csearchpath is empty, it can optionally display a dialog box asking for the directory to search. It has one method you ll use Find which returns the number of files it found and puts an object reference to the Files collection of the COM object into its ofiles property. One major drawback to this class (and FILER.DLL): Microsoft doesn t allow us to distribute the DLL, so you can t use it to provide text search capabilities in your applications; you can only use it to create tools for yourself or other VFP developers. Messagebox Handler (_MsgBox in _DIALOGS.VCX) is a wrapper for the MESSAGEBOX() function. It has cmessage, ntype, and ctitle properties that map to the parameters for MESSAGEBOX(), so, once set, you don t have to specify them each time (although you re likely to change cmessage every time you use this class). It also has an lbeep property that determines whether the class should beep as the dialog box is displayed. The Init method can accept message, type, title, and beep parameters and stores them in the applicable property. You can call the Display method to display the message box using the current values of these properties, or Show to specify any of these values (for example, once ntype, ctitle, and lbeep have been set, you could call Show( my message ) to display the specific message you want). This class is useful, although I built one a long time ago that I ll continue to use. Type Library (_TypeLib in _UTILITY.VCX) is a wrapper for the FoxTLib ActiveX control; it gets the type library information for COM servers. This might be useful if you need to create a developer tool with this capability (although the Class Browser can also display type library information), but I can t see using it in a real-world application. String Library (_StringLib in _UTILITY.VCX) sure sounds promising, doesn t it? However, it s probably the lamest class in the FFC; it has a single method, TrimCRLF, that strips carriage returns and line feeds from the end of the string (gee, I wonder why they didn t create a sample form for this class <g>). I can think of a lot of other functions that could have gone into this class; this is one I ve used so infrequently over the years, I can t imagine why you d build a class just for it. If you need this function, I think you d be better off creating your own string library and stealing the code from this method, and skipping the class altogether. Shell Execute (_ShellExecute in _ENVIRON.VCX) is a great little class. It provides a wrapper for the ShellExecute Windows API function, which executes a file, so you don t have to worry about how to DECLARE or call it. The neat thing about this function (and the class) is that it handles file types registered on your system; passing the filename to ShellExecute will run the associated application if the file isn t an EXE. For example, executing MYDOC.HTML will bring up the default browser for your system and display this file. You don t have to know which application is required or where it s installed on the system. _ShellExecute just has one method, ShellExecute, which accepts up to three parameters: the name of the file to execute, the working directory to set for the application (if it isn t specified, the application s directory is the working directory), and an operation string ( open is used if it isn t specified). This method returns a value indicating success or what type of error occurred (see the Help topic for this class for a list of values). Incidentally, _ENVIRON.VCX contains a couple of other classes, neither of which appear in the Component Gallery. _Set stores the current value for a SET setting (such as TALK, PRINT, NEAR, and so on) and sets it to another value. When the object is destroyed, it automatically restores the setting (although this behavior can be disabled if desired). Using this class saves you from having to use code like the following: lccurrexact = set('exact') set exact off * do something here if lccurrexact = 'ON' set exact on endif lccurrexact = 'ON' 12 FoxTalk February 1999 http://www.pinpub.com

This code would be much worse if you had multiple exit points for the routine; you d have to reset the EXACT setting in each place (yet another reason to avoid multiple exits in a routine). Instead, you d just do the following: loexact = newobject('_set', '_ENVIRON.VCX', '', ; 'EXACT', 'OFF') * do something here When this code ends, loexact goes out of scope, and EXACT is set back to its former setting. _RunCode, the other class in _ENVIRON.VCX, is a code block interpreter. As you know, you can make VFP execute a single line of code at runtime by storing it into a variable and then macro expanding the variable; this allows you to determine what code should execute at runtime rather than design time. However, this technique doesn t work if the code is several lines long (for example, some script code stored in a memo field in a table that should be executed when a record is selected). While you could split the code into individual lines and macro expand each one, this won t work if the code contains program structures such as loops. The _RunCode class has a RunCode method that can execute a block of code. You pass the code as a string or the name of a text file containing the code as the first parameter,.t. for the second parameter if the first parameter is a filename, and.t. if you want errors to be ignored. One use for _RunCode would be to allow users of your application to script certain behaviors (not a typical end-user, but perhaps an administrator or another developer who doesn t have access to the source code). To do this, you d provide a hook or add-in mechanism for the functions whose behavior the user can change. For example, say you allow the user to define add-ins for the add record method of a form. That method might look like this: In this case, entry-level users would get a wizard that leads them through what information is captured when a record is added; the normal add operation is used for everyone else. The add-in code might be stored in a file (in which case the add-in registry would contain the name of the file) or in a memo field of an add-ins table (which would require providing a means for the user to edit the contents of the memo). Conclusion This was our last look at the classes in the FFC. Over the past three columns, we actually looked at fewer than half of the classes available, but it s time to move on to other things. Hopefully, I ve inspired you enough to dig into the FFC and see which classes you ll want to use. 02DHENSC.ZIP at www.pinpub.com/foxtalk Doug Hennig is a partner with Stonefield Systems Group Inc. in Regina, Saskatchewan, Canada. He is the author of Stonefield s add-on tools for FoxPro developers, including Stonefield Database Toolkit and Stonefield Query. He is also the author of The Visual FoxPro Data Dictionary in Pinnacle Publishing s The Pros Talk Visual FoxPro series. Doug has spoken at the 1997 and 1998 Microsoft FoxPro Developers Conferences (DevCon) as well as user groups and regional conferences all over North America. He is a Microsoft Most Valuable Professional (MVP). 75156.2326@compuserve.com, dhennig@stonefield.com. if This.oAddinManager.Execute('AddRecord') return else append blank This.Refresh() endif This.oAddinManager.Execute('AddRecord') In this scenario, the form has an add-in manager that, when passed a method name, checks an add-ins registry (it could be a table or the Windows Registry) to see whether an add-in has been defined for this method, and if so, executes it and returns.t. so the calling routine knows the add-in executed. The add-in might be code written by the administrator that does something like this: if ouser.nuserlevel = 1 do form ADDWIZARD return.t. else return.f. endif ouser.nuserlevel = 1 http://www.pinpub.com FoxTalk February 1999 13

When Does it Happen? What s Really Inside FoxTalk Jim Booth 6.0 In developing applications in Visual FoxPro, we re always depending on events being processed. Every keystroke and mouse click is critically important to the proper functioning of the application. How much do you actually know about when things happen in VFP? This month, Jim will examine some of the important events and find out exactly when they occur. BEFORE I venture into the events that fire during various operations in VFP, let s classify the events and methods that are available in the product. My preference is to refer to these code locations as eventmethods and methods. The difference between an eventmethod and a simple method is how it s fired. A simple method only fires if a line of code has called it. It might have default behavior that comes from VFP, or it might simply be a place where you can write code and then call that code when needed. An event-method is automatically fired based on some event occurring. For example, the KeyPress eventmethod of a control will automatically fire whenever the user presses a key while focus is in that control. It s important to differentiate between the event (pressing a key) and the event-method (KeyPress) that fires as a result of the event occurring. Calling an event-method in program code does not cause the associated event to occur. It simply runs the code in that event-method. Getting and losing focus There are a number of event-methods and methods associated with a control receiving and/or losing focus. These are When (event-method), GotFocus (eventmethod), SetFocus (method), Valid (event-method), and LostFocus (event-method). To take the best advantage of the event model in VFP, it s necessary to clearly understand the event-methods that fire and the point in time at which that they fire. In the process of receiving focus, the sequence of eventmethods is 1 When and then 2 GotFocus. The When event-method can be thought of as the place to determine whether or not focus will be allowed to arrive at the control. Returning a value of.f. from the When event-method will prevent focus from ever arriving at the control. Once the When event-method returns a value of.t., the GotFocus will fire (if the When returns.f., the GotFocus doesn t fire). The GotFocus can be classified as the event-method in which you prepare for the control to receive focus, since it only fires if the control is going to receive focus. The SetFocus method also plays a role in that it has the default behavior of beginning the process of receiving focus for a control. A control s SetFocus method is similar to the FoxPro 2.X _curobj variable. There are two controls that act slightly different in respect to the When event-method the ListBox and the ComboBox. With these two controls, the When fires as it does for other controls, as focus arrives at the object and before the object actually receives focus. The When can return.f. to prevent focus from arriving on that object. However, with lists and combos, the When also fires every time the currently selected item changes. Because of this behavior, it s important to be careful with what code you put in a list or combo s When event-method. You should be aware that the When will fire multiple times for these two controls as the user navigates through the list. The process of losing focus is similar to that of receiving focus. There are two event-methods involved Valid and LostFocus. The Valid fires first and can be used to prevent focus from leaving the control. A return of.f. from the Valid will cause the control to retain the focus. A return of.t. from the Valid will allow focus to leave the control and thus it will fire the LostFocus. Like the When and GotFocus, the Valid can be used to decide whether focus should be allowed to leave the control, and the LostFocus can be used to react to a control losing focus. Updating data VFP is a data management tool, and as such it has some very powerful and easy-to-use data-binding capabilities. Many of the controls in VFP are able to be bound to a data source by setting the value of the control s ControlSource property. Using the ControlSource property causes a control to automatically update the ControlSource when the value of the control is changed. However, like anything else in life, the updating of the ControlSource takes place at a specific point in time. Knowing exactly when the update occurs is critical to getting the most flexibility out of the controls in VFP. A number of questions have arisen regarding what some folks want to classify as bugs related to the updating of a ControlSource. One such situation was for a developer who was issuing a THISFORM.Refresh() in the InteractiveChange event of a text box and was complaining because the text box kept reverting to the original value of the ControlSource. Others have found 14 FoxTalk February 1999 http://www.pinpub.com