Search code examples
c#silverlightbindingcoercion

Coercion in Silverlight does not work


I've a custom control look like this:

generic.xaml

<Style TargetType="controls:MyControl">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="controls:MyControl">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="20" />
            <RowDefinition Height="20" />
          </Grid.RowDefinitions>
          <TextBox Grid.Row="0"
                   Text="{Binding ElementName=slider, Path=Value}" />
          <Slider Grid.Row="1" Name="slider" Width="120"
                  Minimum="1" Maximum="12"
                  Value="{Binding Mode=TwoWay,
                          RelativeSource={RelativeSource TemplatedParent},
                          Path=Value}"/>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

MyControl.cs

public static readonly DependencyProperty ValueProperty =
  DependencyProperty.Register("Value",
  typeof(double),
  typeof(MyControl),
  new PropertyMetadata(0d, OnValueChanged));

  public double Value
  {
    get { return (double)base.GetValue(ValueProperty); }
    set { base.SetValue(ValueProperty, value); }
  }

  private static void OnValueChanged(DependencyObject source,
                                     DependencyPropertyChangedEventArgs e)
  {
    MyControl myControl = (MyControl)source;
    myControl.OnValueChanged((double)e.OldValue, (double)e.NewValue);
  }

  protected virtual void OnValueChanged(double oldValue, double newValue)
  {
    double coercedValue = CoerceValue(newValue);
    if (coercedValue != newValue)
    {
      this.Value = coercedValue;
    }
  }

  private double CoerceValue(double value)
  {
    double limit = 7;
    if (value > limit)
    {
      return limit;
    }
    return value;
  }

The TextBox is just a dummy to show the value.

Now when I add this control to an Application, I am able to set the Slider value greater than 7, although the value of my DependencyProperty is set to 7.

What I am doing wrong? Does the TwoWayBinding does not work in this situation?

Thanks in advance


Solution

  • Steps for my repro:-

    • Create a fresh new Silverlight Application in VS2010 call SilverlightApplication1.
    • Add new "Silverlight Templated Control" to the silverlight project, naming it "MyControl".
    • Copied the inner contents or you ControlTemplate into the ControlTemplate of the themes/Generic.xaml file. This Entire generic file looks like:-

      <Style TargetType="local:MyControl">
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="local:MyControl">
                      <Grid>
                          <Grid.RowDefinitions>
                              <RowDefinition Height="20" />
                              <RowDefinition Height="20" />
                          </Grid.RowDefinitions>
                          <TextBox Grid.Row="0"
                         Text="{Binding ElementName=slider, Path=Value}" />
                          <Slider Grid.Row="1" Name="slider" Width="120"
                        Minimum="1" Maximum="12"
                        Value="{Binding Mode=TwoWay,
                                RelativeSource={RelativeSource TemplatedParent},
                                Path=Value}"/>
                      </Grid>
      
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      

      • Copied your C# placed in in MyControl.cs. The whole file looks like:-

    using System.Windows; using System.Windows.Controls;

    namespace SilverlightApplication1
    {
        public class MyControl : Control
        {
            public MyControl()
            {
                this.DefaultStyleKey = typeof(MyControl);
            }
    
            public static readonly DependencyProperty ValueProperty =
      DependencyProperty.Register("Value",
      typeof(double),
      typeof(MyControl),
      new PropertyMetadata(0d, OnValueChanged));
    
            public double Value
            {
                get { return (double)base.GetValue(ValueProperty); }
                set { base.SetValue(ValueProperty, value); }
            }
    
            private static void OnValueChanged(DependencyObject source,
                                               DependencyPropertyChangedEventArgs e)
            {
                MyControl myControl = (MyControl)source;
                myControl.OnValueChanged((double)e.OldValue, (double)e.NewValue);
            }
    
            protected virtual void OnValueChanged(double oldValue, double newValue)
            {
                double coercedValue = CoerceValue(newValue);
                if (coercedValue != newValue)
                {
                    this.Value = coercedValue;
                }
            }
    
            private double CoerceValue(double value)
            {
                double limit = 7;
                if (value > limit)
                {
                    return limit;
                }
                return value;
            }
    
        }
    }
    
    • Added an instance of MyControl to MainPage.xaml, which now looks like:-

      <UserControl x:Class="SilverlightApplication1.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          xmlns:local="clr-namespace:SilverlightApplication1"
          mc:Ignorable="d"
          d:DesignHeight="300" d:DesignWidth="400">
      
          <Grid x:Name="LayoutRoot" Background="White">
              <local:MyControl />
           </Grid>
      
       </UserControl>
      
    • Run the solution, works fine.