0

I have created a custom control with a dependency property bound to a view model property.

<wpf1:CustomTextBox StringProperty="{Binding StringValue}" />

The view model looks like this

public class ViewModel : INotifyPropertyChanged
{
    public string StringValue
    {
        get { return m_stringValue; }
        set
        {
            m_stringValue = value;
            OnPropertyChanged("StringValue");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string m_stringValue;
}

The DataContext is set in the code behind

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = ViewModel = new ViewModel();
    }

    public ViewModel ViewModel { get; set; }
}

The binding mode of the dependency property is two way by default

    public static readonly DependencyProperty StringPropertyProperty =
        DependencyProperty.Register(
            "StringProperty",
            typeof(string),
            typeof(CustomTextBox),
            new FrameworkPropertyMetadata
            {
                BindsTwoWayByDefault = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                PropertyChangedCallback = PropertyChangedCallback
            });

Now I have the problem, when the view model property StringValue is changed the custom control is notified but when I change the value of the dependency property the value of the view model is not changed.

    private static void PropertyChangedCallback(DependencyObject dO,
    DependencyPropertyChangedEventArgs e)
    {
        var textBox = (CustomTextBox) dO;
        if (textBox.StringProperty == null)
            return;
        DoSomething(textBox.StringProperty)
        textBox.StringProperty = null;
    }

If I set the view model value to "some value" I want the custom control to use that string and reset it to null. So far this works, but the view model value is not synchronized and remains "some value" instead of null. Any ideas how to do this? And why the two way binding does not work that way?

Thanks.

1
  • O/T, but public ViewModel ViewModel { get { return DataContext as ViewModel; } set { DataContext = value; } } saves you any worry about keeping the typed property in sync with DataContext. Commented Oct 31, 2016 at 14:24

2 Answers 2

3

this line

textBox.StringProperty = null;

removes binding which was defined earlier (<wpf1:CustomTextBox StringProperty="{Binding StringValue}" />)

try use

textBox.SetCurrentValue(StringPropertyProperty, null);
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the hint. Unfortunately, even though also the tool tip indicates that SetCurrentValue is exactly what I am looking for, this doesn't work either (same with SetValue). If I use it on a build-in property like Textproperty instead of my dependency property it behaves exactly like expected. Seems there is a difference...
To clarify - SetValue does not remove the binding set on the property if data transfer from target to source is supported, that is for TwoWay and OneWayToSource binding modes.
0

The reason for why you code does not work as expected is that you're assigning new value to your StringProperty while still handling the previous change to that property. I don't really know the mechanics behind that (possibly it's some kind of mechanism meant to prevent potentially infinite recursive calls?), but I am 100% that that is the culprit.

To solve your problem it is sufficient to defer new value assignment until the control is returned from your handler, which can be easily achieved by using the Dispatcher associated with your control:

private static void PropertyChangedCallback(DependencyObject dO,
    DependencyPropertyChangedEventArgs e)
{
    var textBox = (CustomTextBox) dO;
    if (textBox.StringProperty == null)
        return;
    DoSomething(textBox.StringProperty)
    //following lambda will be queued for execution rather than executed immediately
    //and is guaranteed to be executed after this handler has finished
    textBox.Dispatcher.InvokeAsync(() => textBox.StringProperty = null);
}

If you're using .NET version prior to 4.5 you will need to use Dispatcher.BeginInvoke method instead.

1 Comment

Works perfect! Thanks a lot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.