26

How can one register a dependency property whose value is calculated using the value of another dependency property?

Because the .NET property wrappers are bypassed by WPF at run-time, one should not include logic in the getters and setters. The solution to that is typically to use PropertyChangedCallbacks. But those are declared static.

For example, what is the proper way to accomplish this contrived task:

public bool TestBool
{
  get { return (bool)GetValue(TestBoolProperty); }
  set 
  { 
    SetValue(TestBoolProperty, value);
    TestDouble = ((value)?(100.0):(200.0)); // HERE IS THE DEPENDENCY
  }
}
public static readonly DependencyProperty TestBoolProperty =
  DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel));

public double TestDouble
{
  get { return ((double)GetValue(TestDoubleProperty)); }
  set { SetValue(TestDoubleProperty, value); }
}
public static readonly DependencyProperty TestDoubleProperty =
  DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel));

As long as the dependency is not circular, is there a proper means to accomplish this?

Gregyski
  • 1,707
  • 3
  • 18
  • 26

2 Answers2

26

Hmmm... I think you'd better look at dependency properties value coercion. Here is an example with coercion:

public class ViewModel : DependencyObject
{
  public bool TestBool
  {
    get { return (bool)GetValue(TestBoolProperty); }
    set { SetValue(TestBoolProperty, value); }
  }
  public static readonly DependencyProperty TestBoolProperty =
    DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel), new PropertyMetadata(false, OnTestBoolPropertyChanged));

  private static void OnTestBoolPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var vm = (ViewModel)d;
    vm.CoerceValue(TestDoubleProperty);
  }

  public double TestDouble
  {
    get { return ((double)GetValue(TestDoubleProperty)); }
    set { SetValue(TestDoubleProperty, value); }
  }
  public static readonly DependencyProperty TestDoubleProperty =
    DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel), new PropertyMetadata(0.0, null, OnCoerceTestDouble));

  private static object OnCoerceTestDouble(DependencyObject d, object baseValue)
  {
    var vm = (ViewModel) d;
    var testBool = vm.TestBool;
    return ((testBool) ? (100.0) : (200.0));
  }
}
PScr
  • 449
  • 2
  • 11
Anvaka
  • 15,658
  • 2
  • 47
  • 56
  • What is the advantage of using a `CoerceValueCallback` as you have done versus directly changing a dependency property from within another dependency property's `PropertyChangedCallback` as opedog did? I gather from the documentation you linked that yours is the more proper method, but I am curious about the practical difference. – Gregyski Sep 01 '09 at 22:03
  • 1
    Well, to name couple: it doesn't break bindings to this property (i.e. if this property is a target of binding expression it will work after coercing but will be lost after explicit set); it has higher priority in dependency property value resolution (i.e. if you say PropA = "Something" that doesn't mean that PropA == "Something", since coercion could ignore that assignment); It remembers the old value of your property (i.e. next time you call CoerceValue() you will get original value of TestDouble, not the one that was set locally) – Anvaka Sep 01 '09 at 22:38
1

You're actually correct, you should use PropertyChangedCallback. Here's how:

public bool TestBool
{
  get { return (bool)GetValue(TestBoolProperty); }
  set 
  { 
    SetValue(TestBoolProperty, value);
  }
}
public static readonly DependencyProperty TestBoolProperty =
  DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel),
  new PropertyMetadata(false, new PropertyChangedCallback(OnTestBoolChanged)));

private static void OnTestBoolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  ViewModel vm = d as ViewModel;
  vm.TestDouble = value ? 100.0 : 200.0;
}

public double TestDouble
{
  get { return ((double)GetValue(TestDoubleProperty)); }
  set { SetValue(TestDoubleProperty, value); }
}
public static readonly DependencyProperty TestDoubleProperty =
  DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel));
opedog
  • 736
  • 7
  • 21
  • Thank you opedog. In my investigation, I foolishly failed to examine what was passed into the `PropertyChangedCallback`. I adjusted my test project to use this method and it's working. I'm going to try Anvaka's solution next. – Gregyski Sep 01 '09 at 22:18