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

A Visual FoxPro Tab Bar Class
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.
Summary
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.
Description
In an earlier article, A Visual FoxPro Collection Class (Universal Thread Magazine, Vol 2, No. 11, April, 2003), I noted briefly that a collection class is a useful tool in building very powerful compound classes in Visual FoxPro.

A compound class, you will recall, is a reusable abstract class composed of multiple native FoxPro classes.

Collections make abstraction easy because they allow us to address and manipulate groups of objects without actually knowing much about the objects in the group. We don't need to know the name of an object or its place in the object hierarchy. We can read and set object properties through the object reference housed in the collection, and execute object methods the same way.

One of the most universally useful of these 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.

Well, kinda, sortta.

Page frames have a few notable shortcomings.

First, the pages on a page frame are like columns in a grid -- objects we can't modify in the IDE. So we get the look, feel and function VFP provides, and no other. That look, feel and function has certainly improved in Version 8.0, but it still needs a lot of tinkering.

Second, tabs and pages are not separate objects. You cannot display a collection of tabs on a page frame and have the page load only when the tab is clicked. The tab and page are the same object -- a page with a "thingy" sticking up top (or to the side or bottom in Version 8.0). To display a tab, you need to instantiate a whole page.

Unfortunately, this means page frames are often slow to load. The more objects on the pages, the slower the load. In the age 3 GHz computers, this is increasingly less a problem than it was in the era of 300 MHz boxes (about 3 years ago). But it is still not good design or best practice.

Remember the software designer's guiding principal of parsimony? It says (loosely translated) "thou shall not instantiate stuff you don't need right now", which suggests that loading four or five pages of objects so the user can take a quick look at the first page is a waste of time and a lot of Windows resources.

The usual work around is to treat the page as if it were just a tab. Put nothing on the page initially, then load a container of controls onto the page only when the tab is clicked. In doing this, we divorce the tabs from the pages. And that's the trick. If the tabs are separate objects, then we do not have to display the pages until we want to, and we don't want to until the user selects the tab. Now we have better control over the process. But why then load the containers onto a page -- which is already a variety of container? Why not just add them to the form itself and bypass the cumbersome pageframe altogether?

So, what we need is something that holds a collection of tabs. That's what a tab bar is -- a framework for a collection of tabs.

This article looks at some issues involved in designing and implementing a tab bar class using purely FoxPro classes. There are not many issues. A tab bar is a relatively simple compound object.

We need a collection class, two container classes, a shape and a label, and we're in business. If you have never written a custom abstract class before, this is a excellent one to begin with. It is fairly straightforward, but complex enough to be challenging, and the result is a fully customizable abstract class that you can drop on any container or form and immediately use. The Source Code contains a fully functioning, if rather simple, horizontal tab bar class. You can used this as the basis for your own abstract class -- or just use it as it comes out of the box if you don't want to write your own class.

There are lots of possibilities for expansion of the class. An obvious one is to adopt the class so it will display either as a horizontal or vertical tab bar. This means, unfortunately, replacing the VFP label on the Tab container with an ActiveX label that supports vertical text.

I have developed a vertical tab bar using an .ocx label, but it's uses are so limited in my applications that I have not refined it for a few years. Still it is certain possible and where you have use for it, I urge you to do it.

Another expansion is to add the ability to wrap tabs on the bar. An example of wrapped tabs are shown in the illustration below. This increases the number of tabs that may be displayed without running off the edge of the form.

Figure 1: Wrapped Horizontal Tab Bar

The illustration also shows some other compound abstract classes.

  • The thing that looks like a grid is actually a special data entry class composed of stacked bars. I used this class for data entry in a tabular presentation not complex enough to require the huge overhead of a grid.
  • The scrollbar on the form is an instance of another compound class. The scrollbar class was presented in A Visual FoxPro Vertical Scroll Bar Class (Universal Thread Magazine, Vol 3, No. 02, September, 2003).

What is a Tab Bar?

A tab bar is very simply a framework that displays tabs. It must obey a very few rules (these are in no particular order):

  • The frame should support a variable number of tab objects determined by property settings of the tab bar class.
  • At least one tab must be displayed. (A tab bar with no tabs has no purpose in life).
  • Tab objects must respond in a known manner to being selected.
  • The selected tab must be visually differentiated from unselected tabs so the user can quickly identify the current tab.
  • Tab display should be flexible, permitting the user to change the look and feel of the tab bar by setting a few properties.

Designing a Tab Bar

I based the tab bar frame on a container class. The tabs on the bar are again just containers. A shape object is used on the tab container to make it look like the classic "tab", and a label is used to display text. A collection is added to allow us to manipulate the tabs as a group. That's all there is to it.

This is intended to be an abstract class that may be put to multiple disparate uses in our applications. If you are developing applications for multiple clients, it is important to be able to adapt the look and feel of the program to the client's preferences without having to write a lot of additional code.

Accordingly, we want to build into the abstract class as much flexibility as is reasonable.

Abstract Behavior

Tab behavior at this abstract level is very limited. All the native tab does is react to being selected -- repainting the tabs on the tab bar accordingly and passing information to a "hook" method (m_Tab_Action). If the TabMouseEffect property (see below) is set to a value greater than zero, a tab will also display certain visual effects when it is under the mouse cursor.

When a tab is selected, three things need to happen.

  • The formerly selected tab needs to be painted back to an unselected appearance.
  • The newly selected tab needs to be painted to show it is now the selected tab.
  • A hook process needs to be called with an unique identifier that specifies which tab on the bar was just selected.

In our tab bar, all tabs are identified by a integer assigned to the tab's TabIndex property. As a tab is added to the bar, it receives the next available index. The first tab added is index 1, the last tab's index equals the TabCount.

When as tab is selected by mouse clicking the tab, the tab's Click() event assigns its TabIndex to the tab bar's SelectedTab property. This triggers the SelectedTab_Assign() event. From this event the colors and other visual effects of all of the tabs are reset through the Tabs collection.

Any tab that is not the selected tab is repainted as a unselected tab. This means that the formerly selected tab is repainted, but it also means that all of the formerly unselected tabs are also repainted -- unnecessarily since they are already the correct color and size. Very true -- but experimentation proved that there is little benefit and a lot more complexity involved in trying to keep track of which tab was formerly selected and repaint just the formerly selected tab. This "brute force" approach works, is very fast, and is very simple.

This is the code in SelectedTab_Assign() that sets the SelectedTab and calls the m_Set_Properties() method of each Tab object through the Tabs collection of the TabBar class. This method of the tab class takes care of sizing and painting each tab. Note that because each tab is addressed through the collection, we don't have to know anything about the tab. We don't even know its name -- and don't care. All we need to know is that a reference to each tab resides in the collection and can be addressed through the collection object.

*   SelectedTab_Assign()
*
*   OVERVIEW:   Handles display of the selected tab.  The selected tab is normally a different
*               color and size and may have a distinct border -- all of which indicates on 
*               visual inspection that it is the currently selected tab.
LPARAMETERS tnSelectedTab

WITH this
   *   SelectedTab has to be set here so tab colors will be painted correctly 
   *   by the m_Set_Colors() method of each tab.
   .SelectedTab = tnSelectedTab

   *   Scan the Tabs collection and execute the m_Set_Properties() method of each
   *   tab to reset tab heights, if necessary, and repaint the tabs.
   WITH .Tabs
      FOR lnI = 1 TO .Count
         *   Set the tab's forecolor, backcolor, borderstyle, and top
         *   properties to reflect whether the tab is or is not selected.
         .Item[ lnI ].m_Set_Properties()
      NEXT
   ENDWITH
ENDWITH

RETURN                     

This is the code in the m_Set_Properties() method of the Tab class. It calls two Tab class methods to set the appearance of the Tab. The m_Set_Colors() method sets the appropriate text and background colors for the tab based on whether it is or is not the selected tab. The m_Set_Tab_Top() method set the Top property of the tab. Selected tabs are drawn a few pixels taller than unselected tabs to make them more distinguishable.

The code ensures that the selected tab is always moved to the front of the Z-Order. Otherwise the selected tab might be overlapped by a unselected tab. This is not the look we want.

*   Tab.m_Set_Properties()
*
WITH this

   *   Assigns backcolor, bordercolor and forecolor of the tab
   *   depending on whether it is the selected tab.
   .m_Set_Colors()

   *   Set the top of the TabShape.
   .m_Set_Tab_Top()
   
   *   If this tab is the selected tab, ensure it is in front of
   *   the z-order so it is not overlapped by a unselected tab.
   IF .parent.SelectedTab = .TabIndex
      .Zorder()
   ENDIF
   
ENDWITH

RETURN

The code in m_Set_Colors() ensures that the various components of the compound tab object (container, shape and label) are painted correctly. Colors are set selectively to minimize processing time. The only color settings that affect the appearance of the tab are the shape BackColor and BorderColor properties and the text color of the label. No other color need be set.

The colors to be used are obtained from various tab color properties of the tab bar (see the property list below).

*   Tab.m_Set_Colors
*
*   OVERVIEW:   This method is used just to assign colors to various tab
*               components base on whether this tab is the selected or
*               not selected.
*
LOCAL llEnabled, lnTabIndex, lnBackColor, lnBorderColor, lnForeColor

WITH this
   *   The visible part of the tab are the tabShape and tabLabel objects.
   *   The tab container itself is invisible, so any color setting in the
   *   container has no effect on its appearance.
   *  
   *   BackColor   The only BackColor visible is the that of the TabShape, so any BackColor 
   *               assignment is made only to the shape.
   *   BorderColor The only Border visible is the that of the TabShape, so any BorderColor or 
   *               BorderWidth assignment is made only to the shape.
   *   ForeColor   The only ForeColor visible is the text of the TabLabel, so the ForeColor 
   *               assignment is made only to the label.
   *
   llEnabled  = .Enabled
   lnTabIndex = .TabIndex

   WITH .parent

      DO CASE
      CASE !( llEnabled )
         lnBackcolor   = .DisabledTabBackColor
         lnBordercolor = .DisabledTabBorderColor
         lnForecolor   = .DisabledTabForeColor
         
      CASE .SelectedTab = lnTabIndex
         lnBackcolor   = .SelectedTabBackColor
         lnBordercolor = .SelectedTabBorderColor
         lnForecolor   = .SelectedTabForeColor
         
      OTHERWISE  && Normal, unselected tab
         lnBackcolor   = .TabBackColor
         lnBordercolor = .TabBorderColor
         lnForecolor   = .TabForeColor
      ENDCASE
   ENDWITH && .parent
   
   *   Now that we have the colors, actually set the color properties
   *   of the objects on the Tab container.
   WITH .TabShape
      .BackColor   = lnBackColor
      .BorderColor = lnBorderColor
   ENDWITH
   
   .TabLabel.Forecolor = lnForeColor

ENDWITH

RETURN

The selected tab is painted a few pixels taller than unselected tabs. This is done by exposing a few more pixels of the shape object on the selected tab by setting the selected tab Top property. This is taken care of by the Tab.m_Set_Tab_Top() method.

*   Tab.m_Tab_Top()
*
*   OVERVIEW: Sets the tab's Top property depending in whether the tab is or is not
*             selected.  Selected tabs are shown slightly taller than unselected tabs.
WITH this

   *   Tab orientation is 0 (Up) or 2 (Down)
   IF .parent.SelectedTab = .TabIndex
      *   Selected tab is 2 pixels taller than unselected tabs.      
      .TabShape.Top = IIF( .TabOrientation = 0, 1,  0 - .Height )
   ELSE
      .TabShape.Top = IIF( .TabOrientation = 0, 3, -2 - .Height )         
   ENDIF
   
ENDWITH

RETURN

Appearance

The appearance of the tabs on the tab bar is intended to be determined solely by setting tab bar properties. These affect such factors as the colors to be applied to selected and unselected tabs, the shape of the tab, the tab orientation (up or down), the amount of tab overlap and so on.

Quite dramatic appearance changes are possible just be setting a few properties. See the examples below. All of these appearances result just from modifying tab bar property settings. No customization of code is involved.

Figure 2: Some Appearance Changes Possible Through Property Settings

Here is a table of properties of the tab bar that affect appearance with illustrations of some of the effects of changing appearance properties.

Property Type Description
AutoBarSize L Specifies whether the tab bar is automatically sized to fit the cumulative width of the tabs on the bar. Default is .F. If true, the tab bar will always display all tabs.
AutoTabSize L Specifies whether tabs are sized proportionate to the width of their captions. Default is .F.

AutoTabSizeMargin N Specifies (in pixels) the width of the white space to the left and right of caption text in a tab. Minimum is 4 pixels. Has effect only if AutoTabSize is .T. and TabJustify is .F. Default is 10 pixels.

Property Type Description
Curvature N Specifies the amount of curvature to be applied to the tab. For no curvature (square tabs), enter 0 (zero). Default curvature is 20.

DisabledTabBackColor N Specifies the background color to be applied to disabled tabs. Default is gray.
DisabledTabBorderColor N Specifies the border color to be applied to disabled tabs. Default is black.
DisabledTabForeColor N Specifies the text color to be applied to disabled tabs. Default is dark gray.
SelectedTabBackColor N Specifies the background color to be applied to the selected tab. Default is white.
SelectedTabBorderColor N Specifies the border color to be applied to the selected tab. Default is black.
SelectedTabForeColor N Specifies the text color to be applied to the selected tab. Default is black.
ShowTabBorder L Specifies whether the tabs are to be displayed with a border. Default is .T.

TabBackColor N Specifies the background color to be applied to an unselected tab. Default is pale gray.
TabBorderColor N Specifies the border color to be applied to an unselected tab. Default is dark gray.
TabBorderWidth N Specifies the width in pixels of the tab border. Applies only if ShowTabBorder is .T.
TabFontBold L Specifies whether tab captions are to be displayed in bold font. Default is .F.
TabFontItalic L Specifies whether tab captions are to be displayed in italic font. Default is .F.
TabFontName C Specifies the font to be used to display tab caption text. Default is Verdana.
TabFontSize N Specifies the size of the text used to display tab captions. Default is 8 points.
TabForeColor N Specifies the text color to be applied to an unselected tab. Default is black.
TabJustify L Specifies whether tabs are to fill the entire length of the bar by adjusting their Width property. Default is .F.

Property Type Description
TabMouseEffects N Specifies the special effect to be displayed when the mouse moves over a tab: 0 - None, 1 - Underline caption, 2 - Expand tab height (default), 3 - Both effects.

TabOrientation N Specifies the orientation of tabs on the bar. 0 = Up, 2 = Down. Default is 0.

TabOverlap N Specifies the number of pixels by which tabs overlap each other. Default is 0 (no overlap). Set a higher number to increase overlap. Set a negative number to decrease overlap.

TabWidth N Specifies the default width in pixels of a tab. Default is 100. Applies only if AutoTabSize is false.
ToolTipList N A comma delimited list of tool tip text to be applied to each tab in the CaptionList.

CaptionList and SelectedTab

The two most important tab bar properties are CaptionList and SelectedTab.

Caption list contains a comma-delimited list of captions to be displayed on tabs. For example: "Army,Navy,Air Force,Marines"

The tab bar determines the number of tabs to display by counting the number of individual captions included in the comma-delimited CaptionList. The caption list shown above tells the tab bar to display four tabs and set the TabCount to 4. If you do not enter a CaptionList, a warning message will be displayed by the tab bar and it will not instantiate. The tab bar must contain at least one caption.

Captions may be entered as blanks and filled in later in code. To Display four tabs with blank captions, merely enter: ",,," in the caption list. This will result in a tab caption of "*" on each of the four tabs.

If you set a large number of captions, or your captions are long, you may run out of room in the property sheet. The proprty sheet allows no more than 254 characters for the CaptionList property. In this case the CaptionList may be set in code in the Init() of your derived tab class -- but it must be set before DODEFAULT(), for example,

*   MyTabBar.Init()
*
This.CaptionList = "Army,Navy,Air Force,Marines"

RETURN DODEFAULT()

Fortunately, there is virtually no limit to the length of text when a property is set in code rather than on the property sheet, so very long captions are possible. However, there is the practical matter of having them all fit on your form.

The same technique may be used to set long text in the ToolTipList property. This property specifies the tool tip message to be displayed for each tab in the CaptionList and is itself a comma-delimited list. The message for a specific tab may be omitted. For example,

*   MyTabBar.Init()
*
This.ToolTipList = "U.S. Army,U.S. Navy,,U.S. Marines"

RETURN DODEFAULT()

Army, Navy and Marines would display a message the mouse moves over those tabs, but Air Force would not. Presumably everyone know what the Air Force is.

The SelectedTab property is the other important setting if you want a tab to be initially selected when the tab bar is instantiated. As we discussed above, the SelectedTab identifies the tab on the tab bar that is currently selected. When the tab bar is first instantiated, however, it has the additional function of identifying the initial tab to be selected.

If a value between 1 and TabCount is entered, the tab corresponding to the index will be displayed as selected. Tabs are indexed from left to right, so an initial SelectedTab of 3 means that the third tab will be selected when the tab bar first appears.

If you don't want any tab initially selected, leave SelectedTab at its default zero setting.

TabClass and TabLibrary

The tab class used to instantiate tabs on the tab bar is specified in the TabClass property. The default tab class is "Tab". The TabLibrary specifies the class library in which TabClass is located. Its default is "TabBar".

You can design your own tab class and place the class in any library in your file path. Merely change the defaults in TabClass and TabLibray to point to the new tab class and new library.

Gimme Some Action: Using The Tab Bar Class in Your Application

Now we have a splendid abstract tab bar that doesn't actually do anything. Drop it on a form and give it some captions in its CaptionList property and it will happily instantiate. Move the mouse over a tab and it displays interesting visual effects, click on a tab and it changes its appearance to look selected. But otherwise nothing happens.

When a tab is selected all that occurs in the abstract class is the selected tab calls the m_Tab_Action() method of the tab bar class and passes its TabIndex.

If you look at the code in m_Tab_Action() you will see that there is no default code. It is an empty method. Like the Click() event of a command button, the method is waiting for you to do something with it, but it has no default response of any kind. This is the "hook" method. You can put any code in the m_Tab_Action() method of your derived class to implement the behavior you want to occur when a tab is selected.

Dig around in the source code and open the top tab bar on the TabBarDemo form. Edit the m_Tab_Action() method. This is the code that makes the this particular instance of the tab bar do work.

Very simply, the code displays one of six containers on a form. When tab is selected, the code

  • Sets all existing containers invisible by parsing a container collection and setting the Visible property of each object reference to .F.,
  • Adds the container associated with the selected tab (if it does not yet exist),
  • Dimensions and positions the selected container on the form, and finally,
  • Makes the selected container visible.

The form uses a collection object to hold references to the containers on the form. The collection object is added to the form in the Load() event with the following statement:

this.NewObject( "CntCollection", "Collection", "TabBar" )
As each container is added to the form by selecting successive tabs, code in the m_Tab_Action() event adds the container to the container collection using this expression:
   .CntCollection.AddItem( loCnt )

The AddItem() method of the collection class adds the object reference passed in parameters to the collection.

This is certainly not the only solution to addressing object on the form. We could, for example, use the form's Controls collection. This requires a lot more work, however, because some of the objects in the Controls collection are not the containers we want to address. So we would have to write additional code to identify the right containers, then set properties only on those containers.

Using the custom container collection is a much simpler and more elegant solution. Since the only objects in the collection are the container objects we want to address, we do not have to be concerned with determining the provenance of the objects before we alter their property settings.

*   TopTabBar.m_Tab_Action()
*
LPARAMETER tnSelectedTab

LOCAL loCnt, lcCntName, lnI, lcImgName

WITH thisform

   *   Let's keep visual changes from appearing until we are done.
   .LockScreen = .T.

   *   See if there are any containers on the form yet.  The container collection,
   *   CntCollection, added in the Load() of the form, contains an object reference
   *   to each container placed on the form.
   IF .CntCollection.Count > 0

      *   Set all existing containers invisible.  SetAll() is a method of the 
      *   collection class that sets a property of all objects in the collection 
      *   to a specified value.    If you are using  a VFP native collection in 
      *   Version 8.0 or later, that has no SetAll() method, you must either add
      *   the method or change the code here.
      .CntCollection.SetAll( "Visible", .F. )
   ENDIF
   
   *   The BottomTabBar is made visible only for the "Marines" tab.
   .BottomTabBar.Visible = ( tnSelectedTab = 4 )
   
   *   Get the name of the container that is to be displayed when this
   *   tab is clicked.
   lcCntName = "tabcnt" + TRANSFORM( tnSelectedTab )
   
   *   If the container has not already been instantiated, add it to the
   *   form now.
   IF !PEMSTATUS( thisform, lcCntName, 5 ) and .NewObject( lcCntName, "tabcnt", "tabbardemo.vcx" )

      *   Get a reference to the container
      loCnt = EVALUATE( "." + lcCntName )
      
      IF VARTYPE( loCnt ) = "O"
      
         *   Determine the image to be displayed
         lcImgName = "img" + TRANSFORM( tnSelectedTab ) + ".jpg"
         
         *   Assign the correct image to the image control
         *   on the container.  All other properties are already set
         *   in the propertry sheet for the TabCnt class.
         loCnt.Image.Picture = lcImgName
         
         *   Add the new container to the container collection.
         .CntCollection.AddItem( loCnt )
         
      ENDIF
   ENDIF
   
   *   If the container has just been added, we already have a reference to
   *   the container in loCnt, otherwise get the object reference now.
   IF VARTYPE( loCnt ) # "O"
      loCnt = EVALUATE( "." + lcCntName )
   ENDIF
   
   *   Make the container visible -- it is now the only visible container
   *   on the form.
   IF VARTYPE( loCnt ) = "O"
      loCnt.Visible = .T.
   ENDIF

   *   Resize the form to position the container on the form.
   .Resize()
   
   *   Unlock the screen so the changes will be painted by Windows.
   .LockScreen = .F.
   
ENDWITH

RETURN

If you download the source code, and add the TabBar library to your working libraries, you will have an instantly functioning tab bar. Just drop it on a form or other container, set the CaptionList property, add code to m_Tab_Action() to handle the tab selections, and you are in the tab bar business.

To run the demo form (and view the effects of the m_Tab_Action() code shown above), type DO TABBARDEMO at the command line.

Figure 3: Tab Bar Demo

Conclusion

That's just about it. Download the tab bar class and demo. Try it out, use it, modify it. Whatever you want. I only ask that you not sell it and if you include the code in a commercial application, proper attribution be given.

As always I give no warranties, express or implied, of any kind. It may work, it may not. It works for me. I hope it works for you.

Source Code

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, 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....
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 ...