<<

Print Article Seite 1 von 4

Issue Date: FoxTalk May 1998

Teaching OOP to FPW with Stored Procedures Dave Jinkerson and Scott Malinowski

Many FoxPro developers would like to make the transition between FPW and VFP, but they don't know where to start. Dave and Scott will show you how to use your current knowledge of FPW to build the bridge every programmer needs to cross from procedural programming into OOP. In this article, they'll look at an example that will help you move your procedural programming style one step closer to OOP and, in so doing, begin the journey from FPW to VFP.

Object-oriented programming (OOP) is a data-centered of programming in which data and behavior are strongly linked. The proper way to view OOP is as a programming style that captures the behavior of the real world in a way that hides implementation details. With a language that supports OOP, data and behavior are conceived as classes, and 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 programming style and still create an OOP-like result.

ADTs with style A key factor in OOP is the creation of user-defined data types from the language's native data types. In languages that support OOP, user-defined extensions to the native types are called Abstract Data Types (ADT). To create an ADT, you must define a set of values and a collection of operations that can act on those values. But stop and realize that FPW programmers can create sets of values (Tables) and collections of operations (Procedures/Functions) that can act on those values. Keep this thought in your mind; it will become a key piece in teaching FPW, OOP-style.

Databases and stored procedures What is a ? VFP defines a stored procedure as: "A procedure stored in a . The procedure can contain any commands and functions allowed in a user-defined function." I don't see anything here that can't be done in FPW.

However, some VFP developers will dispute the fact that FPW has anything remotely similar to a "Database." Let's not limit our understanding of the word "Database" to imply a Visual FoxPro Database Container (DBC). The word "Database" is in such common use, we'll take a moment to define the word and its context.

A "Database" is a collection of related data. "Data" refers to a logical collection of known facts that can be recorded, organized for a specific purpose, and have implicit meaning when viewed collectively. Admittedly, VFP is a true relational DBMS; however, this doesn't mean that FPW, although lacking a DBC, can't be used to manage a "Database." Don't confuse VFP's DBC with a Database Management System (DBMS). When dealing with VFP's DBC, most of the relational management can be achieved implicitly through the DBC. With FPW, those same relations can be realized and managed, although you're explicitly responsible for their implementation. In fact, the FPW stored procedures just mentioned will become the stored procedures that will act upon our database. Now we're on our way to defining an ADT.

Creating ADTs First, let's look at some relationships that can be established between tables (FPW or VFP). We've taken these relationships from an FPW application that was written to manage ad hoc customer surveys. Surveys are defined in a flat file for ease of definition, training, and management; moreover, for these same reasons, the isn't normalized. The real important parts of this flat file are the survey questions, the choices to those questions, and the data type that collects each choice. In our survey definition file, you can have choices that are any data type supported in FPW. Collectively these data types create a set of values, and the stored procedures represent the operations that can act on those values (sounds suspiciously like an ADT).

Specific surveys are rendered at runtime by querying the survey definition table (our flat file) with specific customer and survey IDs. Once all the survey questions, choices, and data structures are collected, the resulting cursors are then related together to form a value set. What's a value set? The individual data types don't carry much meaning, but as a relational data set (for instance, value set), they form a survey. Moreover, the survey establishes our fundamental ADT.

If you look at an ADT within VFP, you'll find an abstract memory relationship (that is, class definition) between data types (properties) and operations (methods). Conceptually, our example is no different: The abstract memory relationship is created between our runtime cursors. The properties between the data types are the data items and their corresponding values within the survey. Finally, our stored procedures that will operate on these data items represent our methods. Here's the query from the survey definition table, relevant to our example:

select field_name, field_type, field_len, field_dec ; from surveydef ; where clientid = cClient ; And surveyid = cSurvey ; http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06 Print Article Seite 2 von 4

order by order ; into array Struct

The "Struct" array represents the definition of our value set. SELECT field_name, field_type, field_len, field_dec... gives us an abstract template from which to create a specific value set (from the collection of all possible value sets -- that is, our survey flat file) that will represent the complete field structure for each type in the ADT. The fields used in the structure description, for each data item, are identical to FPW's structure extended types (see the COPY STRUCTURE EXTENDED command in the FPW online help). Now we need to take this definition one step further and actually produce our value set. Remember, all we have so far is the definition; what we need is an ADT. We mustn't lose track of how ADTs are created in FPW: They come from the creation of a (or table), the existence of relationships between cursors, and a set of procedures that act on that value set. We can use FPW's (or VFP's) CREATE CURSOR command to create the value set for this example:

CREATE CURSOR ValueSet FROM ARRAY Struct

Now we have the value set created -- and the important thing to remember is that our value set looks exactly like all the other cursors that you've been working with. We haven't redefined the FPW CURSOR-data-type; we've just shown you how to look at this data type in another way -- as the basis for describing an ADT. By looking at a cursor definition in this manner, our hope is to help you understand that ADTs, in languages that support OOP, aren't built any differently. We've taken a base type from the language (Cursor) and built a custom type from it (Survey value set).

You're probably thinking, "Where are the stored procedures?" The other query in our example takes care of this missing item. Remember where this example came from. The Survey definition flat file holds many surveys that are logically grouped together by client and survey IDs. The stored procedures for each data item also carry this client-survey identification. So the query for our stored procedures is as follows:

SELECT field_name, script, run, when, valid, force ; FROM Udf ; WHERE clientid = cClient AND surveyid = cSurvey ; INTO CURSOR StoredProcs

Now we have another cursor that belongs to our ADT -- one that holds only the stored procedures relevant to our example. Let's clarify the structure of this cursor. SELECT field_name, script, run, when, valid, force... gives us the results shown in Table 1.

Table 1. The structure of the cursor created for stored procedures.

Parameter Description "Field_name", The name of each data item in our value set. "Script", The xBASE-script (procedure code) for each field. "Run", The compiled binary equivalent. "When", A logical flag to tell us to fire this code during the "when" event. "Valid", A logical flag to tell us to fire this code during the "valid" event. A logical flag that tell us to always fire the valid code during the "Force", "valid" event, regardless of whether an error has occurred.

If you're wondering, there's a way to implement both "when" and "valid" scripts for a single data item, but for this example we'll make them mutually exclusive. So, we'll make available the "when" event, the "valid" event, but for now, not both. How did we put the compiled binary equivalent of the stored procedure in a memo field? Another question you're probably asking is, how do we get FoxPro to compile at runtime, outside of the development environment? We have answers to both questions.

Storing Xbase code as a binary memo The following code block handles the conversion from Xbase to binary and stuffs the compiled code into a memo field. This code also assumes that you're calling your stored procedure table UDF.dbf, and that both the "script" and "run" fields are memos. Some bounds checking that's hinted at (but not literally enforced) is the maximum bytes written to any given "run" memo. We've limited the number of bytes to 4096 to emphasize two things: 1) always think about code optimization; and 2) anything much over 4096 bytes and you'll start to pay a price for speed at runtime.

* EXTERNAL: Public table 'Udf', * part of the Survey system module. #DEFINE _READ 10 #DEFINE _MAX_BYTES 4096 PRIVATE nPrgHandle, nFxpHandle SELECT Udf SCAN ALL nPrgHandle = FCREATE('run.prg') IF nPrgHandle > 0 = FWRITE(nPrgHandle, Udf.script) COMPILE 'run.prg' NODEBUG nFxpHandle = FOPEN('run.fxp', _READ) IF nFxpHandle > 0 REPLACE Udf.run WITH FREAD(nFxpHandle, _MAX_BYTES) = FCLOSE(nFxpHandle) http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06 Print Article Seite 3 von 4

DELETE FILE 'run.fxp' ENDIF = FCLOSE(nPrgHandle) DELETE FILE 'run.prg' ENDIF ENDSCAN

To answer the question "How do we get FoxPro to compile at runtime, outside of the development 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, at runtime, instead of processing the code itself, we make the decision to load whichever file type is needed: code (.prg) or binary (.fxp). The following function shows how to do it:

FUNCTION SvUdf PRIVATE cFile, nHandle IF SET('DEVELOPMENT') = 'ON' cFile = FULLPATH('RUN.PRG') nHandle = FCREATE(cFile) = FPUTS(nHandle, StoredProcs.script) = FCLOSE(nHandle) DO (cFile) DELETE FILE (cFile) DELETE FILE (STRTRAN(cFile,'.PRG','.FXP')) ELSE cFile = FULLPATH('RUN.FXP') nHandle = FCREATE(cFile) = FWRITE(nHandle, StoredProcs.run) = FCLOSE(nHandle) DO (cFile) DELETE FILE (cFile) ENDIF RETURN .T.

Of course, if you're thinking about taking this example and implementing a production environment, you'll want to add error checking and possibly return something more valuable than "True" to the caller. But the nuts and bolts are simple enough that, with a few more lines of code, you too could start telling everyone that you can compile FoxPro stored procedures at runtime -- without the aid of the development platform. Also worth noting here is that the stored procedures in this example can be anything. Any Xbase code will work.

So now we have the pieces to create our ADT. We have the value set, which represents our collection of data types, and we have our stored procedures that will operate on those data types. We're almost there -- all we need is the event-driven relationship between the value set and the stored procedures.

Creating relationships It's our hope that after we've outlined the relationships that drive our stored procedures, you'll have ideas of your own for not only other relationships that drive the method code, but also for enhancing this example so it applies to other implementations. The relationships that we'll use aren't necessarily the ones that exist in our Survey definition application, but this isn't important to our example. What's important is that your indexes be character based. That way, you'll be able to take advantage of FoxPro's string concatenation operations and not get involved with translations from one data type to another. This will also give you the ability to concatenate one of FoxPro's built-in functions to achieve the power of event-driven code. (Which function? Read on.) It's also important that you completely understand the inner workings of the BROWSE command and the VARREAD() function. Let's see why.

When establishing our relationships between our value set and the operations that act on those values, the question becomes, how do we get the power of event-driven code to take place in FPW? Well, the answer comes from taking a look at where events happen in that platform. In this example, let's use the events from the Browser window or, more accurately, the Edit command equivalent of the Browser window. We use the Browse window for two reasons: First, it works the same from FPW to VFP, and second, the Browse command is a relatively simple example of an event-driven runtime environment that works from within FPW.

In both FPW and VFP, if a Browse, Change, or Edit window is active, VARREAD() returns the name of the current field. In FPW, the field name is returned with the first letter of the field name capitalized (proper). In VFP, the field name comes back in all caps (thank Microsoft for that little "gotcha"). At any rate, this means we can tap into code that's executed when an event occurs or code that can also be called programmatically. An event is just an action recognized by an object. Moreover, code can be written that provides the object's response. This event can be triggered by a user's action on the object or by a system action on the same object.

Putting it all together We've fundamentally defined a collection of data types and a set of operations that operate on those data types. Now let's put it all together and see how VARREAD() can act as the relationship that drives our events. First, index the "StoredProcs" cursor on field_name, then create the VARREAD() relationship between the "ValueSet" cursor and the "StoredProcs" cursor. Here's how to do it:

SELECT StoredProcs INDEX ON field_name tag EventName SELECT ValueSet http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06 Print Article Seite 4 von 4

SET TO LOWER(VARREAD()) ; INTO StoredProcs ADDITIVE

Now when the user is in the Edit mode of the Browse window, the corresponding event code will be available to the environment. But you're probably asking, "What good is that, if I can't respond to the event with code?" But you can! Look in depth at the Browse command, and you'll find all the event hooks that you'll ever need to respond to your event-driven model. Some examples are shown in Table 2.

The Browse command is one of the most powerful commands in FoxPro and, in our opinion, one of the most misunderstood and consequently overlooked commands. Look in the FoxPro Help for a complete listing of all the available switches for the Browse command. Also look into its aliases -- Edit and Change. And remember: The Browse command works the same in VFP as it does in FPW.

Table 2. Examples of event hooks in the BROWSE command.

Parameter Description This event determines whether the cursor can be moved to a :W = lExpression2: field.

:V = lExpression1 [:F] This event lets you perform field-level data validation within [:E = cMessageText]: the Browse window.

Forces the VALID clause to execute before the user :F movesthe cursor to the next record.

Forces your error message text to appear instead of the :E system error message.

Conclusion To put OOP style into your programming, abstract data types (ADT) must be available to the language. But an ADT is just a set of values and a collection of operations that can act on those values. In languages that support OOP, ADT is a user- defined extension to the native types available in the language. However, in a language that doesn't support OOP, we can still realize fundamental ADTs. This example has shown you how to implement stored procedures in FPW. VFP defines a "Stored Procedure" as "A procedure stored in a database. The procedure can contain any commands and functions allowed in a user- defined function." Our example defined a custom set of values and at runtime relates those values with the operations that are allowed to act upon those values. Therefore, we defined an ADT, and by doing so, added a little OOP style to our programming.

http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06