简体   繁体   中英

Two-way data binding with converter doesn't update source

I've got a data binding set up with a converter to transform an awkward XML source to a display- and editing- convenient tree of internal classes. Everything works great for reading from the XML source, but I'm having a devil of a time trying to get changes made to the internal classes to propagate back to the XML source.

Here's the XAML for the use site:

        <local:SampleConverter x:Key="SampleConverter" />
        <Expander Header="Sample" >
            <local:SampleControl 
                Sample="{Binding Path=XmlSource, 
                                 Converter={StaticResource SampleConverter}, 
                                 Mode=TwoWay}" />
        </Expander>

XmlSource is a CLR read-write property (not DependencyProperty) of the parent data bound object. It is a .NET type generated from an XSD.

SampleConverter implements IValueConverter . The Convert method is called and returns non-null data, but the ConvertBack method is never called.

SampleControl is a UserControl that encapsulates UI interaction with the Sample data tree. It's XAML looks like this:

<UserControl x:Class="SampleControl">
    [... other stuff ...]

    <UserControl.Content>
        <Binding Path="Sample" RelativeSource="{RelativeSource Mode=Self}" Mode="TwoWay" TargetNullValue="{StaticResource EmptySampleText}" />
    </UserControl.Content>

    <UserControl.ContentTemplateSelector>
        <local:BoxedItemTemplateSelector />
    </UserControl.ContentTemplateSelector>
</UserControl>

The Sample property is a DependencyProperty in the SampleControl code behind:

public static readonly DependencyProperty SampleProperty =
    DependencyProperty.Register("Sample", typeof(SampleType), typeof(SampleControl), new PropertyMetadata(new PropertyChangedCallback(OnSampleChanged)));

public SampleType Sample
{
    get { return (SampleType)GetValue(SampleProperty); }
    set { SetValue(SampleProperty, value); }
}

private static void OnSampleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue != null)
    {
        ((INotifyPropertyChanged)e.NewValue).PropertyChanged += ((SampleControl)d).MyPropertyChanged;
    }
    else if (e.OldValue != null)
    {
        ((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((SampleControl)d).MyPropertyChanged;
    }
}

private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    ;  // breakpoint here shows change notices are happening
}

The internal classes that the XmlSource is converted to implement INotifyPropertyChanged, and are sending change notifications up the tree, as indicated by a breakpoint in MyPropertyChanged above.

So if the data is reporting that it has changed, why isn't WPF calling my converter's ConvertBack method?

With hints from several similar questions and almost answers here on SO, I have a working solution that preserves the binding. You can manually force the binding to update the source in a strategically placed event, such as LostFocus:

private void mycontrol_LostFocus(object sender, RoutedEventArgs e)
{
    if (mycontrol.IsModified)
    {
        var binding = mycontrol.GetBindingExpression(MyControl.SampleProperty);
        binding.UpdateSource();
    }
}

Had similar problem. The solution was the following:

Instead of the two way binding I used one way, and a converter. The converter converts (encapsulates) the object (in your case, the parent object of the xmlsource property) to a viewModel, and the control binds to it.

The viewModel works like a proxy, holds a reference to the object, manages it's properties, and of course implements INotifyPropertyChanged. In this case you don't need to call the ConvertBack method, since the operations are performed on the proper instance through the viewModel.

So you have a clean view, you don't need any code in the xaml.cs

XmlSource is a CLR read-write property (not DependencyProperty) of the parent data bound object. It is a .NET type generated from an XSD.

I believe this is your problem. A basic CLR property won't notify, and is thus unable to participate in two-way databinding. My guess would be your are getting one-time binding? Do you have any databinding errors in your output window?

You might try creating a wrapper that contains the XmlSource property's class, make that class implement INotifyPropertyChanged, and expose XmlSource as a notifying property (it does not need to be a dependency property).

Also, regarding this comment:

Are you saying that data binding only works if a new instance is assigned to the Sample property, but not if properties of the existing instance referred to by the Sample property are changed and signal their changes via INotifyPropertyChanged?

Sample implementions of INotifyPropertyChanged usually have the notification occuring when the property itself changes, not when children or other properties change. However, you can absolutely fire the notification event on any property at any time. For example, you might have a "Full Name" property that notifies whenever "First Name" or "Last Name" change.

If the above isn't helpful, I would suggest posting a bit more of your code--maybe a simplified repro of what you are trying to get working.

Edit:

I think I misunderstood your question. I think the problem is what you alluded to in your comments--it's a reference type. Might be worth trying this in your OnSampleChanged:

private static void OnSampleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{
    var oldValue = Sample;
    Sample = new SampleType();
    Sample = oldValue;
}

I think this will force the binding system to recognize that something changed on the target side.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM