5

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?

Vadim Kotov
  • 7,103
  • 8
  • 44
  • 57
dthorpe
  • 33,791
  • 3
  • 72
  • 118
  • Your code sample for property change only indicates that properties of the Sample are changing, not the Sample itself. – Ragepotato Jun 15 '11 at 20:52
  • @Ragepotato: 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? – dthorpe Jun 15 '11 at 21:20
  • Yes. The Sample never changed. It is the same object. Properties within it changed. You can test this. Put a button on your control after everything is loaded and Sample gets its initial assignment from your VM. Then assign Sample to a new SampleType. Remember it has to be the proper type or the Binding Engine will ignore it. If you set up your two way binding right, you'll see your convert back get called. – Ragepotato Jun 16 '11 at 05:56
  • Also, something I found valuable once discovering it, if you want two way binding by default on your DP's you can use FrameworkPropertyMetadata instead of PropertyMetadata and set BindsTwoWayByDefault. Just to say you some xaml :) – Ragepotato Jun 16 '11 at 05:59
  • 1
    That makes sense for value types where the content is atomic but not for reference types which are more commonly viewed (conceptually) as the sum of their properties. Particularly when a converter is involved - if the converted value's properties change, then the state of the converted value has changed, and that needs to be communicated back to the source property through ConvertBack. Otherwise converters are only useful for value types and immutable objects. – dthorpe Jun 16 '11 at 17:51
  • Not arguing your point, just venting WPF frustrations in general. :P – dthorpe Jun 16 '11 at 17:52
  • I know exactly what you mean and have run into this same thing before. You could handle the property changed notification on the VM instead. Hope any of this helped. – Ragepotato Jun 16 '11 at 19:14

3 Answers3

2

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();
    }
}
dthorpe
  • 33,791
  • 3
  • 72
  • 118
0

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

jaccso
  • 318
  • 3
  • 6
0

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.

Phil Sandler
  • 26,117
  • 19
  • 77
  • 138
  • The XmlSource CLR property already implements INotifyPropertyChanged. Besides, the converter's ConvertBack would need to be called first in order to obtain a value to assign to the XmlSource property, and ConvertBack isn't being called. – dthorpe Jun 16 '11 at 17:45