Timo Korinth

2 Meter, 2 Mark

Creating a range slider in WPF and Silverlight

| 2 Comments

Final result

RangeSliderWPFSL

Features

The goal of this article is to create a range slider (see the example in the screenshot above) as simple as possible, that is working in Silverlight and WPF.

  • Select a range within a range
  • Switch between normal and range selection
  • Two thumbs to select range
  • Logic to ensure that lower value <= upper value
  • Properties to set minimum, maximum, lower value, upper value and normal mode
  • Databinding for all properties

Creating a user control

This tutorial is based on a great post by Josh Twist (Creating a Range Slider in Silverlight).
Instead of creating a custom control with it’s own derived class and generic.xaml, we are just creating a new user control with a little bit of code behind.

XAML

First we will create our range slider user control with the following content:

<Grid x:Name="LayoutRoot" Background="#FF878889">
	<Border x:Name="progressBorder" Background="#FFC8001E" BorderThickness="1,5" />
	<Slider x:Name="LowerSlider"
		Minimum="{Binding Minimum, ElementName=root, Mode=TwoWay}"
		Maximum="{Binding Maximum, ElementName=root, Mode=TwoWay}"
		Value="{Binding LowerValue, ElementName=root, Mode=TwoWay}" Style="{StaticResource SliderStyle}" />
	<Slider x:Name="UpperSlider"
		Minimum="{Binding Minimum, ElementName=root, Mode=TwoWay}"
		Maximum="{Binding Maximum, ElementName=root, Mode=TwoWay}"
		Value="{Binding UpperValue, ElementName=root, Mode=TwoWay}" Style="{StaticResource SliderStyle}" />
</Grid>

The control contains 3 parts, which are stagged on top of each other:

  • Progress Border (Border) – Range indicator (size is dynamically changed in code behind)
  • Lower Slider (Slider) – Slider for lower value
  • Upper Slider (Slider) – Slider for upper value

The two sliders are data bound to dependency properties in the code behind class of the user control. Because the two sliders are stagged on top of each other, the second slider covers the first slider and prevents the user from interacting with the underlaying slider:
RangeSliderWPFSLFail
The solution to this problem is a a custom slider style, which disables the background of the slider. The background consists of two repeat buttons which are used to change the value of the slider by clicking on the left or right of the thumb. To disable this behavior, we are creating a template for this two repeat buttons, which only contains an empty grid:

<RepeatButton x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" Grid.Column="0" Template="{StaticResource RepeatButtonControlTemplate}" />
<RepeatButton x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" Grid.Column="2" Template="{StaticResource RepeatButtonControlTemplate}" />

...

<ControlTemplate x:Key="RepeatButtonControlTemplate" TargetType="RepeatButton">
	<Grid/>
</ControlTemplate>

This allows us to “click through” the overlaying slider and change the value of the second slider. To implement the functionality of the range slider, that’s all we gonna need to do in the slider control template. Now it’s time to take a look at the code behind class.

Code behind

We just want to focus on the two important code snippets. To see the whole code behind class (dependency properties etc.), download the source code for this project. First of all we are looking at the calculation of the progress border:

private void SetProgressBorder()
{
    double lowerPoint = (this.ActualWidth * (LowerValue - Minimum)) / (Maximum - Minimum);
    double upperPoint = (this.ActualWidth * (UpperValue - Minimum)) / (Maximum - Minimum);
    upperPoint = this.ActualWidth - upperPoint;
    progressBorder.Margin = new Thickness(lowerPoint, 0, upperPoint, 0);
}

You see, there’s no rocket science ;-) The lower and upper point is the distance in pixels from the left border of the control to the actual thumb. It is used to set the progress border margin, so that the border is always shown between the two slider thumbs.

The second interesting code snippet is used to make sure, that the lower slider value is always lower or equal the upper value:

slider.UpperSlider.Value = Math.Max(slider.UpperSlider.Value, slider.LowerSlider.Value);

...

slider.LowerSlider.Value = Math.Min(slider.UpperSlider.Value, slider.LowerSlider.Value);

Depending on which slider was changed, the according value is set to the minimum or maximum value of the two sliders. To see all details of the implementation, feel free to download the source code or ask any questions in the comments.

Source Code:
RangeSlider

2 Comments

  1. Thank you for this great RangeSlider. I had to change two things in your Template to make it work optimally.
    Thumb-Height from “18” to “{Binding Source = {Relative RelativeSource FindAncestor, AncestorType = {x: Type Slider}}, Path = ActualHeight}”
    and
    Track-Width from “400” to “{Binding Source = {Relative RelativeSource FindAncestor, AncestorType = {x: Type Slider}}, Path = ActualWidth}”.
    For a good preview I have added d:DesignHeight = “25”.

    • I use this RangeSlider and i adding a TextBlock above Thumb which bound with Value of each a Slider. So, The Margin of Progress Border not exactly. Can you help me a solution for this issues?

Leave a Reply