![]() |
|
Published 1997-07-01 Printer-friendly version
Clarion for Windows (CW) is a fantastic programming language. It, along with the multitude of things possible in the Windows API and numerous add-on tools available, gives a programmer more power than I had ever dreamed possible back in my DOS days (which wasnt too long ago). There are so many things that can be done with CW, that if I find that I need a feature not inherently built-in to it, a solution is more than likely within my grasp. Sometimes the solution is available as a third party add-on, and sometimes it needs to be written from scratch, but there is almost always a viable solution.
Often, some creative thinking and resourcefulness is what is required to get the job done. The Wright Brothers used a tomato can to fashion a carburetor for their airplane prototype. After some trial and error and perseverance, they had an airplane engine capable of powering the first flight. After they removed the tomatoes from the can, there was a marked improvement in the performance.
This same resourcefulness applies to programming. You take a look at the desired result, take a look at your chest of tools, and depending on the estimated amount of time (and patience!) it will take, roll up your sleeves and get to work on that (figurative) tomato can.
I had heard of some CW programmers looking for a slider control for their programs. In the past, there had been some indicator that Topspeed was going to put a slider control into CW, but they decided to hold off on it for now. Without the "native" CW slider control, the only other options were some 3rd party VBXs and OCXs. Personally, an OCX or VBX needs to provide a significant benefit before I will consider using it. The added hassle of having to register the OCX and the extra overhead in time and resources can be quite significant. This looks like a job for our pal, the tomato can.
For those that dont know what a slider control is, it is a horizontal or vertical "track" along which a "knob" slides. One end of the track represents the lowest possible value of a variable, and the other side represents the highest possible value. In this article, we are going to design a simple slider control using only the tools available from within CW. We will use the Clarion Template Language, some OOP, and some existing controls available from with CW to create our own "slider control".
Please keep in mind that this is just one programmers idea of how a slider could be built. There are many different approaches that could be made. Some could be better, some could be not as good. Most likely, this article will spark ideas from others who will carry the slider torch to new heights, and etch their names into CW history, enjoying the new taste of newly found charisma Well, at least they might have fun writing it.
Also, although I am making an effort to improve my OO (object oriented) programming, there is stuff in my OOP that will be considered "unconventional". In other words, dont use this code as a tool from which to learn OOP.
These are a few things that I had noticed when using other slider controls in various programs. There are probably more, but these are the ones that I thought were important:
Hmm This is the hard part. There are so many different possible approaches here. Lets see what we need, and if there is anything that we already have in CW that would serve the purpose:
For the sake of semi-brevity, I will be generalizing quite a bit here.
Each slider and its related controls are managed by the members (data variables) and methods (procedures and functions) in the slider class. Depending on whether the slider is configured for HORIZONTAL or VERTICAL use, different classes are used. Since a great deal of the necessary code is identical for horizontal as it is for vertical, they both inherit this identical code from the SliderClass class. Where the code needs to be different for horizontal than it does for vertical, the horizontal slider uses the HSliderClass class, and the vertical slider uses the VSliderClass class. This sounds a lot more complicated than it really is.
In the file "SLIDER.INC", the declarations for the classes are laid out. The actual code resides in "SLIDER.CLW". The SliderClass class contains all of the data members and methods that are used. Below the SLIDER class, there are the HSLIDER and VSliderClass classes. Notice in HSliderClass and VSliderClass, that they are using the SliderClass class in their declarations.
HSliderClass CLASS( SliderClass ), TYPE, MODULE('slider.clw')
PreInit PROCEDURE, VIRTUAL
Sync PROCEDURE( ? InitValue ), VIRTUAL
MousePos FUNCTION(), LONG, VIRTUAL
SetToPosition PROCEDURE( LONG NewPos ), VIRTUAL
SliderVal FUNCTION(), REAL, VIRTUAL
CreateCTLS PROCEDURE(*JSSIG IG), VIRTUAL
Graduate PROCEDURE, VIRTUAL
END
The HsliderClass and VsliderClass both have identical method names, which are also the same as the methods declared in SliderClass. The reason that the methods are declared again, is so that they can OVERRIDE the current methods that exist in the SliderClass. By overriding the generic methods in the SliderClass, the Horizontal and Vertical can use custom tailored code that can be called by the existing non-overridden members of the SliderClass.. The VIRTUAL attribute allows the methods of the SliderClass to call the overriding methods of the vertical and horizontal classes, as if those methods had belonged to it in the first place.
The data members are prefixed with an "m_". This is a tip that Ross Santos gave, which helps you to easily differentiate between data members and methods when reading the code. The members are the variables that store the data for each class. They can sort of be considered "Module Data" for each class. This allows each slider control to have its own personal set of variables to store information in.
m_SliderMax REAL !Upper limit of Slider value m_SliderMin REAL !Lower Limit of slider value m_SliderMinPos SHORT !X or Y Position m_SliderMaxPos SHORT !X or Y Position m_SliderCtl SHORT !Field Number of the track m_KnobCtl SHORT !field number of the knob m_KnobColor LONG !Color of the knob m_KnobCenter SHORT !Offset for the center of the knob m_IsSliding BYTE !Currently sliding m_MouseOffset SHORT !X or Y Position m_SizeProp SHORT !Height or Width m_PosProp SHORT !X or Y Position m_SelectCTL SHORT !Entry control to Select() m_ClickCTL SHORT !Handles mouse clicks m_SafeCreate SHORT !Safe number to create new controls m_Graduations BYTE !Number of graduations (tick marks) m_Pixels BYTE !saves the status of PROP:Pixels m_LargeUnits LONG !Large Scroll m_SmallUnits LONG !Small Scroll
The methods are the procedures and functions of the classes. They are all in the "SLIDER.CLW" file. I think that it would be too drawn out to give a "blow by blow" explanation for every line of code. Instead, here is a basic rundown of what they do:
Init FUNCTION( ? InitValue, ? SliderMax ,? SliderMin , *JSSIG IG ),SHORT
The INIT() method is the first function that is called. It allows you (the programmer) to tell the class what window controls are being used, and specifics about the behavior and appearance of the slider.
EventHandler FUNCTION( *? SliderUse ), BYTE
EventHandler() directs traffic as events are fired, and takes appropriate action. If there was a mouse click in the body of the slider, the ClickEvent() method is called. If the user is using the arrow keys to actuate the slider, then the KeyEvent() method is called. Any events pertinent to the slider performance are managed by EventHandler()
Sync PROCEDURE( ? InitValue ), VIRTUAL
The Sync() Method is called when the slider needs to be set to a specific value. It positions the knob to where the USE variables value dictates.
MousePos FUNCTION(), LONG, VIRTUAL
Depending on whether the Horizontal or Vertical class is being used, This function returns MouseX() or MouseY().
SliderVal FUNCTION(), REAL, VIRTUAL
SliderVal() returns the calculated value based on the position of the slider.
SetToPosition PROCEDURE( LONG NewPos ), VIRTUAL
Re-positions the slider knob to its new position, depending on Horizontal or Vertical.
CreateCTLS PROCEDURE(*JSSIG IG), VIRTUAL
This procedure creates the transparent REGION and ENTRY controls used by the slider, and sets their properties.
Select PROCEDURE
Selects the "selectable" control of the slider
Field FUNCTION(), LONG
Used in the accept loop to determine if the current event() was generated by one of the controls within the class.
KeyEvent PROCEDURE
Called by the EventHandler() function. If the event is generated by one of the alerted keys, then this procedure is called.
ClickEvent PROCEDURE
Called by the EventHandler() function. If the event is generated by a mouse click to the center area of the slider, this procedure is called
Graduate PROCEDURE, VIRTUAL
Draws the graduation lines for the slider
SelectBox PROCEDURE
Uses API calls to draw a "Select" box around the slider.
After kicking myself, I always wonder why something that is supposed to be so simple often ends up so complicated. Sometimes complication means that you are using the wrong approach. Sometimes simple things really arent as simple as they seemed 20 hours ago. There are cycles of great new ideas, elation, implementation, frustration, disappointment, and placing of blame on the innocent. However you get there, there is an end result good or bad. In the following questions and answers, I will explain the logic (or lack thereof) behind some of the things in the code that look "unconventional".
A: You had to ask that, did you? In CW by default, a WINDOW uses DLUs (Dialog Units) as the unit of measure for setting and getting the size/position of controls. Depending on the window font, these DLUs can be different sizes. When drawing the graduation lines, I found it to be quite a chore to evenly spread the desired number of lines from one end of the slider to the other. By temporarily setting the WINDOW to use PIXELS as the unit of measure, the lines easily spread out because I could more accurately place the lines where they should go.
A: I didnt want to give any programmers an unpleasant surprise of finding out that any run-time measurements would be rendered incorrect by someone like me taking liberties with their setup. If they were setting the PROP:Width of a button to be 30 dlus, it would, instead be set to 30 PIXELS wide.
A: Cant stop with the pixel questions huh? Actually, I did try to implement what you had said, but it required way too much toggling of the PROP:Pixels mode in rapid succession. The end result was not pretty. The programmer, on the other hand does have the option of setting PROP:Pixels on the window of the procedure. That way, the slider class doesnt have to worry about restoring the property, and the sliders will get the benefit of smaller increments.
A: Very good! That is what it is, alright. When the programmer sets this variable to TRUE, then the INIT() method automatically adjusts the size of the slider track so that it can be EVENLY divided by the number of "tick marks". If it cannot be evenly divided, then the graduation marks cannot possibly be evenly distributed along the length of the slider track. This makes it look quite unattractive. Allowing the INIT() method to do this for you, saves the time of re-sizing the track at design-time.
A: This is to simplify the code. By making it an even number, the slider can be "sliced" into two equal halves. This makes it a lot easier to position the slider equally on the beginning and end of the track.
A: Depending on whether you are in a HORIZONTAL or VERTICAL slider, the Self.m_PosProp represents PROP:Xpos or PROP:Ypos. The Self.m_SizeProp represents either PROP:Width or PROP:Height. By using the Self.m_PosProp instead of PROP:Xpos, the very same code can be used for either a horizontal or vertical slider. Otherwise, the code would have to be duplicated for each slider, and I might as well have written two entirely different classes for each.
A: Unfortunately that was not possible. There are some very different things done with the calculations of the horizontal and vertical sliders. For example, on a horizontal slider, the lowest value is typically when the slider knob is at the LEFT of the track, and the highest value is when the knob is at the RIGHT of the track. No problemo. That would mean that the smallest X-position would coincide with the smallest value and visa versa. BUT on a vertical slider, the highest value coincides with the LOWEST Y-Position and visa versa. This means that the necessary calculations are completely different, and each type of slider needs its own set of methods to handle them.
A: That is there to confuse you and throw you off track. Actually, it is there to save the difference between the mouse position and the knob position when the mouse clicks on the knob. This way, when the mouse is moved while in "Sliding Mode", the same relative position between the knob and the mouse can be maintained throughout the movement of the mouse.
A: Although I have been told that it is not a desirable form to use with OOP, I thought it was the best way to go in this case. By setting the m_PosProp and m_SizeProp members in the PreInit() method, there was much less code written for the Init() method than there would have been if I would have had to write it explicitly. Maybe in the future, when my capacity to grasp all of these design issues increases, it will make more sense to do it a different way.
The slider template is relatively straightforward. There is a GLOBAL extension template that must be populated.
For each procedure that will have a slider, the PROCEDURE extension template must be added.

The first prompt, "Highest Creatable Control", is a number that will be used to start creating extra window controls with. Make sure that this number is high enough to avoid conflict with any other window controls that you may have. Leaving it at the default setting is usually sufficient.
The second prompt, "Set Window to use Pixels" will cause your window to always use PIXELS as the unit of measure. If you set or get control sizes and positions at run-time, remember to keep this in mind if you keep this box checked.
There are 2 control templates. One for Horizontal sliders, and one for Vertical. They are essentially the same, except they populate different configurations of window controls, and, of course, declare instances of different classes.

The prompts are relatively straightforward.
"Field to Use" is the variable that will be updated by the slider.
"Max Range" is the value of the slider when the knob is at the maximum spot.
"Min Range" is the value of the slider when the knob is at the minimum spot.
"Graduations" is the number of "tick marks" that you want.
"AutoAdjust Size" specifies whether you want to allow the class to re-size your slider track to enable even spacing of the tick marks.
"Large Scroll" is the approximate value that you want the slider to change when you press the pageup, pagedown, home, and end keys. If left blank, then the size of the knob is used.
"Small Scroll" is the value that you want the slider to change when the arrow keys are used.
"MSG" is the MSG attribute. The text will appear in the window status bar when the slider is selected.
"ToolTip" is the tooltip.
NOTE: The tooltip is NOT RECOMMENDED when in 16-bit mode. The "Selected" box gets quite corrupted when the tooltip displays. In 32-bit mode, it works fine. I think that this is because of the native windows tooltips being used in 32-bit.
Yes, as a matter of fact, I am (almost). This slider code was primarily written as a tool to give me ideas for another project that I am working on. It was originally a very small, simple slider with limited functionality. It didnt have the graduation marks, couldnt be "selected", and the keyboard couldnt be used, but it was a start. It is definitely not perfect now, but it is quite usable. Perhaps others will have some ideas to improve the functionality and will write some addendums to this offering. It was a fun exercise, and I hope that you enjoy it.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $184
(includes all back issues since '99)
Renewals from $134
Two years: $274
Renewals from $224