4

I'm working with a custom control that has several user-defined dependency properties. I'm running into the same issue described in this question.

My control is setting the default value of a custom dependency property in its constructor. When I use the control in a DataTemplate, the value set in the constructor is always used, even if I try to set it in XAML.

The answer to the linked question explains that the value set in the C# code has a higher priority, and a better approach would be to specify the default value in the dependency property's metadata.

In my case, I can't specify a default because the dependency property doesn't have a single default value that applies in all cases. The default values depend on another property, so I must look them up when the control is created and not when the property is registered.

Here's some code to help illustrate my problem:

public partial class MyControl : UserControl
{
    public static readonly DependencyProperty MyProperty = 
            DependencyProperty.Register(
                "MyProperty",
                typeof(int),
                typeof(MyControl),
                new FrameworkPropertyMetadata(
                    int.MinValue,
                    FrameworkPropertyMetadataOptions.None,
                    new PropertyChangedCallback("OnMyPropertyChanged")));

    public MyControl() : base()
    {
        InitializeComponent();
        this.MyProperty = GetDefaultPropertyValue();
    }

    public int MyProperty
    {
        get { return (int)GetValue(MyProperty); }
        set { SetValue(MyProperty, value); }
    }

    private int GetDefaultPropertyValue()
    {
        // look up the appropriate default based on some other criteria
        return 42;

        // (in reality, the default value for "MyProperty"
        // depends on the value of a "Mode" custom DependencyProperty.
        // this is just hard coded for testing)
    }   
}

The XAML usage looks something like this:

<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />

<!-- View displays default of 42 (desired) -->
<local:MyControl />

<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
    <local:MyControl MyProperty="4"/>
</DataTemplate>

To summarize:

The desired behavior is that the value from XAML is used first. If the value is not specified in the XAML, then I would like to fallback to the default value set in the control's constructor.

If I just include the control directly in a view, I get the expected behavior. If the control is used inside a DataTemplate, then I always get the default set in the constructor (even when the data template explicitly sets another value).

Is there any other way to specify the default value when the control is used in a template? The only option I can think of is to break the control up into several separate but similar controls, each of which uses a default value that is registered with the dependency property (which removes the need to have the default set based on the a Mode property).

zmb
  • 7,605
  • 4
  • 40
  • 55

1 Answers1

4

Setting the default value in OnApplyTemplate while adding a small check should solve this:

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    // Only set the default value if no value is set.
    if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
    {
        this.MyProperty = GetDefaultPropertyValue();
    }
}

Please note that although this will work, it's not ideal since setting the property's value by code will essentially clear any data bindings for this property. For example, the following binding will no longer work once you call MyProperty = 42 in code:

<local:MyControl MyProperty="{Binding SomeProperty}" />

It should be possible to set the value while maintaining any bindings by using SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue()); to modify the property instead of MyProperty = GetDefaultPropertyValue(), but I'm not sure I like that too much either.

A better solution

What I would do is introduce a new read-only property in addition to the existing one, which will act as a calculated property. For example:

private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
    DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
    new PropertyMetadata(int.MinValue));

public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;

public int MyCalculatedProperty
{
    get { return (int)GetValue(MyCalculatedPropertyProperty); }
    private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}

private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}

public MyControl()
    : base()
{
    InitializeComponent();
    MyCalculatedProperty = GetDefaultPropertyValue();
}
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
  • This does work, thanks! Since I'm only setting the property by code if it wasn't set in XAML, then is it safe to assume there are no data bindings to break? (we don't ever create bindings in code) – zmb Sep 29 '14 at 19:13
  • In your example you showed how you set the property in xaml: `` If that's the only way the property should be set and never with bindings (see my edit) then it's not something you should worry about, although it's not considered good practice to override bindings. – Adi Lester Sep 29 '14 at 19:18
  • I understand that `MyProperty = 42;` will break any bindings. If I were using a binding like you added in your edit, then as long as the binding didn't evaluate to `MyProperty.DefaultMetadata.DefaultValue`, that wouldn't be called and the binding wouldn't be broken, right? Could you elaborate on your reccomended solution with the read only property? I'm not sure I follow. – zmb Sep 29 '14 at 19:27
  • 1
    I got things working with the better solution and have accepted your answer. I have no idea how this works though. How does adding a read-only dependency property have any effect on the original property? – zmb Sep 29 '14 at 20:08
  • See also more details about the difference between `SetValue` and `SetCurrentValue` in [this answer](https://stackoverflow.com/a/4232379/1518546) – John Cummings Apr 05 '18 at 14:08