0
votes

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.

2
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.15ee8f99-57ff-4f92-890c-b56153

2 Answers

0
votes

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.

3
votes

this line

textBox.StringProperty = null;

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

try use

textBox.SetCurrentValue(StringPropertyProperty, null);