13

I have a class which implements INotifyPropertyChanged. An instance of this class is declared as a DependencyProperty in a Window, e.g.,

    public IMyClass MyClass
    {
        get { return (IMyClass)GetValue(MyClassProperty); }
        set { SetValue(MyClassProperty, value); }
    }
    public static readonly DependencyProperty MyClassProperty=
        DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow), new UIPropertyMetadata(null));

In the XAML, I have an element which is bound to this class using

Text="{Binding MyClass, Converter={StaticResource someConverter}}

Whenever I change a property in MyClass, I would like someConverter to be triggered. However, it only happens when I completely swap out MyClass. Is there a way to tie DependencyProperty updates to my MyClass PropertyChanged?

Update. In the spirit of AresAvatar's solution, here's what we have so far. The issue remaining is how to call InvalidateProperty (without having MyClass track it...)

    public IMyClass MyClass
    {
        get { return (IMyClass)GetValue(MyClassProperty); }
        set { SetValue(MyClassProperty, value); }
    }
    public static readonly DependencyProperty MyClassProperty =
        DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow),
        new UIPropertyMetadata(null, new PropertyChangedCallback(OnMyClassChanged)));

    private static void OnMyClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue != null)
        {
            ((IMyClass)e.OldValue).PropertyChanged -= ((MainWindow)d).MyClass_PropertyChanged;
        }

        if (e.NewValue != null)
        {
            ((IMyClass)e.NewValue).PropertyChanged += ((MainWindow)d).MyClass_PropertyChanged;
        }
    }

    private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        this.InvalidateProperty(MyClassProperty);  <----- still does not refresh binding, but called.
    }
H.B.
  • 166,899
  • 29
  • 327
  • 400
tofutim
  • 22,664
  • 20
  • 87
  • 148
  • I had a similar issue, still not resolved: http://stackoverflow.com/questions/6363883/two-way-data-binding-with-converter-doesnt-update-source – dthorpe Jun 30 '11 at 23:01
  • dthorpe, my answer below should also work for your issue. You want to hook the NotifyPropertyChanged into a binding invalidation just as tofutim does here. – Ed Bayiates Jun 30 '11 at 23:05
  • @dthorpe - your question was very helpful. – tofutim Jun 30 '11 at 23:30
  • 1
    Try using this: BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty); if (exp != null) exp.UpdateTarget(); – Ed Bayiates Jul 01 '11 at 00:07
  • I tried both the UpdateTarget and InvalidateProperty options with no luck. They are being called, however. I think the underlying Binding code must have some check of whether the object is the same one or not and cannot be forced to re-call the Converter. – tofutim Jul 01 '11 at 00:34
  • I think the answer is here: http://stackoverflow.com/questions/2453146/getting-this-pointer-inside-dependency-property-changed-callback – G.Y Feb 27 '13 at 21:36

3 Answers3

7

Converters should not do more work than simple conversions, your question sounds like the converter uses a lot of properties of the object to create some combined value. Use a MultiBinding instead which hooks into all the different properties on the object you need, that way the MultiValueConverter on that MultiBinding will fire if any of those properties change.

Further, since you seem to create text you might be able to get away without using any converter at all as the StringFormat might be enough.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • Actually, I'm using MultiBinding already with MyClass as one of the bound objects. Are you proposing that I MultiBind to MyClass.Prop1, MyClass.Prop2 and MyClass.Prop3? Surely there is a way of saying that this object has changed? – tofutim Jun 30 '11 at 22:54
  • @toutim: That is what i propose, yes. I do not know of a way to notify of such changes, and it's not something i would suggest if there is a way. Edit: What AresAvatar says might be just that way. – H.B. Jun 30 '11 at 22:55
  • All said and done, I was never able to get InvalidateProperty to re-trigger the Converter, your solution while I resist from aesthetic standpoints actually works! – tofutim Jul 01 '11 at 00:02
2

The only technique I've found is to call the binding's UpdateSource method in a strategically placed event handler, such as LostFocus.

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

If you don't care about chattiness or if your control doesn't take input focus, you could do this in mycontrol_PropertyChanged event or similar. However, forcing a conversion cycle on every property change or every keystroke may interfere with validation.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
1

In MyClass, implement a NotifyPropertyChanged event. Then add a property changed callback to your MyClass DependencyProperty. In the DP's property changed callback, hook your new MyClass NotifyPropertyChanged event to a second callback function (and unhook the previous value, if any, with a -= operator). In the second callback function, call DependencyObject.InvalidateProperty so that the binding gets updated.

Edit: you may need to trigger a binding update with:

BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty);
if (exp != null)
    exp.UpdateTarget();

class MyClass : INotifyPropertyChanged
{
    /// <summary>
    /// Event raised when a property is changed
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the property changed event
    /// </summary>
    /// <param name="e">The arguments to pass</param>
    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, e);
    }

    /// <summary>
    /// Notify for property changed
    /// </summary>
    /// <param name="name">Property name</param>
    protected void NotifyPropertyChanged(string name)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(name));
    }


    /// <summary>
    /// The parent container object
    /// </summary>
    public Container Parent { get; set; }


    // Some data
    int x;
}


class Container : DependencyObject
{
    public static readonly DependencyProperty MyClassProperty = DependencyProperty.Register("MyClass", typeof(MyClass), typeof(Container), new FrameworkPropertyMetadata(MyClassPropChanged));

    public MyClass MyClass
    {
        get { return (MyClass)GetValue(MyClassProperty); }
        set { SetValue(MyClassProperty, value); }
    }

    void MyClassPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Container ct = d as Container;
        if (ct == null)
            return;

        MyClass oldc = e.OldValue as MyClass;
        if (oldc != null)
        {
            oldc.PropertyChanged -= new PropertyChangedEventHandler(MyClass_PropertyChanged);
            oldc.Parent = null;
        }
        MyClass newc = e.NewValue as MyClass;
        if (newc != null)
        {
            newc.Parent = ct;
            newc.PropertyChanged += new PropertyChangedEventHandler(MyClass_PropertyChanged);
        }
    }


    void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        MyClass mc = sender as MyClass;
        if (mc == null || mc.Parent == null)
            return;
        mc.Parent.InvalidateProperty(Container.MyClassProperty);
    }
}
Ed Bayiates
  • 11,060
  • 4
  • 43
  • 62
  • Why the multiple event hooks? – dthorpe Jun 30 '11 at 23:02
  • If I understand him correctly he wants changes in the MyClass instance set into the DP, to cause the DP to be invalidated. To do that, you need to use the the MyClass instance's event, and the property changed event in the DP. – Ed Bayiates Jun 30 '11 at 23:03
  • @AresAvatar I setup the PropertyChangedCallback (apparently has to be static) to unhook the e.OldValue PropertyChanged and hook the e.NewValue PropertyChanged - but how do I pass the DependencyObject to the PropertyChanged callback? – tofutim Jun 30 '11 at 23:10
  • @tofutim: Set the NotifyPropertyChanged changed callback to a member function of the class which contains your DependencyProperty. In the DP property changed callback you get a copy of MyClass. – Ed Bayiates Jun 30 '11 at 23:12
  • PropertyChangedCallback insists on a static Callback, which propgates all the way through. In the DP callback you do get a copy of MyClass, but I haven't found a way to pass it into PropertyChanged (which does have sender - but this is MyClass). The only thing I can think of at the moment is to make MyClass track the Dp, but that seems wierd. – tofutim Jun 30 '11 at 23:16
  • I'll try to edit and add some code to make what I am saying clearer. – Ed Bayiates Jun 30 '11 at 23:19
  • @AresAvatar let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1034/discussion-between-tofutim-and-aresavatar) – tofutim Jun 30 '11 at 23:20
  • I have it to where in calls InvalidateProperty.. but this does not re-call the Converter. – tofutim Jun 30 '11 at 23:47
  • I'll check it yours is definitely the most elegant. – tofutim Jul 01 '11 at 00:23
  • No, still doesn't call the Converter for some reason. I feel that it is because both InvalidateProperty and UpdateTarget just set the MyClass value to the original value. The binding then somehow thinks that no changes have happened (because it is still MyClass). In the end only HB's solution seems to work. I'll give it sometime though if you have more ideas. – tofutim Jul 01 '11 at 00:26
  • InvalidateProperty hasn't worked for me. The only thing that has worked for me is GetBindingExpression and calling UpdateSource() on the binding. (@tofutim: your last note says you were calling UpdateTarget. You want UpdateSource, not UpdateTarget) – dthorpe Jul 08 '11 at 19:29