Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

VFP Version 7.0 Mouse Handling Even Before Version 7.0
James Edgar, January 1, 2001
(Originally published under this title in Virtual FoxPro Users Group Newsletter of January 2001.) Mouse management in Visual FoxPro can be a little trying. It’s hard to determine in code where the mouse is positioned on a form and it seems almost impossible to force FoxPro to consistently ...
(Originally published under this title in Virtual FoxPro Users Group Newsletter of January 2001.)

Mouse management in Visual FoxPro can be a little trying.

It’s hard to determine in code where the mouse is positioned on a form and it seems almost impossible to force FoxPro to consistently display the hourglass pointer during lengthy process. It’s an hourglass over one object, an arrow over another, then an I-bar and so on. A lot of elaborate code has been devoted to working around just this one mouse problem.

Much more code has been devoted to keeping track of the mouse position so that visual effects can be displayed when the mouse is over an object and cancelled when the mouse leaves the object. Very necessary for "cool" controls. Some developers have even given up trying to control the mouse in FoxPro and have resorted to the Windowsä API mouse events to get control of that pesky mouse - more solution, in my humble opinion, than the problem justifies.

Finally, after years of developer requests, Version 7.0 is giving us two new mouse events to make controlling the mouse a little easier: MouseEnter() which occurs when the mouse first moves over an object, and MouseLeave() which occurs when the mouse leaves the object. With these new events every control will know when the mouse has moved over it and when the mouse has left and can take appropriate action.

But even before Version 7.0 is released, we can easily duplicate this functionality. In fact, I have been using the global mouse controller described in this article since Version 3.0 giving me all of the “new” functionality provided by Version 7.0 since about 1996.

How Does It Work?

What is required is a mediator to monitor mouse movement in the application and a process in each visible VFP object to notify the mediator when the mouse is over the object.

We have the notification capability in the MouseMove() event. Every object’s MouseMove() is capable of passing a message to an external mediator saying, in effect, "mouse is here". If the mediator knows were the mouse is, then it also knows where the mouse is not. These two pieces of information are all we require to build an effective mouse handler.

Let’s say the mouse is over ObjectA. Each time the mouse is moved, the ObjectA MouseMove() event passes a "mouse is here" message to the mouse handler. Eventually the mouse moves onto ObjectB. ObjectB is now sending a "mouse is here" message. The mouse handler knows two things: (1) the mouse is over ObjectB and (2) no longer over ObjectA. It has detected a transition event from one object to another.

This is the event we want to capture and manage using a mouse handler. When a transition occurs, the mouse handler should call the MouseLeave() event of the object being left and MouseEnter() event of the object being moved onto -- duplicating the "new" functionality provided in Version 7.0.

We can also use the mediator to resolve the problem of displaying a consistent hourglass mouse pointer when the application is busy.

Once a busy state is declared, the mediator can ensure that each object moved over by the mouse displays an hourglass pointer while it is under the mouse. We could also use the mediator to ensure that none of the objects under the mouse get focus or otherwise react to mouse click events while a busy condition is in effect. But this would be a little outside the scope of this article. Suffice to say it is quite do-able with very little work.

Finally, we want our code to survive the transition to Version 7.0 without having to be extensively modified.

These then are the goals of our mouse handler. It should call an object’s MouseEnter() method when the mouse first moves over the object, execute an object’s MouseLeave() method when the mouse moves off the object, and display a consistent hourglass pointer for all objects when an "system busy" state has been declared. Once this much has been accomplished, I am certain you will find a great variety of other uses for the mouse handler.

Where Do We Put It?

We know a mediator has to be outside of the objects whose behavior it is mediating, but where, exactly?

We could put it on each form. A mouse handler object attached to a form could easily monitor mouse movement on its form. And since mouse coordinates in VFP mouse events are stated in form coordinates, having the mouse handler on the form is attractive from the point of view of managing many mouse-related issues. The problem with that approach, however, it that the transition that occurs when the mouse moves from one form to another is difficult to trap. It requires the mediator on the leaving form to pass a message to the mediator on the receiving form. To do that, it is going to have to know when the mouse has left the form. How does it do that? I don’t know. I have never figured out a good solution.

VFP provides a couple of places above the form level where the mouse mediator might be placed. One is in the application object referenced by the _VFP system variable. Another the main Visual FoxPro window referenced by the _SCREEN system variable. A final possibility is the global application you have created. A mediator located in an object above the form level will have no problem with the transition from form to form or even form to _SCREEN (with the work-around detailed below). But these are objects, and objects take some time to load each time they are called. We want the mouse handler to be very fast with as little load overhead as possible.

After much experimenting, I finally decided create the mouse handler as a floating global object assigned to a public variable. I call it goMouseHandler. It is outside all forms. It does not require a parent object to be loaded each time it is called. It is very fast - so fast that you will detect no difference in mouse movement speed when the mouse handler is working.

The MouseHandler Class

So let’s build a mouse handler.

First we create an object based on the custom class (or line class for those who prefer the smaller object footprint) that we will call, for lack of any imagination whatsoever, "MouseHandler". If you use line class as your object base, be sure to set width and height to zero, and visible to false.

In the Init() event of MouseHandler, include the following code:

*MouseHandler.Init()

WITH this

   *Add internal properties. These properties are used for internal
   *and are not intended to be displayed on the property sheet.
   *array used to store parameters passed by an object’s MouseMove() event
   .AddProperty("aParameters[4]" )

   *Specifies whether the hourglass cursor should be displayed for all
   *objects coming under the mouse.
   .AddProperty("MouseBusy", .F. )

   *Stores original mouse pointer if the object is displaying the hourglass.
   *If no pointer is currently stored, its value defaults to -1.</i>
   .AddProperty("MouseCursor", -1 )

   *A reference to the object over which the mouse is passing.</i>
   .AddProperty("oObjectUnderMouse", .NULL. )

ENDWITH

RETURN

In the Destroy() event you will need this code:

*Mousehandler.Destroy()
*Nullify the object reference in oObjectUnderMouse and the global variable
*goMouseHandler. If the object references are not destroyed, they may
*cause a form or the application to "hang"

STORE .NULL. TO this.oObjectUnderMouse, goMouseHandler

RETURN
Now we need two new methods: MouseOver() and SetBusy(). SetBusy() will set/unset the global “busy” flag and MouseOver() will handle mouse pointer display and calling MouseEnter() and MouseLeave() as appropriate.

MouseOver() Method

MouseOver() is the core method of the mouse handler. It manages of the transition event when the mouse leaves one object and enters another and displays the hourglass pointer when a "busy" state is in effect.

Its basic operation is very simple. It receives a reference to the object currently under the mouse as the parameter toObjectUnderMouse. It has previously stored a reference to the last object under the mouse in the property oObjectUnderMouse. It compares toObjectUnderMouse to oObjectUnderMouse. If the two objects are the same, the mouse is still over the same object, no transition event has occurred and no action is required.

If the two objects are different, however, then the mouse has left the old object and has entered toObjectUnderMouse. Now the action begins.

MouseOver() first uses PEMSTATUS() to test whether the old object has a MouseLeave() method. If so, that method is executed. Then it determines (again using PEMSTATUS()) whether the new object has a MouseEnter() method. If one is found, it is executed. You do not need to add MouseEnter() or MouseLeave() events to any control classes for which you do not want to display special mouse effects. The mouse handler does not try to call a MouseEnter() or MouseLeave() event that does not exist.

Then the MouseBusy state is checked. If the application is in a "busy" condition (the MouseBusy property is .T.), toObjectUnderMouse’s mouse pointer is saved and changed to a hourglass pointer. OobjectUnderMouse’s original pointer was stored in the MouseCursor property. That mouse pointer is now restored. The method hard-codes the hourglass as the busy mouse pointer, but it could be easily changed to any pointer, including a custom cursor, stored in something like a MouseBusyCursor property. Since I always use the hourglass, I have not bothered.

Finally, a reference to the new object, toObjectUnderMouse is assigned to the property oObjectUnderMouse, and the method waits for a call from another MouseMove() event from another toObjectUnderMouse.

This basic functioning is complicated a little by the fact the we do not want to call MouseEnter() or MouseLeave() if the application is executing under Version 7.0 of Visual FoxPro. VFP will trigger these events and we do not want them to execute twice. Also, we do not want to execute these methods if the application is "busy. There is also some "error avoidance" code that we insanely paranoid defensive programmers include with great abandon.

Here is the fully commented method code for MouseOver():

*Mousehandler.Mouseover()
*
*OVERVIEW:
*Each MouseMove() event of each object on a form may pass a reference to itself
*to this method each time MouseMove() fires. *If an appropriate object
*reference is received, this method determines whether the mouse
*pointer has *left one object and entered another. If so, it triggers the
*MouseLeave() event
*of the object the mouse pointer just left and the MouseEnter() event of the
*object the mouse pointer just entered.

*PARAMETERS:

*<u>tcObjectUnderMouse</u> A reference to the object the mousepointers now over.
*Passed from the .MouseMove() event of the object.
*<u>nButton</u> Contains a number that specifies which mouse button(s) was being held
*down as the mouse was moved.
*<u>nShift</u> Contains a number specifying the state of modifier keys when the mouse
*was moved.

*<u>XCoord</u>, <u>nYCoord</u> Contain the current horizontal (nXCoord) and vertical
*(nYCoord) position of the mouse pointer within the form.

*RETURN: No significant return

LPARAMETERS toObjectUnderMouse, nButton, nShift, nXCoord, nYCoord

WITH this

   *Handle the transition event only if the reference passed to
   *toObjectUnderMouse is to a valid object reference.

   IF VARTYPE(toObjectUnderMouse ) = "O"
      *Find out if the mouse was formerly over another object. If so,
      *oObjectUnderMouse will be an object reference. Otherwise it will be
      *.NULL. and VARTYPE() will return "X"

      IF VARTYPE(.oObjectUnderMouse ) = "O"
         *See if toObjectUnderMouse and .oObjectUnderMouse are the same
         *object. If so, the mouse is still over the old object and no
         *transition management is required. Event triggering occurs only
         *if the mouse pointer has moved from one object to another.

         IF .oObjectUnderMouse # toObjectUnderMouse
            *If the two objects are not identical, the mouse has left
            *.oObjectUnderMouse and moved over toObjectUnderMouse If
            *the old object has a mouseLeave() event, it should now be
            *called. The parameters nButton, nShift, nXCoord,
            *nYCoord are passed to the MouseLeave() event to conform to
            *VFP Ver. 7.0 architecture.
            *<u>NOTE</u>: The MouseLeave() event is intended to contain code
            *that is triggered as the mouse pointer leaves an ob-
            *ject. It usually undoes the changes made to special
            *visual effects by MouseEnter().

            IF VERSION( 5 ) < 700 .AND. PEMSTATUS(.oObjectUnderMouse, "MouseLeave", 5 )
               .oObjectUnderMouse.MouseLeave;
                (;
                .aParameters[1 ], ; && nButton of the old object
                .aParameters[2 ], ; && nShift of the old object
                .aParameters[3 ], ; && nXCoord of the old object
                .aParameters[4 ] ; && nYCoord of the old object
                )
            ENDIF

            *If the stored mousepointer is other than -1, this object's
            *mousePointer was stored in .MouseBusy and the hourglass
            *pointer is being displayed. Restore the old mouse pointer.

            IF !( .MouseCursor < 0 ) .OR. .MouseBusy
               .oObjectUnderMouse.MousePointer = .MouseCursor
            ENDIF

         ENDIF ;&& VARTYPE( .oObjectUnderMouse ) = "O"

      ENDIF ;&& VARTYPE( toObjectUnderMouse ) = "O"

      *See if the new object under the mouse, toObjectUnderMouse, has a
      *MouseEnter() event. If so, trigger the event. The parameters
      *nButton, nShift, nXCoord, nYCoord are passed to the mouseEnter()
      *event to conform to VFP Ver. 7.0 architecture.
      *NOTE: MouseEnter() is used for code that needs be processed just
      *one time as the mouse moves onto an object. In contrast,
      *MouseMove() code is processed continuously while the pointer moves
      *over the object and may therefore, be processed multiple
      *times before the pointer leaves the object. Typically, MouseEnter()
      *code is used to display special visual effects, custom tool tips
      *and custom status bar messages.

      IF !( .MouseBusy ) .AND. VERSION( 5 ) < 700 .AND. ;
       PEMSTATUS( toObjectUnderMouse,"mouseEnter", 5 )
         toObjectUnderMouse.MouseEnter(nButton, nShift, nXCoord, nYCoord )
      ENDIF

      *If .MouseBusy is true, store the object's current mousepointer and
      *display the hourglass pointer. The original pointer will be restored
      *when the mousepointer leaves the object.

      IF .MouseBusy .AND. toObjectUnderMouse.MousePointer # 11 && hourglass

         *If the mousepointer is already an hourglass, then this object’s
         *pointer has already been set to the busy pointer. Do not set it
         *again or store the hourglass as the "old" pointer.
         .MouseCursor = toObjectUnderMouse.MousePointer
         toObjectUnderMouse.MousePointer = 11
      ENDIF

   ENDIF

   *Make toObjectUnderMouse the "old" object by assigning its reference to 
   *.oObjectUnderMouse.
   .oObjectUnderMouse = toObjectUnderMouse

   *Store the button/key codes and coordinates of toObjectUnderMouse
   .aParameters[1 ] = nButton
   .aParameters[2 ] = nShift
   .aParameters[3 ] = nXCoord
   .aParameters[4 ] = nYCoord

   *Nullify local object references before exiting. VFP documentation says
   *this is not necessary, but it is good practice to clean up by setting all
   *local objects to .NULL. to avoid hanging forms that will not release. It
   *has, in fact, happened to me more than once. 
   toObjectUnderMouse = .NULL.

ENDWITH

RETURN
SetBusy() Method

We now have the method that displays an hourglass pointer when the "system busy" state is set. So how do we declare a "busy" state?

We call the mouse handler’s SetBusy() method with a .T. argument to set a busy state or a .F. argument to cancel a busy state. If the method is called without passing an argument, it returns the current busy state as a logical .T./F. Here is its code:

*MouseHandler.SetBusy

*OVERVIEW:
*Sets mouseBusy to true or false. MouseBusy specifies whether the
*hourglass cursor is displayed for the object under the mouse.

*SYNTAX:   
*goMouseHandler.SetBusy( .T. ) will cause the object under the
*mouse to display an hourglass pointer.
*goMouseHandler.SetBusy( .F. ) cancels display of the hourglass pointer.

LPARAMETERS tlBusy

WITH this

   *If no parameter has been passed, return the current MouseBusy setting.
   IF PCOUNT() < 1 .OR. VARTYPE( tlBusy ) # "L"
      RETURN .MouseBusy
      ELSE
      *If a parameter has been passed and the parameter is of logical type,
      *set .MouseBusy to the parameter
      .MouseBusy = tlBusy

      *Since there is probably an object already under the mouse,
      *immediately set its pointer to busy/not busy as appropriate.
      IF tlBusy

        *Save the object’s current mousepointer to MouseCursor
        .MouseCursor = .oObjectUnderMouse.MousePointer
        *Set the object’s mouse pointer to an Hourglas ( = 11 )
        .oObjectUnderMouse.Mousepointer = 11

        ELSE
        oObjectUnderMouse.Mousepointer = .MouseCursor
        *A MouseCursor of -1 indicates that there is no
        *mousepointer now being stored.
        .MouseCursor = -1

      ENDIF
   ENDIF

ENDWITH

RETURN
Actually, were I writing this code today rather than five or six years ago, I would probably just set MouseBusy directly. But in those days I was into the pure OOP paradigm that requires that properties not be set from outside an object except through a method of the object. I’m not nearly that fanatic these days.

Set the ‘busy" state just before a lengthy process. During the process, no matter where the user moves the mouse within the VFP application, the hourglass mouse pointer will be displayed. Cancel the "busy" state when the process is completed. The code below illustrates how to set and cancel the busy state so that a nested process does not inadvertently set the busy state off before the lengthy processing is completed:

LOCAL llMouseBusy

IF VARTYPE( goMouseHandler ) = "O"
   WITH .goMouseHandler
      llMouseBusy = .SetBusy()
      .SetBusy(.T. )
   ENDWITH
ENDIF

*Start some lengthy process
*
*
*End some lengthy process

IF VARTYPE( goMouseHandler ) = "O"
   GoMouseHandler.SetBusy(llMouseBusy )
ENDIF

RETURN
And that’s all there is to the mouse handler.

InstallMouseHandler Class

Now we need to install it. The class that takes care of installation is the InstallMouseHandler() class. This does not have to be a class. It could just as readily be a function or procedure. But creating it as a class permits me to keep the MouseHandler and InstallMouseHandler classes in the same class library with some other mouse classes so everything is in one place and easy to find.

The InstallMouseHandler object never actually instantiates. It does all of its work in its Init() method then returns .F. which prevents the object from being created. Note that the class library that contains all of my mouse classes is "mousehnd.vcx". You will want to substitute your own class library name in the code below.

This is its Init() code:

*InstallMousehandler.Init()

*OVERVIEW:
*This method installs the mousehandler class if the mousehandler has not
*already been installed in the public variable goMouseHandler.

IF VARTYPE( goMouseHander ) # "O"   
   PUBLIC goMouseHandler
   goMouseHandler = NEWOBJECT("mousehandler" , "mousehnd" )
ENDIF

*False is returned to keep the object from actually being created. It has
*already done all its work so there is no reason to keep it hanging around.

RETURN .F.
This code is so simple that you may want to avoid the slight extra overhead of having a class do the work and just include these four lines of code wherever you want the mouse handler to be created.

You should install the mouse handler in your start-up code as early in the process as possible, and certainly before the first form is displayed. If you run forms outside of your application environment during development, as I do, you will also want to install the mouse handler in the Load() of your abstract form. Call InstallMouseHandler with the following code. Again, you will want to substitute the name of your class library for "mousehnd".

NEWOBJECT( "InstallMouseHandler","mousehnd" )
MouseMove() Event

The mouse handler cannot do its work unless it knows what object the mouse is currently over. The MouseMove() event is where this information gets passed to the mouse handler. Include the following code in the MouseMove() event of every abstract class in your toolbox that has a visible presence, including your abstract form class and any OLE classes.

*MyAbstractClass.MouseMove()
*
LPARAMETERS nButton, nShift, nXCoord, nYCoord

*Test for the default mouse handler. If one exists, execute its MouseOver()
*event. In VFP Version 6.0 and earlier, goMousehandler.MouseOver() calls the
*MouseEnter() and MouseLeave() methods of objects under the mouse. For Ver 7.0,
*the VFP native mouse handler triggers MouseEnter() and MouseLeave() events, and
*goMouseHandler is relegated to merely handling system-busy events that
*require the hourglass mousepointer be displayed.
IF VARTYPE( goMouseHandler ) = "O"
   WITH goMouseHandler
      IF VERSION( 5 ) < 700 .OR. .SetBusy()
         .MouseOver( this, nButton, nShift,nXCoord, nYCoord )
      ENDIF
   ENDWITH
ENDIF

RETURN
Mouse Handling in the _SCREEN Object

The _SCREEN system variable does not refer to a true object, but an "object-like" thing. Because _SCREEN is an object-like thing and not an object, we cannot modify its methods or events. It has no MouseMove() event, and if it did, FoxPro would not allow you to add code to it. So we solve that problem another, way:

Create a container class called something like "ScreenBackground" and add it to your mouse handler library. Set its properties as follows in its Init() (or set them on the property sheet), but be sure to include a call to Resize() in the Init() method.

*ScreenBackground.Init()
*
WITH this
   .Backstyle = 0 && Transparent
   .Borderwidth = 0 && No border
   .Resize() ;&& Resize the object to maximum screen size.
ENDWITH

RETURN
Here is the resize code for ScreenBackground:
*ScreenBackground.Resize()
*
WITH this
   *Set position and size. The Height and Width properties are set to the
   *Maximum possible dimension of the _SCREEN. No matter how large or small
   *_SCREEN is made, ScreenBackground will always cover it.
   .Top = 0
   .Left = 0
   .Width = SYSMETRIC(21) ;&& Maximum screen width in current resolution.
   .Height = SYSMETRIC(22 ;&& Maximum screen height in current resolution.

   *Ensure the ScreenBackGround is visible. MouseMove() does not register on
   *an invisible object.
   .Visible = .T.
   *Make sure this object moves behind whatever else might be on the _SCREEN.
   .zOrder( 1 )
ENDWITH

RETURN
You may, of course, move this code to the Init() method. I have it in Resize() because in development I test code on a multi-monitor setup and therefore need to resize ScreenBackground from time to time in code.

In the ScreenBackground MouseMove() event, include the MouseMove() code listed above.

Now we can add ScreenBackground to _SCREEN with

_SCREEN.NewObject("ScreenBackground", "ScreenBackground","MouseHnd" )
which you should do as early in your startup process as possible. And, unless you want don’t mind if ScreenBackgound is always displayed, use the following code in your ON SHUTDOWN process to remove it,
_SCREEN.RemoveObjct("ScreenBackground" )
While ScreenBackground exists, although it appears to the user that s/he is moving the mouse over the _SCREEN, s/he is actually moving over the ScreenBackground container and MouseMove()s are firing like mad.

MouseEnter() and MouseLeave() Methods

These two methods added to each of your abstract classes are where all the fun occurs. Creating and installing the mouse handler gives you the potential to create wizard special mouse effects. Buts it’s all potential until you place code in the MouseEnter() and MouseLeave() methods of each visible class for which special effects are desired. MouseEnter() is typically used to display visual effects, and MouseLeave() to clear the special effects.

You imagination is the limit when it comes to creating "cool" effects. Here is an example of a modest effect for an option group. When the mouse moves over the group, its border is displayed. When the mouse moves away, the border disappears.

Here is the MouseEnter() method…

*MyAbstractOptionGroup.MouseEnter()
*Display the option group border when the mouse is over the control
LPARAMETERS nButton, nShift, nXCoord, nYCoord

This.borderstyle = 1 && Single line border

RETURN
…and the corresponding MouseLeave() method.
*MyAbstractOptionGroup.MouseLeave()
*Remove the border displayed when the mouse was over the control.
LPARAMETERS nButton, nShift, nXCoord, nYCoord

This.borderstyle = 0 && No border

RETURN
Object Clean-Up

The final issue is clean up. We are creating and destroying a lot of objects, and we want to make certain none are left hanging around to trouble us when we close forms or quit the application.

Quitting the application is not very worrisome. Add code to your ON SHUTDOWN process to nullify goMousehandler, such as…

goMousehandler = .NULL.

RELEASE goMousehandler
VFP nicely handles application shutdown so even if you omit this code, you probably will have no problem. But why take the chance?

Closing forms, however, is a very different story.

It is very, very important that you include the following code in your Form’s Destroy() event. If you do not, the form may hang because goMousehandler may own an object reference to the form or an object on the form. To get rid of that reference, just point goMousehandler to an object guaranteed not to be on the form, such as…

goMouseHandler.MouseOver(_SCREEN, 0, 0, 0, 0 )
Transitioning to Version 7.0

When Version 7.0 in all its net-ready glory finally ships, you need do nothing to ensure your mouse-handling scheme continues to work. VFP will take over triggering your MouseEnter() and MouseLeave() events, but the_SCREEN work-around you wrote will still apply and you are months ahead of the development curve because you have written, debugged and tested your MouseEnter() and MouseLeave() code. And for all its advanced, web-interactive, features, Version 7.0 still does not have a good solution to the hourglass pointer display problem that you have now nicely taken care of.

Happy coding!

James Edgar
J. M. Edgar has been a software developer in xBase languages since 1984. He received a Masters degree from the University of Maryland and studied law at McGeorge School of Law, the University of Nebraska and Georgetown Law School, earning a Juris Doctor in 1980. He is a member of Phi Beta Kappa, Phi Kappa Phi, Alpha Kappa Delta, and an American Jurisprudence Fellow. He is admitted to practice law in California, the Federal Eastern District of California and the United States Tax Court. He has worked for the U.S. Justice Department, as an Assistant Chief of Police of a major California city, a lawyer in private practice and president of a multi-state insurance agency as well as heading his own software development company. He can be contacted on the Universal Thread or at jmedgar@yahoo.com.
More articles from this author
James Edgar, April 1, 2003
We all know what a collection is. Visual FoxPro uses a lot of them. The _SCREEN has a "forms" collection, a Form owns the "controls" collection. The Pageframe has a "pages" collection and every grid its "columns" collection. Each container class also has an "objects" collection (which turns out...
James Edgar, December 1, 2003
One of the most universally useful compound abstract classes is a tab bar. Unfortunately, Visual FoxPro does not have a native tab bar class. The VFP designers evidently figured that since we have a page frame class, we don't need a tab bar because a page frame works like a tab bar.
James Edgar, July 1, 2003
Lots of Visual FoxPro components include a splendid native scroll bar. The grid, list box, edit box and form classes all contain embedded bars. The VFP designers wisely concluded that these classes are likely to display information that might not be all visible in the same view, and therefore some ...
James Edgar, January 1, 2001
This paper was written in 1993 and has been modified periodically from time to time since that date. It has absolutely no application to the law in alien lands such as Canada or Louisiana. There is an ancient truism among attorneys that "An oral contract is not worth the paper it wasn't written on....