WPF PropertyChange does not cascade - animating one custom class dependencyproperty changes other properties - PropertyChange only fires for first

StackOverflow https://stackoverflow.com/questions/1452379

Question

Very simplified, this is my problem: (solution in the bottom)

I have a custom class, inheriting Animatable and INotifyPropertyChange, containing one DependencyProperty (which I animate, say from 1 to 10) and one extra property (which I want to use from the XAML with a binding):

MyClass : Animatable, INotifyPropertyChanged {

    public static DependencyProperty AnimateValueProperty = DependencyProperty.Register(
      "AnimateValue",
      typeof(double),
      typeof(MyClass)
     );

    public double AnimateValue {
        get { return (double)GetValue(AnimateValueProperty); }
        set {
                SetValue(AnimateValueProperty, value);
                OnPropertyChanged(new PropertyChangedEventArgs("AnimateValue"));
                OnPropertyChanged(new PropertyChangedEventArgs("GuiValue")); //Added in edit
            }
        }
    }

    public double GuiValue {
        get { 
            return AnimateValue + 10; 
            //More fancy stuff happening in the real class...
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
        if (PropertyChanged != null) {
            PropertyChanged(this, e);
        }
    }

    protected override Freezable CreateInstanceCore() {
        return new MyClass();
    }
}

When I bind the GUI element to the property I actually animate (AnimateValue) everything works as expected - when I bind the GUI element to the related property (GuiValue) nothing happens. I guess that the changed event doesn't reach that property, but I really don't know how to solve the problem. Any pointers?

Any help is greatly appreciated!

Edit: Thanks for the two replies from Adam Sills and Kai Wang, stupid of me to leave out the second OnPropertyChange call, however I have tried that before and it didnt work. To debug, I put a writeline in the AnimateValue.set function and behold - it does not get executed at all!

I also tried changing the getters to always return 20 - no change! :-o It seems like the animation completely skips the property get and set and animates and reads the underlying dependencyproperty... Or have I misunderstood? Animation code I use is:

DoubleAnimation da = new DoubleAnimation (1, 10, new Duration(TimeSpan.FromSeconds(3)));
myClassInstance.BeginAnimation(MyClass.AnimateValueProperty, da);

Hmm... After writing that code here it seems very reasonable that the setter is not used (although I would really like it to be!) - but still, the basic problem stands and I still dont get why the getter is not used!

Edit2: Solution!

As suggested below I couldnt use the INotifyPropertyChange for the properties connecting to the DependencyProperty - the animation and XAML use the DepencencyProperty internally and does not use the getters and setters at all.

Hence - PropertyChangedCallback to notify others on change, CoerceValueCallback to "fix" data. It is also possible to validate data, all of this is done in the DependencyProperty constructor. Thanks all!

Thanks!

/Victor

Was it helpful?

Solution

here is a property changed notifaction pattern for use with dependency properties

  public Boolean PasswordBoxVisible {
     get { return (Boolean)GetValue(PasswordBoxVisibleProperty); }
     set { SetValue(PasswordBoxVisibleProperty, value); }
  }

  public static readonly DependencyProperty PasswordBoxVisibleProperty =
      DependencyProperty.Register("PasswordBoxVisible", typeof(Boolean), typeof(UserControl), new UIPropertyMetadata(false, new PropertyChangedCallback(PropChanged)));

  //this method is called when the dp is changed
  static void PropChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
       UserControl userControl = o as UserControl;
       //now you can use this to call some methods, raise other value changed etc, in this case OtherProperty.
       userControl.NotifyPropertyChanged("OtherProperty");
  }

I have a feeling, but am not entirely sure, that what you might need to do is coerce the other values. have a look at the coerce value callback section in this article

OTHER TIPS

By setting the value of AnimateValue, you are telling WPF that it has changed. But you are never telling WPF that GuiValue has changed. How is the WPF data binding framework supposed to know that GuiValue has changed when you don't tell it?

When you update AnimateValue and raise a notification of the change (which itself isn't necessary because it is a Dependency Property), you need to also raise a notification about GuiValue because it is dependent on AnimateValue but is calculated manually inside the property.

Basically just add

OnPropertyChanged(new PropertyChangedEventArgs("GuiValue"));

to the AnimateValue set block.

UPDATE:

Based on your new edits, if doing your property setting via XAML, the getter/setter of a Dependency Property is never called, it's all done through GetValue/SetValue directly.

If you need to response locally to changes, override OnPropertyChanged or provide a change callback inside DependencyProperty.Register

I am not sure if I understand what you mean. But see if this helps.

    public double AnimateValue {
    get { return (double)GetValue(AnimateValueProperty); }
    set {
            SetValue(AnimateValueProperty, value);
            OnPropertyChanged(new PropertyChangedEventArgs("AnimateValue"));
            OnPropertyChanged(new PropertyChangedEventArgs("GuiValue "));
        }
    }
}

If your object is derived from Dependency Object then you can not use INotifyPropertyChanged because the Binding architecture will only use Dependency Property Resgistration in order to use binding.

For this you need Read Only Dependency Property which I have explained as below.

public static DependencyProperty AnimateValueProperty = 
   DependencyProperty.Register(
      "AnimateValue",
      typeof(double),
      typeof(MyClass)
   );

 // This is for the Readonly GuiValueKey property
 public static DependencyPropertyKey GuiValueKey = 
     DependencyProperty.RegisterReadOnly (
     "GuiValue",
      typeof(double),
      typeof(MyClass),
      new UIPropertyMetadata(0.0)
);

public double AnimateValue {
    get { return (double)GetValue(AnimateValueProperty); }
    set {
            SetValue(AnimateValueProperty, value);
            // Following statement update GuiValue wherever it
            // is binded
            this.SetValue(GuiValueKey,value + 10);
        }
    }
}

// you dont even need following line...
// but its good to have it..
public double GuiValue {
    get { 
        return (double)this.GetValue(GuiValueKey); 
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top