Paul Michaels
Paul Michaels

Reputation: 16695

Updating a secondary property from a DependencyProperty

I have a WPF control that is based on the TextBox control:

public class DecimalTextBox : TextBox

I have a dependency property that is bound to, which manages the numeric value, and is responsible for setting the Text property:

public decimal NumericValue
{
    get { return (decimal)GetValue(NumericValueProperty); }
    set
    {
        if (NumericValue != value)
        {
            SetValue(NumericValueProperty, value);
            SetValue(TextProperty, NumericValue.ToString());                    

            System.Diagnostics.Debug.WriteLine($"NumericValue Set to: {value}, formatted: {Text}");
        }
    }
}

protected override void OnTextChanged(TextChangedEventArgs e)
{            
    base.OnTextChanged(e);

    if (decimal.TryParse(Text, out decimal num))
    {
        SetValue(NumericValueProperty, num);
    }
}

This works well when entering a value into the textbox itself (it updates the underlying values, etc...). However, when the bound property of NumericValue is changed, despite updating the NumericValue DP, the Text property is not updated. In the tests that I've done, it would appear that the reason for this is that the set method above is not called when the bound value is updated. The binding in question looks like this:

<myControls:DecimalTextBox NumericValue="{Binding Path=MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Can anyone point me in the right direction as to why this property setter is not firing, or is there a better way to approach this?

Upvotes: 0

Views: 47

Answers (2)

Clemens
Clemens

Reputation: 128061

As explained in Custom Dependency Properties and XAML Loading and Dependency Properties, you should not call anything else than GetValue and SetValue in the CLR wrapper of a dependency property:

Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.


In order to get notified about value changes, you'll have to register a PropertyChangedCallback with the dependency property metadata.

public static readonly DependencyProperty NumericValueProperty =
    DependencyProperty.Register(
        "NumericValue", typeof(decimal), typeof(DecimalTextBox),
        new PropertyMetadata(NumericValuePropertyChanged));

public decimal NumericValue
{
    get { return (decimal)GetValue(NumericValueProperty); }
    set { SetValue(NumericValueProperty, value); }
}

private static void NumericValuePropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var textBox = (DecimalTextBox)obj;
    textBox.Text = e.NewValue.ToString();
}

Upvotes: 1

grek40
grek40

Reputation: 13448

The WPF binding is not actually using your getter and setter, but instead directly interacts with the dependency property NumericValueProperty. In order to update the text, subscribe to the PropertyChanged event of the NumericValueProperty instead of trying to do anything special in the setter.

Subscribe to the change in your DependencyProperty definition, similar to the following:

// Using a DependencyProperty as the backing store for NumericValue.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty NumericValueProperty =
    DependencyProperty.Register("NumericValue", typeof(decimal), typeof(DecimalTextBox), new FrameworkPropertyMetadata(0.0m, new PropertyChangedCallback(OnNumericValueChanged)));

private static void OnNumericValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var self = d as DecimalTextBox;
    // if the new numeric value is different from the text value, update the text
}

Upvotes: 0

Related Questions