Sliders!
Posted June 7 1999
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 usMsg,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,
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.

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.

Figure 3. The trackbar template Style settings.

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.
Article comments
Search ClarionMag
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.
