4.3.1 Application

Application

All classes in this category are designed to handle large-scale issues that occur on the application level. Among these classes are window managers, error handlers and the like.

Data Session Manager

Class

_datasession

Base class

Custom

Class library

_app.vcx

Parent class

_custom

Sample

...\Samples\Vfp98\Solution\Ffc\environ.scx
...\Samples\Vfp98\Solution\Ffc\dsession.scx

Dependencies

_base.vcx , _app.h

This class handles table manipulations and updates in various data sessions. It can be used to iterate through forms and data sessions to save or revert data. Typical scenarios are application shutdown or the now widely used "save all" functionality that saves the content of all currently open documents or forms. This class can handle free tables as well as data that is stored in a database. Of course, the class is limited by Visual FoxPro's limitations regarding transactions.

Instantiating the class is very simple, due to its self-contained nature. A simple CreateObject() or NewObject() is enough, given that the class library is in the path:

oDataSession = NewObject("_datasession","_app.vcx")

Of course, you can also drop this object in a form or container. In this case, the Data Session object is placed in the appropriate data session right away. This is good if you want to handle QueryUnload events that occur whenever a form is closed. When iterating through data sessions to handle updates globally, I recommend instantiating this object as a member object of the application object or even as an independent object inside one of the application object's methods.

For now, I'll assume that we instantiated the Session object as a stand-alone object, as in the example above. I also assume we have a couple of forms with private data sessions. We can now use the Session object to check whether data has been updated in one of the form's data sessions. To do so, we first have to define the data session we intend to use:

oDataSession.SetSessionID(2)

We can always query the previously used session ID, querying the iSavedSessionID property:

? oDataSession.iSavedSessionID

Now that we've specified the session, we can check whether data has been updated:

? oDataSession.DataChanged()

This method returns .T. or .F. depending on whether or not there is updated data. We can also influence the way the class checks for updated data by setting the iDataChangedMode property. Setting it to 0, which is the default, checks for any changes. Setting it to 1 specifies that we want to ignore fields that are not in the update fields list. Setting it to 2 specifies that we don't want to check for views that aren't set to send updates.

Once we detect a session that has modified data, we either have to decide what we want to do, or we can ask the user what he has in mind. One option would be to confirm the changes and write them to the data source. We can do this using the Update() method:

oDataSession.Update(.T.)

I pass a .T. as the parameter because that hides a dialog box that asks the user whether he wants to save his changes or not. Passing .T. as the first parameter tells the object that the user already confirmed the change. This is important in various scenarios. Of course you might want to update changes no matter what. This option is also important if you want to iterate through various data sessions and want the user to confirm his changes only once, rather than asking him for every form. Often the programmer also wants to use his own dialogs instead of the ones provided by the foundation classes. In this case you can bring up your own dialog beforehand, and pass the result as a parameter. I like to use dialogs that provide "Yes to all" and "No to all" options.

The second parameter we pass is very similar. It is a more generic flag that indicates that the update is already confirmed. This parameter also hides the dialog and confirms the update right away. If both parameters are passed, the first one has the higher priority. This is convenient if you want to write generic routines. Parameter 2 could represent an option setting or the application state, while parameter 1 represents the current user.

Additional parameters are a reference to the form the data session belongs to, which is important if more than one form shares a data session. In this case the form gets activated and also gets the focus, which means that the form is brought forward. If you don't want the form to be brought forward, you can pass a fourth parameter, which is a logical parameter that specifies whether the form should be activated. The combination of parameters 3 and 4 can be very important in scenarios with multiple top-level forms.

If we don't want to write the changes to disk, we can use the Revert() method like so:

aDataSession.Revert(.T.,,loForm,.F.)

The parameters are exactly the same as the ones supported by the Update() method.

Another significant method is DataFlush(). It ensures that all the data was written to the hard disk and didn't get stuck in some buffer. This method doesn't require any parameters.

Whenever we take action, we can check whether it succeeded using the lSuccess property:

? oDataSession.lSuccess

Once we are done, we should return to the original data session to make sure we leave the environment as we found it. We can use the RestoreSessionID() method:

oDataSession.RestoreSessionID()

Note that this only restores the last used session. In other words, you cannot set the session ID to more than one session (for example, when iterating through all sessions) and restore the original session afterwards. To do that, you have to remember the initial session ID, like so:

oDataSession = NewObject("_datasession","_app.vcx")
LOCAL lnSessionID, lnCounter
lnSessionID = oDataSession.iSavedSessionID

FOR lnCounter = 1 TO Screen.FormCount
oDataSession.SetSessionID(Screen.Forms(lnCounter).DataSessionID)
IF oDataSession.DataChanged()
oDataSession.Update()
ENDIF
ENDFOR

oDataSession.SetSessionID(lnSessionID)

When actions are taken, the Data Session object can optionally use transactions. We can specify that by setting the lUseTransactions property:

oDataSession.lUseTransactions = .T.

When a Data Session object is dropped in a form, it can be called from the QueryUnload() event. The Data Session object also has a QueryUnload() method that checks for updates and, if appropriate, confirms the changes. This is the easiest way to use this object. Here is an example:

oDataSession.QueryUnload()
IF oDataSession.lSuccess

NODEFAULT

ENDIF

The QueryUnload() method also can be configured by a couple of parameters to specify whether the user should be asked (parameter 1) and whether the form should be activated (parameters 2 and 3).

Another method GetActiveControlRef() is not directly related to the data problem handled by the data session class, but it is quite useful nevertheless. It returns an object reference to the control that's currently active. Natively, Visual FoxPro already provides a reference to the active control. Unfortunately, this reference doesn't work if it is a control inside a grid object. The GetActiveControlRef() method resolves this problem. You can simply pass it the reference provided by Visual FoxPro and the method checks whether the reference is correct. If not, it returns the real active control. This can be done like so:

LOCAL loActiveControl
loActiveControl = oDataSession.GetActiveControlRef(_Screen.ActiveForm.ActiveControl)

Like many other foundation classes, the data session class features the GetMessageBoxTitle() title. It simply returns the caption of the message boxes as it is defined in _app.h, which is located in the VFP98\FFC directory You can overwrite this method in subclasses to return a different message box caption, which will then be used by the class for all the displayed message boxes.

Error Object

Class

_error

Base class

Custom

Class library

_app.vcx

Parent class

_custom

Sample

...\Samples\Vfp98\Solution\Ffc\error.scx

Dependencies

_base.vcx , _app.h

The Error object is a generic way to handle errors that occur in objects as well as in procedural code. The object judges errors and handles them accordingly. Sometimes user interaction is required, but some errors can be handled internally or even be ignored all together. The Error object also logs all occurring errors. However, the provided behavior is rather trivial, which is not surprising, because most error handling is specific to each application. If this weren't the case, Visual FoxPro could handle those errors internally without requiring programmer interaction. For this reason, I recommend subclassing the error class to enrich it with additional behavior (see below).

The Error object can be instantiated inside certain forms, but I recommend instantiating it in the application object to make it available to the whole application. The object is easy to instantiate because it has no external dependencies that must be set beforehand. A simple NewObject() should do the trick:

oError = NewObject("_error","_app.vcx")

To invoke the Error object, it must be set as the global error handler like so:

ON ERROR oError.Handle(Error(),Program(),LineNo())

The Handle() method is responsible for handling errors. It is called with three parameters. Parameter 1 is the error number. Parameter 2 is the name of the program, procedure or method that caused the error, and parameter 3 specifies the line number on which the error occurred.

The Error object can also be invoked from within the Error() event of individual records like so:

PROCEDURE Error(nError, cMethod, nLine)
oError.Handle(nError, cMethod, nLine)
ENDPROC

To reduce the risk of causing further errors in the Error() event, I didn't instantiate the Error object inside the event. Generally I recommend using the ON ERROR setting instead of using the Error() event, because it is more global and generally a bit easier. Also, switching to different error behavior, or passing errors to other objects is not trivial. I used to use complex error handlers that could pass on errors to other objects using the chain of responsibility pattern (see Chapter 10), but I finally switched to a simple error-handling model. Simplicity seems to be key for error handling. When an error occurs, the system already is in an unstable state, and using complex error handling mechanisms usually doesn't help, unless you're willing to put a tremendous amount of time into the error handler and handle each possible error individually in each object. This is a nearly impossible task, especially when using ActiveX components and automation servers. In my experience, using complex error handlers only introduces the risk of creating additional errors without raising the chances of handling errors dramatically.

When the Handle() method is invoked, the Error object classifies the error and tries to handle it accordingly. Obviously, the Error object can handle errors in a generic manner only. For this reason I recommend subclassing the Error object to behave properly for each application (see below). Once the error is analyzed, an error message is displayed and the user is asked for assistance (if appropriate). Also, the error is logged to a log file. The log file can be specified using the SetLog() method and the cLogDBF and cLogAlias properties like so:

oError.cLogDBF = "mylog.dbf"
oError.cLogAlias = "errorlog"
oError.SetLog()

The table name and alias also can be passed as the first and second parameter, but this is not yet documented and therefore I cannot recommend doing it this way.

If the specified error log table doesn't exist, it is created whenever the SetLog() is issued. The table is simple. It has a timestamp, a memo that has detailed information about the error, and another memo that is not used by the Error object but that can be used in subclasses for additional information.

The Error object features a DisplayErrorLog() method. By default, this brings up a simple browse, which is not very user friendly. You can change this in a subclass.

This brings us to the topic of subclassing the Error class. Most importantly, you need to add error-handling behavior specific to your application. There are various ways to do that. The Handle() method calls a number of additional methods that do the actual error handling and logging. I recommend overwriting or changing these additional methods rather than using the Handle() method.

The first method called is FillArrays(). This method first fills the aErrors array of the Error object with all possible error codes using the AError() function. The next array that gets populated is the aErrorClass array, which is also a member of the Error object. This array is two-dimensional. The second column is the name of the error class, while the first column holds all error numbers that belong to this specific class or group. Multiple numbers are separated by a forward slash (/). The error can then be classified by comparing the error number with the numbers in the aErrorClass array. If you would like to change the way errors are classified, you can simply overwrite the FillArrays() method. Typically you would do a DoDefault() first to get the original settings and then you would change some settings in the existing array.

Once the arrays are defined, the error is classified and the error class is stored in the cCurrentClass property. This property is used by the methods that are called next. Among them are IsFatal() and IsTrivial(). When a fatal error occurs, the application simply shuts down in an orderly fashion, trying to save as much data as possible and do some damage control. Trivial errors can be ignored. You can modify both methods to change the action taken or to change the error classes that are considered trivial or fatal. Note that both messages are called with one parameter that specifies whether an error message should be displayed. An error message will be displayed as a message box or as a wait window, depending on whether the error handler runs in an automation server (where user interaction is not possible), or in a message box in a monolithic application or interface tier. The information about whether the object is used as a server is stored in the lServer property. When your application is in server mode, make sure you never bring up any interface components that require user interaction.

Fatal and trivial errors are not logged in the log table, simply because trivial errors aren't worth logging, and fatal errors are so bad that logging would fail anyway, thus causing more trouble and jeopardizing damage control.

Once the error handler knows about the nature of the problem, it logs the error. But before doing so, it calls the OKToReport() method to determine whether it is okay to log the error. This method doesn't have any attached behavior. It simply returns .T., which means that all errors are logged. You can overwrite this method to set some rules about whether or not the error should be logged. To log a record, the LogErrorReport() method is called. This method displays an error message (if the object is not in server mode), makes sure the log file is available and finally utilizes the FillLogRecord() method to insert a new record and write the data to the file. You can overwrite this method if you want to log different information.

Once the error is logged, the error handler requires some help from the user. To do this, the UserHandlesError() method is invoked. Typically, the method displays a "Cancel/Ignore/Retry" type of message. Before this happens, the OKToContinue() method is queried to make sure the user can be asked. Again, this method always returns .T., but it can be overwritten to set some rules about whether the user can be asked. You can overwrite the UserHandlesError() method, or better yet, add some code to handle errors individually.

 

Object State

Class

_objectstate

Base class

Custom

Class library

_app.vcx

Parent class

_custom

Sample

...\Samples\Vfp98\Solution\Ffc\environ.scx

Dependencies

_base.vcx , _app.h

The Object State object can be used to restore an object's property values (the "object state"). When instantiating the Object State object, you must tell it what object to monitor. Do this by passing a reference to the object, which should be inspected when instantiating the object. In the following example, we create an Object State object that monitors changes made in the Visual FoxPro Screen object:

oObjectState = NewObject("_objectstate","_app.vcx","",_Screen)

Object State objects are passive observers. They don't monitor changes automatically, but they have to be notified. This is done by making changes through the Object State object using the Set() method, like so:

oObjectState.Set("Caption","New Screen Caption",.T.)

The first parameter is the name of the property we want to set, the second parameter is the new property value, and the third parameter specifies whether or not the change should be restored later on. I have to admit that I don't fully understand the reason for having a third parameter. Setting it to .F. would be just the same as setting the property directly. I can only imagine that the third parameter might be important for some very generic programs. It can't hurt to have this third parameter, but the fact that the default for parameter 3 is .F. makes the Object State object a little cumbersome to use.

When the Object State object is released from memory, it restores the initial state of the observed object. In our example, the original screen caption of the Visual FoxPro main window would be restored.

The Object State object features a number of properties and methods that allow you to influence it substantially. The lAutomatic property specifies whether changes should be logged and whether the original state should be restored automatically when the Object State object is destroyed. The Restore() and Save() methods can be used to manually save property values or to restore original settings. The Save() method requires the property name and value. The Restore() method optionally supports the property name as a parameter. If no parameter is passed, all properties are restored.

 

System Toolbar

Class

_systoolbars

Base class

Custom

Class library

_app.vcx

Parent class

_custom

Sample

...\Samples\Vfp98\Solution\Ffc\environ.scx

Dependencies

_base.vcx , _app.h

The System Toolbar object handles Visual FoxPro's internal toolbars, which can be rather annoying when testing applications because they don't go away automatically when you start your application. This object has two important methods HideSystemToolbars() and ShowSystemToolbars(). Their purpose is straightforward. You can call them to (surprise!) show and hide the system toolbars.

The System Toolbar object also has an lAutomatic property. It specifies whether system toolbars are automatically hidden when the object gets created and shown when the object is destroyed. By default, this property is set to .F.

Trace Aware Timer

Class

_traceawaretimer

Base class

Timer

Class library

_app.vcx

Parent class

_timer

Sample

...\Samples\Vfp98\Solution\Ffc\environ.scx

Dependencies

_base.vcx

The Trace Aware Timer basically is a regular Visual FoxPro timer object. The difference between this and a regular timer is that this one is much easier to debug because it's aware of the Visual FoxPro debug environment. When the Trace Window is opened, the timer switches into "sleep mode," which basically means that it fires less often. You can specify how frequently it fires in the iTraceInterval property. The default is 10000 (10 seconds).

As you might have noticed, the Visual FoxPro debugger allows you to turn off timer events altogether. This would be the same as setting the iTraceInterval property to 0. However, this might not be the intended behavior if you need to debug code that is influenced by a timer, or if somebody needs to debug the timer itself.

The Trace Aware Timer also has a property called iRegularInterval. Do not set this property directly, because it is always overwritten with the native Interval setting. For this reason you should always use the Interval property to set the standard interval for the timer.

 

Window Handler

Class

_windowhandler

Base class

Custom

Class library

_ui.vcx

Parent class

_custom

Sample

...\Samples\Vfp98\Solution\Ffc\whandler.scx

Dependencies

_base.vcx, _ui.h

The Window Handler object handles existing windows. It executes trivial tasks such as arranging windows; it does not create and manage windows as widely believed. Its most powerful method is CascadeFormInstances(), which arranges all the instantiated windows. It can be called without any parameters to arrange all existing windows, or you can pass parameters to influence what windows are rearranged and where to put them. The documentation says the first parameter is the name of the window, which should be rearranged. This is kind of a wishy-washy definition. It's really the value of the form's Name property. Multiple windows can have the same name, in which case all of them are rearranged. Parameter 2 is a logical parameter that specifies whether forms that are automatically centered (AutoCenter = .T.) should also be rearranged (.F.) or not (.T.). The default is .F., which means that all forms are arranged. Parameters 3 and 4 allow specifying the top and left properties for the first form. By default both values are 0, which means that the first form is positioned in the top-left corner of the parent form.

Besides rearranging windows, the Window Handler object also makes it easier to work with top-level forms. The GetCurrentTopFormRef() method returns an object reference to the current top-level window. If you don't use top-level windows, this method returns a reference to the _Screen object.

One of the harder tasks when using top-level windows is to provide the functionality typically defined in the Edit menu (such as Cut, Copy and Paste). Often, top-level windows don't have menus. The method InvokeMenuItemInFrame() allows you to execute these menu items programmatically. You simply need to pass the action you want to trigger as a parameter. The parameter is a string such as NEXT or PREVIOUS. Also supported are UNDO, REDO, CUT, COPY, PASTE, CLEAR, SELECTALL, FIND, FINDAGAIN and REPLACE.

Overall, this is one of the more disappointing Fox Foundation Classes.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net