TERMENI importanti pentru acest document
An application is greater than the sum of its parts. Good application development requires an appreciation and understanding of the idea of application services and ensuring that each component is available to the rest of the system.
Forms or objects created for some specific purpose do much of the actual 'work' of an application. It is possible to accomplish some meaningful task by running a form from the command window or by instantiating some object and calling its methods. However, doing so does not demonstrate the functioning of an application.
An application needs to expose all of the functionality of the system through a seamlessly integrated user interface that addresses many more issues than simply insulating the user from the DO FORM command.
Each component of an application provides services to the user or to another component of the application—or some combination of both. A form in a database application (assuming it’s visible) provides a service to users; it allows them to enter, view, or alter data stored in the database. A command button on that form also provides a service, allowing users to communicate their wishes, and provides a service to the rest of the form by passing along user input to the appropriate method. A menu likewise serves the user’s need to launch a particular form, and may carry out that wish by passing it along to a form manager.
Some components exist only to provide system-level services—that is, their responsibility is to manage or facilitate some aspect of the application internally. A method of a form may provide a service to the form by disabling the Save button until the user has modified the current record. However, in this book, we’ll refer to system-level services as those that apply system-wide, meaning 'global to the application' rather than the usual connotation of 'on the level of the operating system.' System-level services transform individual components such as data tables, forms, reports, and menus into a complete application.
System-level services can include some or all of the following functions. This list is by no means exhaustive. Individual applications may require additional services, and you may decide to provide—or a particular application may need—additional system-level services.
How these services (and other services that are provided on a smaller scope, usually at the form level) are provided represents a significant part of the 'under the hood' design of an application, and not surprisingly, an important part of any application framework. Services that are built into the classes and code libraries used to build system-level components, or into the foundation class hierarchy, will unavoidably place the application somewhere on an important continuum that the developer must keep in mind as design decisions are made. The extremes of this continuum represent maximal and minimal flexibility and abstraction. The more abstract these services, the more easily they can be adapted to specific needs of the application; the more concrete, the more they will dictate how specific needs of the application are met. Abstract services are highly generic and reusable. Concrete services are tailored to meet more specific application needs.
One way that a compromise can be reached between these two design extremes is to provide global, system-level services through a multitude of individual components, each instantiated from object classes, or object subclasses. This makes it easier to substitute different components when the need arises, or to subclass certain components while leaving others intact. However, there is no rule that says they can’t be provided by a single, monolithic application object. That decision will be influenced by the specifics of the application and your own preferences or style of work. Chapter 10 will discuss objects for providing many of these system-level services in greater detail. For the remainder of this chapter, however, we will that the application object is the only system-level object that we need be concerned about. We can assume that this object either provides all global system-level services directly, or is responsible for instantiating all subsidiary system-level objects such as form and menu managers or error handlers.
common with any other programming language, Visual FoxPro 6 must have a program
module identified as '
At the bare minimum, this 'main' program (often called, not surprisingly, MAIN.PRG), must perform the following tasks:
Let’s consider each of these items in more detail.
While it’s possible to set the default or working directory to the application in the property sheet of the Start menu shortcut, it can also be set incorrectly. Thus, it’s important to ensure that the program is running from the correct directory.
The most common practice is to place the program file itself into the same directory that you wish to make the default directory. This eliminates many pathing problems in trying to locate external program elements and data files. This also makes it extremely easy to set the default directory without having to store this information in the registry or a configuration file. The SYS(16) function (when used with the optional argument of 0: SYS(16,0)) returns the drive and full path and filename of the 'top level' program that is currently executing. This, together with the JUSTPATH() function that is now a native Visual FoxPro 6 function, can be used to determine where the application is installed:
lcHomeDirectory = JUSTPATH(SYS(16,0))
SET DEFAULT TO (lcHomeDirectory)
Here we come up against an issue that is related to the highly interactive mode in which Visual FoxPro applications are written. In conventional programming languages that use native-code compilers, the testing environment is not significantly different from the production environment. However, when running a program module, whether it be a form, a program, or a report, if not doing so from within a compiled application, the program module has to be in the Visual FoxPro path, or the name of the program must include its relative path in order to be located and executed.
One of the great advantages of Visual FoxPro’s highly interactive development environment is the ability to test a program module or the entire application without having to go through an edit/save/compile/run cycle. To fully exploit this, our MAIN.PRG has to be able to set the path so that all program components can be found.
However, while it is necessary to have MAIN.PRG issue a SET PATH to allow the production application to locate the database files, most program modules are compiled into the application (whether compiled to an .APP or .EXE). Thus, it isn’t necessary for the path to be set to the directories containing these module’s source files in the production environment, where the development directory structure doesn’t usually exist. But it is very handy to be able to run and test the entire application by issuing the command DO MAIN (or whatever you call your 'main' program) in the command window, in which case all of the directories in the development directory tree need to be in the search path.
The solution is to maintain, during development, a program in the project directory that is also included in the project, and is called by MAIN.PRG. This program determines whether the application is running in 'development mode'—that is, by issuing DO MAIN in the command window—or whether we're running a compiled .APP or .EXE. Then, based on this determination, this program sets the path appropriately:
* Partial Program Listing LocalPath.PRG
SET PATH TO LocalData;RemoteData
IF RIGHT(SYS(16,0),3) = 'FXP'
SET PATH TO SET('PATH') + ';..COMMONLIBS;..COMMONPROGS;FORMS;PROGS;INCLUDE'
Note that the way LocalPath.PRG works is to identify if the 'top level' program has an extension of .FXP, indicating that we’re running MAIN.PRG (automatically compiled to an .FXP file). If this is the case, then it isn’t sufficient to simply include the data directories in the search path, but we must include all directories containing source code
In production, SYS(16,0) will return a filename with an extension of .APP or .EXE, and therefore won’t bother including the development directories in the SET PATH command.
As the application progresses, the directory structure may change, and it is necessary to keep LocalPath.PRG up to date with these changes. An additional advantage of keeping LocalPath.PRG in the application directory is that by simply issuing DO LocalPath.PRG in the command window, all of the development directories are placed into our search path during development.
The next task that MAIN.PRG must accomplish is to ensure that all necessary class and API libraries and procedure files are loaded into memory. MAIN.PRG will have to instantiate the application object, so at the very minimum its class library must be included. It also may call functions and procedures in either API libraries or procedure files, or instantiate objects whose class definitions reside in other class libraries. Thus, it is best to make no distinction about what may or may not be needed at the moment, and simply load all procedure files and libraries that are used in the application.
We can rely again on our LocalPath.PRG file to take care of this task. During development, these libraries and procedure files are added to the list of SET commands in LocalPath.PRG as they are created or employed in some part of the application. In addition to its function within the context of the application, again as with the pathing, this is handy during development; issuing DO LocalPath.PRG in the command window puts all of our resources at our immediate disposal.
Here is another section of LocalPath.PRG:
SET PROCEDURE TO proclib ADDITIVE
SET PROCEDURE TO applib ADDITIVE
SET CLASSLIB TO ccontrls ADDITIVE
SET CLASSLIB TO cenviron ADDITIVE
SET CLASSLIB TO cforms ADDITIVE
There are more than 100 SET commands in Visual FoxPro 6. (I don’t know how many there are exactly—I got tired of counting at 100). In the development environment, we usually prefer to have many of these options set a particular way, yet prefer (and indeed rely on) them to be set differently in our applications. Thus the need, in development mode, to be able to change these settings for testing our application, and to restore them to the settings preferred by the developer when the application terminates.
SET TALK is probably the best example of this situation, because most developers prefer to have TALK set ON during interactive work at the command window, but there are very few situations (other than running a query) in which TALK is set ON in an application. In fact, to avoid any cryptic messages on the desktop or in the status bar, it is important to record the current setting of SET TALK (so it can be restored when the application exits), and issue SET TALK OFF as soon as possible.
Likewise, the developer may operate with SET EXCLUSIVE ON, but an application may very quickly access a shared table for the purpose of executing a login procedure, so it’s likewise important to save the current SET EXCLUSIVE setting for later restoration, and to SET EXCLUSIVE OFF.
Including the TALK and EXCLUSIVE settings, we have thus far talked about seven environmental settings that MAIN.PRG directly or indirectly must fiddle with, and may be different than what the developer prefers. So it is necessary for MAIN.PRG to save the current settings for TALK, EXCLUSIVE, DEFAULT, PATH, CLASSLIB, LIBRARY and PROCEDURE, and provide some way of restoring them after the application has terminated.
One really handy behavior associated with the creation and destruction of objects is that the Init() event (and its associated method code) fires once each time an object is instantiated, and the Destroy() event (and its associated code) fires once each time an object is destroyed. This arrangement is tailor-made for saving and restoring a state or condition. As a result, most developers use a couple of objects for setting such environmental settings, and optionally, restoring them when the objects are destroyed. Such an object can be instantiated by MAIN.PRG, but this would require that the class library in which the environment-setting object is defined be loaded into memory with a SET CLASSLIB command. To get around this chicken-and-egg situation, we store the current settings for these six values to memory variables that are then transferred to our environment setting object after it has been instantiated. This object can be instantiated by MAIN.PRG or as part of the instantiation of the application object.
After storing the current environmental settings that get changed almost immediately, setting the default directory, setting the path, and loading the necessary libraries, we’re all set to instantiate the application object. By instantiating the application object as a private or public memory variable, it is scoped to the entire application, and is therefore visible, and all of its properties and services are available to all other components and modules in the system. These services may include, but are not limited to, any or all of the system-level services discussed above.
Some application frameworks and some developers place the READ EVENTS command in a method of the application object that is called by MAIN.PRG. However, it facilitates error handling if the RETURN TO MASTER command can be used to return control to MAIN.PRG; thus, it is better to issue the READ EVENTS in MAIN.PRG.
Also by keeping the READ EVENTS in MAIN.PRG, we have the added ability to avoid issuing that command when we are running in the development mode, thus leaving the command window and the ability to edit forms and programs available to the developer while the application is running. This is accomplished by allowing MAIN.PRG to accept a parameter that indicates that the system is being run in development mode and controlling the creation of oApp and the issuing of READ EVENTS according to this parameter. The following code shows how this might be done.
* Other setup code as described above
IF TYPE('oApp') <> 'U'
oApp = CreateObject('ApplicationClass')
IF NOT plDevMode
I know, you thought I said that MAIN.PRG is the first thing that gets executed. Well, yes, but there is something that comes into play before MAIN.PRG, and that’s the CONFIG.FPW file.
This file can be stored external to the application, or it can be compiled into the application itself. When running the compiled application, it is often preferred to delay the appearance of the Visual FoxPro main screen until after the program is loaded, a splash screen has been displayed, the caption and icon have been set, and so on. This is also important in an application that uses a single SDI form as the user interface, where the Visual FoxPro main screen is never made visible. The single line in the CONFIG.FPW file that allows all of this to be accomplished is the command SCREEN=OFF.
If you use this technique, be aware that your application object will need to make the _SCREEN or _VFP object visible at the appropriate time.
To summarize, Listing 3-1 illustrates a typical minimal MAIN.PRG.
Listing 3-1 An example of MAIN.PRG, the startup program.
IF SET('TALK') = 'ON'
SET TALK OFF
LcOldTalk = 'ON'
pcOldExclusive = SET('EXCLUSIVE')
pcOldDefault = SET('DEFAULT')
pcOldPath = SET('PATH')
pcOldClassLib = SET('CLASSLIB')
pcOldProc = SET('PROCEDURE')
pcOldLib = SET('LIBRARY')
IF TYPE('oApp') <> 'U'
oApp = CREATEOBJECT('aApplication')
IF TYPE('oApp') = 'O' AND !ISNULL(oApp)
* If the application object has successfully
* instantiated, it will store the private
* memvars holding the environmental settings
* created above
RELEASE LIKE p*
IF NOT plDevMode
RELEASE ALL EXTENDED
Note that this is an example of a minimal MAIN.PRG. Typically, MAIN.PRG would immediately use the system services of the application object to put up a system menu, clear a splash screen, run a logon procedure, and so on. All this would be done before issuing READ EVENTS.
Note, too, that the menu—or more likely the application object—must be responsible for issuing the CLEAR EVENTS command to terminate the application.
The most basic of the components that begin to integrate an application, referred to only in passing above, but worthy of separate consideration, is some kind of mechanism to allow the user to launch forms, reports, and other processes. While this component also provides a significant service to the user, its role in integrating the other components of the application makes it worthy of consideration as a system-level service provider. In addition, the level of cooperation between various function-launching mechanisms and the rest of the system often requires a high degree of integration between these components at a system level.
Unfortunately, Microsoft continues to drag its feet in giving us a menu object. As a result, the menu is not discussed much in the context of Visual FoxPro applications, but this service is still usually performed by way of a menu system. However, we’re increasingly seeing toolbars (some that can be user-configurable) as a launching mechanism, and in the good old DOS days, hotkeys or keystroke commands were common.
With Visual FoxPro 6, it’s possible to employ any combination of these techniques to allow the user to access an order-entry form or a report of customer contacts. The choice of which methods will be employed is primarily of importance to the end user, and must be decided with a great deal of user input. While some methods such as using function keys may seem archaic, they may be perfectly reasonable in keyboard-centric applications. An application that is a rewrite of an existing application, which has a large number of current users, will be adapted to more easily if familiar interface elements are preserved. If the user interface on the old system made extensive use of function keys, preserving this as an interface element in the rewrite makes perfect sense.
There is also no requirement that there must be only one way of interfacing with the application. Allowing the user to decide between using keystrokes or a mouse, or between a toolbar and a menu, shows a great deal of sensitivity to the fact that different users have different ways of working. While Microsoft may like to make high-handed assumptions about how we interact with our computers, there is no reason why we should perpetuate this behavior.
During development, being forced to compile and run the entire application every time we test a component can be a real impediment to the entire process. If, for (an extreme) example, we’re working on a method or procedure that will end up being 100 lines of code, and embodies some rather tricky logic, it’s usually best to write it incrementally, testing each stage of the algorithm before proceeding to the next. Sometimes I actually write the method as a .PRG, substituting memory variables for object properties, write a little, test a little, write a little, and then cut and paste it into the object method. Then I search and replace to change the memory variables to object properties.
Sometimes this technique can’t be used, especially if we’re working with visible form components. In this case, it is preferable to add a control, or add a bit of code to a method, set a property or two, and then run the form. Fix any problems discovered, add another control or set another property, then re-run the form. If we are forced to compile and run the entire application to test each component, then we’re going to be much less inclined to introduce new code and components incrementally. We’re going to try to make the most of each compile/run cycle by writing as much code, setting as many properties, and adding as many objects as we can manage before each test run. This has the potential to make the debugging process a nightmare, because the number of changes we make before each test run makes it very difficult to zero in on the particular change that has introduced the error, particularly in a scenario in which there are complex algorithms in code or heavy interaction between objects.
I'm actually discussing two issues here. The first is to avoid having to test components only within the context of the entire application, and the second is to make such testing easier when desired. What is desirable is the ability to both test components individually, and (when desired) to easily run the entire application without having to rebuild and recompile.
Maintaining the LocalPath.PRG file mentioned earlier will make it possible to have an application that you can run and test without being forced to recompile the entire application. With this program called by your MAIN.PRG, all the source code is accessible, API class and procedure libraries are loaded, and paths are set. It’s then possible to run your entire application by simply issuing DO MAIN in the command window with it running as it would if you had compiled it. It will still be necessary to occasionally rebuild the application, checking the 'Recompile all files' option in the Build… dialog, particularly if you make use of include files, which are only incorporated into object classes and program files when the files are compiled.
To explore the issue of being able to run most components individually, outside the context of the application, it’s helpful to consider a principle in programming known as 'loose coupling' of program components. This means reducing the interdependency of program elements, so that each is a 'black box' that is familiar to the rest of the system only insofar as the rest of the system is familiar with its interface. The 'interface' here, of course, refers not to a user interface, but the interface that the component presents to the rest of the system, the methods, exposed properties, method or function arguments, and return value data types. The simpler this interface (the fewer the exposed methods and properties, and the fewer the arguments passed to methods or functions), the more the coupling of the component can be described as 'loose.' This makes components interchangeable as long as they present a consistent interface. Loose coupling also allows components to function to some extent without depending on the presence of other components or objects, or depending on a certain system state (like having certain tables open, or certain work areas selected, or certain objects instantiated). Adhering to high standards of loose coupling will make it easier to develop and test program components. Let’s consider this idea by example.
Consider a form that uses a grid to display some information to the user. The grid’s Column Count property is 40, the grid columns’ Movable and Resizable properties and the grid’s AllowHeaderSizing and AllowRowSizing properties are all set .T. Clearly, the user could spend considerable time customizing the appearance of this grid, with the columns sized and ordered, and the headers and rows sized as desired. One way to save these settings, allowing them to be restored the next time the user runs this particular form is to write the user’s preferences to the system registry, and then read them from the registry when the form is next instantiated. There are numerous examples of object classes that define objects designed to accomplish this task, one of which ships with Visual FoxPro 6 (REGISTRY.VCX). It’s possible to make a design decision that such an object will be added to any form that required its services, so that this feature of the form could be easily tested. On the other hand, another design decision could be made that such an object really provides a system-level service, and as a result, the registry-interfacing object should be instantiated as a child object of the application object, named for example 'oRegistry', available for use by any form in the system.
In this situation, if the form is run during development using DO FORM <formName> in the command window, and it attempts to restore the grid’s settings from the registry by referencing the oRegistry object, we’re going to get an error, and the form may not run at all. To enable us to run this form during development in this scenario, we need to make some more design decisions.
By implementing either of these techniques, the form can be run and tested by itself from the command window, or in the context of the running application. The second technique's advantage: not only can the form run, but the behavior of saving and restoring the grid settings can be tested when the form is run in stand-alone mode.
Loose coupling of program elements is a programming principle that promotes flexibility and maintainability, but the principle of 'loose coupling' can make each program component more manageable during development and testing as well. Whenever possible, you should be able to run any form or any other program module as a stand-alone module for testing purposes. Clearly, this may be completely impossible for some forms, objects and functions, depending on their roles in the system, but making your program modules independent of other program elements will improve your application overall, and will make development and testing easier.
With the ideas in this chapter behind us, we can proceed to talk more about the process of developing the application. We'll delve more deeply into how to put the rest of the pieces together within this framework to get a database application up and running. The next few chapters start with the stuff underlying almost all applications created with Visual FoxPro: the data that the application maintains and manipulates.
Politica de confidentialitate|
Adauga cod HTML in site