Introduction
The sliding scale is a very simple, but fast, performing, and powerful .NET user control, figured as a graphical scale gauge with a linear representation of data. The instrument consists of a static pointer (needle) and a movable linear scale. The static needle refers to the current position of the moving scale, and shows the input values through a graphical position along the scale. The sliding scale is endless, and can represent an unlimited value (type double
) at a certain moment.
Background
On the CodeProject, there are several very useful and good-looking instruments already available. I want to take the trouble to add one more to the chain.
This project is developed in C#, with SharpDevelop and .NET 2.0, using GDI+.
The sliding scale is inherited from System.Windows.Forms.UserControl
, with an overridden OnPaint
method and some new properties:
Value |
The current position of the scale. |
ScaleRange |
The visible range of the scale. |
LargeTicksCount |
The number of large ticks. |
SmallTickCount |
The number of small ticks. |
ShadowEnabled |
Is the shadow enabled? |
ShadowColor |
The shadow color of the component. |
NeedleColor |
The color of the scale needle. |
In the code, the following properties from the parent control are used:
BackColor |
The background color of the component. |
BorderStyle |
Indicates whether the control should have a border. |
ForeColor |
The foreground color used to display the text and ticks. |
The sliding scale is composed of four layers.
- Background (with a background color or transparent) and border.
- Linear scale (with a number of small and / or large ticks with their values).
- Shadow (for 3D-like look that can be disabled).
- Needle.
The complete logic is placed in the OnPaint
method. There is a small invariant part for purists, closed in a signed region, which can be moved outside the OnPaint
. Everything else happens in a few simple steps:
- Calculate the position of the first large tick, that can be showed, and its value.
- Iterate over all other large ticks, and draw them and their value numbers.
- Iterate over all small ticks, if exists, and draw them.
- Draw the shadow, if enabled, with a
LinearGradientBrush
.
- Draw the needle over all others in its defined color.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
#region Can be moved outside OnPaint ...
int W = this.ClientRectangle.Width;
int H = this.ClientRectangle.Height;
int Wm = W / 2;
int Hm = H / 2;
double largeTicksDistance = scaleRange / largeTicksCount;
double smallTicksDistance = largeTicksDistance / (smallTicksCount+1);
float smallTicksPixels = (float)(W/scaleRange*smallTicksDistance);
#endregion Can be moved outside OnPaint
double tickValue = Math.Floor((curValue-scaleRange/2) /
largeTicksDistance) * largeTicksDistance;
float tickPosition = (float)Math.Floor(Wm - W/scaleRange*(curValue-tickValue));
Pen pen = new Pen(ForeColor);
Brush brush = new SolidBrush(ForeColor);
for (int L=0; L<=largeTicksCount; L++)
{
e.Graphics.DrawLine(pen, tickPosition-0,0, tickPosition-0,15);
e.Graphics.DrawLine(pen, tickPosition-1,0, tickPosition-1,15);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
e.Graphics.DrawString(Math.Round(tickValue,2).ToString(),
Font, brush,
new PointF(tickPosition, Hm),sf);
for (int S=1; S<=smallTicksCount; S++)
{
tickValue += smallTicksDistance;
tickPosition += smallTicksPixels;
e.Graphics.DrawLine(pen, tickPosition,0,tickPosition,10);
}
tickValue += smallTicksDistance;
tickPosition += smallTicksPixels;
}
brush.Dispose();
pen.Dispose();
if (ShadowEnabled)
{
LinearGradientBrush LGBrush = null;
LGBrush = new LinearGradientBrush(new Rectangle(0, 0, Wm, H),
Color.FromArgb(255, ShadowColor),
Color.FromArgb(0, BackColor),
000, true);
e.Graphics.FillRectangle(LGBrush, new Rectangle(0,0, Wm, H));
LGBrush = new LinearGradientBrush(new Rectangle(Wm+1, 0, Wm, H),
Color.FromArgb(255, ShadowColor),
Color.FromArgb(0, BackColor),
180, true);
e.Graphics.FillRectangle(LGBrush, new Rectangle(Wm+1, 0, Wm, H));
LGBrush.Dispose();
}
e.Graphics.DrawLine(new Pen(NeedleColor), Wm-0,0, Wm-0,H);
e.Graphics.DrawLine(new Pen(NeedleColor), Wm-1,0, Wm-1,H);
}
Using the code
This control has a good set of default property values so that, once included in your own solution, it can be simply selected from the tools palette, dropped to your form, and used immediately. Otherwise, there are only a few other listed properties to be configured. You just need one line of code to get it working:
slidingScale1.Value = your value here!
The included archive files contain the complete source code and the compiled binaries of the component, and a simple test application.
Possible enhancements
There are many possibilities to improve this control. Here are some ideas, if someone wants to do it:
Owing to the LinearGradientBrush
shading, there is a three dimensional feel, the sliding scale looks already like a real instrument. I personally miss some flexibility in order to draw the numbers on the face of the scale and to customize the lengths of the ticks.
In order to set up the instrument so that it behaves as a real instrument, we must look at two important areas:
- The scale should be limited and/or round.
- A real scale is not an endless ribbon, but it has a real size.
- Limiting the scale is not a hard problem. We can do that by adding two new properties,
MinValue
and MaxValue
, and by controlling the changes of the existing Value
property. Only values from a defined range should be accepted.
- A round scale is not that simple to realize. For this feature, we need several new properties and a good dose of calculations.
- The scale shouldn't react promptly to value changing.
- A real scale has a self moment of inertia, and cannot jump in a short time from one end to the other. Value changing must be realized so that the scale moves itself and slides smoothly in small steps from the old position to the new one. It should immediately begin sliding/rotating from the current position towards and arrive at the end position after a certain time.
- This behavior can be realized so that we first set a new value as the desired value when the current value should be changed, and enable a timer which increments (decrements) the current value in small steps until it will arrive at the desired one.
- Simpler said, we should make an animated sliding scale.
These are some other desired features:
- Vertical scale representation.
- Scale with marked tolerance limits (OTL, UTL, ...).
- Scale with different colored regions.
The sliding scale shown here, in terms of measurement technology, is an “indicator”. We can make it a “control”. Therefore, we should override the OnMouseDown
and OnMouseMove
methods to process mouse events, and implement a ChangeValue
method to announce value changing.
Conclusion
This is the preliminary version of the control, and many features have to be implemented yet. I will attempt to cover this as soon as possible.
Any suggestions/comments/feedback is welcome and highly appreciated.
If you like it, please vote, and if you use it commercially, describe your success story in the discussion board below.
History
- 08.10.2008 - Version 1.0 released.
- 16.10.2008 - Version 2.0 released. Added new properties and vertical orientation.