Search code examples
c#wpfbindingdependency-propertiescode-behind

Setting a binding from code-behind in PropertyChangedCallback of a DependencyProperty


I'm trying to implement a UserControl which hosts other controls which are bound to certain properties of it. In my case it'd display a Slider next to a NumericUpDown - both controls would be bound to the UserControl's Value property. My approach would be defining a DependencyProperty for each control so when using this control, properties like Minimum, TickPlacement etc. can still be changed by setting a new slider as value for the corresponding property:

<!-- Displays a slider with default properties next to a text block-->
<Foo/>

<!-- Displays a sider with custom properties -->
<Foo>
    <Foo.Slider>
        <Slider Maximum="500" TickPlacement="Both" TickFrequency="20"/>
    </Foo.Slider>
</Foo>

The code-behind looks like this:

public partial class Foo : UserControl
{
    public Foo()
    {
        InitializeComponent();
        Slider = new Slider();
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(Foo), new PropertyMetadata(0.0));
    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    public static readonly DependencyProperty SliderProperty = DependencyProperty.Register(nameof(Slider), typeof(Slider), typeof(Foo),
        new PropertyMetadata(
            new PropertyChangedCallback((obj, e) =>
            {
                Slider slNew = (Slider)e.NewValue;
                if (slNew == null)
                    obj.SetValue(SliderProperty, new Slider());
                else
                    slNew.SetBinding(ValueProperty,
                        new Binding("Value")
                        {
                            Source = obj,
                            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                            Mode = BindingMode.TwoWay
                        });
            })));
    public Slider Slider
    {
        get => (Slider)GetValue(SliderProperty);
        set => SetValue(SliderProperty, value ?? new Slider());
    }
}

The relevant part of the XAML code looks like this:

<UserControl x:Name="ctrlBase" [...]>
    <Grid [...]>
        <Grid.ColumnDefinitions [...]/>
        <ContentControl DataContext="{Binding ElementName=ctrlBase}"
                        Content="{Binding Path=Slider}"/>
        <!-- NumericUpDown to be implemented -->
        <TextBlock Grid.Column="1"
                   Text="{Binding ElementName=ctrlBase, Path=Value}/>
    </Grid>
</UserControl>

The actual problem is that the binding created from code-behind doesn't seem to work. I assume this due to the following:

  1. Moving the slider won't change the value displayed by the text block next to the slider.
  2. Changing the Value property from XAML changes the value displayed by the text block but not the slider's value.

Solution

  • You're using the wrong ValueProperty. It should be the one of the Slider class, not Foo.ValueProperty:

    slNew.SetBinding(Slider.ValueProperty, new Binding("Value") { ... });
    

    It would have worked if you had used Slider.ValueProperty.AddOwner instead of DependencyProperty.Register.


    That said, a simpler approach would probably be to simply declare a Slider in your UserControl's XAML and provide a default Slider Style in the Foo Resources during instantiation:

    <Foo ...>
        <Foo.Resources>
            <Style TargetType="Slider">
                ...
            </Style>
        </Foo.Resources>
    </Foo>