Sliders!

By Pierre Tremblay

Posted June 7 1999

Printer-friendly version

A while ago I was reading the newsgroup and I saw a message from a Clarion programmer asking if a slider control was available somewhere. I was wondering why Clarion doesn't support this control natively and I decided that I should see if there was any possibility of adding this to my toolbox. After a quick look in my MSDN library I discovered that the control is only 32bit. This wasn't a big deal, so I added it to my toolbox.

A control is simply a child window which belongs to a parent window. For the creation of a slider control (which Microsoft calls a trackbar), you need to make some Windows API calls and subclass the parent window procedure in order to track messages that the control sends to the parent window.

A slider can be horizontal or vertical and depending on this it will notify the parent window by sending a WM_HSCROLL or WM_VSCROLL message. An application can also send messages to the control to set the range, the slider position, and many more properties.

The slider control can also be restricted to a selected range of values within the usual range of the control (see Figure 1 below).

I decided the ideal interface to manage the control functionality would be a class, and in this article I will set the foundation of that class. I say ideal because it seems to be very natural to ask the control to do things that the application needs. For example, it looks friendlier in the source code to see:

cTrack1.SetLimit(1, 50)

instead of using the SendMessage API call, where you have to look up the right message to send and find out the way to set the low and high word of the LPARAM parameter. Of course, there is a need to make that call somewhere but once it is inside the class, it doesn't need to be looked at again!

What is needed?

There are three very important elements needed for managing what I will called a "foreign" control on a Clarion window.

The first one is a subclassing procedure which will trap messages sent by the control. Basically this procedure will trap the WM_HSCROLL and WM_VSCROLL received by the window procedure.

Subclassing a window procedure is the process of defining a new procedure within the application which will be called by Windows instead of the Clarion internal message handler. The new procedure should do whatever needs to be done for the WM_HSCROLL and WM_VSCROLL messages and then call the original Clarion internal procedure for messages that it doesn't process.

Another important element is a way to call a class method as if it were a Windows callback procedure. This is not directly possible because a class method has an implicit first parameter of SELF which is a reference to the object itself, and Windows expects the callback procedure to have a specific set of parameters, none of which is a Clarion object. The way the code will address this is by using a queue as a dispatcher. The layout of the queue is shown in Listing 1.

Listing 1. The dispatcher queue.
HandlerQueue       QUEUE
ParentHWND        HWND
ChildHWND         HWND
QControl          &cControl
            END

At the creation of the control, the code stores the parent window handle, the control handle itself and a reference to the class object managing the control. The queue is sorted using the parent and the child handles. Once in the window procedure, the subclassed message handler code will simply make a lookup in the queue and call the cControl.WndProc method of the referenced object. All of this makes it possible to have more than one trackbar on the same window, or different control classes derived from the same base class.

Listing 2. The subclassed message handler.
TBTestWinProc        PROCEDURE  (unsigned MyHWnd, unsigned usMsgcr.gif (850 bytes)
    ,UNSIGNED WParam,LONG lParam) ! Declare Procedure

  CODE
  ! Look in the queue if there is a corresponding 
  ! object to handle this message
  HandlerQueue.qHWnd = MyHwnd
  HandlerQueue.qControlHwnd = lParam
  GET(HandlerQueue, HandlerQueue.qhWnd, HandlerQueue.qControlHwnd)
  IF ~errorcode()
    ! An object is there? Call its WndProc
    RETURN(HandlerQueue.qControl.WndProc(MyHwnd, usMsg, |
    WParam, lParam))
  END
  ! Else return the default window procedure
  RETURN(CallWindowProc(mWndProc, MyHwnd, usMsg, WParam, lParam))

The last element is the class itself. This defines the interface that the programmer will deal with. In this first draft of that class, there is a base class and a derived class. The base class defines different properties and a do-nothing WndProc method. All derived classes will need to define that virtual method.

Subclassing the window procedure

As I said, it is necessary to subclass the Clarion internal window procedure in order to trap the WM_HSCROLL and WM_VSCROLL send by the trackbar to its parent window. Subclassing the window is achieved using the Windows API function call SetWindowLong. This function accepts as parameters the handle of the window to subclass, the index representing the value to be changed and the new value itself.

The handle of the window is available using the {PROP:ClientHandle} property. The others parameters for the call of SetWindowLong will be GWL_WNDPROC and the address of the new window procedure.

SetWindowLong(Window{prop:ClientHandle}, GWL_WNDPROC, cr.gif (850 bytes)
    ADDRESS(NewWndProc))

The call to this function will return the address of the original window procedure. This returned value is extremely important to keep because it will be needed in order to call the original procedure for those messages not processed by the code. In that case, the CallWindowProc API call will be used and the first parameter of that function is the address of the original procedure. The others parameters are the same as what is passed by Window to the callback function. See the generated code in the example application for the details.

I must add that there is another way to set the window procedure if you really want to avoid a Windows API call. The address of the internal window procedure for a given window can be obtained by using the {PROP:WndProc} property. This property is read/write. You will need first to read the address of the actual window procedure and use another line of code to set the new one. The advantage of using the SetWindowLong is everything is done in only one line of code.

When the user interacts with the trackbar a WM_HSCROLL or WM_VSCROLL message is sent to the parent window. At this point the window procedure needs to know exactly what the user did with the control. This is the job of a notification code, which will usually be paired with the WM_HSCROLL/VSCROLL message. The notification code will be in the low word of the Wparam parameter. For the TB_THUMPOSITION and TB_THUMBTRACK, the high-order word of the Wparam specifies the position of the slider. For all other notification codes, the high-order word is zero.

There are different messages that can be sent to the trackbar control. If you want to set the slider position, you will send a TBM_SETPOS message. To retrieve the slider position, you send a TBM_GETPOS message.

The class presented with this article is basically a wrapper around those different messages. For example, to retrieve the position of the slider, you will call the GetPos method.

Var = cTrack1.GetPos()

The class has also a virtual method (cTrack1.TakeNotification) to let you place your own source for a particular notification code. In fact, the template (see below) generates this method if you set the checkbox to limit the slider thumb inside of the selected range. To track a particular notification code, you can use the cTrack1.Notification() method which returns the current notification code under process.

CASE SELF.Notification()
       Of TB_BOTTOM
       Message('User pressed END key')
       END

Figure 1 shows the trackbar in action. Note that in this example the slider thumb has been restricted to the area in the middle of the trackbar shown by the solid bar and the extra tick marks. See Figures 2 and 3 for the template settings.

Figure 1. The trackbar control.

slider_fig1.gif (2411 bytes)

The template

The template supplied with this article (as well as the source code) will take care of almost any aspect of trackbar use. The only errors that the template will check for are that the application must be 32 bits and a variable should be assigned to keep the value of the slider thumb position.

The use of the template is pretty straightforward. You'll need to copy TRACKBAR.TPL to your \TEMPLATE directory and register it. You'll also need to copy the TRACKBAR.INC and TRACKBAR.CLW files to your \LIBSRC directory. You may also need to refresh the ABC class list so that the AppGen can see the trackbar classes. To do this go to any classes tab (such as on global properties or the trackbar control template) and click Refresh Application Builder Class Information.

If your main concern is simply to have a trackbar on the screen and have your application be aware of the position value of the thumb, you don't need any embed code. Just populate the control template on the window and set the Slider Variable property on the template's General tab. The variable will be updated each time the thumb is moved.

Figures 2 and 3 show the template settings used to create the trackbar shown in Figure 1.

Figure 2. The trackbar template General settings.

slider_fig2.gif (6094 bytes)

Figure 3. The trackbar template Style settings.

slider_fig3.gif (5442 bytes)

It is possible to have the control drawn with a border around it. In that case, the tooltip, which shows the current value of the thumb position, will be displayed at the edge of the control rather then moving with the thumb as the user drags it. Setting the checkbox on the style tab in the template extension dialog sets the tooltip. Without a border, the tooltip will follow the slider when the user drags it.

This control template is using a region control to visually place the control on the screen. Once the trackbar is created on the screen, the region control is destroyed.

There are some special cases where you need to use embed points in order to ensure the application behaves properly. One of those cases is when the control is placed on a tab sheet. Fortunately, I was lucky enough to have someone pointing out to me (thanks Robert!) that in this situation, the control will stay displayed no matter which tab is selected.

This ended up with the creation of two new methods called Hide and Unhide. You need to place the code in the EVENT:NewSelection of the sheet control and hide/unhide the trackbar control.

Listing 3. Hiding/unhiding the trackbar control on a tab.
CASE EVENT()
OF EVENT:NewSelection
  CASE choice(?)
  OF 1
    CTrack1.Unhide()
    CTrack2.Hide()
  OF 2
    CTrack1.Hide()
    Ctrack2.Unhide()
  END
END

Hide and Unhide use the Window API function ShowWindow and set the second parameter to true (Unhide) or false (Hide).

Vertical trackbars can also be a problem. It seems that Windows places the smallest value of the range at the top of the trackbar. The simplest, best, and quickest workaround is to set the range using a negative value as the lower limit. So instead of calling SetLimit(1,50), you will call SetLimit(-50, -1) and you will have the trackbar displayed in a more natural way.

Of course, the template is also ABC compliant. Each trackbar object used in the procedure will appear under the Local Objects tree in the embeditor

In conclusion, this trackbar was a very enjoyable small project. I am also happy to donate the source to the Clarion Open Source Project.

Download the source

Pierre Tremblay has worked in the programming and corporate world for the last 16 years, and has been as an independent contractor for TopSpeed Consulting Division since April 1998. He is also a member of Team TopSpeed.

Article comments

Post a comment

You must be logged on to post comments.

Clarion Roadmap

Try the roadmap (beta)

Search ClarionMag

 

Advanced search

From the archives

Feature Interview: SoftVelocity's Bob Zaunere

3/12/2001 3:00:00 AM

Bob Zaunere, SoftVelocity's President and CEO, recently spoke with Dave Harms about the Clarion product line and SoftVelocity's plans for the future.