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

A Visual FoxPro Vertical Scroll Bar Class
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 ...
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 means of bringing the hidden parts into sight was required. Hence the scroll bars. Clever folks, these VFP designers.

What VFP has not provided, however, is a general scroll bar class that we can use to scroll controls that have not been blessed with their own internal scroll bars. Containers, images and shapes immediately come to mind. All of these may, in suitable situations, be dimensioned so that they are larger than the parent container object on which they reside. The fact that VFP does not provide a means to bring the hidden parts of the control into view greatly lessens the usefulness of these classes. We as FoxPro programmers are so used to not having scrolling containers that we simply never think of them as an option to solve application issues. To keep large objects (such as a container full of controls) in view, we use what are in effect "workarounds", including setting the minimum size of forms so the controls are always visible.

I came across the problem a few years ago when I had to put a lot of instructions and data fields on a tax form that the client wanted to look just like the IRS paper version of the tax form. No amount of pleading, weeping or begging on bended knee would dissuade my client from this silliness, so I had to come up with a way of scrolling the objects on the form. Problem was, in those days VFP forms did not have a scroll bar (and even if it had, it would not have helped me with this problem).

First I took a look at the various ActiveX scroll bars. My-oh-my! The fellow that suggested these were compatible with FoxPro must be a great optimist and own a lot of stock in Rolaids®. I know my consumption went up quite a bit, as did my blood pressure, before I figured these things were a lost cause. They actually can be made to work, but only after an awful lot of tweaking. They are really never very happy in a FoxPro OLE environment and express their displeasure by doing unexpected things at random moments -- like suddenly becoming invisible. They were designed for Visual Basic, and it shows.

Anyway, after mending fences with friends, family and household pets, I decided to look into creating a scroll bar class made out of purely VFP components. Surely with all the many kinds of base classes available, such a thing should be possible.

This article looks at the issues involved in designing and implementing a vertical scroll bar control using purely FoxPro classes. Why a vertical scroll bar? Two good reasons: First, there seems to be much more use for a vertical scroll bar than a horizontal scroll bar. Second, I have never had a use for a horizontal scroll bar, and have, therefore never written one -- which pretty much eliminates a horizontal scroll bar class from consideration. But, if you need a horizontal scroll bar, the concepts contained in this article and the code in the accompanying source code apply equally and could easily be adapted.

Figure 1: Elements of a Vertical ScrollBar

Defining a Scroll Bar

A scroll bar is a control that "moves one object on the screen in relation to a second, fixed object on the screen in a specified direction in a range of motion determined by the object being scrolled".

Stated in one lump like that, it sounds fairly complicated. But after breaking a scroll bar down into its various component parts and discrete functions, the control is reasonably simple to create.

The fixed object is almost always container of some kind: container, pageframe, or form. But it does not have to be. It can be any control that can be dimensioned -- and, in fact, does not have to be an object at all. It could be, theoretically at least, merely a set of dimensions. We will refer to it as the base object as opposed to the object being scrolled, which we will call the scroll object.

The scroll object can be any control, although some kinds of controls such as containers, images and shapes, are more likely to be useful as srcoll objects than, say, textboxes. For scrolling to occur, the scroll object must larger than the base object in the scrolling dimension so that only a portion of the scroll object can be viewed at any one time. It is characteristic of Microsoft scroll bars that if the scroll object ever becomes the same size or smaller than the base object, then the scroll bar becomes either disabled of invisible. In VFP it is disabled rather than invisible. This is an intuitive behavior since in such situation the scroll bar has nothing more to do.

Figure 2: Relationship between Scroll Object and Slide Control (size)

The scroll bar determines the direction of scroll. Vertical scroll bars scroll up and down; horizontal scroll bars left and right. The scrolling dimension is the height of the scroll object for vertical scroll bars and its width for horizontal scroll bars.

Figure 3: Range of motion

Range of motion is determined by relative sizes of the object being scrolled and the base object. For a vertical scroll bar it is the result of subtracting the height of the base object from the height of the scroll object. This has an effect not only on the function of the scroll bar, but also on its appearance. The center slide control is always sized to reflect this relationship.

As the two objects become closer in size, the slide control becomes taller. As the scroll object grows larger in relation to the base object, the slide control shrinks (but not below a specified minimum height). The effect of this process is to grow and shrink the scroll bar field so that the range of movement of the slide accurately reflects at all times the range of movement of the scroll object. The field is that portion of the scroll bar between the end buttons that contains the slide (see the illustration above). If the two objects are the same size, the slide control occupies the entire field so there is no range of motion available (at which point the scroll bar becomes disabled).

There is also a relationship between the position of the scroll object in relation to the base object and the position of the scroll bar slide. In a vertical scroll process, if we move the slide to the top of the field, we want the scroll object at its top. If we move the slide to the bottom of the field, we want the scroll object to be at its bottom.


Figure 4: Relationship between Scroll Object ans Slide Control (Position)

In the most common situation, the base object is the container parent of the scroll object. A container (scroll object) on a form (base object) is a typical example. But there is no requirement that the scroll object bear any particular relationship to the base object in the containership hierarchy. Indeed, they do not even have to be on the same form. They only need be somewhere in the same VFP application environment at the same time. Neither the scroll object nor the base object are aware of the other's existence and never communicate directly. Any relationship between the two objects is fully mediated through the scroll bar.

The scroll bar needs to know the dimensions of the base object and both the position and dimensions of the scroll object. If these properties change, the scroll object needs to be informed of the change. This is all the information the scroll bar needs to perform all of its functions. How it gets this information we will develop further below.

Designing a Vertical Scroll Bar

Components

The scroll bar itself is built out of native FoxPro components: containers, a timer, lines and a label. It has five principal components: a tall, skinny container that becomes the bar itself, a timer, and three buttons. There is an end button at the top, one at the bottom, and a "button" in the middle that acts as a slide control. These are all tiny containers. The top and bottom buttons display up and down arrows respectively. For this I use a label. The borders of the buttons are lines that are colored to make the button appear either flat or 3-dimensional. All the buttons can be derived from a single class. For the slide control, just set the label caption to blank and ignore it.

Why not use a command button rather than messing around with containers, lines and a label? If you want to, go ahead. I don't like the fact that the VFP command button does not really have a flat aspect. The standard interface for most of my clients is the modern, web-like, up-to-date, cutting-edge flat look. Others insist on the moldy, obsolete, antediluvian, musty 3-D look. I, myself, have no preference either way, but need the flexibility to have either look, as the client requires.

Features and Behavior

We want a scroll bar class that emulates all of the functionality of a Windows scroll bar.

Features Behavior
Line Scroll Mouse clicking the top or bottom end button scrolls up or down one "line".
Continuous Line Scroll Holding the left mouse button down on the top or bottom end button continues to scroll one line at a time.
Page Scroll Clicking on the bar above or below the slide control scrolls up or down one "page".
Continuous Page Scroll Holding the left mouse button down on the bar continues to scroll one page at a time.
Slide Drag Dragging the slide control up and down scrolls the scroll object up or down. This functionality comes in two flavors: Continuous Scroll, in which the scroll object is scrolled as the slide is moved, and Discontinuous Scroll in which the Scroll Object is not moved until the user releases the mouse button after positioning the slide. This functionality is controlled by a property of the scroll bar, ContinuousScroll that may be set either true or false at design time.
Slide Sizing and Positioning The size and position of the center slide control is related to the relative size of the scroll object compared to the base object.
Automatic Disable If the object being scrolled is sized so it becomes the same size or smaller than the base object, we want the scroll button to disable itself. At that point, since all objects on the scroll object are visible, scrolling is no longer necessary to bring hidden objects into view.

There are also some "features" of the standard Windows scroll bar we absolutely want to avoid. The most annoying of these is the tendency of the Microsoft scroll bar to jitter the scroll object when the center slide is clicked.

This results from the fact that the internal process for aligning the slide control with the scroll object is defective. The slide is not quite aligned after a scroll, so when the slide is clicked again it suddenly wakes up to the fact that the scroll object is not where it thinks it ought to be and moves it to where it believes it should be. This is usually no more than a few pixels of movement, but it creates a jitter-jump effect this can be very distracting.

Such behavior cannot be intentional, so it can be fairly classified as a bug -- a long-standing bug, in fact, that has been around since at least Win95 and is ubiquitous to Microsoft products. Microsoft really should fix this problem, but, of course, it has thousands of other Windows bugs to fix first that are more important than this little annoyance.

Most of the scroll bar's functionality is fairly simple to implement through standard VFP events. The MouseDown() events of the end buttons and bar field are captured to implement line and page scrolling. Making the slide move is an exercise in writing a drag process. The real challenge is making the scroll bar respond to holding the mouse button down. There is no MouseHeldDown() event in VFP, and therefore no single event to which to attach code. When we press the left mouse button over an end button, for example, a single MouseDown() event is raised. When we release the mouse button a single MouseUp() event results. We can instruct our scroll bar to scroll one line in the appropriate direction when a MouseDown() event registers, but how do we tell it to continue to scroll as long as the mouse button is not released?

For this we need to raise multiple scroll events and, therefore, we need a timer control.

The MouseDown() events on each end button and the bar are all linked to a Timer. When a MouseDown() event occurs, the timer is activated to scroll one line or page at a time until the mouse button is released.

This is a little tricky because we don't want the timer to activate and produce multiple scrolls if the user wants to scroll just one time with a single click. For that reason, we need some sort of delay so that the timer does not kick in immediately but waits until it's certain the user is actually holding down the mouse button. This delay is implemented in a custom timer method that we will look at below.

Line and Page

We know we are going to scroll one line when an end button is pressed, and one page when the user clicks on the scroll bar above or below the slide. But what is a "line" and what is a "page"?

Figure 5: Vertical Scroll Line and Page on a VFP Grid

In a native VFP scroll control, these properties are already defined. In a grid, for example, a "line" is one grid row, a "page" is the number of visible rows - 1. The grid's scroll bar does not need to be told what a line or page is, it's already hard-coded.

For our general-purpose vertical scroll bar, line and page need to be defined for each separate use of the bar.

A line is simply the number of pixels scrolled when the user clicks an up or down end button. This setting is contained in the scroll bar's LineIncrement property and set it by default to 10 pixels. In almost all applications this will need to be restated. For example, if you download the scroll bar examples (see the bottom of this article), you will see that the scroll object displays bars 16 pixels high with a 1-pixel overlap. The LineIncrement property is defaulted to 15 pixels so that a scroll always breaks at an exact bar border, preventing the display of partial bars.

The number of pixels in a page is set by reference to the base object. A page is defined as the height of the base object less the height of one line. Why "less the height of one line"? This ensures that one line from the old page is visible each time a page scroll occurs, which helps the user maintain visual orientation if multiple pages are being scrolled rapidly. Scroll one page in a grid and you will see that one row from the old page is always visible when a page scroll occurs. This is standard scroll bar behavior.

If you don't want to scroll the full height of the base container, we will provide a property that permits page scrolls of less than full height of the base object. This is the PageScrollFactor property of the scroll bar.

Its range of settings is 0 to 1, with a default setting of 1.

Zero indicates no page scrolling (this defaults a page scroll to the same increment as a line scroll). A setting of 1 specifies that the "page" will be defined as the full height of the base object (less the one line). Any setting between 0 and 1 specifies the fraction of the height of the base container that will be used as a "page". For instance, a setting of .5 sets the scroll "page" to one-half the height of the base container.

Appearance and Sizing

We want our scroll bar to look as well as act like a Windows scroll bar. Windows paints and sizes its scroll bars from system settings.

A vertical scroll bar is typically the same height as its parent container. If you are using a vertical and horizontal scroll bar on the same container, the height of the vertical scroll bar has to be adjusted for the height of the horizontal scroll bar so they do not overlap. The scroll bar's Resize() event code handles adjusting the height of the scroll bar to the height of the parent container. To make this work, however, the container must call the Resize() method of the scroll bar in its own Resize() code. More about this below.

The width of the bar is a Windows setting. We can capture that setting using SYSMETRIC(5). SYSMETRIC(5) returns the "width in pixels of a button on a vertical scroll bar". The button width is also the scroll bar width. The width of the scroll bar is set in its Resize() event called from Init().

The color of the scroll bar and its buttons are likewise Windows settings and depend on the user's active color scheme. The user can adopt a seemingly unlimited number of color schemes so it is impossible to predict in advance the colors that a scroll bar and its components will be painted by Windows.

However, we can get the colors from an API function, GetSysColor(). This little gem of an API function returns the 32 (more or less) colors Windows is currently using in its system palette. For example, of you want the color of the caption bar background of the active window, this is the function that will give it to you.

I have wrapped the API function call in the SystemColor() method of the vertical scroll bar. All this does is determine whether the API function GetSysColor has been declared, and declares it if it has not. Then it fetches the system color specified by the color index passed to it as a parameter and returns the resulting Windows system color to the calling process.

*   VerticalScrollBar.SystemColor()
*
*   OVERVIEW:  Returns a Windows system color to the calling process by
*              calling the API function GetSysColor() with a Windows® color
*              index to get the color number.
*            
*   PARAMETERS: 
*
*      tnWindowsColorIndex   
*                An integer representing a Windows system color
*                to be returned.
*
*   RETURNS:   A color as an integer.  If GetSysColor() cannot find the color, it
*              returns zero, so the function will also return zero (black).
*
*   CALLS:     GetSysColor() Windows API function.
*   
*   HISTORY:   Created 03/30/99 (JME)
*   
*   System color indices used in VerticalScrollBar are the following:
*    0   -   scroll bar body color
*   15   -   Command button face color.
*   16   -   Command button shadow color.

LPARAMETERS tnWindowsColorIndex

*   VFP 7.0 and later.
LOCAL laDLLs[ 1 ]

*   Create an array containing the names of declared API functions
ADLLS( laDLLs )

*   VFP 7.0 and later.
*   ASCAN() this array (non-case-sensitive) for a declaration of GetSysColor.
*   If the API function does not appear in the array, it has not yet been declared, so
*   declare it now.
IF ASCAN( laDLLS, "GetSysColor", 1, ALEN( laDLLs, 1 ), 1, 11 ) = 0
   DECLARE INTEGER GetSysColor in Win32API INTEGER
ENDIF

RETURN GetSysColor( tnWindowsColorIndex )

The only limit to this approach occurs if the user redefines his windows color settings while the scroll bar is visible. In such case our scroll bar will not be repainted to the new color scheme until the user closes and reopens the form on which the bar is located. Considering the amount of effort that would be required to capture changes in Windows color settings as the user makes them, this is a limitation I am perfectly willing to live with. Moreover, it appears that native VFP controls have much the same limitation.

Scroll-Bar-Awareness

The only way to make the scroll bar a true, self contained, drop-on-the-form-and-forget control is to permanently link it to a parent container. Since this is the way the scroll bar would be used most of the time, such an arrangement would seem reasonable. In fact, in the source code there is a ScrollbarContainer class that implements just this configuration. It contains a scroll bar permanently bonded to its base container. The scrolled object is included as a second container, also on the base container. To use it, drop it a form (or on any other container), select the ScrollObject container and drop any combination of controls on it that you want. Size the class appropriately, keeping in mind that if the ScrollObject is not taller than the ScrollBarContainer, the scroll bar will be disabled (since it has nothing to do). Run the form and you have a working, self-contained, scroll bar -- self-contained because the communication between the two containers and the scroll-bar is already coded. The containers are, in effect, "scroll-bar-aware".

If you use the scroll bar on a form or container that is not already scroll-bar-aware, you will need to build that awareness into the form or container.

Identifying Base and Scroll Objects: Assigning an Object Reference

The first thing the scroll bar needs to know is the identity of its base and scroll objects. Scroll bar properties BaseObjectName and ScrollObjectName keep track of this information. The easiest way for an object to register itself as either the base or scroll object is to pass a reference to itself to BaseObjectName or ScrollObjectname as appropriate. Both of these scroll bar properties are linked to _Assign() methods that extract the information required from the object reference. Here is an example of code in the Init() of a container that registers itself as the base object and its scroll object as the scroll object by passing object references.

*   ScrollBarContainer.Init()
*
WITH this

   *   Tell the scroll bar the name of the base object and...
   .VerticalScrollBar.BaseObjectName = this
   
   *   the name of the scroll object.
   .VerticalScrollBar.ScrollObjectname = this.ScrollObject
   
ENDWITH

RETURN .T.   

As indicated above, when an object reference is passed to either property, its _Assign() method intercepts the reference and extracts from it the information it needs to recreate a reference to the object at any time.

Here is the code that does that for the BaseObjectName. The ScrollObjectName_Assign() code is similar.

*   VerticalScrollBar.BaseObjectName_Assign()
*
*   OVERVIEW:   Assign the BaseObjectname from an object reference or object name.  
*
*   PARAMETERS:
*      tuName   May contain the full hierarchy path name to the object as
*                text or an object reference to be evaluated.
*
LPARAMETERS tuName

WITH this
   
   IF VARTYPE( tuName ) = "O"
      *   An object reference was passed.  Get the full path name from
      *   SYS( 1272) then translate out the form name and replace it with
      *   "thisform" so EVALUATE() will return the object reference when
      *   evaluating the name.
      tuName = STRTRAN( SYS( 1272, tuName ), ;
         LOWER( thisform.Name ) + ".", "thisform." )
   ENDIF
      
   IF VARTYPE( tuName ) = 'C'
      .BaseObjectName = tuName
   ENDIF
   
   *   See if the scroll bar should be enabled.  It is enabled only if
   *   .BaseObjectName and .ScrollObjectName are both not empty,
   *   both evaluate to an valid object reference, and the height of the
   *   scroll object is greater than the height of the base object.  
   *   This is all tested in Enabled_Assign().
   .Enabled = .T.
ENDWITH

RETURN   

The VerticalScrollBar class makes constant use of references to the base and scroll objects, so it would be natural for the class to store these references in properties. There are two properties, in fact, that appear at first glance to contain object references: BaseObject and ScrollObject. But each is actually initialized to .NULL. and that setting never changes. The sole purpose if these "pseudo-properties" is to provide a link to _Access() events.

Each time an attempt is made to read the value of BaseObject or ScrollObject, the _Access() event intercepts the attempt and returns a reference to the object identified by name in BaseObjectName or ScrollObjectName. Here is the _Access() code for BaseObject. The ScrollObject_Access() code is similar.

*   VerticalScrollBar.BaseObject_Access()
*
*   OVERVIEW:   Returns a reference to the base object or .NULL. if
*               it cannot be identified or does not evaluate to an object.
WITH this

   IF !EMPTY( .BaseObjectName ) .AND. ;
      TYPE( .BaseObjectName ) = "O"
      
      RETURN EVALUATE( .BaseObjectName )   
   ENDIF
ENDWITH

RETURN .NULL.

This oblique approach to maintaining object references is made necessary by FoxPro's incredibly inept clean up process when a form is closed.

VFP does not know how to clean up an object reference stored as the property of another object. What happens is that as a form is destroyed, VFP attempts to destroy an object. But if a reference to that object exists as a property of another object, the first object cannot be destroyed. It can only be destroyed when the last reference to itself has been removed.

What the process should do it destroy all of the objects it can, storing information about those it cannot somewhere -- probably in an array. After the first pass, parse the array destroying objects that now can be destroyed because the objects that formerly contained their reference have now been destroyed. Continue processing objects in the array until all have been destroyed or objects are encountered that contain mutual references to each other.

This is the impasse that arises when object A contains a property that references object B that contains a property that references object A. Neither object can be destroyed until the other has been destroyed first (kind of like the old Boston traffic ordinance: "When two motor vehicles approach an unmarked intersection at the same time, neither may proceed until the other has passed"). In this situation, the process requires that the properties of one object be polled until the object reference is uncovered and set to .NULL., then the other object is destroyed, which, finally, frees up the first object to be destroyed.

Would this work in all cases? No. It would not work, for example, if a reference to an object on this form is contained in the property of an object on another form. But it would work a lot better than the exisiting process -- if it can be called a "process".

What FoxPro now does is the worst possible thing. When it encounters an object that cannot be destroyed, FoxPro immediately stops trying to destroy objects on the form, then destroys the form reference, leaving us with an open form that now cannot possibly be released short of closing VFP and restarting.

This must have been one of those designs written the day after a really great office bash.

There are many published work-arounds for this problem. Most simply require the property reference be removed in the Destroy() method of the form before the native processing that destroys the objects on the form. See, for example, Atkins, Kramek and Schummer, MegaFox: 1002 Things You Wanted to Know About Extending Visual FoxPro, p. 533. (By the way, this is a book that every VFP application developer should own and consult frequently.)

My preferred solution is just to never, never, ever assign an object reference to an object property.

The consequence for the scroll bar class is that each time a reference to the base or scroll object is needed, it has to be recreated locally from the information stored in the BaseObjectName and ScrollObjectName properties. This should be slow, but in fact is so incredibly fast that in testing I have been unable to measure any appreciable difference between creating the object on the fly and using an existing stored reference.

Identifying Base and Scroll Objects: Assigning a Reference String

Rather than assigning an object reference to BaseObjectName or ScrollObjectName, you can also directly assign the text string that will evaluate to the object reference -- a reference string. This string has to be a fully qualified hierarchy path name to the objects in a form such that EVALUATE() will return the object reference. If you download the examples, open up the ScrollForm2 class in VerticalScrollBar.vcx and take a look at the assignment in the Init() method.

Here assigning the object reference to BaseObjectName and ScrollObjectName would be ineffective since neither the base object nor the scroll object is on the same form as the scroll bar. Passing the object would result in "thisform" as the base object and "thisform.image" as the scroll object. Neither is correct and would prevent the scroll bar from functioning. (BaseObject_Access() and ScrollObject_Access() would always return .NULL.)

Assigning a reference string to the properties is the only way of making the assignment in this instance.

*   ScrollForm2.Init()
*
LOCAL loForm

loForm = EVALUATE( "_SCREEN.Forms[ 2 ]" )

IF VARTYPE( loForm ) = "O"

   WITH this
   
      WITH .VerticalScrollBar

         .ScrollObjectName = "_SCREEN.Forms[ 2 ].Image"
         .BaseObjectName     = "_SCREEN.Forms[ 2 ]"
         
         *   Resize the scroll bar to position it on the form.
         .Resize()
         
      ENDWITH
   
   ENDWITH
   
ENDIF

RETURN

Notification of Height Changes

The scroll bar also needs to know if the height of the base object, scroll object or its parent container changes.

When scrolling, the scroll bar gets references to both base and scroll objects and extracts the height of each for use in the scrolling functions. But between scrolls, if the height of any of these external objects changes, the scroll bar needs to be notified of the change.

All that is required is that the scroll bar's Resize() code be executed. This code takes care of ferreting out the height of these three objects and makes its own internal adjustments to any height changes it finds. Here is the code in the ScrollBarContainer.Resize() event that calls the Resize() method of the VerticalScrollBar object.

*   ScrollBarContainer.Resize()
*
*   OVERVIEW:   Notify the scroll bar of the new height of this object calling
*            its Resize() method.  This method takes care of all size and appearance
*            adjustments required to respond to a change in height.
*
RETURN this.VerticalScrollBar.Resize()

That's all that is required -- but it is required.

Implementing Continuous Scrolling

All scrolling initiated by mouse clicking on an end button or the scroll bar field is handled by a timer control. (Scrolling by dragging the center slide is handled by a different process contained in the ScrollSlide() method of the VerticalScrollBar class).

The dynamics of the process are actually fairly simple, though it took a while to figure it all out.

If the user presses the left mouse button while over an end button or the bar background and immediately releases the mouse button, we want to scroll one time. If the user holds the mouse button down, we want to continue to scroll until,

  • The user releases the mouse button or,
  • The scroll object reaches the end of its range of movement.

The TimerStart() method of the timer, as its name implies, begins the scroll process. Whether it initiates a line or page scroll depends on the object that it is called from. If it is called from the MouseDown() event of the scroll bar, it scrolls a page (PageScroll = .T.). If from the MouseDown() event of an end button, it scrolls a line (PageScroll = .F.).

After determining the distance and direction to scroll from parameters and setting appropriate timer properties, TimerStart() calls the Timer() event passing a parameter that specifies the number of milliseconds to delay before the next Timer() event fires. During the delay period, the user can release the mouse button, halting all subsequent scrolling, or continue to hold the mouse button down, which will, after the delay period elapses, commence a continuous scroll process until the user releases the mouse button.

*   ScrollTimer.TimerStart()
*
*   OVERVIEW:   Starts timer processing by directly executing the first Timer() event.
*
*   PARAMETERS:
*
*      tlPageScroll   
*            Specifies the whether a page or line scroll isbeing processed.  .T. = page scroll.
*
*      tnScrollDirection   
*            Specifies the scroll direction: 1 = up, 2 = down
*
LPARAMETERS tlPageScroll, tnScrollDirection

WITH this

   *   The TimerDelay specifies in milliseconds the amount of time
   *   between the initial mouse press and the start of continuous
   *   scrolling.
   lnTimerDelay = .parent.TimerDelay
   
   *   Enable the timer so the Timer() event will fire.  If the timer is
   *   disabled, the Timer() event does not fire.
   .Enabled = .T.

   *   Specifies Page or Line scroll:  .T. = page scroll, .F. = line scroll
   .PageScroll = tlPageScroll
   
   *   ScrollDirection: 1 = Up, -1 = Down
   .ScrollDirection = tnScrollDirection
   
   *   Scroll the ScrollObject one time and set the Interval property to the
   *   lnTimerDelay period specified in milliseconds.  
   .Timer( lnTimerDelay )
   
ENDWITH

RETURN

The code in the Timer() event responds to the call from TimerStart() by scrolling one line or page in the specified direction and setting the timer's Interval property to the delay period specified in the TimerDelay property of the scroll bar. TimerDelay specifies the number of milliseconds to wait before initiating a continuous scroll.

After setting the delay, the timer waits for the delay interval to elapse. If, during the delay period, the user releases the mouse button, the appropriate MouseUp() event calls TimerStop() to set the timer enabled property to .F. (which prevents the timer from firing any more Timer() events) and sets the interval property back to zero. The net effect is that the user has scrolled just one line or page.

However, if the delay period expires, the Timer() event fires again. This time it gets the TimerInterval from the VerticalScrollBar and assigns it to Interval. TimerInterval specifies the period in milliseconds between scroll events. So long as the user continues to hold the mouse button down, the Timer() event will continue to scroll each time the timer interval elapses. When the user releases the mouse button, the resulting MouseUp() event triggers TimerStop() to halt the timer so no further scroll events occur. Here is the Timer() code that makes all this happen.

*   ScrollTimer.Timer()
*
LPARAMETERS tnInterval

LOCAL llPageScroll, lnScrollDirection

WITH this

   lnScrollDirection     = .ScrollDirection
   llPageScroll       = .PageScroll
   
   WITH .parent  && VertScrollBar
      
      *   The mouse is still on top of the an end button or on top of the bar.
      *   If it is not, then don't scroll.  The mouse will not normally move off
      *   the object unless some outside operation moves focus somewhere else.  In
      *   such case, we don't want to leave a continuous scroll process running. 
      IF ( llPageScroll           .AND. .MouseOver )            .OR. ;
         ( lnScrollDirection = 1  .AND. .TopArrow.MouseOver )   .OR. ;
         ( lnScrollDirection = -1 .AND. .BottomArrow.MouseOver )
      
         *   If the timer Interval property is zero, this means that the initial scroll is complete.  
         * Now set the timer Interval for continuous scrolling.
         IF EMPTY( tnInterval )
            tnInterval = .TimerInterval
         ENDIF
      
         *   Scroll the Scroll Object.
         .Scroll( ;
            lnScrollDirection * ;
            IIF( llPageScroll, .PageIncrement, .LineIncrement ) )

      ELSE
         *   If the mouse has moved off the button, turn
         *   the timer off by setting its interval to zero.
         *   This rarely occurs, but when it does, it causes
         *   a run-away scroll.
         tnInterval = 0   
      ENDIF

   ENDWITH
   
   *   Set the timer interval.  Zero turns the timer off.  Any
   *   value > 0, triggers this event at each interval.  If tnInteral was
   *   passed as a value > 0, tnInterval is the timer delay interval.  If
   *   tnInterval is zero, then the Interval is set to the scroll bar TimerInverval 
   *   property.
   .Interval = tnInterval
   
ENDWITH

RETURN   

A MouseUp() of the VerticalScrollBar or either end button triggers TimerStop(). TimerStop() stops the timer by setting its enabled property false and its interval property back to zero. This has the effect of either preventing a continuous scroll from initiating or stopping a continuous scroll that has begun -- depending on where in the process the mouse button was released.

*   ScrollTimer.TimerStop
*
*   OVERVIEW:   Stops timer processing.

WITH this

   *   Disabling the timer causes it to pause execution...
   .Enabled = .F.
   
   *   giving the Interval setting or zero time to take effect.
   .Interval = 0

ENDWITH

RETURN

Using The VerticalScrollBar Class in Your Application

To see how the ScrollBarContainer functions, execute

DO SCROLL
from the command window. This will display a form containing a scroll object (container) on the left filled with colored bars. The scroll bar on the right is glued to the right edge of the form and is always as tall as the form is high.

Figure 6: The example form

The controls in the middle allow you to test VerticalScrollBar features. Here are the properties of the vertical scroll bar that can be set in the property sheet.

Control In the Example Property Description Settings
Scroll Bar Appearance ButtonSpecialEffect Sets the scroll bar's appearance, flat or 3D. 0 = 3D, 2 = Flat
Scroll Bar Enabled Enabled Enables/Disables the scroll bar. 
Scroll Increment LineIncrement Sets the number of pixels scrolled when a line scroll is executed. Default is 10 pixels.

Control In the Example Property Description Settings
Continuous Scroll ContinuousScroll Determines whether scrolling is continuous when the center slide is moved, or whether no scroll occurs until the mouse button is released. .T. = Continuous Scroll
.F. = Discontinuous Scroll
Page Scroll Factor PageScrollFactor Determines how much of a page is scroll when a page scroll is executed. The range is 0 to 1 where 1 scrolls the entire page and 0 sets a page scroll equal to a line scroll -- effectively eliminating the page scroll option.
[None] TimerDelay Specifies the number of milliseconds to delay before initiating a continuous scroll Default is 400 ms.
This works for even the slowest computer in our inventory. It can probably be lowered to 200 ms for most computers.
[None] TimerInterval Specifies the number of milliseconds between each scroll event of a continuous scroll Default is 100 ms.
Setting this below 50 ms may result in run-on scrolls, so be careful.

Change these settings and try various scrolling combinations to see how the scroll bar works.

The scroll bar can also be used on any type of container class: container, form, or page. And it may be used to scroll any object, even an object on another form, provided the object to be scrolled is in the VFP environment.

All the VerticalScrollBar needs to know is the name of the BaseObject and the name of the ScrollObject. With this information, it can scroll anything in the current VFP environment.

This is illustrated by Example 2, displayed by executing

DO SCROLL2
at the command window.

Figure 7: The forms of the second example

There are two forms involved in this example. The form on the right contains the scroll bar. The form on the left contains an image that is the object to be scrolled. The BaseObject is the left form itself. The ScrollObject is the image control on the left form.

Even though the scroll bar is on a completely different form from the object being scrolled, the scrolling process still works smoothly.

Conclusion

That's just about it. The source code contains the class library, SCROLLBAR.VCX, that has all of the components of a working vertical scroll bar including all code, and the examples described above. 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, 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, 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 ...