Slider control lets the user to select a value from a range. What about selecting multiple values from a given minimum and maximum?

There is no possibility to tune Slider to work as it depicted above. That’s a case to implement a custom control. Before we start, download an example from here.

Main properties.

The new control is going to have minimum and maximum properties to restrict user input, start and end range to save what the user has selected. Let’s begin from introducing a new class RangeSelector with the required dependency properties:

Style and template.

When RangeSelector is added and inherited from Windows.UI.Xaml.Controls.Control, we can declare the style and template for the control. Any custom control style should be placed in Themes/Generic.xaml file:

1

2

3

<Style TargetType="controls:RangeSelector"/>

Don’t forget to update constructor to apply the defined style when RangeSelector is being created:

1

2

3

4

5

6

publicRangeSelector()

{

DefaultStyleKey=typeof(RangeSelector);

}

The visual part of any control is a template. Slider control templae has two rectangles whith a thumb. A Slider thumb is Border element. The full template easy to get with Blend. A good article about how to do it is here.

My RangeSelector has two thumbs, left and right. I utilize two rectangles to apply selection effect. The first one, I use to visualize a set of available values. And another rectangle, to show a selected range. When the control is loaded, the first rectangle is not visible because the full range is selected. In the template, the both rectangles are placed at the same level of logical tree:

I use clipping to shrink or expand the track. The thumbs have RenderTransform set to TranslateTransform. My intent is to use TranslateTransform.X property to update a thumb position, when the user is dragging a thumb:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<Thumbx:Name="LeftThumb"

CanDrag="True"

ManipulationMode="TranslateX"

HorizontalAlignment="Left"

Style="{StaticResource ThumbStyle}">

<Thumb.RenderTransform>

<TranslateTransform/>

</Thumb.RenderTransform>

</Thumb>

<Thumbx:Name="RightThumb"

CanDrag="True"

ManipulationMode="TranslateX"

HorizontalAlignment="Right"

Style="{StaticResource ThumbStyle}">

<Thumb.RenderTransform>

<TranslateTransform/>

</Thumb.RenderTransform>

</Thumb>

Finally, the completed style:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

<Style TargetType="controls:RangeSelector">

<Setter Property="Foreground" Value="#00FF00" />

<Setter Property="VerticalAlignment" Value="Top" />

<Setter Property="Background" Value="Gray" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="controls:RangeSelector">

<Grid>

<Grid.Resources>

<Style x:Key="ThumbStyle" TargetType="Thumb">

<Setter Property="Height" Value="24" />

<Setter Property="Width" Value="10" />

<Setter Property="Padding" Value="4" />

<Setter Property="BorderThickness" Value="0" />

<Setter Property="Background" Value="#00FF00" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="Thumb">

<Border Padding="{TemplateBindingPadding}"

BorderBrush="{TemplateBindingBorderBrush}"

BorderThickness="{TemplateBindingBorderThickness}"

Background="{TemplateBindingBackground}" CornerRadius="4"/>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</Grid.Resources>

<Rectangle Fill="{TemplateBinding Background}"

Height="{ThemeResource SliderTrackThemeHeight}" />

<Rectangle

x:Name="TrackRect"

Height="{ThemeResource SliderTrackThemeHeight}"

Fill="{TemplateBinding Foreground}">

<Rectangle.Clip>

<RectangleGeometry x:Name="TrackRectangle"Rect="0,0,0,0" />

</Rectangle.Clip>

</Rectangle>

<Thumb x:Name="LeftThumb"

CanDrag="True"

ManipulationMode="TranslateX"

HorizontalAlignment="Left"

Style="{StaticResource ThumbStyle}">

<Thumb.RenderTransform>

<TranslateTransform />

</Thumb.RenderTransform>

</Thumb>

<Thumb x:Name="RightThumb"

CanDrag="True"

ManipulationMode="TranslateX"

HorizontalAlignment="Right"

Style="{StaticResource ThumbStyle}">

<Thumb.RenderTransform>

<TranslateTransform />

</Thumb.RenderTransform>

</Thumb>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Control.Foreground property is used to highlight a selected range and Control.Background to show space available for dragging.

Handle user input.

Now, lets subscribe on DragStarted and DragCompleted events. The right place to do that is RangeSelector.OnApplyTemplate() method. Inside the method we can get references to the elements we defined in the style and add required events handlers:

LeftThumbDragDelta and RightThumbDragDelta are responsible for updating the horizontal position of the thumbs. There are two corner cases that we need to take care of:

The thumbs can’t intersect;

The thumbs should be steak to bounds of the track rectangle.

To prevent the intersection from the left side, I check if sum of the left thumb position and a horizontal delta is less than the right thumb X coordinate. For the right thumb, I only apply a shift if the right thumb X positioin is greater than the left thumb X position. To keep the both thumbs on the track, it’s enough to compare the sum of the current positoin and a horizontal change with the point (0,0). Less words, more action. The code bellow serves the described cases:

You already noticed that after a thumb position has been updated, there is a call of UpdateTrack() method. The method sets TrackRect clipping. To calculate the width between the thumbs, I transform the both thumbs poistion, subtract the right X from the left X and set the result to the clipping rectangle:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

privatevoidUpdateTrack()

{

doublerightThumbX=GetRightThumbRelatedPosition();

doubleleftThumbX=GetLeftThumbRelatedPosition();

if(rightThumbX-leftThumbX<0)//if right position hasn't updated yet

return;

doubletrackWidth=rightThumbX-leftThumbX;

_trackGeometry.Rect=trackWidth>0

?newRect(leftThumbX,0,trackWidth,_trackRect.Height)

:newRect(0,0,Window.Current.Bounds.Width,_trackRect.Height);

}

privatedoubleGetLeftThumbRelatedPosition()

{

GeneralTransform transform=_leftThumb.TransformToVisual(this);

returntransform.TransformPoint(newPoint()).X;

}

privatedoubleGetRightThumbRelatedPosition()

{

GeneralTransform transform=_rightThumb.TransformToVisual(this);

returntransform.TransformPoint(newPoint()).X;

}

The RangeSelector is ready to use. The source code is available at github. Happy coding!