Bind Silverlight 3 Slider Value, Minimum and Maximum attributes in EXACTLY the right order!

3 minute read

Today I want to share a piece of very hard won knowlegde with you. That piece of knowlegde is: if you want to use a Silverlight Slider, and you want to get the Value, Minimum and Maximum attributes from binding, you need to bind them in exactly the right order. That order is:
  • Maximum
  • Minimum
  • Value
or else it simply won't work. It took me the better part of a sunny saturday afternoon to figure that out. To clarify things, I will show what I was doing when I ran into this. I have a simple business class that looks like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

namespace LocalJoost.CloudMapper.Ui
{
  public class TileRequestParms 
  {
    private double _xmin;
    public double XMin { get { return _xmin; } }

    private double _ymin;
    public double YMin { get { return _ymin; } }

    private double _xmax;
    public double XMax { get { return _xmax; } }

    private double _ymax;
    public double YMax { get { return _ymax; } }

    private double _xLow;
    [Display(Name = "X lower left:")]
    public double XLow
    {
      get
      {
        return _xLow;
      }
      set
      {
        ValidateValue(value, XMin, XHigh);
        _xLow = value;
      }
    }

    private double _yLow;
    [Display(Name = "Y lower left:")]
    public double YLow
    {
      get
      {
        return _yLow;
      }
      set
      {
        ValidateValue(value, YMin, YHigh);
        _yLow = value;
      }
    }

    private double _xHigh;
    [Display(Name = "X upper right:")]
    public double XHigh
    {
      get
      {
        return _xHigh;
      }
      set
      {
        ValidateValue(value, XLow, XMax);
        _xHigh = value;

      }
    }

    private double _yHigh;
    [Display(Name = "Y upper right:")]
    public double YHigh
    {
      get
      {
        return _yHigh;
      }
      set
      {
        ValidateValue(value, YLow, YMax);
        _yHigh = value;
      }
    }

    public TileRequestParms(double xLow, double yLow, 
        double xHigh, double yHigh)
    {
      _xmax = _xHigh = xHigh;
      _ymax = _yHigh = yHigh;
      _xmin = _xLow = xLow;
      _ymin = _yLow = yLow;
    }

    private void ValidateValue(double value, 
      double minValue, double maxValue)
    {
      if (value < minValue || value > maxValue)
      {
        throw new ArgumentException(
           string.Format("Valid value {0} - {1}", minValue,
                             maxValue));
      }
    }
  }
}
Its purpose is very simple: the user can input two coordinates that are inside a rectangle defined by the max and min values in the constructor. In addition, XMax can never be smaller than XMin, and YMax never smaller than YMin. Now I wanted the user to be able to input the value both by a text box and a slider. And I decided to use the new Silverlight 3 DataForm and control to control binding:
<dataFormToolkit:DataField Grid.Row="0" Grid.Column="0" 
  Label="X lower left: ">
    <StackPanel>
        <TextBox x:Name ="tbXLow" Text="{Binding XLow,Mode=TwoWay}" />
        <Slider x:Name="slXLow" 
         Value="{Binding Text,ElementName=tbXLow, Mode=TwoWay}" 
         Minimum="{Binding XMin}" Maximum="{Binding XMax}"/>
    </StackPanel>
</dataFormToolkit:DataField>
<dataFormToolkit:DataField Grid.Row="0" Grid.Column="1" 
      Label="Y lower left: ">
    <StackPanel>
        <TextBox x:Name ="tbYLow" Text="{Binding YLow,Mode=TwoWay}" />
        <Slider x:Name="slYLow" 
         Value="{Binding Text,ElementName=tbYLow, Mode=TwoWay}" 
         Minimum="{Binding YMin}" Maximum="{Binding YMax}"/>
    </StackPanel>
</dataFormToolkit:DataField>
<dataFormToolkit:DataField Grid.Row="1" Grid.Column="0" 
    Label="X upper right: ">
    <StackPanel>
        <TextBox x:Name ="tbXHigh" Text="{Binding XHigh,Mode=TwoWay}" />
        <Slider x:Name="slXHigh" 
         Value="{Binding Text,ElementName=tbXHigh, Mode=TwoWay}" 
         Minimum="{Binding XMin}" Maximum="{Binding XMax}"/>
    </StackPanel>
</dataFormToolkit:DataField>
<dataFormToolkit:DataField Grid.Row="1" Grid.Column="1" 
  Label="Y upper right: ">
    <StackPanel>
        <TextBox x:Name ="tbYHigh" Text="{Binding YHigh,Mode=TwoWay}" />
        <Slider x:Name="slYHigh" 
         Value="{Binding Text,ElementName=tbYHigh, Mode=TwoWay}" 
         Minimum="{Binding YMin}" Maximum="{Binding YMax}"/>
    </StackPanel>
</dataFormToolkit:DataField>
Now the annoying thing is: is worked for X, but not for Y. The sliders stayed put and refused to work. After a considerable time of debugging and I noticed one thing: it worked when I put hard coded values for Minimum and Maximum, but not if I used binded values. For X, I was using a minimum 0 and a maximum of 300000. But I was using a minimum of 300000 for Y, and 600000 for a maximum (Yes, that's the RD coordinate system, for my Dutch readers). With a bit of debugging I was able to find out the both minimum and maximum value of the first slider where set to 300000, as was it's value. So I reasoned: maybe, by setting the value, the maximum value is set already (because the minimum is already 0) and it does 'not like' to get a max value twice. Or something among that lines. So what if I first set the maximum, then the minimum, and only then the value? So I changed the XAML for the first slider
<dataFormToolkit:DataField Grid.Row="1" Grid.Column="0" 
  Label="X upper right: ">
    <StackPanel>
        <TextBox x:Name ="tbXHigh" Text="{Binding XHigh,Mode=TwoWay}" />
        <Slider Maximum="{Binding XMax}" Minimum="{Binding XMin}"
         x:Name="slXHigh" 
         Value="{Binding Text,ElementName=tbXHigh, Mode=TwoWay}"/>
    </StackPanel>
</dataFormToolkit:DataField>
And it worked. It's these little things that make a developer's life interesting, isn't it? Although, in my case, it also gave this here developer a pretty potent headache.