Skip Navigation LinksHome > View Post
Creating a Range Slider in Silverlight (and some of the binding gotchas in SL3)

Yesterday, I posted about how to Create a Range Slider in WPF using composition within a UserControl.

Today, I want to do the same in Silverlight and _try_ to use the same technique(s).

The obvious things that need to change are:

  1. The Slider ControlTemplate
  2. Removing the ElementName binding
I can hear you all now: "No! Josh! Silverlight 3 has ElementName binding support! You don't need to remove it! Nooooo!".

Sadly though, we can't use it the way I did in yesterday's post. Let's recap the big tip I was using:

<UserControl x:Class="UserControlFun.RangeSlider"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="root">
    <Slider x:Name="LowerSlider"
        Minimum="{Binding ElementName=root, Path=Minimum}"
        Maximum="{Binding ElementName=root, Path=Maximum}"
        Value="{Binding ElementName=root, Path=LowerValue}" />
    <Slider x:Name="UpperSlider"
        Minimum="{Binding ElementName=root, Path=Minimum}"
        Maximum="{Binding ElementName=root, Path=Maximum}"
        Value="{Binding ElementName=root, Path=UpperValue}" />
</UserControl>

As you can see we give the UserControl element a name. This is supported in WPF but not recommended in Silverlight. This is because in Silverlight an element can only have one name and therefore if we specify a Name where we use the control, like this:

<local:RangeSlider x:Name="overridingName" />

the internal ElementName binding will now fail (silently) because we changed the name. One solution might be to *not* rename the element where it's used but there's another problem: we can only use this control once or we'll have two elements with the same name.

This is very sad and I hope this is fixed in future releases. However, there is a workaround that seems to achieve what I want to achieve.

The trick is to use ambient binding (no ElementName) by programmatically setting the DataContext in the control's constructor:

public RangeSlider()
{
    InitializeComponent();
    this.Loaded += new RoutedEventHandler(RangeSlider_Loaded);
    LayoutRoot.DataContext = this;
}

Notice that we specifically don't set this.DataContext = this;. That would break any bindings specified on the UserControl externally. Instead, we head for the first child of our UserControl - in this case, called 'LayoutRoot'.

Here's the Xaml for the UserControl:

<UserControl x:Class="UserControlFunSL3.RangeSlider"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Grid x:Name="LayoutRoot" VerticalAlignment="Top">

    <!-- resources will go here -->

        <Border BorderThickness="0,1,0,0" BorderBrush="Black" VerticalAlignment="Center" Height="1" Margin="5,0,5,0"/>

        <Slider x:Name="LowerSlider"
            Minimum="{Binding Minimum}"
            Maximum="{Binding Maximum}"
            Value="{Binding LowerValue, Mode=TwoWay}"
            Margin="0,0,10,0"
            Template="{StaticResource sliderTemplate}"
            />

        <Slider x:Name="UpperSlider"
            Minimum="{Binding Minimum}"
            Maximum="{Binding Maximum}"
            Value="{Binding UpperValue, Mode=TwoWay}"
            Margin="10,0,0,0"
            Template="{StaticResource sliderTemplate}"
            />
    </Grid>
</UserControl>

Finally, we need our Silverlight specific Slider Template:

<Grid.Resources>
    <ControlTemplate x:Key="buttonTemplate" TargetType="RepeatButton">
    <!-- just empty-->
        <Grid />
    </ControlTemplate>
    <ControlTemplate x:Key="sliderTemplate" TargetType="Slider">
        <Grid x:Name="HorizontalTemplate" Background="{TemplateBinding Background}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <RepeatButton Template="{StaticResource buttonTemplate}" IsTabStop="False" IsEnabled="False" x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" Grid.Column="0"/>
            <Thumb IsTabStop="True" Height="18" x:Name="HorizontalThumb" Width="11" Grid.Column="1">
                <Thumb.Template>
                    <ControlTemplate TargetType="Thumb">
                        <Rectangle Fill="Red"
                            Stroke="Black"
                            StrokeThickness="1" />
                    </ControlTemplate>
                </Thumb.Template>
            </Thumb>
            <RepeatButton Template="{StaticResource buttonTemplate}" IsTabStop="False" IsEnabled="False" x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" Grid.Column="2"/>
        </Grid>
    </ControlTemplate>

</Grid.Resources>

And we're done. Almost everything else is the same as our WPF control. Why not have a play with the control right here:

Or, get the source here: Download Source (7KB). Usual disclaimers for demoware apply.

Josh Post By Josh Twist
4:12 AM
16 Jul 2009

» Next Post: Bindorama - Binding Craziness now for Silverlight too!
« Previous Post: Creating a Range Slider in WPF (and other cool tips and tricks for UserControls)

Comments:

Posted by Rob @ 16 Jul 2009 6:57 AM
This gotcha is a fine example the consequence of Silverlight's lack of NameScopes.

Posted by Ole Jak @ 05 Oct 2009 1:50 PM
Maybe there should be a middle resizing point like here http://dougmccune.com/blog/2007/01/21/draggable-slider-component-for-flex/

Posted by andrew @ 07 Dec 2009 11:31 AM
very gooood :))

Post a comment:

Name  

E-mail (never shared)

URL

Comments  

Captcha ImageRefresh Image
What's this?
Enter code above