Silverlight Slider that snaps to rounded value when dragged

The other day I had a problem where a slider had five distinct values and the user would drag the slider to their selection. An example of this would be a question like “From 1 to 5, please select how much you like puppies”. Of course, everyone would answer 5 to that question but that’s not the point… When a user clicked the slider (as opposed to dragging the thumb), the slider change would work fine, and the slider would go to the right place. The problem was that when the dragged the slider to change the value they could drag it to an intermediary value. Small change was 1 and large change was 1, but the could select 1.1393938… which would leave the slider sitting in a position between 1 and 2 somewhere.

The answer was to jump in to Silverlight Spy and have a bit of a hunt around the Slider code. I pulled out some of the code and created a new class inherited from Slider, and now when I drag it snaps to the right positions!

The code here is not complete, it doesn’t properly support IsDirectionReversed or Vertical sliders, but you could quite easily fix it for your own purposes.

Basically, just create a new control and inherit from Slider, then override the method as below.

Keep in mind, this to me is a fairly hacky approach :)

public class MySlider : Slider
    {
        protected override void OnValueChanged(double oldValue, double newValue)
        {
            int val = Convert.ToInt32(Math.Round(newValue));        

            Thumb ElementHorizontalThumb = GetTemplateChild("HorizontalThumb") as Thumb;

            double maximum = base.Maximum;
            double minimum = base.Minimum;

            double num3 = val;
            double num4 = 1.0 - ((maximum - num3) / (maximum - minimum));

            RepeatButton ElementHorizontalLargeDecrease = GetTemplateChild("HorizontalTrackLargeChangeDecreaseRepeatButton") as RepeatButton;
            RepeatButton ElementHorizontalLargeIncrease = GetTemplateChild("HorizontalTrackLargeChangeIncreaseRepeatButton") as RepeatButton;

            Grid grid = GetTemplateChild("HorizontalTemplate") as Grid;

            if (grid != null)
            {

                if ((grid.ColumnDefinitions != null) && (grid.ColumnDefinitions.Count == 3))
                {
                    grid.ColumnDefinitions[0].Width = new GridLength(1.0, GridUnitType.Auto);
                    grid.ColumnDefinitions[2].Width = new GridLength(1.0, GridUnitType.Star);

                    if (ElementHorizontalLargeDecrease != null)
                    {
                        ElementHorizontalLargeDecrease.SetValue(Grid.ColumnProperty, 0);
                    }
                    if (ElementHorizontalLargeIncrease != null)
                    {
                        ElementHorizontalLargeIncrease.SetValue(Grid.ColumnProperty, 2);
                    }
                }
                if ((ElementHorizontalLargeDecrease != null) && (ElementHorizontalThumb != null))
                {
                    ElementHorizontalLargeDecrease.Width = Math.Max(0.0, num4 * (base.ActualWidth - ElementHorizontalThumb.ActualWidth));
                }
            }
        }

    }

The main crux of this is that I first round the value, then pass it in as “num3″ which is then used to calculate the real position of the thumb slider.

As I said, a bit hacky, but it works nice!

I checked the Slider for a property that might turn it in to this mode, but couldn’t see anything… I think it would be a nice addition to the in built slider control.

6 Responses to “Silverlight Slider that snaps to rounded value when dragged”

  1. Martin Hägerås Says:

    I just did this in the ValueChanged handler:
    ////
    void Slider3_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
    {
    Slider3.Value = Math.Round(e.NewValue);
    }
    ////

    Seems to work fine :)

  2. Jordan Says:

    Hey Martin,

    Thanks for your input :)

    Yes your method works, but doesn’t take a couple if things in to account:

    Firstly by setting Slide.Value manually you’ll break any data binding on the Value property -> my designs are usually MVVM, so this isnt a great idea.

    Second – if you put a Debug.WriteLine in your value changed handler, you’ll see a non rounded change, then a rounded change – i.e. this method is calling Value twice. (0.951156812339332 then 1)

    Third – your method assumes you have access to code behind, which is usually only the case in UserControls, not so much in data templates or CustomControls :)

    I normally really hate inhertiting off controls to do something so simple, but unfortunately I was stuck with the restrictions I listed here.

    Once again thanks for sharing your thoughts!

  3. Asheesh Soni Says:

    http://asheeshsoni.blogspot.com/2009/06/silverlight-slider-jump-value-by.html

  4. Asheesh Soni Says:

    Thanks.

    Now where is the post on SL->JS->HTML graceful degradation? :-?

  5. Jordan Says:

    I know!!! too busy!

Leave a Reply