views:

127

answers:

2

I have a situation I'm not sure how to debug. I'm hoping someone can suggest why it might not be working and point me in the right direction.

I have a TIFF image from a database which is exposed as a property of type System.Data.Linq.Binary. I want to display the individual frames from that TIFF image in an ItemsControl so I've written a converter that takes the Binary datatype and returns an ObservableCollection of BitmapFrames. I'm binding ItemsControl.ItemsSource to the Binary property using the converter along with "Mode=TwoWay" and "UpdateSourceTrigger=PropertyChanged".

The display of the images is working fine. The problem is that if I add a frame to the collection the display updates, but that change is not transferred back to the Binary property in the source object. The ConvertBack() method in my converter is never called (indicating to me that the binding is never even trying to update the source). If I manually make a call to BindingExpression.UpdateSource() as if it were set for "UpdateSourceTrigger=Explicit" the Binary property does update correctly.

So if a binding is set for "Mode=TwoWay" and "UpdateSourceTrigger=PropertyChanged" and the object implements INotifyPropertyChanged (which ObserverableCollection does), why doesn't the binding actually try to update the source?

Thanks!

+1  A: 

I have used this blog posting with success to help debug binding problems. Specifically the "TraceLevel" method.

Bea Stollnitz - How can I debug WPF bindings?

John Myczek
+2  A: 

This is happening because for TwoWay bindings, WPF only detects when the property gets a new value, not when an object referenced by the property changes.

In your case your property contains the ObservableCollection created by your converter. Although the contents of the ObservableCollection has been modified, and it fires INotifyPropertyChanged, the bound property itself has not changed: It still refrences the same ObservableCollection as before. Because of this, WPF DataBinding is not triggered and your source is not updated.

When you call UpdateSource() manually, it forces the ObservableCollection to be passed through your converter and back to your data object, so it works.

The easiest way to get the behavior you desire is:

  1. Instead of binding to the data field, bind to the data object, and extract the desired field in the converter (if you want to make a generic converter that can access any field, pass the field as a parameter).

  2. In the converter when you construct the ObservableCollection, add a CollectionChanged event that updates the original object whenever it fires.

Here is the general idea in code:

  public MyConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      PropertyInfo property = object.GetType().GetProperty((string)parameter);

      var coll = BinaryToCollection((Binary)property.GetValue(object, null));

      coll.CollectionChanged += (sender, e) =>
      {
        property.SetValue(object, CollectionToBinary(coll));
      };
      return coll;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }

    private ObservableCollection<SomeType> BinaryToCollection(Binary data)
    {
      // conversion details here
    }

    private Binary CollectionToBinary(ObservableCollection<SomeType> coll)
    {
      // conversion details here
    }

  }

In this case your binding would change from

 <ItemsControl ItemsSource="{Binding something.property, Mode=TwoWay, Converter={...}}"

to

  <ItemsControl ItemsSource="{Binding something, Converter={...}}"

with the converter parameter being the property name

Hope this helps!

Ray Burns
Thanks Ray! I followed what you're saying, though I have to say I'm surprised. It's not how I had visualized the process as working.I appreciate your suggestion for a solution, but it doesn't fit well in my case. I ended up solving it by watching for NotifyCollectionChangedEvents and using those to trigger a BindingExpression.UpdateSource(). That approach just fit my situation better.
Scott Bussinger