Using Winforms' TrackBar with real numbers (not just integers)

Published August 13, 2012

Here's the problem: In .NET development it would be nice to use a slider control (TrackBar) with continuous real numbers, not just discrete integer values. e.g. we want one that can go from 0 to 1.0 in 0.1 increments.

The TrackBar control only accepts integers. The arithmetic is easy but it's not that obvious how to link it all together.

1) Assume existence of an object/struct called myData, which has a field called 'Value' (which stores the value). We're going to set up a binding between it and the UI.

// these are constants here but you'd figure them out dynamically from your data
float inteveral = 0.1; // our increment
float min = 0, max = 1.0;

var data = new Dictionary<string, object>();

TrackBar tBar = new TrackBar();
Binding b = new Binding("Value", myData, "Value", 
  true, DataSourceUpdateMode.OnPropertyChanged);
b.Format += new ConvertEventHandler(Trackbar_Bind_Format);
b.Parse += new ConvertEventHandler(Trackbar_Bind_Parse);

double multiplier = 1 / interval;
tBar.Maximum = System.Convert.ToInt32(min * multiplier);
tBar.Minimum = System.Convert.ToInt32(max * multiplier);
data["multiplier"] = multiplier;
tBar.Tag = data;

So what we've done here is we've created our control, and we've set up a data binding. We've also translated the continuous range into a set of integers. This won't yet work (or compile) as-is because our bindings format and parse handlers are undefined. These translate the object's float value into a trackbar value and vice-versa. Also note we've created a dictionary - what's that all about? Basically, we can associate arbitrary data with a control and stick it under the Tag property. As a side-note, it's not a great idea to rely too much on this, it's not type-safe and it's hard to read all the resulting casts. A more robust solution is to subclass/extend the control, but I'm using it here for convenience.

2) Handling the binding. We need to handle ourselves the actions 'Format' and 'Parse'.

Format takes the object's value and formats it for the UI.
Parse takes the UI's value and parses it for the object.

The syntax of these is a little whacky, we actually alter the ConvertEventArgs object instead of returning a mapping. The unboxing is ugly too, and a bit hard to follow, but that's how it is.

Here's how it works:

void Trackbar_Bind_Format(object sender, ConvertEventArgs e)
	// c is our TrackBar
	Control c = ((Binding)sender).Control;
	var data = (Dictionary<string, object>)c.Tag;
	double multiplier = (double)(data["multipler"]);
	// set the integer value for the TrackBar
	e.Value = System.Convert.ToInt32((double)e.Value * multiplier);
void Trackbar_Bind_Parse(object sender, ConvertEventArgs e)
	Control c = ((Binding)sender).Control;
	var data = (Dictionary<string, object>)c.Tag;
	double multiplier = (double)(data["multipler"]);
	double value = (int)e.Value;
	// set the double value for the object
	e.Value = value / multiplier;